diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index 9b6bfbb19..f4f2a6351 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -2435,6 +2435,7 @@ export type PostError = { export enum PostErrorCode { BadRequest = 'BAD_REQUEST', + NotFound = 'NOT_FOUND', Unauthorized = 'UNAUTHORIZED' } diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index c9346054f..5e3e584ca 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -1867,6 +1867,7 @@ type PostError { enum PostErrorCode { BAD_REQUEST + NOT_FOUND UNAUTHORIZED } diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index be237727e..572ae119e 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -151,7 +151,7 @@ import { webhookResolver, webhooksResolver, } from './index' -import { postsResolver } from './posts' +import { postResolver, postsResolver } from './posts' import { markEmailAsItemResolver, recentEmailsResolver, @@ -355,6 +355,7 @@ export const functionResolvers = { highlights: highlightsResolver, folderPolicies: folderPoliciesResolver, posts: postsResolver, + post: postResolver, }, User: { async intercomHash(user: User) { @@ -902,4 +903,5 @@ export const functionResolvers = { ...resultResolveTypeResolver('UpdateFolderPolicy'), ...resultResolveTypeResolver('DeleteFolderPolicy'), ...resultResolveTypeResolver('Posts'), + ...resultResolveTypeResolver('Post'), } diff --git a/packages/api/src/resolvers/posts/index.ts b/packages/api/src/resolvers/posts/index.ts index c0ee1bd36..bb35f0bb8 100644 --- a/packages/api/src/resolvers/posts/index.ts +++ b/packages/api/src/resolvers/posts/index.ts @@ -1,12 +1,18 @@ import { Post } from '../../entity/post' import { PostEdge, + PostErrorCode, + PostResult, PostsErrorCode, PostsResult, + QueryPostArgs, QueryPostsArgs, ResolverFn, } from '../../generated/graphql' -import { findPublicPostsByUserId } from '../../services/post' +import { + findPublicPostById, + findPublicPostsByUserId, +} from '../../services/post' import { Merge } from '../../util' import { ResolverContext } from '../types' @@ -39,7 +45,6 @@ export const postsResolver: ResolverFn< } const posts = await findPublicPostsByUserId(userId, limit + 1, offset) - console.log(posts) const hasNextPage = posts.length > limit if (hasNextPage) { @@ -62,3 +67,33 @@ export const postsResolver: ResolverFn< }, } } + +export const postResolver: ResolverFn< + Merge, + never, + ResolverContext, + QueryPostArgs +> = async (_, { id }, { log }) => { + if (!id) { + log.error('Invalid args', { id }) + + return { + errorCodes: [PostErrorCode.BadRequest], + } + } + + const post = await findPublicPostById(id) + + if (!post) { + log.error('Post not found', { id }) + + return { + errorCodes: [PostErrorCode.NotFound], + } + } + + return { + post, + } +} + diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 1d2b5ca7a..5e1f8958b 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -3451,6 +3451,7 @@ const schema = gql` enum PostErrorCode { UNAUTHORIZED BAD_REQUEST + NOT_FOUND } # Mutations diff --git a/packages/api/src/services/post.ts b/packages/api/src/services/post.ts index 474b09870..a3974c5c0 100644 --- a/packages/api/src/services/post.ts +++ b/packages/api/src/services/post.ts @@ -45,3 +45,14 @@ export const deletePosts = async (userId: string, postIds: string[]) => { } ) } + +export const findPublicPostById = async (id: string) => { + return getRepository(Post).findOneBy({ + id, + user: { + profile: { + private: false, + }, + }, + }) +} diff --git a/packages/api/test/resolvers/post.test.ts b/packages/api/test/resolvers/post.test.ts index 52fa30dab..d0ec1e6a5 100644 --- a/packages/api/test/resolvers/post.test.ts +++ b/packages/api/test/resolvers/post.test.ts @@ -4,7 +4,7 @@ import { createPosts, deletePosts } from '../../src/services/post' import { updateProfile } from '../../src/services/profile' import { deleteUser } from '../../src/services/user' import { createTestUser } from '../db' -import { graphqlRequest, loginAndGetAuthToken } from '../util' +import { generateFakeUuid, graphqlRequest, loginAndGetAuthToken } from '../util' describe('Post Resolvers', () => { let loginUser: User @@ -139,4 +139,107 @@ describe('Post Resolvers', () => { }) }) }) + + describe('postResolver', () => { + const query = ` + query Post($id: ID!) { + post(id: $id) { + ... on PostSuccess { + post { + id + title + content + ownedByViewer + } + } + ... on PostError { + errorCodes + } + } + } + ` + + let postId: string + + before(async () => { + const post = { + title: 'Post', + content: 'Content', + user: loginUser, + } + const newPost = await createPosts(loginUser.id, [post]) + + postId = newPost[0].id + }) + + after(async () => { + await deletePosts(loginUser.id, [postId]) + }) + + it('should return an error if the args are invalid', async () => { + const response = await graphqlRequest(query, '', { + id: '', + }) + + expect(response.body.data.post.errorCodes).to.eql(['BAD_REQUEST']) + }) + + it('should return an error if the post is not found', async () => { + const response = await graphqlRequest(query, '', { + id: generateFakeUuid(), + }) + + expect(response.body.data.post.errorCodes).to.eql(['NOT_FOUND']) + }) + + context('when the user is authenticated', () => { + it('should return the post', async () => { + const response = await graphqlRequest(query, authToken, { + id: postId, + }) + + expect(response.body.data.post.post.id).to.eql(postId) + expect(response.body.data.post.post.ownedByViewer).to.be.true + }) + }) + + context('when the user is not authenticated', () => { + context('when user profile is public', () => { + before(async () => { + await updateProfile(loginUser.id, { private: false }) + }) + + after(async () => { + await updateProfile(loginUser.id, { private: true }) + }) + + it('should return the post', async () => { + const response = await graphqlRequest(query, '', { + id: postId, + }) + + expect(response.body.data.post.post.id).to.eql(postId) + expect(response.body.data.post.post.ownedByViewer).to.be.false + }) + }) + + context('when user profile is private', () => { + before(async () => { + await updateProfile(loginUser.id, { private: true }) + }) + + after(async () => { + await updateProfile(loginUser.id, { private: false }) + }) + + it('should return an error', async () => { + const response = await graphqlRequest(query, '', { + id: postId, + }) + + expect(response.body.data.post.errorCodes).to.eql(['NOT_FOUND']) + }) + }) + }) + }) })