From 284697da5b23292311e958e7a9334ccdb72d6122 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 6 Dec 2023 13:52:17 +0800 Subject: [PATCH] fix sort --- packages/api/src/resolvers/article/index.ts | 6 +- packages/api/src/services/library_item.ts | 115 +++++++++++--------- 2 files changed, 64 insertions(+), 57 deletions(-) diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index 22413fe27..a1e7c33cb 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -745,8 +745,6 @@ export const updatesSinceResolver = authorized< UpdatesSinceError, QueryUpdatesSinceArgs >(async (_obj, { since, first, after, sort: sortParams, folder }, { uid }) => { - const sort = sortParamsToSort(sortParams) - const startCursor = after || '' const size = Math.min(first || 10, 100) // limit to 100 items let startDate = new Date(since) @@ -754,18 +752,18 @@ export const updatesSinceResolver = authorized< // for android app compatibility startDate = new Date(0) } + const sort = sortParamsToSort(sortParams) // create a search query const query = `updated:${startDate.toISOString()}${ folder ? ' in:' + folder : '' - }` + } sort:${sort.by}-${sort.order}` const { libraryItems, count } = await searchLibraryItems( { from: Number(startCursor), size: size + 1, // fetch one more item to get next cursor includeDeleted: true, - sort, query, }, uid diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index 54442d635..f4809b8bb 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -94,13 +94,13 @@ export enum SortOrder { } export interface Sort { - by: SortBy + by: string order?: SortOrder } interface Select { column: string - alias: string + alias?: string } export const sortParamsToSort = ( @@ -153,7 +153,7 @@ export const buildQuery = ( searchQuery: LiqeQuery, parameters: ObjectLiteral[] = [], selects: Select[] = [], - orders: { by: string; order?: SortOrder }[] = [], + orders: Sort[] = [], useFolders = false ) => { const escapeQueryWithParameters = ( @@ -189,10 +189,8 @@ export const buildQuery = ( alias, }) - orders.push({ - by: alias, - order: SortOrder.DESCENDING, - }) + // always sort by rank first + orders.unshift({ by: alias, order: SortOrder.DESCENDING }) return escapeQueryWithParameters( `websearch_to_tsquery('english', :${param}) @@ library_item.search_tsv`, @@ -388,8 +386,20 @@ export const buildQuery = ( default: { // check for date ranges const [start, end] = date.split('..') - startDate = start && start !== '*' ? new Date(start) : undefined - endDate = end && end !== '*' ? new Date(end) : undefined + // validate date + if (start && start !== '*') { + startDate = new Date(start) + if (!startDate.getTime()) { + throw new Error('Invalid start date.') + } + } + + if (end && end !== '*') { + endDate = new Date(end) + if (!endDate.getTime()) { + throw new Error('Invalid end date.') + } + } } } @@ -627,24 +637,29 @@ export const searchLibraryItems = async ( args: SearchArgs, userId: string ): Promise<{ libraryItems: LibraryItem[]; count: number }> => { - const { from = 0, size = 10, sort } = args + const { from = 0, size = 10 } = args - // default order is descending - const sortOrder = sort?.order || SortOrder.DESCENDING - // default sort by saved_at - const sortField = sort?.by || SortBy.SAVED - - const selectColumns = getColumns(libraryItemRepository) - .map((column) => `library_item.${column}`) + const selects: Select[] = getColumns(libraryItemRepository) + .map((column) => ({ column: `library_item.${column}` })) .filter( - (column) => - column !== 'library_item.readableContent' && - column !== 'library_item.originalContent' + (select) => + select.column !== 'library_item.readableContent' && + select.column !== 'library_item.originalContent' ) - let searchQuery: LiqeQuery | undefined + const parameters: ObjectLiteral[] = [] + const orders: Sort[] = [] + let query: string | null = null + if (args.query) { - searchQuery = parseSearchQuery(args.query) + const searchQuery = parseSearchQuery(args.query) + query = buildQuery( + searchQuery, + parameters, + selects, + orders, + args.useFolders + ) } // add pagination and sorting @@ -652,34 +667,12 @@ export const searchLibraryItems = async ( async (tx) => { const queryBuilder = tx .createQueryBuilder(LibraryItem, 'library_item') - .select(selectColumns) .where('library_item.user_id = :userId', { userId }) - if (searchQuery) { - const parameters: ObjectLiteral[] = [] - const selects: Select[] = [] - const orders: Sort[] = [] - const whereClause = buildQuery( - searchQuery, - parameters, - selects, - orders, - args.useFolders - ) - whereClause && - queryBuilder - .andWhere(whereClause) - .setParameters(parameters.reduce((a, b) => ({ ...a, ...b }), {})) - - selects.forEach((select) => { - queryBuilder.addSelect(select.column, select.alias) - }) - - // add order by - orders.forEach((order) => { - queryBuilder.addOrderBy(order.by, order.order, 'NULLS LAST') - }) - } + // add select + selects.forEach((select) => { + queryBuilder.addSelect(select.column, select.alias) + }) if (!args.includePending) { queryBuilder.andWhere("library_item.state <> 'PROCESSING'") @@ -689,14 +682,30 @@ export const searchLibraryItems = async ( queryBuilder.andWhere("library_item.state <> 'DELETED'") } - const libraryItems = await queryBuilder - .addOrderBy(`library_item.${sortField}`, sortOrder, 'NULLS LAST') - .skip(from) - .take(size) - .getMany() + if (query) { + // add where clause from query + queryBuilder + .andWhere(query) + .setParameters(parameters.reduce((a, b) => ({ ...a, ...b }), {})) + } const count = await queryBuilder.getCount() + // default order by saved at descending + if (orders.length === 0) { + orders.push({ + by: 'library_item.saved_at', + order: SortOrder.DESCENDING, + }) + } + + // add order by + orders.forEach((order) => { + queryBuilder.addOrderBy(order.by, order.order, 'NULLS LAST') + }) + + const libraryItems = await queryBuilder.skip(from).take(size).getMany() + return { libraryItems, count } }, undefined,