From 1da8dc65450398b6482e803967950c496b3692e1 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Fri, 3 Nov 2023 14:53:46 +0800 Subject: [PATCH] update library_item table --- packages/api/src/entity/feed_item.ts | 53 --------- packages/api/src/generated/graphql.ts | 111 ------------------ packages/api/src/generated/schema.graphql | 41 ------- packages/api/src/resolvers/following/index.ts | 61 ++++++++++ packages/api/src/routers/feed_router.ts | 7 -- packages/api/src/schema.ts | 46 -------- packages/db/migrations/0145.do.feed.sql | 72 ------------ packages/db/migrations/0145.do.following.sql | 39 ++++++ packages/db/migrations/0145.undo.feed.sql | 13 -- .../db/migrations/0145.undo.following.sql | 22 ++++ 10 files changed, 122 insertions(+), 343 deletions(-) delete mode 100644 packages/api/src/entity/feed_item.ts create mode 100644 packages/api/src/resolvers/following/index.ts delete mode 100644 packages/api/src/routers/feed_router.ts delete mode 100755 packages/db/migrations/0145.do.feed.sql create mode 100755 packages/db/migrations/0145.do.following.sql delete mode 100755 packages/db/migrations/0145.undo.feed.sql create mode 100755 packages/db/migrations/0145.undo.following.sql diff --git a/packages/api/src/entity/feed_item.ts b/packages/api/src/entity/feed_item.ts deleted file mode 100644 index 541e6e194..000000000 --- a/packages/api/src/entity/feed_item.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - Column, - CreateDateColumn, - Entity, - JoinColumn, - ManyToOne, - PrimaryGeneratedColumn, - UpdateDateColumn, -} from 'typeorm' -import { Feed } from './feed' - -@Entity() -export class FeedItem { - @PrimaryGeneratedColumn('uuid') - id!: string - - @Column('text') - guid!: string - - @Column('text') - title!: string - - @Column('array') - links!: string[] - - @Column('text') - author?: string | null - - @Column('text') - summary?: string | null - - @Column('array') - categories?: string[] | null - - @Column('text') - content?: string | null - - @Column('text') - previewContent?: string | null - - @CreateDateColumn({ default: () => 'CURRENT_TIMESTAMP' }) - createdAt!: Date - - @UpdateDateColumn({ default: () => 'CURRENT_TIMESTAMP' }) - updatedAt!: Date - - @Column('timestamptz') - publishedAt?: Date | null - - @ManyToOne(() => Feed, { onDelete: 'CASCADE' }) - @JoinColumn({ name: 'feed_id' }) - feed!: Feed -} diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index 8dfbe5a08..849afba50 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -837,50 +837,6 @@ export type FiltersSuccess = { filters: Array; }; -export type Following = { - __typename?: 'Following'; - author?: Maybe; - categories?: Maybe>; - content?: Maybe; - createdAt: Scalars['Date']; - feedItemId: Scalars['ID']; - hiddenAt?: Maybe; - id: Scalars['ID']; - isHidden: Scalars['Boolean']; - isSaved: Scalars['Boolean']; - links: Array; - previewContent?: Maybe; - publishedAt?: Maybe; - savedAt?: Maybe; - summary?: Maybe; - title: Scalars['String']; - updatedAt: Scalars['Date']; -}; - -export type FollowingEdge = { - __typename?: 'FollowingEdge'; - cursor: Scalars['String']; - node: Following; -}; - -export type FollowingError = { - __typename?: 'FollowingError'; - errorCodes: Array; -}; - -export enum FollowingErrorCode { - BadRequest = 'BAD_REQUEST', - Unauthorized = 'UNAUTHORIZED' -} - -export type FollowingResult = FollowingError | FollowingSuccess; - -export type FollowingSuccess = { - __typename?: 'FollowingSuccess'; - edges: Array; - pageInfo: PageInfo; -}; - export type GenerateApiKeyError = { __typename?: 'GenerateApiKeyError'; errorCodes: Array; @@ -1815,7 +1771,6 @@ export type Query = { deviceTokens: DeviceTokensResult; feeds: FeedsResult; filters: FiltersResult; - following: FollowingResult; getUserPersonalization: GetUserPersonalizationResult; groups: GroupsResult; hello?: Maybe; @@ -1857,14 +1812,6 @@ export type QueryFeedsArgs = { }; -export type QueryFollowingArgs = { - after?: InputMaybe; - first?: InputMaybe; - since?: InputMaybe; - until?: InputMaybe; -}; - - export type QueryRulesArgs = { enabled?: InputMaybe; }; @@ -3581,12 +3528,6 @@ export type ResolversTypes = { FiltersResult: ResolversTypes['FiltersError'] | ResolversTypes['FiltersSuccess']; FiltersSuccess: ResolverTypeWrapper; Float: ResolverTypeWrapper; - Following: ResolverTypeWrapper; - FollowingEdge: ResolverTypeWrapper; - FollowingError: ResolverTypeWrapper; - FollowingErrorCode: FollowingErrorCode; - FollowingResult: ResolversTypes['FollowingError'] | ResolversTypes['FollowingSuccess']; - FollowingSuccess: ResolverTypeWrapper; GenerateApiKeyError: ResolverTypeWrapper; GenerateApiKeyErrorCode: GenerateApiKeyErrorCode; GenerateApiKeyInput: GenerateApiKeyInput; @@ -4071,11 +4012,6 @@ export type ResolversParentTypes = { FiltersResult: ResolversParentTypes['FiltersError'] | ResolversParentTypes['FiltersSuccess']; FiltersSuccess: FiltersSuccess; Float: Scalars['Float']; - Following: Following; - FollowingEdge: FollowingEdge; - FollowingError: FollowingError; - FollowingResult: ResolversParentTypes['FollowingError'] | ResolversParentTypes['FollowingSuccess']; - FollowingSuccess: FollowingSuccess; GenerateApiKeyError: GenerateApiKeyError; GenerateApiKeyInput: GenerateApiKeyInput; GenerateApiKeyResult: ResolversParentTypes['GenerateApiKeyError'] | ResolversParentTypes['GenerateApiKeySuccess']; @@ -4958,47 +4894,6 @@ export type FiltersSuccessResolvers; }; -export type FollowingResolvers = { - author?: Resolver, ParentType, ContextType>; - categories?: Resolver>, ParentType, ContextType>; - content?: Resolver, ParentType, ContextType>; - createdAt?: Resolver; - feedItemId?: Resolver; - hiddenAt?: Resolver, ParentType, ContextType>; - id?: Resolver; - isHidden?: Resolver; - isSaved?: Resolver; - links?: Resolver, ParentType, ContextType>; - previewContent?: Resolver, ParentType, ContextType>; - publishedAt?: Resolver, ParentType, ContextType>; - savedAt?: Resolver, ParentType, ContextType>; - summary?: Resolver, ParentType, ContextType>; - title?: Resolver; - updatedAt?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type FollowingEdgeResolvers = { - cursor?: Resolver; - node?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type FollowingErrorResolvers = { - errorCodes?: Resolver, ParentType, ContextType>; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type FollowingResultResolvers = { - __resolveType: TypeResolveFn<'FollowingError' | 'FollowingSuccess', ParentType, ContextType>; -}; - -export type FollowingSuccessResolvers = { - edges?: Resolver, ParentType, ContextType>; - pageInfo?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type GenerateApiKeyErrorResolvers = { errorCodes?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -5470,7 +5365,6 @@ export type QueryResolvers; feeds?: Resolver>; filters?: Resolver; - following?: Resolver>; getUserPersonalization?: Resolver; groups?: Resolver; hello?: Resolver, ParentType, ContextType>; @@ -6505,11 +6399,6 @@ export type Resolvers = { FiltersError?: FiltersErrorResolvers; FiltersResult?: FiltersResultResolvers; FiltersSuccess?: FiltersSuccessResolvers; - Following?: FollowingResolvers; - FollowingEdge?: FollowingEdgeResolvers; - FollowingError?: FollowingErrorResolvers; - FollowingResult?: FollowingResultResolvers; - FollowingSuccess?: FollowingSuccessResolvers; GenerateApiKeyError?: GenerateApiKeyErrorResolvers; GenerateApiKeyResult?: GenerateApiKeyResultResolvers; GenerateApiKeySuccess?: GenerateApiKeySuccessResolvers; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index b071c75df..8b704c249 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -742,46 +742,6 @@ type FiltersSuccess { filters: [Filter!]! } -type Following { - author: String - categories: [String!] - content: String - createdAt: Date! - feedItemId: ID! - hiddenAt: Date - id: ID! - isHidden: Boolean! - isSaved: Boolean! - links: [String!]! - previewContent: String - publishedAt: Date - savedAt: Date - summary: String - title: String! - updatedAt: Date! -} - -type FollowingEdge { - cursor: String! - node: Following! -} - -type FollowingError { - errorCodes: [FollowingErrorCode!]! -} - -enum FollowingErrorCode { - BAD_REQUEST - UNAUTHORIZED -} - -union FollowingResult = FollowingError | FollowingSuccess - -type FollowingSuccess { - edges: [FollowingEdge!]! - pageInfo: PageInfo! -} - type GenerateApiKeyError { errorCodes: [GenerateApiKeyErrorCode!]! } @@ -1376,7 +1336,6 @@ type Query { deviceTokens: DeviceTokensResult! feeds(input: FeedsInput!): FeedsResult! filters: FiltersResult! - following(after: String, first: Int, since: Date, until: Date): FollowingResult! getUserPersonalization: GetUserPersonalizationResult! groups: GroupsResult! hello: String diff --git a/packages/api/src/resolvers/following/index.ts b/packages/api/src/resolvers/following/index.ts new file mode 100644 index 000000000..08c219436 --- /dev/null +++ b/packages/api/src/resolvers/following/index.ts @@ -0,0 +1,61 @@ +import { + FeedEdge, + FeedsError, + FeedsErrorCode, + FeedsSuccess, + QueryFeedsArgs, +} from '../../generated/graphql' +import { feedRepository } from '../../repository/feed' +import { authorized } from '../../utils/helpers' + +export const feedsResolve = authorized< + FeedsSuccess, + FeedsError, + QueryFeedsArgs +>(async (_, { input }, { log }) => { + try { + const startCursor = input.after || '' + const start = + startCursor && !isNaN(Number(startCursor)) ? Number(startCursor) : 0 + const first = Math.min(input.first || 10, 100) // cap at 100 + + const { feeds, count } = await feedRepository.searchFeeds( + input.query || '', + first + 1, // fetch one extra to check if there is a next page + start, + input.sort?.by, + input.sort?.order || undefined + ) + + const hasNextPage = feeds.length > first + const endCursor = String(start + feeds.length - (hasNextPage ? 1 : 0)) + + if (hasNextPage) { + // remove an extra if exists + feeds.pop() + } + + const edges: FeedEdge[] = feeds.map((feed) => ({ + node: feed, + cursor: endCursor, + })) + + return { + __typename: 'FeedsSuccess', + edges, + pageInfo: { + hasPreviousPage: start > 0, + hasNextPage, + startCursor, + endCursor, + totalCount: count, + }, + } + } catch (error) { + log.error('Error fetching feeds', error) + + return { + errorCodes: [FeedsErrorCode.BadRequest], + } + } +}) diff --git a/packages/api/src/routers/feed_router.ts b/packages/api/src/routers/feed_router.ts deleted file mode 100644 index 9480a29da..000000000 --- a/packages/api/src/routers/feed_router.ts +++ /dev/null @@ -1,7 +0,0 @@ -import express from "express"; - -export function feedRouter() { - const router = express.Router() - - router.post('/') -} diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index fd961d197..4cd073b8f 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -2645,46 +2645,6 @@ const schema = gql` author: String } - union FollowingResult = FollowingSuccess | FollowingError - - type FollowingSuccess { - edges: [FollowingEdge!]! - pageInfo: PageInfo! - } - - type FollowingEdge { - cursor: String! - node: Following! - } - - type FollowingError { - errorCodes: [FollowingErrorCode!]! - } - - enum FollowingErrorCode { - UNAUTHORIZED - BAD_REQUEST - } - - type Following { - id: ID! - feedItemId: ID! - title: String! - links: [String!]! - author: String - summary: String - categories: [String!] - content: String - previewContent: String - createdAt: Date! - updatedAt: Date! - savedAt: Date - hiddenAt: Date - publishedAt: Date - isHidden: Boolean! - isSaved: Boolean! - } - # Mutations type Mutation { googleLogin(input: GoogleLoginInput!): LoginResult! @@ -2847,12 +2807,6 @@ const schema = gql` groups: GroupsResult! recentEmails: RecentEmailsResult! feeds(input: FeedsInput!): FeedsResult! - following( - after: String - first: Int - since: Date - until: Date - ): FollowingResult! } ` diff --git a/packages/db/migrations/0145.do.feed.sql b/packages/db/migrations/0145.do.feed.sql deleted file mode 100755 index a97e176a6..000000000 --- a/packages/db/migrations/0145.do.feed.sql +++ /dev/null @@ -1,72 +0,0 @@ --- Type: DO --- Name: feed --- Description: Create feed, feed_item and user_feed_item tables - -BEGIN; - -CREATE TABLE omnivore.feed ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(), - title text NOT NULL, - url text NOT NULL, - author text, - description text, - image text, - created_at timestamptz NOT NULL DEFAULT current_timestamp, - updated_at timestamptz NOT NULL DEFAULT current_timestamp, - published_at timestamptz, - UNIQUE(url) -); - -CREATE INDEX IF NOT EXISTS feed_title_idx ON omnivore.feed(title); - -CREATE TRIGGER update_feed_modtime BEFORE UPDATE ON omnivore.feed FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); - -GRANT SELECT, INSERT, UPDATE ON omnivore.feed TO omnivore_user; - -CREATE TABLE omnivore.feed_item ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(), - guid text NOT NULL, - title text NOT NULL, - links text[] NOT NULL, - author text, - summary text, - categories text[], - content text, - preview_content text, - created_at timestamptz NOT NULL DEFAULT current_timestamp, - updated_at timestamptz NOT NULL DEFAULT current_timestamp, - published_at timestamptz, - feed_id uuid NOT NULL REFERENCES omnivore.feed(id) ON DELETE CASCADE, - UNIQUE(guid) -); - -CREATE INDEX IF NOT EXISTS feed_item_feed_id_idx ON omnivore.feed_item(feed_id); - -CREATE TRIGGER update_feed_item_modtime BEFORE UPDATE ON omnivore.feed_item FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); - -GRANT SELECT, INSERT, UPDATE ON omnivore.feed_item TO omnivore_user; - -CREATE TABLE omnivore.user_feed_item ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(), - user_id uuid NOT NULL REFERENCES omnivore.user(id) ON DELETE CASCADE, - feed_item_id uuid NOT NULL REFERENCES omnivore.feed_item(id) ON DELETE CASCADE, - created_at timestamptz NOT NULL DEFAULT current_timestamp, - updated_at timestamptz NOT NULL DEFAULT current_timestamp, - hidden_at timestamptz, - saved_at timestamptz -); - -CREATE INDEX IF NOT EXISTS user_feed_item_user_id_idx ON omnivore.user_feed_item(user_id); -CREATE INDEX IF NOT EXISTS user_feed_item_feed_item_id_idx ON omnivore.user_feed_item(feed_item_id); - -CREATE TRIGGER update_user_feed_item_modtime BEFORE UPDATE ON omnivore.user_feed_item FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); - -ALTER TABLE omnivore.user_feed_item ENABLE ROW LEVEL SECURITY; - -CREATE POLICY user_feed_item_policy ON omnivore.user_feed_item - USING (user_id = omnivore.get_current_user_id()) - WITH CHECK (user_id = omnivore.get_current_user_id()); - -GRANT SELECT, INSERT, UPDATE ON omnivore.user_feed_item TO omnivore_user; - -COMMIT; diff --git a/packages/db/migrations/0145.do.following.sql b/packages/db/migrations/0145.do.following.sql new file mode 100755 index 000000000..dcf3ab65d --- /dev/null +++ b/packages/db/migrations/0145.do.following.sql @@ -0,0 +1,39 @@ +-- Type: DO +-- Name: following +-- Description: Create tables for following feature + +BEGIN; + +ALTER TABLE omnivore.subscriptions + ADD COLUMN is_public boolean, + ADD COLUMN is_fetching_content boolean; + +ALTER TABLE omnivore.library_item + ADD COLUMN hidden_at timestamptz, + ADD COLUMN shared_at timestamptz, + ADD COLUMN shared_by text, + ADD COLUMN links jsonb, + ADD COLUMN preview_content text, + ADD COLUMN seen_at timestamptz, + ALTER COLUMN saved_at DROP NOT NULL; + +CREATE TABLE omnivore.feed ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(), + title text NOT NULL, + url text NOT NULL, + author text, + description text, + image text, + created_at timestamptz NOT NULL DEFAULT current_timestamp, + updated_at timestamptz NOT NULL DEFAULT current_timestamp, + published_at timestamptz, + UNIQUE(url) +); + +CREATE INDEX feed_title_idx ON omnivore.feed(title); + +CREATE TRIGGER update_feed_modtime BEFORE UPDATE ON omnivore.feed FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); + +GRANT SELECT, INSERT, UPDATE ON omnivore.feed TO omnivore_user; + +COMMIT; diff --git a/packages/db/migrations/0145.undo.feed.sql b/packages/db/migrations/0145.undo.feed.sql deleted file mode 100755 index 92770ee98..000000000 --- a/packages/db/migrations/0145.undo.feed.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Type: UNDO --- Name: feed --- Description: Create feed, feed_item and user_feed_item tables - -BEGIN; - -DROP TABLE IF EXISTS omnivore.user_feed_item; - -DROP TABLE IF EXISTS omnivore.feed_item; - -DROP TABLE IF EXISTS omnivore.feed; - -COMMIT; diff --git a/packages/db/migrations/0145.undo.following.sql b/packages/db/migrations/0145.undo.following.sql new file mode 100755 index 000000000..1b1e13de2 --- /dev/null +++ b/packages/db/migrations/0145.undo.following.sql @@ -0,0 +1,22 @@ +-- Type: UNDO +-- Name: following +-- Description: Create tables for following feature + +BEGIN; + +DROP TABLE omnivore.feed; + +ALTER TABLE omnivore.library_item + DROP COLUMN hidden_at, + DROP COLUMN shared_at, + DROP COLUMN shared_by, + DROP COLUMN links, + DROP COLUMN preview_content, + DROP COLUMN seen_at, + ALTER COLUMN saved_at SET NOT NULL; + +ALTER TABLE omnivore.subscriptions + DROP COLUMN is_public, + DROP COLUMN is_fetching_content; + +COMMIT;