From cd13e5874a20f6d0619cf9062880b5eb096ffae5 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 10 Oct 2022 17:44:55 +0800 Subject: [PATCH 1/9] Create search_history table and entity class --- packages/api/src/entity/search_history.ts | 29 +++++++++++++++++++ .../db/migrations/0097.do.search_history.sql | 17 +++++++++++ .../migrations/0097.undo.search_history.sql | 9 ++++++ 3 files changed, 55 insertions(+) create mode 100644 packages/api/src/entity/search_history.ts create mode 100755 packages/db/migrations/0097.do.search_history.sql create mode 100755 packages/db/migrations/0097.undo.search_history.sql diff --git a/packages/api/src/entity/search_history.ts b/packages/api/src/entity/search_history.ts new file mode 100644 index 000000000..99cf4044f --- /dev/null +++ b/packages/api/src/entity/search_history.ts @@ -0,0 +1,29 @@ +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm' +import { User } from './user' + +@Entity({ name: 'search_history' }) +export class SearchHistory { + @PrimaryGeneratedColumn('uuid') + id!: string + + @ManyToOne(() => User, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'user_id' }) + user!: User + + @Column('varchar', { length: 255 }) + keyword!: string + + @CreateDateColumn({ default: () => 'CURRENT_TIMESTAMP' }) + createdAt!: Date + + @UpdateDateColumn({ default: () => 'CURRENT_TIMESTAMP' }) + updatedAt!: Date +} diff --git a/packages/db/migrations/0097.do.search_history.sql b/packages/db/migrations/0097.do.search_history.sql new file mode 100755 index 000000000..205bb66c0 --- /dev/null +++ b/packages/db/migrations/0097.do.search_history.sql @@ -0,0 +1,17 @@ +-- Type: DO +-- Name: search_history +-- Description: Create search_history table which contains searched keyword and timestamp + +BEGIN; + +CREATE TABLE omnivore.search_history ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(), + user_id uuid NOT NULL REFERENCES omnivore.user ON DELETE CASCADE, + keyword VARCHAR(255) NOT NULL, + created_at timestamptz NOT NULL DEFAULT current_timestamp, + updated_at timestamptz NOT NULL DEFAULT current_timestamp +); + +CREATE TRIGGER search_history_modtime BEFORE UPDATE ON omnivore.search_history FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); + +COMMIT; diff --git a/packages/db/migrations/0097.undo.search_history.sql b/packages/db/migrations/0097.undo.search_history.sql new file mode 100755 index 000000000..f6729d9dd --- /dev/null +++ b/packages/db/migrations/0097.undo.search_history.sql @@ -0,0 +1,9 @@ +-- Type: UNDO +-- Name: search_history +-- Description: Create search_history table which contains searched keyword and timestamp + +BEGIN; + +DROP TABLE IF EXISTS omnivore.search_history; + +COMMIT; From 4e14518189094dad9ac39ea16beab9e8aa756599 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 11 Oct 2022 11:26:18 +0800 Subject: [PATCH 2/9] Replace keyword with term --- packages/api/src/entity/search_history.ts | 2 +- packages/db/migrations/0097.do.search_history.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api/src/entity/search_history.ts b/packages/api/src/entity/search_history.ts index 99cf4044f..6796c1997 100644 --- a/packages/api/src/entity/search_history.ts +++ b/packages/api/src/entity/search_history.ts @@ -19,7 +19,7 @@ export class SearchHistory { user!: User @Column('varchar', { length: 255 }) - keyword!: string + term!: string @CreateDateColumn({ default: () => 'CURRENT_TIMESTAMP' }) createdAt!: Date diff --git a/packages/db/migrations/0097.do.search_history.sql b/packages/db/migrations/0097.do.search_history.sql index 205bb66c0..74486d70f 100755 --- a/packages/db/migrations/0097.do.search_history.sql +++ b/packages/db/migrations/0097.do.search_history.sql @@ -7,7 +7,7 @@ BEGIN; CREATE TABLE omnivore.search_history ( id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(), user_id uuid NOT NULL REFERENCES omnivore.user ON DELETE CASCADE, - keyword VARCHAR(255) NOT NULL, + term VARCHAR(255) NOT NULL, created_at timestamptz NOT NULL DEFAULT current_timestamp, updated_at timestamptz NOT NULL DEFAULT current_timestamp ); From a5c0ef19ec45b74fea90d4acc5f5ccbbd693f93c Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 11 Oct 2022 14:53:48 +0800 Subject: [PATCH 3/9] Add recentSearches API GQL schema --- packages/api/src/generated/graphql.ts | 60 +++++++++++++++++++++++ packages/api/src/generated/schema.graphql | 22 +++++++++ packages/api/src/schema.ts | 22 +++++++++ 3 files changed, 104 insertions(+) diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index 24f683035..63d11bc1e 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -1351,6 +1351,7 @@ export type Query = { labels: LabelsResult; me?: Maybe; newsletterEmails: NewsletterEmailsResult; + recentSearches: RecentSearchesResult; reminder: ReminderResult; search: SearchResult; sendInstallInstructions: SendInstallInstructionsResult; @@ -1483,6 +1484,30 @@ export type ReadState = { readingTime?: Maybe; }; +export type RecentSearch = { + __typename?: 'RecentSearch'; + createdAt: Scalars['Date']; + id: Scalars['ID']; + term: Scalars['String']; +}; + +export type RecentSearchesError = { + __typename?: 'RecentSearchesError'; + errorCodes: Array; +}; + +export enum RecentSearchesErrorCode { + BadRequest = 'BAD_REQUEST', + Unauthorized = 'UNAUTHORIZED' +} + +export type RecentSearchesResult = RecentSearchesError | RecentSearchesSuccess; + +export type RecentSearchesSuccess = { + __typename?: 'RecentSearchesSuccess'; + searches: Array; +}; + export type Reminder = { __typename?: 'Reminder'; archiveUntil: Scalars['Boolean']; @@ -2734,6 +2759,11 @@ export type ResolversTypes = { Reaction: ResolverTypeWrapper; ReactionType: ReactionType; ReadState: ResolverTypeWrapper; + RecentSearch: ResolverTypeWrapper; + RecentSearchesError: ResolverTypeWrapper; + RecentSearchesErrorCode: RecentSearchesErrorCode; + RecentSearchesResult: ResolversTypes['RecentSearchesError'] | ResolversTypes['RecentSearchesSuccess']; + RecentSearchesSuccess: ResolverTypeWrapper; Reminder: ResolverTypeWrapper; ReminderError: ResolverTypeWrapper; ReminderErrorCode: ReminderErrorCode; @@ -3075,6 +3105,10 @@ export type ResolversParentTypes = { Query: {}; Reaction: Reaction; ReadState: ReadState; + RecentSearch: RecentSearch; + RecentSearchesError: RecentSearchesError; + RecentSearchesResult: ResolversParentTypes['RecentSearchesError'] | ResolversParentTypes['RecentSearchesSuccess']; + RecentSearchesSuccess: RecentSearchesSuccess; Reminder: Reminder; ReminderError: ReminderError; ReminderResult: ResolversParentTypes['ReminderError'] | ResolversParentTypes['ReminderSuccess']; @@ -4031,6 +4065,7 @@ export type QueryResolvers; me?: Resolver, ParentType, ContextType>; newsletterEmails?: Resolver; + recentSearches?: Resolver; reminder?: Resolver>; search?: Resolver>; sendInstallInstructions?: Resolver; @@ -4062,6 +4097,27 @@ export type ReadStateResolvers; }; +export type RecentSearchResolvers = { + createdAt?: Resolver; + id?: Resolver; + term?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type RecentSearchesErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type RecentSearchesResultResolvers = { + __resolveType: TypeResolveFn<'RecentSearchesError' | 'RecentSearchesSuccess', ParentType, ContextType>; +}; + +export type RecentSearchesSuccessResolvers = { + searches?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type ReminderResolvers = { archiveUntil?: Resolver; id?: Resolver; @@ -4827,6 +4883,10 @@ export type Resolvers = { Query?: QueryResolvers; Reaction?: ReactionResolvers; ReadState?: ReadStateResolvers; + RecentSearch?: RecentSearchResolvers; + RecentSearchesError?: RecentSearchesErrorResolvers; + RecentSearchesResult?: RecentSearchesResultResolvers; + RecentSearchesSuccess?: RecentSearchesSuccessResolvers; Reminder?: ReminderResolvers; ReminderError?: ReminderErrorResolvers; ReminderResult?: ReminderResultResolvers; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index a15f5c497..ecd1d4c25 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -983,6 +983,7 @@ type Query { labels: LabelsResult! me: User newsletterEmails: NewsletterEmailsResult! + recentSearches: RecentSearchesResult! reminder(linkId: ID!): ReminderResult! search(after: String, first: Int, query: String): SearchResult! sendInstallInstructions: SendInstallInstructionsResult! @@ -1021,6 +1022,27 @@ type ReadState { readingTime: Int } +type RecentSearch { + createdAt: Date! + id: ID! + term: String! +} + +type RecentSearchesError { + errorCodes: [RecentSearchesErrorCode!]! +} + +enum RecentSearchesErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union RecentSearchesResult = RecentSearchesError | RecentSearchesSuccess + +type RecentSearchesSuccess { + searches: [RecentSearch!]! +} + type Reminder { archiveUntil: Boolean! id: ID! diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index c2e6f3805..dfd54c776 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -1885,6 +1885,27 @@ const schema = gql` NOT_FOUND } + union RecentSearchesResult = RecentSearchesSuccess | RecentSearchesError + + type RecentSearchesSuccess { + searches: [RecentSearch!]! + } + + type RecentSearch { + id: ID! + term: String! + createdAt: Date! + } + + type RecentSearchesError { + errorCodes: [RecentSearchesErrorCode!]! + } + + enum RecentSearchesErrorCode { + UNAUTHORIZED + BAD_REQUEST + } + # Mutations type Mutation { googleLogin(input: GoogleLoginInput!): LoginResult! @@ -2002,6 +2023,7 @@ const schema = gql` typeaheadSearch(query: String!, first: Int): TypeaheadSearchResult! updatesSince(after: String, first: Int, since: Date!): UpdatesSinceResult! integrations: IntegrationsResult! + recentSearches: RecentSearchesResult! } ` From ff1267571383b67d42e402b2a4601d996c5dfc5d Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 12 Oct 2022 10:37:29 +0800 Subject: [PATCH 4/9] Make user_id and term unique --- packages/api/src/entity/search_history.ts | 4 ---- packages/db/migrations/0097.do.search_history.sql | 4 +--- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/api/src/entity/search_history.ts b/packages/api/src/entity/search_history.ts index 6796c1997..e10a79dff 100644 --- a/packages/api/src/entity/search_history.ts +++ b/packages/api/src/entity/search_history.ts @@ -5,7 +5,6 @@ import { JoinColumn, ManyToOne, PrimaryGeneratedColumn, - UpdateDateColumn, } from 'typeorm' import { User } from './user' @@ -23,7 +22,4 @@ export class SearchHistory { @CreateDateColumn({ default: () => 'CURRENT_TIMESTAMP' }) createdAt!: Date - - @UpdateDateColumn({ default: () => 'CURRENT_TIMESTAMP' }) - updatedAt!: Date } diff --git a/packages/db/migrations/0097.do.search_history.sql b/packages/db/migrations/0097.do.search_history.sql index 74486d70f..23eee07ba 100755 --- a/packages/db/migrations/0097.do.search_history.sql +++ b/packages/db/migrations/0097.do.search_history.sql @@ -9,9 +9,7 @@ CREATE TABLE omnivore.search_history ( user_id uuid NOT NULL REFERENCES omnivore.user ON DELETE CASCADE, term VARCHAR(255) NOT NULL, created_at timestamptz NOT NULL DEFAULT current_timestamp, - updated_at timestamptz NOT NULL DEFAULT current_timestamp + unique (user_id, term) ); -CREATE TRIGGER search_history_modtime BEFORE UPDATE ON omnivore.search_history FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); - COMMIT; From fd1d43c10441874c8f4199c25f24c098ad102e1c Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 12 Oct 2022 10:49:07 +0800 Subject: [PATCH 5/9] Add search history repo methods --- packages/api/src/services/search_history.ts | 38 +++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 packages/api/src/services/search_history.ts diff --git a/packages/api/src/services/search_history.ts b/packages/api/src/services/search_history.ts new file mode 100644 index 000000000..2521ea64a --- /dev/null +++ b/packages/api/src/services/search_history.ts @@ -0,0 +1,38 @@ +import { SearchHistory } from '../entity/search_history' +import { getRepository } from '../entity/utils' + +export const getRecentSearches = async ( + userId: string +): Promise => { + // get top 10 recent searches + return getRepository(SearchHistory).find({ + where: { user: { id: userId } }, + order: { createdAt: 'DESC' }, + take: 10, + }) +} + +export const saveSearchHistory = async ( + userId: string, + term: string +): Promise => { + const searchHistory = new SearchHistory() + searchHistory.user = { id: userId } as any + searchHistory.term = term + searchHistory.createdAt = new Date() + await getRepository(SearchHistory).save(searchHistory) +} + +export const deleteSearchHistory = async (userId: string): Promise => { + await getRepository(SearchHistory).delete({ user: { id: userId } }) +} + +export const deleteSearchHistoryById = async ( + userId: string, + searchHistoryId: string +): Promise => { + await getRepository(SearchHistory).delete({ + user: { id: userId }, + id: searchHistoryId, + }) +} From 1725f96d825f24cb4510de5e3f0de58216c2f008 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 12 Oct 2022 12:20:48 +0800 Subject: [PATCH 6/9] Add recentSearches API implementation and test --- .../api/src/resolvers/function_resolvers.ts | 3 + .../src/resolvers/recent_searches/index.ts | 36 +++++++++ packages/api/src/services/search_history.ts | 2 +- .../test/resolvers/recent_searches.test.ts | 74 +++++++++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 packages/api/src/resolvers/recent_searches/index.ts create mode 100644 packages/api/test/resolvers/recent_searches.test.ts diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index 1b957a371..50cd8580b 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -100,6 +100,7 @@ import { generateUploadFilePathName, } from '../utils/uploads' import { getPageByParam } from '../elastic/pages' +import { recentSearchesResolver } from './recent_searches' /* eslint-disable @typescript-eslint/naming-convention */ type ResultResolveType = { @@ -196,6 +197,7 @@ export const functionResolvers = { typeaheadSearch: typeaheadSearchResolver, updatesSince: updatesSinceResolver, integrations: integrationsResolver, + recentSearches: recentSearchesResolver, }, User: { async sharedArticles( @@ -604,4 +606,5 @@ export const functionResolvers = { ...resultResolveTypeResolver('SetIntegration'), ...resultResolveTypeResolver('Integrations'), ...resultResolveTypeResolver('DeleteIntegration'), + ...resultResolveTypeResolver('RecentSearches'), } diff --git a/packages/api/src/resolvers/recent_searches/index.ts b/packages/api/src/resolvers/recent_searches/index.ts new file mode 100644 index 000000000..d8efadf9e --- /dev/null +++ b/packages/api/src/resolvers/recent_searches/index.ts @@ -0,0 +1,36 @@ +import { authorized } from '../../utils/helpers' +import { + RecentSearchesError, + RecentSearchesErrorCode, + RecentSearchesSuccess, +} from '../../generated/graphql' +import { analytics } from '../../utils/analytics' +import { env } from '../../env' +import { getRepository } from '../../entity/utils' +import { User } from '../../entity/user' +import { getRecentSearches } from '../../services/search_history' + +export const recentSearchesResolver = authorized< + RecentSearchesSuccess, + RecentSearchesError +>(async (_obj, _params, { claims: { uid }, log }) => { + log.info('recentSearches') + + analytics.track({ + userId: uid, + event: 'recentSearches', + properties: { + env: env.server.apiEnv, + }, + }) + + const user = await getRepository(User).findOneBy({ id: uid }) + if (!user) { + return { errorCodes: [RecentSearchesErrorCode.Unauthorized] } + } + + const searches = await getRecentSearches(uid) + return { + searches, + } +}) diff --git a/packages/api/src/services/search_history.ts b/packages/api/src/services/search_history.ts index 2521ea64a..561d04782 100644 --- a/packages/api/src/services/search_history.ts +++ b/packages/api/src/services/search_history.ts @@ -17,7 +17,7 @@ export const saveSearchHistory = async ( term: string ): Promise => { const searchHistory = new SearchHistory() - searchHistory.user = { id: userId } as any + searchHistory.user.id = userId searchHistory.term = term searchHistory.createdAt = new Date() await getRepository(SearchHistory).save(searchHistory) diff --git a/packages/api/test/resolvers/recent_searches.test.ts b/packages/api/test/resolvers/recent_searches.test.ts new file mode 100644 index 000000000..4d6a95c67 --- /dev/null +++ b/packages/api/test/resolvers/recent_searches.test.ts @@ -0,0 +1,74 @@ +import 'mocha' +import { expect } from 'chai' +import { User } from '../../src/entity/user' +import { PageContext } from '../../src/elastic/types' +import { createTestUser, deleteTestUser } from '../db' +import { graphqlRequest, request } from '../util' +import { createPubSubClient } from '../../src/datalayer/pubsub' +import { getRepository } from '../../src/entity/utils' +import { SearchHistory } from '../../src/entity/search_history' + +describe('recent_searches resolver', () => { + let user: User + let authToken: string + let ctx: PageContext + + before(async () => { + // create fake user and login + user = await createTestUser('fakeUser') + const res = await request + .post('/local/debug/fake-user-login') + .send({ fakeEmail: user.email }) + authToken = res.body.authToken + ctx = { + pubsub: createPubSubClient(), + refresh: true, + uid: user.id, + } + }) + + after(async () => { + // clean up + await deleteTestUser(user.name) + }) + + describe('recentSearches API', () => { + const recentSearchesQuery = ` + query { + recentSearches { + ... on RecentSearchesSuccess { + searches { + term + } + } + } + } + ` + + before(async () => { + // create fake recent searches + await getRepository(SearchHistory).save([ + { + user: { id: user.id }, + term: 'test1', + }, + { + user: { id: user.id }, + term: 'test2', + }, + ]) + }) + + after(async () => { + await getRepository(SearchHistory).delete({ user: { id: user.id } }) + }) + + it('returns recent searches', async () => { + const response = await graphqlRequest( + recentSearchesQuery, + authToken + ).expect(200) + expect(response.body.data.recentSearches.searches).to.be.lengthOf(2) + }) + }) +}) From 8f96c167f86bb511572ee008a0765a17f738f872 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 12 Oct 2022 12:30:22 +0800 Subject: [PATCH 7/9] Save search term in search history --- packages/api/src/resolvers/article/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index 40b77af12..c94c5f5e8 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -95,6 +95,7 @@ import { updatePage, } from '../../elastic/pages' import { searchHighlights } from '../../elastic/highlights' +import { saveSearchHistory } from '../../services/search_history' export type PartialArticle = Omit< Article, @@ -905,6 +906,11 @@ export const searchResolver = authorized< } }) + // save in search history + if (searchQuery.query) { + await saveSearchHistory(claims.uid, searchQuery.query) + } + return { edges, pageInfo: { From d4f32f7bddc91140b69bc76ec2086ad311769542 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 12 Oct 2022 13:27:19 +0800 Subject: [PATCH 8/9] Save search term in search history and update timestamp --- packages/api/src/entity/search_history.ts | 2 ++ packages/api/src/services/search_history.ts | 15 +++++++---- packages/api/test/resolvers/article.test.ts | 28 +++++++++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/packages/api/src/entity/search_history.ts b/packages/api/src/entity/search_history.ts index e10a79dff..ab0e051ab 100644 --- a/packages/api/src/entity/search_history.ts +++ b/packages/api/src/entity/search_history.ts @@ -5,10 +5,12 @@ import { JoinColumn, ManyToOne, PrimaryGeneratedColumn, + Unique, } from 'typeorm' import { User } from './user' @Entity({ name: 'search_history' }) +@Unique('search_history_user_id_term_key', ['user', 'term']) export class SearchHistory { @PrimaryGeneratedColumn('uuid') id!: string diff --git a/packages/api/src/services/search_history.ts b/packages/api/src/services/search_history.ts index 561d04782..59da78836 100644 --- a/packages/api/src/services/search_history.ts +++ b/packages/api/src/services/search_history.ts @@ -16,11 +16,16 @@ export const saveSearchHistory = async ( userId: string, term: string ): Promise => { - const searchHistory = new SearchHistory() - searchHistory.user.id = userId - searchHistory.term = term - searchHistory.createdAt = new Date() - await getRepository(SearchHistory).save(searchHistory) + await getRepository(SearchHistory).upsert( + { + user: { id: userId }, + term, + createdAt: new Date(), + }, + { + conflictPaths: ['user', 'term'], + } + ) } export const deleteSearchHistory = async (userId: string): Promise => { diff --git a/packages/api/test/resolvers/article.test.ts b/packages/api/test/resolvers/article.test.ts index d64a23d4e..950bd85c0 100644 --- a/packages/api/test/resolvers/article.test.ts +++ b/packages/api/test/resolvers/article.test.ts @@ -34,6 +34,7 @@ import { } from '../../src/elastic/pages' import { addHighlightToPage } from '../../src/elastic/highlights' import { refreshIndex } from '../../src/elastic' +import { SearchHistory } from '../../src/entity/search_history' chai.use(chaiString) @@ -842,6 +843,33 @@ describe('Article API', () => { after(async () => { await deletePagesByParam({ userId: user.id }, ctx) + await getRepository(SearchHistory).delete({ user: { id: user.id } }) + }) + + context('when we search for a keyword', () => { + before(() => { + keyword = 'search' + }) + + it('saves the term in search history', async () => { + await graphqlRequest(query, authToken).expect(200) + const searchHistories = await getRepository(SearchHistory).findBy({ + user: { id: user.id }, + }) + expect(searchHistories.length).to.eq(1) + expect(searchHistories[0].term).to.eq(keyword) + const searchHistory = searchHistories[0] + + // Check that the search history is updated + await graphqlRequest(query, authToken).expect(200) + const newSearchHistories = await getRepository(SearchHistory).findBy({ + user: { id: user.id }, + }) + expect(newSearchHistories.length).to.eq(1) + expect(newSearchHistories[0].createdAt).to.be.greaterThan( + searchHistory.createdAt + ) + }) }) context('when type:highlights is not in the query', () => { From bccb949fceb08b754a587d9d9bc90efb0d765cb3 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 12 Oct 2022 13:35:01 +0800 Subject: [PATCH 9/9] Save advanced search terms in search history too --- packages/api/src/resolvers/article/index.ts | 6 +++--- packages/db/migrations/0097.do.search_history.sql | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index c94c5f5e8..6168687e6 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -906,9 +906,9 @@ export const searchResolver = authorized< } }) - // save in search history - if (searchQuery.query) { - await saveSearchHistory(claims.uid, searchQuery.query) + // save query, including advanced search terms, in search history + if (params.query) { + await saveSearchHistory(claims.uid, params.query) } return { diff --git a/packages/db/migrations/0097.do.search_history.sql b/packages/db/migrations/0097.do.search_history.sql index 23eee07ba..f14ecf29b 100755 --- a/packages/db/migrations/0097.do.search_history.sql +++ b/packages/db/migrations/0097.do.search_history.sql @@ -12,4 +12,6 @@ CREATE TABLE omnivore.search_history ( unique (user_id, term) ); +GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.search_history TO omnivore_user; + COMMIT;