diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index bd016a345..54b9fcfba 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -57,6 +57,7 @@ import { findHighlightsByLibraryItemId } from '../../services/highlights' import { addLabelsToLibraryItem, findLabelsByIds, + findLabelsByLibraryItemId, findOrCreateLabels, saveLabelsInLibraryItem, } from '../../services/labels' @@ -69,6 +70,7 @@ import { updateLibraryItemReadingProgress, updateLibraryItems, } from '../../services/library_item' +import { findRecommendationsByLibraryItemId } from '../../services/recommendation' import { parsedContentToLibraryItem } from '../../services/save_page' import { findUploadFileById, @@ -610,7 +612,7 @@ export const searchResolver = authorized< QuerySearchArgs >(async (_obj, params, { log, uid }) => { const startCursor = params.after || '' - const first = params.first || 10 + const first = Math.min(params.first || 10, 100) // limit to 100 items // the query size is limited to 255 characters if (params.query && params.query.length > 255) { @@ -641,25 +643,7 @@ export const searchResolver = authorized< libraryItems.pop() } - await Promise.all( - libraryItems.map(async (libraryItem) => { - if ( - libraryItem.highlightAnnotations && - libraryItem.highlightAnnotations.length > 0 - ) { - // fetch highlights for each item - libraryItem.highlights = await findHighlightsByLibraryItemId( - libraryItem.id, - uid - ) - } - }) - ) - const edges = libraryItems.map((libraryItem) => { - if (libraryItem.siteIcon && !isBase64Image(libraryItem.siteIcon)) { - libraryItem.siteIcon = createImageProxyUrl(libraryItem.siteIcon, 128, 128) - } if (params.includeContent && libraryItem.readableContent) { // convert html to the requested format const format = params.format || ArticleFormat.Html diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index c2cc34f9a..bc58b54bf 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -6,15 +6,19 @@ import { Subscription } from '../entity/subscription' import { Article, + Highlight, Label, PageType, Recommendation, SearchItem, } from '../generated/graphql' +import { findHighlightsByLibraryItemId } from '../services/highlights' import { findLabelsByLibraryItemId } from '../services/labels' import { findRecommendationsByLibraryItemId } from '../services/recommendation' import { findUploadFileById } from '../services/upload_file' import { + highlightDataToHighlight, + isBase64Image, recommandationDataToRecommendation, validatedDate, wordsCount, @@ -483,30 +487,64 @@ export const functionResolvers = { if (item.wordCount) return item.wordCount return item.content ? wordsCount(item.content) : undefined }, + siteIcon(item: { siteIcon?: string }) { + if (item.siteIcon && !isBase64Image(item.siteIcon)) { + return createImageProxyUrl(item.siteIcon, 128, 128) + } + + return item.siteIcon + }, + async highlights( + item: { + id: string + highlights?: Highlight[] + highlightAnnotations?: string[] | null + }, + _: unknown, + ctx: WithDataSourcesContext + ) { + if (item.highlights) return item.highlights + + if (item.highlightAnnotations && item.highlightAnnotations.length > 0) { + const highlights = await findHighlightsByLibraryItemId(item.id, ctx.uid) + return highlights.map(highlightDataToHighlight) + } + + return [] + }, async labels( - item: { id: string; labels?: Label[] }, + item: { id: string; labels?: Label[]; labelNames?: string[] | null }, _: unknown, ctx: WithDataSourcesContext ) { if (item.labels) return item.labels - return findLabelsByLibraryItemId(item.id, ctx.uid) + if (item.labelNames && item.labelNames.length > 0) { + return findLabelsByLibraryItemId(item.id, ctx.uid) + } + + return [] }, async recommendations( item: { id: string recommendations?: Recommendation[] + recommenderNames?: string[] | null }, _: unknown, ctx: WithDataSourcesContext ) { if (item.recommendations) return item.recommendations - const recommendations = await findRecommendationsByLibraryItemId( - item.id, - ctx.uid - ) - return recommendations.map(recommandationDataToRecommendation) + if (item.recommenderNames && item.recommenderNames.length > 0) { + const recommendations = await findRecommendationsByLibraryItemId( + item.id, + ctx.uid + ) + return recommendations.map(recommandationDataToRecommendation) + } + + return [] }, }, Subscription: { diff --git a/packages/api/src/services/groups.ts b/packages/api/src/services/groups.ts index 0a3de5891..e3ec84ebe 100644 --- a/packages/api/src/services/groups.ts +++ b/packages/api/src/services/groups.ts @@ -280,3 +280,7 @@ export const getGroupsWhereUserCanPost = async ( .innerJoinAndSelect('members.user', 'user') .getMany() } + +export const deleteGroup = async (groupId: string) => { + return getRepository(Group).delete(groupId) +} diff --git a/packages/api/test/resolvers/article.test.ts b/packages/api/test/resolvers/article.test.ts index 083bd389e..7cb3cdcbe 100644 --- a/packages/api/test/resolvers/article.test.ts +++ b/packages/api/test/resolvers/article.test.ts @@ -4,6 +4,7 @@ import chaiString from 'chai-string' import 'mocha' import sinon from 'sinon' import { DeepPartial } from 'typeorm' +import { Group } from '../../src/entity/groups/group' import { Highlight } from '../../src/entity/highlight' import { Label } from '../../src/entity/label' import { LibraryItem, LibraryItemState } from '../../src/entity/library_item' @@ -18,6 +19,7 @@ import { UploadFileStatus, } from '../../src/generated/graphql' import { getRepository } from '../../src/repository' +import { createGroup, deleteGroup } from '../../src/services/groups' import { createHighlight } from '../../src/services/highlights' import { createLabel, @@ -153,6 +155,13 @@ const searchQuery = (keyword = '') => { highlights { id } + labels { + id + name + } + recommendations { + name + } } } pageInfo { @@ -1455,6 +1464,64 @@ describe('Article API', () => { }) } ) + + context('when recommendedBy:* is in the query', () => { + let items: LibraryItem[] = [] + let group: Group + + before(async () => { + keyword = 'recommendedBy:*' + + group = ( + await createGroup({ + admin: user, + name: 'test group', + }) + )[0] + + // Create some test items + items = await createLibraryItems( + [ + { + user, + title: 'test title 1', + readableContent: '

test 1

', + slug: 'test slug 1', + originalUrl: `${url}/test1`, + recommendations: [ + { + recommender: user, + group, + }, + ], + }, + { + user, + title: 'test title 2', + readableContent: '

test 2

', + slug: 'test slug 2', + originalUrl: `${url}/test2`, + }, + ], + user.id + ) + }) + + after(async () => { + await deleteLibraryItems(items, user.id) + await deleteGroup(group.id) + }) + + it('returns recommended items', async () => { + const res = await graphqlRequest(query, authToken).expect(200) + + expect(res.body.data.search.pageInfo.totalCount).to.eq(1) + expect(res.body.data.search.edges[0].node.id).to.eq(items[0].id) + expect( + res.body.data.search.edges[0].node.recommendations[0].name + ).to.eq(group.name) + }) + }) }) describe('TypeaheadSearch API', () => {