diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index f4f2a6351..8b4830ad8 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -515,7 +515,6 @@ export type CreatePostError = { }; export enum CreatePostErrorCode { - BadRequest = 'BAD_REQUEST', Unauthorized = 'UNAUTHORIZED' } diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index 5e3e584ca..0e8c2a93d 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -460,7 +460,6 @@ type CreatePostError { } enum CreatePostErrorCode { - BAD_REQUEST UNAUTHORIZED } diff --git a/packages/api/src/repository/index.ts b/packages/api/src/repository/index.ts index d8c574725..d4005ed25 100644 --- a/packages/api/src/repository/index.ts +++ b/packages/api/src/repository/index.ts @@ -72,7 +72,7 @@ export const authTrx = async ( ): Promise => { let { uid, userRole } = options - // if uid and dbRole are not passed in, then get them from the claims + // if uid and dbRole are not passed in, then get them from the http context if (!uid && !userRole) { const claims: Claims | undefined = httpContext.get('claims') uid = claims?.uid diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index 572ae119e..8bb629929 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 { postResolver, postsResolver } from './posts' +import { createPostResolver, postResolver, postsResolver } from './posts' import { markEmailAsItemResolver, recentEmailsResolver, @@ -318,6 +318,7 @@ export const functionResolvers = { createFolderPolicy: createFolderPolicyResolver, updateFolderPolicy: updateFolderPolicyResolver, deleteFolderPolicy: deleteFolderPolicyResolver, + createPost: createPostResolver, }, Query: { me: getMeUserResolver, @@ -904,4 +905,5 @@ export const functionResolvers = { ...resultResolveTypeResolver('DeleteFolderPolicy'), ...resultResolveTypeResolver('Posts'), ...resultResolveTypeResolver('Post'), + ...resultResolveTypeResolver('CreatePost'), } diff --git a/packages/api/src/resolvers/posts/index.ts b/packages/api/src/resolvers/posts/index.ts index bb35f0bb8..2bde99791 100644 --- a/packages/api/src/resolvers/posts/index.ts +++ b/packages/api/src/resolvers/posts/index.ts @@ -1,5 +1,9 @@ import { Post } from '../../entity/post' import { + CreatePostError, + CreatePostErrorCode, + CreatePostSuccess, + MutationCreatePostArgs, PostEdge, PostErrorCode, PostResult, @@ -10,10 +14,13 @@ import { ResolverFn, } from '../../generated/graphql' import { + createPosts, + createPublicPost, findPublicPostById, findPublicPostsByUserId, } from '../../services/post' import { Merge } from '../../util' +import { authorized } from '../../utils/gql-utils' import { ResolverContext } from '../types' type PartialPostEdge = Merge< @@ -97,3 +104,34 @@ export const postResolver: ResolverFn< } } +export const createPostResolver = authorized< + Merge, + CreatePostError, + MutationCreatePostArgs +>(async (_, { input }, { uid, log }) => { + const { title, content, highlightIds, libraryItemIds, thought, thumbnail } = + input + + const postToCreate = { + userId: uid, + title, + content, + highlightIds: highlightIds || undefined, + libraryItemIds: libraryItemIds || undefined, + thought: thought || undefined, + thumbnail: thumbnail || undefined, + } + + const post = await createPublicPost(uid, postToCreate) + if (!post) { + log.error('Failed to create post', { postToCreate }) + + return { + errorCodes: [CreatePostErrorCode.Unauthorized], + } + } + + return { + post, + } +}) diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 5e1f8958b..07035d315 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -3354,8 +3354,8 @@ const schema = gql` } input CreatePostInput { - title: String! - content: String! + title: String! @sanitize(minLength: 1, maxLength: 255) + content: String! @sanitize(minLength: 1) thumbnail: String libraryItemIds: [ID!] highlightIds: [ID!] @@ -3374,7 +3374,6 @@ const schema = gql` enum CreatePostErrorCode { UNAUTHORIZED - BAD_REQUEST } input UpdatePostInput { diff --git a/packages/api/src/services/post.ts b/packages/api/src/services/post.ts index a3974c5c0..f67b0cd5c 100644 --- a/packages/api/src/services/post.ts +++ b/packages/api/src/services/post.ts @@ -1,5 +1,6 @@ import { DeepPartial } from 'typeorm' import { Post } from '../entity/post' +import { Profile } from '../entity/profile' import { authTrx, getRepository } from '../repository' export const findPublicPostsByUserId = async ( @@ -26,6 +27,30 @@ export const findPublicPostsByUserId = async ( return posts } +export const createPublicPost = async ( + userId: string, + post: DeepPartial +) => { + return authTrx( + async (trx) => { + const newPost = await trx.getRepository(Post).save(post) + + // Make user profile public when user creates a post + await trx.getRepository(Profile).update( + { user: { id: userId } }, + { + private: false, + } + ) + + return newPost + }, + { + uid: userId, + } + ) +} + export const createPosts = async ( userId: string, posts: Array> diff --git a/packages/api/test/resolvers/post.test.ts b/packages/api/test/resolvers/post.test.ts index d0ec1e6a5..bc17c094f 100644 --- a/packages/api/test/resolvers/post.test.ts +++ b/packages/api/test/resolvers/post.test.ts @@ -1,7 +1,11 @@ import { expect } from 'chai' import { User } from '../../src/entity/user' -import { createPosts, deletePosts } from '../../src/services/post' -import { updateProfile } from '../../src/services/profile' +import { + createPosts, + deletePosts, + findPublicPostById, +} from '../../src/services/post' +import { findProfile, updateProfile } from '../../src/services/profile' import { deleteUser } from '../../src/services/user' import { createTestUser } from '../db' import { generateFakeUuid, graphqlRequest, loginAndGetAuthToken } from '../util' @@ -242,4 +246,46 @@ describe('Post Resolvers', () => { }) }) }) + + describe('createPostResolver', () => { + const mutation = ` + mutation CreatePost($input: CreatePostInput!) { + createPost(input: $input) { + ... on CreatePostSuccess { + post { + id + title + content + } + } + ... on CreatePostError { + errorCodes + } + } + } + ` + + it('should create a post', async () => { + const response = await graphqlRequest(mutation, authToken, { + input: { + title: 'Post', + content: 'Content', + }, + }) + + expect(response.body.data.createPost.post.title).to.eql('Post') + expect(response.body.data.createPost.post.content).to.eql('Content') + + const postId = response.body.data.createPost.post.id as string + + const post = await findPublicPostById(postId) + expect(post).to.exist + expect(post?.title).to.eql('Post') + + const profile = await findProfile(loginUser) + expect(profile?.private).to.be.false + + await deletePosts(loginUser.id, [postId]) + }) + }) })