diff --git a/packages/api/src/entity/post.ts b/packages/api/src/entity/post.ts index 2451c7817..d689d9202 100644 --- a/packages/api/src/entity/post.ts +++ b/packages/api/src/entity/post.ts @@ -19,11 +19,11 @@ export class Post { @JoinColumn({ name: 'user_id' }) user!: User - @Column('uuid', { array: true }) - libraryItemIds!: string[] + @Column('uuid', { array: true, nullable: true }) + libraryItemIds?: string[] - @Column('uuid', { array: true }) - highlightIds!: string[] + @Column('uuid', { array: true, nullable: true }) + highlightIds?: string[] @Column('text') title!: string diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index a57b12e5b..9b6bfbb19 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -509,6 +509,32 @@ export type CreateNewsletterEmailSuccess = { newsletterEmail: NewsletterEmail; }; +export type CreatePostError = { + __typename?: 'CreatePostError'; + errorCodes: Array; +}; + +export enum CreatePostErrorCode { + BadRequest = 'BAD_REQUEST', + Unauthorized = 'UNAUTHORIZED' +} + +export type CreatePostInput = { + content: Scalars['String']; + highlightIds?: InputMaybe>; + libraryItemIds?: InputMaybe>; + thought?: InputMaybe; + thumbnail?: InputMaybe; + title: Scalars['String']; +}; + +export type CreatePostResult = CreatePostError | CreatePostSuccess; + +export type CreatePostSuccess = { + __typename?: 'CreatePostSuccess'; + post: Post; +}; + export type CreateReactionError = { __typename?: 'CreateReactionError'; errorCodes: Array; @@ -749,6 +775,23 @@ export type DeleteNewsletterEmailSuccess = { newsletterEmail: NewsletterEmail; }; +export type DeletePostError = { + __typename?: 'DeletePostError'; + errorCodes: Array; +}; + +export enum DeletePostErrorCode { + BadRequest = 'BAD_REQUEST', + Unauthorized = 'UNAUTHORIZED' +} + +export type DeletePostResult = DeletePostError | DeletePostSuccess; + +export type DeletePostSuccess = { + __typename?: 'DeletePostSuccess'; + success: Scalars['Boolean']; +}; + export type DeleteReactionError = { __typename?: 'DeleteReactionError'; errorCodes: Array; @@ -1800,6 +1843,7 @@ export type Mutation = { createHighlight: CreateHighlightResult; createLabel: CreateLabelResult; createNewsletterEmail: CreateNewsletterEmailResult; + createPost: CreatePostResult; deleteAccount: DeleteAccountResult; deleteDiscoverArticle: DeleteDiscoverArticleResult; deleteDiscoverFeed: DeleteDiscoverFeedResult; @@ -1809,6 +1853,7 @@ export type Mutation = { deleteIntegration: DeleteIntegrationResult; deleteLabel: DeleteLabelResult; deleteNewsletterEmail: DeleteNewsletterEmailResult; + deletePost: DeletePostResult; deleteRule: DeleteRuleResult; deleteWebhook: DeleteWebhookResult; editDiscoverFeed: EditDiscoverFeedResult; @@ -1859,6 +1904,7 @@ export type Mutation = { updateLabel: UpdateLabelResult; updateNewsletterEmail: UpdateNewsletterEmailResult; updatePage: UpdatePageResult; + updatePost: UpdatePostResult; updateSubscription: UpdateSubscriptionResult; updateUser: UpdateUserResult; updateUserProfile: UpdateUserProfileResult; @@ -1922,6 +1968,11 @@ export type MutationCreateNewsletterEmailArgs = { }; +export type MutationCreatePostArgs = { + input: CreatePostInput; +}; + + export type MutationDeleteAccountArgs = { userID: Scalars['ID']; }; @@ -1967,6 +2018,11 @@ export type MutationDeleteNewsletterEmailArgs = { }; +export type MutationDeletePostArgs = { + id: Scalars['ID']; +}; + + export type MutationDeleteRuleArgs = { id: Scalars['ID']; }; @@ -2205,6 +2261,11 @@ export type MutationUpdatePageArgs = { }; +export type MutationUpdatePostArgs = { + input: UpdatePostInput; +}; + + export type MutationUpdateSubscriptionArgs = { input: UpdateSubscriptionInput; }; @@ -2346,6 +2407,62 @@ export type ParseResult = { title: Scalars['String']; }; +export type Post = { + __typename?: 'Post'; + author: User; + content: Scalars['String']; + createdAt: Scalars['Date']; + highlights?: Maybe>; + id: Scalars['ID']; + libraryItems?: Maybe>; + ownedByViewer: Scalars['Boolean']; + thought?: Maybe; + thumbnail?: Maybe; + title: Scalars['String']; + updatedAt: Scalars['Date']; +}; + +export type PostEdge = { + __typename?: 'PostEdge'; + cursor: Scalars['String']; + node: Post; +}; + +export type PostError = { + __typename?: 'PostError'; + errorCodes: Array; +}; + +export enum PostErrorCode { + BadRequest = 'BAD_REQUEST', + Unauthorized = 'UNAUTHORIZED' +} + +export type PostResult = PostError | PostSuccess; + +export type PostSuccess = { + __typename?: 'PostSuccess'; + post: Post; +}; + +export type PostsError = { + __typename?: 'PostsError'; + errorCodes: Array; +}; + +export enum PostsErrorCode { + BadRequest = 'BAD_REQUEST', + Unauthorized = 'UNAUTHORIZED' +} + +export type PostsResult = PostsError | PostsSuccess; + +export type PostsSuccess = { + __typename?: 'PostsSuccess'; + edges: Array; + pageInfo: PageInfo; +}; + export type PreparedDocumentInput = { document: Scalars['String']; pageInfo: PageInfoInput; @@ -2383,6 +2500,8 @@ export type Query = { labels: LabelsResult; me?: Maybe; newsletterEmails: NewsletterEmailsResult; + post: PostResult; + posts: PostsResult; recentEmails: RecentEmailsResult; recentSearches: RecentSearchesResult; rules: RulesResult; @@ -2445,6 +2564,18 @@ export type QueryIntegrationArgs = { }; +export type QueryPostArgs = { + id: Scalars['ID']; +}; + + +export type QueryPostsArgs = { + after?: InputMaybe; + first?: InputMaybe; + userId: Scalars['ID']; +}; + + export type QueryRulesArgs = { enabled?: InputMaybe; }; @@ -3824,6 +3955,33 @@ export type UpdatePageSuccess = { updatedPage: Article; }; +export type UpdatePostError = { + __typename?: 'UpdatePostError'; + errorCodes: Array; +}; + +export enum UpdatePostErrorCode { + BadRequest = 'BAD_REQUEST', + Unauthorized = 'UNAUTHORIZED' +} + +export type UpdatePostInput = { + content?: InputMaybe; + highlightIds?: InputMaybe>; + id: Scalars['ID']; + libraryItemIds?: InputMaybe>; + thought?: InputMaybe; + thumbnail?: InputMaybe; + title?: InputMaybe; +}; + +export type UpdatePostResult = UpdatePostError | UpdatePostSuccess; + +export type UpdatePostSuccess = { + __typename?: 'UpdatePostSuccess'; + post: Post; +}; + export enum UpdateReason { Created = 'CREATED', Deleted = 'DELETED', @@ -4324,6 +4482,11 @@ export type ResolversTypes = { CreateNewsletterEmailInput: CreateNewsletterEmailInput; CreateNewsletterEmailResult: ResolversTypes['CreateNewsletterEmailError'] | ResolversTypes['CreateNewsletterEmailSuccess']; CreateNewsletterEmailSuccess: ResolverTypeWrapper; + CreatePostError: ResolverTypeWrapper; + CreatePostErrorCode: CreatePostErrorCode; + CreatePostInput: CreatePostInput; + CreatePostResult: ResolversTypes['CreatePostError'] | ResolversTypes['CreatePostSuccess']; + CreatePostSuccess: ResolverTypeWrapper; CreateReactionError: ResolverTypeWrapper; CreateReactionErrorCode: CreateReactionErrorCode; CreateReactionInput: CreateReactionInput; @@ -4377,6 +4540,10 @@ export type ResolversTypes = { DeleteNewsletterEmailErrorCode: DeleteNewsletterEmailErrorCode; DeleteNewsletterEmailResult: ResolversTypes['DeleteNewsletterEmailError'] | ResolversTypes['DeleteNewsletterEmailSuccess']; DeleteNewsletterEmailSuccess: ResolverTypeWrapper; + DeletePostError: ResolverTypeWrapper; + DeletePostErrorCode: DeletePostErrorCode; + DeletePostResult: ResolversTypes['DeletePostError'] | ResolversTypes['DeletePostSuccess']; + DeletePostSuccess: ResolverTypeWrapper; DeleteReactionError: ResolverTypeWrapper; DeleteReactionErrorCode: DeleteReactionErrorCode; DeleteReactionResult: ResolversTypes['DeleteReactionError'] | ResolversTypes['DeleteReactionSuccess']; @@ -4589,6 +4756,16 @@ export type ResolversTypes = { PageInfoInput: PageInfoInput; PageType: PageType; ParseResult: ParseResult; + Post: ResolverTypeWrapper; + PostEdge: ResolverTypeWrapper; + PostError: ResolverTypeWrapper; + PostErrorCode: PostErrorCode; + PostResult: ResolversTypes['PostError'] | ResolversTypes['PostSuccess']; + PostSuccess: ResolverTypeWrapper; + PostsError: ResolverTypeWrapper; + PostsErrorCode: PostsErrorCode; + PostsResult: ResolversTypes['PostsError'] | ResolversTypes['PostsSuccess']; + PostsSuccess: ResolverTypeWrapper; PreparedDocumentInput: PreparedDocumentInput; Profile: ResolverTypeWrapper; Query: ResolverTypeWrapper<{}>; @@ -4823,6 +5000,11 @@ export type ResolversTypes = { UpdatePageInput: UpdatePageInput; UpdatePageResult: ResolversTypes['UpdatePageError'] | ResolversTypes['UpdatePageSuccess']; UpdatePageSuccess: ResolverTypeWrapper; + UpdatePostError: ResolverTypeWrapper; + UpdatePostErrorCode: UpdatePostErrorCode; + UpdatePostInput: UpdatePostInput; + UpdatePostResult: ResolversTypes['UpdatePostError'] | ResolversTypes['UpdatePostSuccess']; + UpdatePostSuccess: ResolverTypeWrapper; UpdateReason: UpdateReason; UpdateReminderError: ResolverTypeWrapper; UpdateReminderErrorCode: UpdateReminderErrorCode; @@ -4952,6 +5134,10 @@ export type ResolversParentTypes = { CreateNewsletterEmailInput: CreateNewsletterEmailInput; CreateNewsletterEmailResult: ResolversParentTypes['CreateNewsletterEmailError'] | ResolversParentTypes['CreateNewsletterEmailSuccess']; CreateNewsletterEmailSuccess: CreateNewsletterEmailSuccess; + CreatePostError: CreatePostError; + CreatePostInput: CreatePostInput; + CreatePostResult: ResolversParentTypes['CreatePostError'] | ResolversParentTypes['CreatePostSuccess']; + CreatePostSuccess: CreatePostSuccess; CreateReactionError: CreateReactionError; CreateReactionInput: CreateReactionInput; CreateReactionResult: ResolversParentTypes['CreateReactionError'] | ResolversParentTypes['CreateReactionSuccess']; @@ -4993,6 +5179,9 @@ export type ResolversParentTypes = { DeleteNewsletterEmailError: DeleteNewsletterEmailError; DeleteNewsletterEmailResult: ResolversParentTypes['DeleteNewsletterEmailError'] | ResolversParentTypes['DeleteNewsletterEmailSuccess']; DeleteNewsletterEmailSuccess: DeleteNewsletterEmailSuccess; + DeletePostError: DeletePostError; + DeletePostResult: ResolversParentTypes['DeletePostError'] | ResolversParentTypes['DeletePostSuccess']; + DeletePostSuccess: DeletePostSuccess; DeleteReactionError: DeleteReactionError; DeleteReactionResult: ResolversParentTypes['DeleteReactionError'] | ResolversParentTypes['DeleteReactionSuccess']; DeleteReactionSuccess: DeleteReactionSuccess; @@ -5157,6 +5346,14 @@ export type ResolversParentTypes = { PageInfo: PageInfo; PageInfoInput: PageInfoInput; ParseResult: ParseResult; + Post: Post; + PostEdge: PostEdge; + PostError: PostError; + PostResult: ResolversParentTypes['PostError'] | ResolversParentTypes['PostSuccess']; + PostSuccess: PostSuccess; + PostsError: PostsError; + PostsResult: ResolversParentTypes['PostsError'] | ResolversParentTypes['PostsSuccess']; + PostsSuccess: PostsSuccess; PreparedDocumentInput: PreparedDocumentInput; Profile: Profile; Query: {}; @@ -5339,6 +5536,10 @@ export type ResolversParentTypes = { UpdatePageInput: UpdatePageInput; UpdatePageResult: ResolversParentTypes['UpdatePageError'] | ResolversParentTypes['UpdatePageSuccess']; UpdatePageSuccess: UpdatePageSuccess; + UpdatePostError: UpdatePostError; + UpdatePostInput: UpdatePostInput; + UpdatePostResult: ResolversParentTypes['UpdatePostError'] | ResolversParentTypes['UpdatePostSuccess']; + UpdatePostSuccess: UpdatePostSuccess; UpdateReminderError: UpdateReminderError; UpdateReminderInput: UpdateReminderInput; UpdateReminderResult: ResolversParentTypes['UpdateReminderError'] | ResolversParentTypes['UpdateReminderSuccess']; @@ -5702,6 +5903,20 @@ export type CreateNewsletterEmailSuccessResolvers; }; +export type CreatePostErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type CreatePostResultResolvers = { + __resolveType: TypeResolveFn<'CreatePostError' | 'CreatePostSuccess', ParentType, ContextType>; +}; + +export type CreatePostSuccessResolvers = { + post?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type CreateReactionErrorResolvers = { errorCodes?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -5874,6 +6089,20 @@ export type DeleteNewsletterEmailSuccessResolvers; }; +export type DeletePostErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type DeletePostResultResolvers = { + __resolveType: TypeResolveFn<'DeletePostError' | 'DeletePostSuccess', ParentType, ContextType>; +}; + +export type DeletePostSuccessResolvers = { + success?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type DeleteReactionErrorResolvers = { errorCodes?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -6693,6 +6922,7 @@ export type MutationResolvers>; createLabel?: Resolver>; createNewsletterEmail?: Resolver>; + createPost?: Resolver>; deleteAccount?: Resolver>; deleteDiscoverArticle?: Resolver>; deleteDiscoverFeed?: Resolver>; @@ -6702,6 +6932,7 @@ export type MutationResolvers>; deleteLabel?: Resolver>; deleteNewsletterEmail?: Resolver>; + deletePost?: Resolver>; deleteRule?: Resolver>; deleteWebhook?: Resolver>; editDiscoverFeed?: Resolver>; @@ -6752,6 +6983,7 @@ export type MutationResolvers>; updateNewsletterEmail?: Resolver>; updatePage?: Resolver>; + updatePost?: Resolver>; updateSubscription?: Resolver>; updateUser?: Resolver>; updateUserProfile?: Resolver>; @@ -6826,6 +7058,56 @@ export type PageInfoResolvers; }; +export type PostResolvers = { + author?: Resolver; + content?: Resolver; + createdAt?: Resolver; + highlights?: Resolver>, ParentType, ContextType>; + id?: Resolver; + libraryItems?: Resolver>, ParentType, ContextType>; + ownedByViewer?: Resolver; + thought?: Resolver, ParentType, ContextType>; + thumbnail?: Resolver, ParentType, ContextType>; + title?: Resolver; + updatedAt?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type PostEdgeResolvers = { + cursor?: Resolver; + node?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type PostErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type PostResultResolvers = { + __resolveType: TypeResolveFn<'PostError' | 'PostSuccess', ParentType, ContextType>; +}; + +export type PostSuccessResolvers = { + post?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type PostsErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type PostsResultResolvers = { + __resolveType: TypeResolveFn<'PostsError' | 'PostsSuccess', ParentType, ContextType>; +}; + +export type PostsSuccessResolvers = { + edges?: Resolver, ParentType, ContextType>; + pageInfo?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type ProfileResolvers = { bio?: Resolver, ParentType, ContextType>; id?: Resolver; @@ -6857,6 +7139,8 @@ export type QueryResolvers; me?: Resolver, ParentType, ContextType>; newsletterEmails?: Resolver; + post?: Resolver>; + posts?: Resolver>; recentEmails?: Resolver; recentSearches?: Resolver; rules?: Resolver>; @@ -7695,6 +7979,20 @@ export type UpdatePageSuccessResolvers; }; +export type UpdatePostErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type UpdatePostResultResolvers = { + __resolveType: TypeResolveFn<'UpdatePostError' | 'UpdatePostSuccess', ParentType, ContextType>; +}; + +export type UpdatePostSuccessResolvers = { + post?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type UpdateReminderErrorResolvers = { errorCodes?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -7972,6 +8270,9 @@ export type Resolvers = { CreateNewsletterEmailError?: CreateNewsletterEmailErrorResolvers; CreateNewsletterEmailResult?: CreateNewsletterEmailResultResolvers; CreateNewsletterEmailSuccess?: CreateNewsletterEmailSuccessResolvers; + CreatePostError?: CreatePostErrorResolvers; + CreatePostResult?: CreatePostResultResolvers; + CreatePostSuccess?: CreatePostSuccessResolvers; CreateReactionError?: CreateReactionErrorResolvers; CreateReactionResult?: CreateReactionResultResolvers; CreateReactionSuccess?: CreateReactionSuccessResolvers; @@ -8009,6 +8310,9 @@ export type Resolvers = { DeleteNewsletterEmailError?: DeleteNewsletterEmailErrorResolvers; DeleteNewsletterEmailResult?: DeleteNewsletterEmailResultResolvers; DeleteNewsletterEmailSuccess?: DeleteNewsletterEmailSuccessResolvers; + DeletePostError?: DeletePostErrorResolvers; + DeletePostResult?: DeletePostResultResolvers; + DeletePostSuccess?: DeletePostSuccessResolvers; DeleteReactionError?: DeleteReactionErrorResolvers; DeleteReactionResult?: DeleteReactionResultResolvers; DeleteReactionSuccess?: DeleteReactionSuccessResolvers; @@ -8158,6 +8462,14 @@ export type Resolvers = { OptInFeatureSuccess?: OptInFeatureSuccessResolvers; Page?: PageResolvers; PageInfo?: PageInfoResolvers; + Post?: PostResolvers; + PostEdge?: PostEdgeResolvers; + PostError?: PostErrorResolvers; + PostResult?: PostResultResolvers; + PostSuccess?: PostSuccessResolvers; + PostsError?: PostsErrorResolvers; + PostsResult?: PostsResultResolvers; + PostsSuccess?: PostsSuccessResolvers; Profile?: ProfileResolvers; Query?: QueryResolvers; Reaction?: ReactionResolvers; @@ -8305,6 +8617,9 @@ export type Resolvers = { UpdatePageError?: UpdatePageErrorResolvers; UpdatePageResult?: UpdatePageResultResolvers; UpdatePageSuccess?: UpdatePageSuccessResolvers; + UpdatePostError?: UpdatePostErrorResolvers; + UpdatePostResult?: UpdatePostResultResolvers; + UpdatePostSuccess?: UpdatePostSuccessResolvers; UpdateReminderError?: UpdateReminderErrorResolvers; UpdateReminderResult?: UpdateReminderResultResolvers; UpdateReminderSuccess?: UpdateReminderSuccessResolvers; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index d7e61f20f..c9346054f 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -455,6 +455,30 @@ type CreateNewsletterEmailSuccess { newsletterEmail: NewsletterEmail! } +type CreatePostError { + errorCodes: [CreatePostErrorCode!]! +} + +enum CreatePostErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +input CreatePostInput { + content: String! + highlightIds: [ID!] + libraryItemIds: [ID!] + thought: String + thumbnail: String + title: String! +} + +union CreatePostResult = CreatePostError | CreatePostSuccess + +type CreatePostSuccess { + post: Post! +} + type CreateReactionError { errorCodes: [CreateReactionErrorCode!]! } @@ -673,6 +697,21 @@ type DeleteNewsletterEmailSuccess { newsletterEmail: NewsletterEmail! } +type DeletePostError { + errorCodes: [DeletePostErrorCode!]! +} + +enum DeletePostErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union DeletePostResult = DeletePostError | DeletePostSuccess + +type DeletePostSuccess { + success: Boolean! +} + type DeleteReactionError { errorCodes: [DeleteReactionErrorCode!]! } @@ -1625,6 +1664,7 @@ type Mutation { createHighlight(input: CreateHighlightInput!): CreateHighlightResult! createLabel(input: CreateLabelInput!): CreateLabelResult! createNewsletterEmail(input: CreateNewsletterEmailInput): CreateNewsletterEmailResult! + createPost(input: CreatePostInput!): CreatePostResult! deleteAccount(userID: ID!): DeleteAccountResult! deleteDiscoverArticle(input: DeleteDiscoverArticleInput!): DeleteDiscoverArticleResult! deleteDiscoverFeed(input: DeleteDiscoverFeedInput!): DeleteDiscoverFeedResult! @@ -1634,6 +1674,7 @@ type Mutation { deleteIntegration(id: ID!): DeleteIntegrationResult! deleteLabel(id: ID!): DeleteLabelResult! deleteNewsletterEmail(newsletterEmailId: ID!): DeleteNewsletterEmailResult! + deletePost(id: ID!): DeletePostResult! deleteRule(id: ID!): DeleteRuleResult! deleteWebhook(id: ID!): DeleteWebhookResult! editDiscoverFeed(input: EditDiscoverFeedInput!): EditDiscoverFeedResult! @@ -1684,6 +1725,7 @@ type Mutation { updateLabel(input: UpdateLabelInput!): UpdateLabelResult! updateNewsletterEmail(input: UpdateNewsletterEmailInput!): UpdateNewsletterEmailResult! updatePage(input: UpdatePageInput!): UpdatePageResult! + updatePost(input: UpdatePostInput!): UpdatePostResult! updateSubscription(input: UpdateSubscriptionInput!): UpdateSubscriptionResult! updateUser(input: UpdateUserInput!): UpdateUserResult! updateUserProfile(input: UpdateUserProfileInput!): UpdateUserProfileResult! @@ -1800,6 +1842,56 @@ input ParseResult { title: String! } +type Post { + author: User! + content: String! + createdAt: Date! + highlights: [Highlight!] + id: ID! + libraryItems: [Article!] + ownedByViewer: Boolean! + thought: String + thumbnail: String + title: String! + updatedAt: Date! +} + +type PostEdge { + cursor: String! + node: Post! +} + +type PostError { + errorCodes: [PostErrorCode!]! +} + +enum PostErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union PostResult = PostError | PostSuccess + +type PostSuccess { + post: Post! +} + +type PostsError { + errorCodes: [PostsErrorCode!]! +} + +enum PostsErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union PostsResult = PostsError | PostsSuccess + +type PostsSuccess { + edges: [PostEdge!]! + pageInfo: PageInfo! +} + input PreparedDocumentInput { document: String! pageInfo: PageInfoInput! @@ -1835,6 +1927,8 @@ type Query { labels: LabelsResult! me: User newsletterEmails: NewsletterEmailsResult! + post(id: ID!): PostResult! + posts(after: String, first: Int, userId: ID!): PostsResult! recentEmails: RecentEmailsResult! recentSearches: RecentSearchesResult! rules(enabled: Boolean): RulesResult! @@ -3068,6 +3162,31 @@ type UpdatePageSuccess { updatedPage: Article! } +type UpdatePostError { + errorCodes: [UpdatePostErrorCode!]! +} + +enum UpdatePostErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +input UpdatePostInput { + content: String + highlightIds: [ID!] + id: ID! + libraryItemIds: [ID!] + thought: String + thumbnail: String + title: String +} + +union UpdatePostResult = UpdatePostError | UpdatePostSuccess + +type UpdatePostSuccess { + post: Post! +} + enum UpdateReason { CREATED DELETED diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index aa226adc3..8c4543808 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -150,6 +150,7 @@ import { webhookResolver, webhooksResolver, } from './index' +import { postsResolver } from './posts' import { markEmailAsItemResolver, recentEmailsResolver, @@ -352,6 +353,7 @@ export const functionResolvers = { hiddenHomeSection: hiddenHomeSectionResolver, highlights: highlightsResolver, folderPolicies: folderPoliciesResolver, + posts: postsResolver, }, User: { async intercomHash(user: User) { @@ -777,6 +779,23 @@ export const functionResolvers = { name: (recommendation: Recommendation) => recommendation.group.name, recommendedAt: (recommendation: Recommendation) => recommendation.createdAt, }, + Post: { + author(post: { userId: string }, _: unknown, ctx: WithDataSourcesContext) { + return ctx.dataLoaders.users.load(post.userId) + }, + ownedByViewer(post: { userId: string }, ctx: WithDataSourcesContext) { + return post.userId === ctx.uid + }, + libraryItems( + post: { libraryItemIds: string[] }, + ctx: WithDataSourcesContext + ) { + return ctx.dataLoaders.libraryItems.loadMany(post.libraryItemIds) + }, + highlights(post: { highlightIds: string[] }, ctx: WithDataSourcesContext) { + return ctx.dataLoaders.highlights.loadMany(post.highlightIds) + }, + }, ...resultResolveTypeResolver('Login'), ...resultResolveTypeResolver('LogOut'), ...resultResolveTypeResolver('GoogleSignup'), @@ -875,4 +894,5 @@ export const functionResolvers = { ...resultResolveTypeResolver('CreateFolderPolicy'), ...resultResolveTypeResolver('UpdateFolderPolicy'), ...resultResolveTypeResolver('DeleteFolderPolicy'), + ...resultResolveTypeResolver('Posts'), } diff --git a/packages/api/src/resolvers/posts/index.ts b/packages/api/src/resolvers/posts/index.ts new file mode 100644 index 000000000..ada8dd0ac --- /dev/null +++ b/packages/api/src/resolvers/posts/index.ts @@ -0,0 +1,63 @@ +import { Post } from '../../entity/post' +import { + PostEdge, + PostsErrorCode, + PostsResult, + QueryPostsArgs, + ResolverFn, +} from '../../generated/graphql' +import { findPostsByUserId } from '../../services/post' +import { Merge } from '../../util' +import { ResolverContext } from '../types' + +type PartialPostEdge = Merge< + PostEdge, + { + node: Post + } +> +type PartialPostsResult = Merge< + PostsResult, + { + edges?: Array + } +> +export const postsResolver: ResolverFn< + PartialPostsResult, + never, + ResolverContext, + QueryPostsArgs +> = async (_, { first, after, userId }, { log }) => { + const limit = first || 10 + const offset = parseInt(after || '0') + if (isNaN(offset) || offset < 0 || limit > 50) { + log.error('Invalid args', { after, first }) + + return { + errorCodes: [PostsErrorCode.BadRequest], + } + } + + const posts = await findPostsByUserId(userId, limit + 1, offset) + + const hasNextPage = posts.length > limit + if (hasNextPage) { + posts.pop() + } + const endCursor = String(offset + posts.length) + + const edges = posts.map((post) => ({ + cursor: endCursor, + node: post, + })) + + return { + edges, + pageInfo: { + startCursor: String(offset), + endCursor, + hasPreviousPage: offset > 0, + hasNextPage, + }, + } +} diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 68caafc98..1d2b5ca7a 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -3339,6 +3339,120 @@ const schema = gql` afterDays: Int } + type Post { + id: ID! + title: String! + content: String! + author: User! + ownedByViewer: Boolean! + thumbnail: String + thought: String + libraryItems: [Article!] + highlights: [Highlight!] + createdAt: Date! + updatedAt: Date! + } + + input CreatePostInput { + title: String! + content: String! + thumbnail: String + libraryItemIds: [ID!] + highlightIds: [ID!] + thought: String + } + + union CreatePostResult = CreatePostSuccess | CreatePostError + + type CreatePostSuccess { + post: Post! + } + + type CreatePostError { + errorCodes: [CreatePostErrorCode!]! + } + + enum CreatePostErrorCode { + UNAUTHORIZED + BAD_REQUEST + } + + input UpdatePostInput { + id: ID! + title: String + content: String + thumbnail: String + libraryItemIds: [ID!] + highlightIds: [ID!] + thought: String + } + + union UpdatePostResult = UpdatePostSuccess | UpdatePostError + + type UpdatePostSuccess { + post: Post! + } + + type UpdatePostError { + errorCodes: [UpdatePostErrorCode!]! + } + + enum UpdatePostErrorCode { + UNAUTHORIZED + BAD_REQUEST + } + + union DeletePostResult = DeletePostSuccess | DeletePostError + + type DeletePostSuccess { + success: Boolean! + } + + type DeletePostError { + errorCodes: [DeletePostErrorCode!]! + } + + enum DeletePostErrorCode { + UNAUTHORIZED + BAD_REQUEST + } + + union PostsResult = PostsSuccess | PostsError + + type PostsSuccess { + edges: [PostEdge!]! + pageInfo: PageInfo! + } + + type PostEdge { + cursor: String! + node: Post! + } + + type PostsError { + errorCodes: [PostsErrorCode!]! + } + + enum PostsErrorCode { + UNAUTHORIZED + BAD_REQUEST + } + + union PostResult = PostSuccess | PostError + + type PostSuccess { + post: Post! + } + + type PostError { + errorCodes: [PostErrorCode!]! + } + + enum PostErrorCode { + UNAUTHORIZED + BAD_REQUEST + } + # Mutations type Mutation { googleLogin(input: GoogleLoginInput!): LoginResult! @@ -3353,24 +3467,10 @@ const schema = gql` mergeHighlight(input: MergeHighlightInput!): MergeHighlightResult! updateHighlight(input: UpdateHighlightInput!): UpdateHighlightResult! deleteHighlight(highlightId: ID!): DeleteHighlightResult! - # createHighlightReply( - # input: CreateHighlightReplyInput! - # ): CreateHighlightReplyResult! - # updateHighlightReply( - # input: UpdateHighlightReplyInput! - # ): UpdateHighlightReplyResult! - # deleteHighlightReply(highlightReplyId: ID!): DeleteHighlightReplyResult! - # createReaction(input: CreateReactionInput!): CreateReactionResult! - # deleteReaction(id: ID!): DeleteReactionResult! uploadFileRequest(input: UploadFileRequestInput!): UploadFileRequestResult! saveArticleReadingProgress( input: SaveArticleReadingProgressInput! ): SaveArticleReadingProgressResult! - # setShareArticle(input: SetShareArticleInput!): SetShareArticleResult! - # updateSharedComment( - # input: UpdateSharedCommentInput! - # ): UpdateSharedCommentResult! - # setFollow(input: SetFollowInput!): SetFollowResult! setBookmarkArticle( input: SetBookmarkArticleInput! ): SetBookmarkArticleResult! @@ -3380,11 +3480,7 @@ const schema = gql` createArticleSavingRequest( input: CreateArticleSavingRequestInput! ): CreateArticleSavingRequestResult! - # setShareHighlight(input: SetShareHighlightInput!): SetShareHighlightResult! reportItem(input: ReportItemInput!): ReportItemResult! - # updateLinkShareInfo( - # input: UpdateLinkShareInfoInput! - # ): UpdateLinkShareInfoResult! setLinkArchived(input: ArchiveLinkInput!): ArchiveLinkResult! createNewsletterEmail( input: CreateNewsletterEmailInput @@ -3472,6 +3568,9 @@ const schema = gql` input: UpdateFolderPolicyInput! ): UpdateFolderPolicyResult! deleteFolderPolicy(id: ID!): DeleteFolderPolicyResult! + createPost(input: CreatePostInput!): CreatePostResult! + updatePost(input: UpdatePostInput!): UpdatePostResult! + deletePost(id: ID!): DeletePostResult! } # FIXME: remove sort from feedArticles after all cached tabs are closed @@ -3481,17 +3580,6 @@ const schema = gql` me: User user(userId: ID, username: String): UserResult! article(username: String!, slug: String!, format: String): ArticleResult! - # sharedArticle( - # username: String! - # slug: String! - # selectedHighlightId: String - # ): SharedArticleResult! - # feedArticles( - # after: String - # first: Int - # sort: SortParams - # sharedByUser: ID - # ): FeedArticlesResult! users: UsersResult! validateUsername(username: String!): Boolean! # getFollowers(userId: ID): GetFollowersResult! @@ -3547,6 +3635,8 @@ const schema = gql` hiddenHomeSection: HiddenHomeSectionResult! highlights(after: String, first: Int, query: String): HighlightsResult! folderPolicies: FolderPoliciesResult! + posts(userId: ID!, after: String, first: Int): PostsResult! + post(id: ID!): PostResult! } schema { diff --git a/packages/api/src/services/post.ts b/packages/api/src/services/post.ts new file mode 100644 index 000000000..c53df7997 --- /dev/null +++ b/packages/api/src/services/post.ts @@ -0,0 +1,26 @@ +import { Post } from '../entity/post' +import { getRepository } from '../repository' + +export const findPostsByUserId = async ( + userId: string, + limit: number, + offset: number +) => { + const posts = await getRepository(Post).find({ + where: { + user: { + id: userId, + profile: { + private: false, + }, + }, + }, + order: { + createdAt: 'DESC', + }, + take: limit, + skip: offset, + }) + + return posts +} diff --git a/packages/db/migrations/0183.do.post.sql b/packages/db/migrations/0183.do.post.sql index 26f1d5e31..a5e410761 100755 --- a/packages/db/migrations/0183.do.post.sql +++ b/packages/db/migrations/0183.do.post.sql @@ -19,6 +19,24 @@ CREATE TABLE omnivore.post ( CREATE INDEX post_user_id_idx ON omnivore.post(user_id); -ALTER TABLE omnivore.user_profile ALTER COLUMN private SET DEFAULT true; +GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.post TO omnivore_user; + +ALTER TABLE omnivore.post ENABLE ROW LEVEL SECURITY; + +CREATE POLICY read_post ON omnivore.post + FOR SELECT TO omnivore_user + USING (true); + +CREATE POLICY write_post ON omnivore.post + FOR INSERT TO omnivore_user + WITH CHECK (user_id = omnivore.get_current_user_id()); + +CREATE POLICY update_post ON omnivore.post + FOR UPDATE TO omnivore_user + USING (user_id = omnivore.get_current_user_id()); + +CREATE POLICY delete_post ON omnivore.post + FOR DELETE TO omnivore_user + USING (user_id = omnivore.get_current_user_id()); COMMIT; diff --git a/packages/db/migrations/0183.undo.post.sql b/packages/db/migrations/0183.undo.post.sql index aa74c9420..f49547e78 100755 --- a/packages/db/migrations/0183.undo.post.sql +++ b/packages/db/migrations/0183.undo.post.sql @@ -4,8 +4,6 @@ BEGIN; -ALTER TABLE omnivore.user_profile ALTER COLUMN private SET DEFAULT false; - DROP TABLE omnivore.post; COMMIT;