diff --git a/apps/server/src/core/comment/comment.service.ts b/apps/server/src/core/comment/comment.service.ts index 61fb15c9..630605be 100644 --- a/apps/server/src/core/comment/comment.service.ts +++ b/apps/server/src/core/comment/comment.service.ts @@ -9,16 +9,14 @@ import { UpdateCommentDto } from './dto/update-comment.dto'; import { CommentRepo } from '@docmost/db/repos/comment/comment.repo'; import { Comment, Page, User } from '@docmost/db/types/entity.types'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; -import { PaginationResult } from '@docmost/db/pagination/pagination'; import { PageRepo } from '@docmost/db/repos/page/page.repo'; -import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo'; +import { CursorPaginationResult } from '@docmost/db/pagination/cursor-pagination'; @Injectable() export class CommentService { constructor( private commentRepo: CommentRepo, private pageRepo: PageRepo, - private spaceMemberRepo: SpaceMemberRepo, ) {} async findById(commentId: string) { @@ -68,14 +66,14 @@ export class CommentService { async findByPageId( pageId: string, pagination: PaginationOptions, - ): Promise> { + ): Promise> { const page = await this.pageRepo.findById(pageId); if (!page) { throw new BadRequestException('Page not found'); } - return await this.commentRepo.findPageComments(pageId, pagination); + return this.commentRepo.findPageComments(pageId, pagination); } async update( diff --git a/apps/server/src/core/group/services/group.service.ts b/apps/server/src/core/group/services/group.service.ts index 62db153a..fb66eb36 100644 --- a/apps/server/src/core/group/services/group.service.ts +++ b/apps/server/src/core/group/services/group.service.ts @@ -11,7 +11,7 @@ import { UpdateGroupDto } from '../dto/update-group.dto'; import { KyselyTransaction } from '@docmost/db/types/kysely.types'; import { GroupRepo } from '@docmost/db/repos/group/group.repo'; import { Group, InsertableGroup, User } from '@docmost/db/types/entity.types'; -import { PaginationResult } from '@docmost/db/pagination/pagination'; +import { CursorPaginationResult } from '@docmost/db/pagination/cursor-pagination'; import { GroupUserService } from './group-user.service'; @Injectable() @@ -132,12 +132,8 @@ export class GroupService { async getWorkspaceGroups( workspaceId: string, paginationOptions: PaginationOptions, - ): Promise> { - const groups = await this.groupRepo.getGroupsPaginated( - workspaceId, - paginationOptions, - ); - return groups; + ): Promise> { + return this.groupRepo.getGroupsPaginated(workspaceId, paginationOptions); } async deleteGroup(groupId: string, workspaceId: string): Promise { diff --git a/apps/server/src/core/page/services/page-history.service.ts b/apps/server/src/core/page/services/page-history.service.ts index 23ab8617..a3e96639 100644 --- a/apps/server/src/core/page/services/page-history.service.ts +++ b/apps/server/src/core/page/services/page-history.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { PageHistoryRepo } from '@docmost/db/repos/page/page-history.repo'; import { PageHistory } from '@docmost/db/types/entity.types'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; -import { PaginationResult } from '@docmost/db/pagination/pagination'; +import { CursorPaginationResult } from '@docmost/db/pagination/cursor-pagination'; @Injectable() export class PageHistoryService { @@ -15,12 +15,10 @@ export class PageHistoryService { async findHistoryByPageId( pageId: string, paginationOptions: PaginationOptions, - ): Promise> { - const pageHistory = await this.pageHistoryRepo.findPageHistoryByPageId( + ): Promise> { + return this.pageHistoryRepo.findPageHistoryByPageId( pageId, paginationOptions, ); - - return pageHistory; } } diff --git a/apps/server/src/core/page/services/page.service.ts b/apps/server/src/core/page/services/page.service.ts index 9bfb5e1c..aa085779 100644 --- a/apps/server/src/core/page/services/page.service.ts +++ b/apps/server/src/core/page/services/page.service.ts @@ -10,9 +10,9 @@ import { PageRepo } from '@docmost/db/repos/page/page.repo'; import { InsertablePage, Page, User } from '@docmost/db/types/entity.types'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; import { - executeWithPagination, - PaginationResult, -} from '@docmost/db/pagination/pagination'; + CursorPaginationResult, + executeWithCursorPagination, +} from '@docmost/db/pagination/cursor-pagination'; import { InjectKysely } from 'nestjs-kysely'; import { KyselyDB } from '@docmost/db/types/kysely.types'; import { generateJitteredKeyBetween } from 'fractional-indexing-jittered'; @@ -180,7 +180,7 @@ export class PageService { spaceId: string, pagination: PaginationOptions, pageId?: string, - ): Promise { + ): Promise & { hasChildren: boolean }>> { let query = this.db .selectFrom('pages') .select([ @@ -195,7 +195,6 @@ export class PageService { 'deletedAt', ]) .select((eb) => this.pageRepo.withHasChildren(eb)) - .orderBy('position', (ob) => ob.collate('C').asc()) .where('deletedAt', 'is', null) .where('spaceId', '=', spaceId); @@ -205,12 +204,18 @@ export class PageService { query = query.where('parentPageId', 'is', null); } - const result = executeWithPagination(query, { - page: pagination.page, + return executeWithCursorPagination(query, { perPage: 250, + after: pagination.cursor, + fields: [ + { expression: 'position', direction: 'asc', orderModifier: (ob) => ob.collate('C').asc() }, + { expression: 'id', direction: 'asc' }, + ], + parseCursor: (cursor) => ({ + position: cursor.position, + id: cursor.id, + }), }); - - return result; } async movePageToSpace(rootPage: Page, spaceId: string) { @@ -259,7 +264,7 @@ export class PageService { await this.aiQueue.add(QueueJob.PAGE_MOVED_TO_SPACE, { pageId: pageIds, - workspaceId: rootPage.workspaceId + workspaceId: rootPage.workspaceId, }); } }); @@ -387,9 +392,14 @@ export class PageService { workspaceId: page.workspaceId, creatorId: authUser.id, lastUpdatedById: authUser.id, - parentPageId: page.id === rootPage.id - ? (isDuplicateInSameSpace ? rootPage.parentPageId : null) - : (page.parentPageId ? pageMap.get(page.parentPageId)?.newPageId : null), + parentPageId: + page.id === rootPage.id + ? isDuplicateInSameSpace + ? rootPage.parentPageId + : null + : page.parentPageId + ? pageMap.get(page.parentPageId)?.newPageId + : null, }; }), ); @@ -569,22 +579,22 @@ export class PageService { async getRecentSpacePages( spaceId: string, pagination: PaginationOptions, - ): Promise> { - return await this.pageRepo.getRecentPagesInSpace(spaceId, pagination); + ): Promise> { + return this.pageRepo.getRecentPagesInSpace(spaceId, pagination); } async getRecentPages( userId: string, pagination: PaginationOptions, - ): Promise> { - return await this.pageRepo.getRecentPages(userId, pagination); + ): Promise> { + return this.pageRepo.getRecentPages(userId, pagination); } async getDeletedSpacePages( spaceId: string, pagination: PaginationOptions, - ): Promise> { - return await this.pageRepo.getDeletedPagesInSpace(spaceId, pagination); + ): Promise> { + return this.pageRepo.getDeletedPagesInSpace(spaceId, pagination); } async forceDelete(pageId: string, workspaceId: string): Promise { diff --git a/apps/server/src/core/space/services/space-member.service.ts b/apps/server/src/core/space/services/space-member.service.ts index 16ab7c65..93da3e3c 100644 --- a/apps/server/src/core/space/services/space-member.service.ts +++ b/apps/server/src/core/space/services/space-member.service.ts @@ -13,7 +13,7 @@ import { SpaceRepo } from '@docmost/db/repos/space/space.repo'; import { RemoveSpaceMemberDto } from '../dto/remove-space-member.dto'; import { UpdateSpaceMemberRoleDto } from '../dto/update-space-member-role.dto'; import { SpaceRole } from '../../../common/helpers/types/permission'; -import { PaginationResult } from '@docmost/db/pagination/pagination'; +import { CursorPaginationResult } from '@docmost/db/pagination/cursor-pagination'; @Injectable() export class SpaceMemberService { @@ -68,18 +68,16 @@ export class SpaceMemberService { spaceId: string, workspaceId: string, pagination: PaginationOptions, - ) { + ): Promise> { const space = await this.spaceRepo.findById(spaceId, workspaceId); if (!space) { throw new NotFoundException('Space not found'); } - const members = await this.spaceMemberRepo.getSpaceMembersPaginated( + return await this.spaceMemberRepo.getSpaceMembersPaginated( spaceId, pagination, ); - - return members; } async addMembersToSpaceBatch( @@ -276,7 +274,7 @@ export class SpaceMemberService { async getUserSpaces( userId: string, pagination: PaginationOptions, - ): Promise> { - return await this.spaceMemberRepo.getUserSpaces(userId, pagination); + ): Promise> { + return this.spaceMemberRepo.getUserSpaces(userId, pagination); } } diff --git a/apps/server/src/core/space/services/space.service.ts b/apps/server/src/core/space/services/space.service.ts index 0f3c123a..cc811f97 100644 --- a/apps/server/src/core/space/services/space.service.ts +++ b/apps/server/src/core/space/services/space.service.ts @@ -8,7 +8,6 @@ import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; import { SpaceRepo } from '@docmost/db/repos/space/space.repo'; import { KyselyDB, KyselyTransaction } from '@docmost/db/types/kysely.types'; import { Space, User } from '@docmost/db/types/entity.types'; -import { PaginationResult } from '@docmost/db/pagination/pagination'; import { UpdateSpaceDto } from '../dto/update-space.dto'; import { executeTx } from '@docmost/db/utils'; import { InjectKysely } from 'nestjs-kysely'; @@ -17,6 +16,7 @@ import { SpaceRole } from '../../../common/helpers/types/permission'; import { QueueJob, QueueName } from 'src/integrations/queue/constants'; import { Queue } from 'bullmq'; import { InjectQueue } from '@nestjs/bullmq'; +import { CursorPaginationResult } from '@docmost/db/pagination/cursor-pagination'; @Injectable() export class SpaceService { @@ -130,13 +130,8 @@ export class SpaceService { async getWorkspaceSpaces( workspaceId: string, pagination: PaginationOptions, - ): Promise> { - const spaces = await this.spaceRepo.getSpacesInWorkspace( - workspaceId, - pagination, - ); - - return spaces; + ): Promise> { + return this.spaceRepo.getSpacesInWorkspace(workspaceId, pagination); } async deleteSpace(spaceId: string, workspaceId: string): Promise { diff --git a/apps/server/src/core/workspace/services/workspace-invitation.service.ts b/apps/server/src/core/workspace/services/workspace-invitation.service.ts index 2defcbba..60824253 100644 --- a/apps/server/src/core/workspace/services/workspace-invitation.service.ts +++ b/apps/server/src/core/workspace/services/workspace-invitation.service.ts @@ -23,7 +23,7 @@ import InvitationAcceptedEmail from '@docmost/transactional/emails/invitation-ac import { TokenService } from '../../auth/services/token.service'; import { nanoIdGen } from '../../../common/helpers'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; -import { executeWithPagination } from '@docmost/db/pagination/pagination'; +import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination'; import { DomainService } from 'src/integrations/environment/domain.service'; import { InjectQueue } from '@nestjs/bullmq'; import { QueueJob, QueueName } from '../../../integrations/queue/constants'; @@ -64,12 +64,12 @@ export class WorkspaceInvitationService { ); } - const result = executeWithPagination(query, { - page: pagination.page, + return executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [{ expression: 'id', direction: 'asc' }], + parseCursor: (cursor) => ({ id: cursor.id }), }); - - return result; } async getInvitationById(invitationId: string, workspace: Workspace) { diff --git a/apps/server/src/core/workspace/services/workspace.service.ts b/apps/server/src/core/workspace/services/workspace.service.ts index 1a5e7f8d..7be5b642 100644 --- a/apps/server/src/core/workspace/services/workspace.service.ts +++ b/apps/server/src/core/workspace/services/workspace.service.ts @@ -19,7 +19,6 @@ import { User } from '@docmost/db/types/entity.types'; import { GroupUserRepo } from '@docmost/db/repos/group/group-user.repo'; import { GroupRepo } from '@docmost/db/repos/group/group.repo'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; -import { PaginationResult } from '@docmost/db/pagination/pagination'; import { UpdateWorkspaceUserRoleDto } from '../dto/update-workspace-user-role.dto'; import { UserRepo } from '@docmost/db/repos/user/user.repo'; import { EnvironmentService } from '../../../integrations/environment/environment.service'; @@ -28,12 +27,12 @@ import { jsonArrayFrom } from 'kysely/helpers/postgres'; import { addDays } from 'date-fns'; import { DISALLOWED_HOSTNAMES, WorkspaceStatus } from '../workspace.constants'; import { v4 } from 'uuid'; -import { AttachmentType } from 'src/core/attachment/attachment.constants'; import { InjectQueue } from '@nestjs/bullmq'; import { QueueJob, QueueName } from '../../../integrations/queue/constants'; import { Queue } from 'bullmq'; import { generateRandomSuffixNumbers } from '../../../common/helpers'; import { isPageEmbeddingsTableExists } from '@docmost/db/helpers/helpers'; +import { CursorPaginationResult } from '@docmost/db/pagination/cursor-pagination'; @Injectable() export class WorkspaceService { @@ -376,13 +375,8 @@ export class WorkspaceService { async getWorkspaceUsers( workspaceId: string, pagination: PaginationOptions, - ): Promise> { - const users = await this.userRepo.getUsersPaginated( - workspaceId, - pagination, - ); - - return users; + ): Promise> { + return this.userRepo.getUsersPaginated(workspaceId, pagination); } async updateWorkspaceUserRole( diff --git a/apps/server/src/database/pagination/cursor-pagination.ts b/apps/server/src/database/pagination/cursor-pagination.ts index 82ef17b9..d0a90287 100644 --- a/apps/server/src/database/pagination/cursor-pagination.ts +++ b/apps/server/src/database/pagination/cursor-pagination.ts @@ -110,7 +110,7 @@ type CursorPaginationResultRow< type CursorPaginationMeta = { limit: number; - hasMore: boolean; + hasNextPage: boolean; nextCursor: string | null; }; @@ -233,16 +233,16 @@ export async function executeWithCursorPagination< const rows = await qb.limit(opts.perPage + 1).execute(); - const hasMore = rows.length > opts.perPage; + const hasNextPage = rows.length > opts.perPage; // If we fetched an extra row to determine if we have a next page, that // shouldn't be in the returned results - if (hasMore) rows.pop(); + if (hasNextPage) rows.pop(); if (reversed) rows.reverse(); const endRow = rows[rows.length - 1]; - const nextCursor = hasMore && endRow ? generateCursor(endRow) : null; + const nextCursor = hasNextPage && endRow ? generateCursor(endRow) : null; return { items: rows.map((row) => { @@ -257,7 +257,7 @@ export async function executeWithCursorPagination< }), meta: { limit: opts.perPage, - hasMore, + hasNextPage, nextCursor, }, }; diff --git a/apps/server/src/database/pagination/pagination-options.ts b/apps/server/src/database/pagination/pagination-options.ts index 63d02f18..c3ac984a 100644 --- a/apps/server/src/database/pagination/pagination-options.ts +++ b/apps/server/src/database/pagination/pagination-options.ts @@ -21,6 +21,10 @@ export class PaginationOptions { @Max(100) limit = 20; + @IsOptional() + @IsString() + cursor?: string; + @IsOptional() @IsString() query: string; diff --git a/apps/server/src/database/repos/comment/comment.repo.ts b/apps/server/src/database/repos/comment/comment.repo.ts index 965bd611..e4b536e5 100644 --- a/apps/server/src/database/repos/comment/comment.repo.ts +++ b/apps/server/src/database/repos/comment/comment.repo.ts @@ -8,7 +8,7 @@ import { UpdatableComment, } from '@docmost/db/types/entity.types'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; -import { executeWithPagination } from '@docmost/db/pagination/pagination'; +import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination'; import { ExpressionBuilder } from 'kysely'; import { DB } from '@docmost/db/types/db'; import { jsonObjectFrom } from 'kysely/helpers/postgres'; @@ -37,15 +37,14 @@ export class CommentRepo { .selectAll('comments') .select((eb) => this.withCreator(eb)) .select((eb) => this.withResolvedBy(eb)) - .where('pageId', '=', pageId) - .orderBy('createdAt', 'asc'); + .where('pageId', '=', pageId); - const result = executeWithPagination(query, { - page: pagination.page, + return executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [{ expression: 'id', direction: 'asc' }], + parseCursor: (cursor) => ({ id: cursor.id }), }); - - return result; } async updateComment( diff --git a/apps/server/src/database/repos/group/group-user.repo.ts b/apps/server/src/database/repos/group/group-user.repo.ts index 5c144ec4..03f4f1a5 100644 --- a/apps/server/src/database/repos/group/group-user.repo.ts +++ b/apps/server/src/database/repos/group/group-user.repo.ts @@ -9,7 +9,7 @@ import { dbOrTx, executeTx } from '@docmost/db/utils'; import { sql } from 'kysely'; import { GroupUser, InsertableGroupUser } from '@docmost/db/types/entity.types'; import { PaginationOptions } from '../../pagination/pagination-options'; -import { executeWithPagination } from '@docmost/db/pagination/pagination'; +import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination'; import { GroupRepo } from '@docmost/db/repos/group/group.repo'; import { UserRepo } from '@docmost/db/repos/user/user.repo'; @@ -52,8 +52,7 @@ export class GroupUserRepo { .selectFrom('groupUsers') .innerJoin('users', 'users.id', 'groupUsers.userId') .selectAll('users') - .where('groupId', '=', groupId) - .orderBy('createdAt', 'asc'); + .where('groupId', '=', groupId); if (pagination.query) { query = query.where((eb) => @@ -61,9 +60,11 @@ export class GroupUserRepo { ); } - const result = await executeWithPagination(query, { - page: pagination.page, + const result = await executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [{ expression: 'users.id', direction: 'asc', key: 'id' }], + parseCursor: (cursor) => ({ id: cursor.id }), }); result.items.map((user) => { diff --git a/apps/server/src/database/repos/group/group.repo.ts b/apps/server/src/database/repos/group/group.repo.ts index 6d0e4257..18726b03 100644 --- a/apps/server/src/database/repos/group/group.repo.ts +++ b/apps/server/src/database/repos/group/group.repo.ts @@ -10,8 +10,8 @@ import { import { ExpressionBuilder, sql } from 'kysely'; import { PaginationOptions } from '../../pagination/pagination-options'; import { DB } from '@docmost/db/types/db'; -import { executeWithPagination } from '@docmost/db/pagination/pagination'; import { DefaultGroup } from '../../../core/group/dto/create-group.dto'; +import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination'; @Injectable() export class GroupRepo { @@ -104,17 +104,19 @@ export class GroupRepo { } async getGroupsPaginated(workspaceId: string, pagination: PaginationOptions) { - let query = this.db + let baseQuery = this.db .selectFrom('groups') .selectAll('groups') .select((eb) => this.withMemberCount(eb)) - .where('workspaceId', '=', workspaceId) - .orderBy('memberCount', 'desc') - .orderBy('createdAt', 'asc'); + .where('workspaceId', '=', workspaceId); if (pagination.query) { - query = query.where((eb) => - eb(sql`f_unaccent(name)`, 'ilike', sql`f_unaccent(${'%' + pagination.query + '%'})`).or( + baseQuery = baseQuery.where((eb) => + eb( + sql`f_unaccent(name)`, + 'ilike', + sql`f_unaccent(${'%' + pagination.query + '%'})`, + ).or( sql`f_unaccent(description)`, 'ilike', sql`f_unaccent(${'%' + pagination.query + '%'})`, @@ -122,12 +124,23 @@ export class GroupRepo { ); } - const result = executeWithPagination(query, { - page: pagination.page, + const query = this.db.selectFrom(baseQuery.as('sub')).selectAll('sub'); + return executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [ + { + expression: 'sub.memberCount', + direction: 'desc', + key: 'memberCount', + }, + { expression: 'sub.id', direction: 'asc', key: 'id' }, + ], + parseCursor: (cursor) => ({ + memberCount: parseInt(cursor.memberCount, 10), + id: cursor.id, + }), }); - - return result; } withMemberCount(eb: ExpressionBuilder) { diff --git a/apps/server/src/database/repos/page/page-history.repo.ts b/apps/server/src/database/repos/page/page-history.repo.ts index cfefd485..af158c9e 100644 --- a/apps/server/src/database/repos/page/page-history.repo.ts +++ b/apps/server/src/database/repos/page/page-history.repo.ts @@ -8,7 +8,7 @@ import { PageHistory, } from '@docmost/db/types/entity.types'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; -import { executeWithPagination } from '@docmost/db/pagination/pagination'; +import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination'; import { jsonObjectFrom } from 'kysely/helpers/postgres'; import { ExpressionBuilder } from 'kysely'; import { DB } from '@docmost/db/types/db'; @@ -65,15 +65,14 @@ export class PageHistoryRepo { .selectFrom('pageHistory') .selectAll() .select((eb) => this.withLastUpdatedBy(eb)) - .where('pageId', '=', pageId) - .orderBy('createdAt', 'desc'); + .where('pageId', '=', pageId); - const result = executeWithPagination(query, { - page: pagination.page, + return executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [{ expression: 'id', direction: 'desc' }], + parseCursor: (cursor) => ({ id: cursor.id }), }); - - return result; } async findPageLastHistory(pageId: string, trx?: KyselyTransaction) { diff --git a/apps/server/src/database/repos/page/page.repo.ts b/apps/server/src/database/repos/page/page.repo.ts index f2b27abb..a7aa3c53 100644 --- a/apps/server/src/database/repos/page/page.repo.ts +++ b/apps/server/src/database/repos/page/page.repo.ts @@ -8,7 +8,7 @@ import { UpdatablePage, } from '@docmost/db/types/entity.types'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; -import { executeWithPagination } from '@docmost/db/pagination/pagination'; +import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination'; import { validate as isValidUUID } from 'uuid'; import { ExpressionBuilder, sql } from 'kysely'; import { DB } from '@docmost/db/types/db'; @@ -281,15 +281,20 @@ export class PageRepo { .select(this.baseFields) .select((eb) => this.withSpace(eb)) .where('spaceId', '=', spaceId) - .where('deletedAt', 'is', null) - .orderBy('updatedAt', 'desc'); + .where('deletedAt', 'is', null); - const result = executeWithPagination(query, { - page: pagination.page, + return executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [ + { expression: 'updatedAt', direction: 'desc' }, + { expression: 'id', direction: 'desc' }, + ], + parseCursor: (cursor) => ({ + updatedAt: new Date(cursor.updatedAt), + id: cursor.id, + }), }); - - return result; } async getRecentPages(userId: string, pagination: PaginationOptions) { @@ -298,12 +303,19 @@ export class PageRepo { .select(this.baseFields) .select((eb) => this.withSpace(eb)) .where('spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(userId)) - .where('deletedAt', 'is', null) - .orderBy('updatedAt', 'desc'); + .where('deletedAt', 'is', null); - return executeWithPagination(query, { - page: pagination.page, + return executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [ + { expression: 'updatedAt', direction: 'desc' }, + { expression: 'id', direction: 'desc' }, + ], + parseCursor: (cursor) => ({ + updatedAt: new Date(cursor.updatedAt), + id: cursor.id, + }), }); } @@ -331,15 +343,20 @@ export class PageRepo { ), ), ]), - ) - .orderBy('deletedAt', 'desc'); + ); - const result = executeWithPagination(query, { - page: pagination.page, + return executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [ + { expression: 'deletedAt', direction: 'desc' }, + { expression: 'id', direction: 'desc' }, + ], + parseCursor: (cursor) => ({ + deletedAt: new Date(cursor.deletedAt), + id: cursor.id, + }), }); - - return result; } withSpace(eb: ExpressionBuilder) { diff --git a/apps/server/src/database/repos/share/share.repo.ts b/apps/server/src/database/repos/share/share.repo.ts index 3cf4ab3b..87181a6e 100644 --- a/apps/server/src/database/repos/share/share.repo.ts +++ b/apps/server/src/database/repos/share/share.repo.ts @@ -8,7 +8,7 @@ import { UpdatableShare, } from '@docmost/db/types/entity.types'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; -import { executeWithPagination } from '@docmost/db/pagination/pagination'; +import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination'; import { validate as isValidUUID } from 'uuid'; import { ExpressionBuilder, sql } from 'kysely'; import { DB } from '@docmost/db/types/db'; @@ -143,12 +143,19 @@ export class ShareRepo { .select((eb) => this.withPage(eb)) .select((eb) => this.withSpace(eb, userId)) .select((eb) => this.withCreator(eb)) - .where('spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(userId)) - .orderBy('updatedAt', 'desc'); + .where('spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(userId)); - return executeWithPagination(query, { - page: pagination.page, + return executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [ + { expression: 'updatedAt', direction: 'desc' }, + { expression: 'id', direction: 'desc' }, + ], + parseCursor: (cursor) => ({ + updatedAt: new Date(cursor.updatedAt), + id: cursor.id, + }), }); } diff --git a/apps/server/src/database/repos/space/space-member.repo.ts b/apps/server/src/database/repos/space/space-member.repo.ts index 64e4ba2c..e4fd8bc6 100644 --- a/apps/server/src/database/repos/space/space-member.repo.ts +++ b/apps/server/src/database/repos/space/space-member.repo.ts @@ -10,7 +10,7 @@ import { } from '@docmost/db/types/entity.types'; import { PaginationOptions } from '../../pagination/pagination-options'; import { MemberInfo, UserSpaceRole } from './types'; -import { executeWithPagination } from '@docmost/db/pagination/pagination'; +import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination'; import { GroupRepo } from '@docmost/db/repos/group/group.repo'; import { SpaceRepo } from '@docmost/db/repos/space/space.repo'; @@ -98,7 +98,7 @@ export class SpaceMemberRepo { spaceId: string, pagination: PaginationOptions, ) { - let query = this.db + let baseQuery = this.db .selectFrom('spaceMembers') .leftJoin('users', 'users.id', 'spaceMembers.userId') .leftJoin('groups', 'groups.id', 'spaceMembers.groupId') @@ -114,12 +114,11 @@ export class SpaceMemberRepo { 'spaceMembers.createdAt', ]) .select((eb) => this.groupRepo.withMemberCount(eb)) - .where('spaceId', '=', spaceId) - .orderBy((eb) => eb('groups.id', 'is not', null), 'desc') - .orderBy('spaceMembers.createdAt', 'asc'); + .select(sql`case when groups.id is not null then 1 else 0 end`.as('isGroup')) + .where('spaceId', '=', spaceId); if (pagination.query) { - query = query.where((eb) => + baseQuery = baseQuery.where((eb) => eb( sql`f_unaccent(users.name)`, 'ilike', @@ -138,9 +137,19 @@ export class SpaceMemberRepo { ); } - const result = await executeWithPagination(query, { - page: pagination.page, + const query = this.db.selectFrom(baseQuery.as('sub')).selectAll('sub'); + + const result = await executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [ + { expression: 'sub.isGroup', direction: 'desc', key: 'isGroup' }, + { expression: 'sub.createdAt', direction: 'asc', key: 'createdAt' }, + ], + parseCursor: (cursor) => ({ + isGroup: parseInt(cursor.isGroup, 10), + createdAt: new Date(cursor.createdAt), + }), }); let memberInfo: MemberInfo; @@ -235,8 +244,7 @@ export class SpaceMemberRepo { .selectFrom('spaces') .selectAll() .select((eb) => [this.spaceRepo.withMemberCount(eb)]) - .where('id', 'in', this.getUserSpaceIdsQuery(userId)) - .orderBy('createdAt', 'asc'); + .where('id', 'in', this.getUserSpaceIdsQuery(userId)); if (pagination.query) { query = query.where((eb) => @@ -252,9 +260,11 @@ export class SpaceMemberRepo { ); } - return executeWithPagination(query, { - page: pagination.page, + return executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [{ expression: 'id', direction: 'asc' }], + parseCursor: (cursor) => ({ id: cursor.id }), }); } } diff --git a/apps/server/src/database/repos/space/space.repo.ts b/apps/server/src/database/repos/space/space.repo.ts index ed0d6b1e..149eb1f9 100644 --- a/apps/server/src/database/repos/space/space.repo.ts +++ b/apps/server/src/database/repos/space/space.repo.ts @@ -9,7 +9,7 @@ import { } from '@docmost/db/types/entity.types'; import { ExpressionBuilder, sql } from 'kysely'; import { PaginationOptions } from '../../pagination/pagination-options'; -import { executeWithPagination } from '@docmost/db/pagination/pagination'; +import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination'; import { DB } from '@docmost/db/types/db'; import { validate as isValidUUID } from 'uuid'; import { EventEmitter2 } from '@nestjs/event-emitter'; @@ -110,8 +110,7 @@ export class SpaceRepo { .selectFrom('spaces') .selectAll('spaces') .select((eb) => [this.withMemberCount(eb)]) - .where('workspaceId', '=', workspaceId) - .orderBy('createdAt', 'asc'); + .where('workspaceId', '=', workspaceId); if (pagination.query) { query = query.where((eb) => @@ -127,12 +126,12 @@ export class SpaceRepo { ); } - const result = executeWithPagination(query, { - page: pagination.page, + return executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [{ expression: 'id', direction: 'asc' }], + parseCursor: (cursor) => ({ id: cursor.id }), }); - - return result; } withMemberCount(eb: ExpressionBuilder) { diff --git a/apps/server/src/database/repos/user/user.repo.ts b/apps/server/src/database/repos/user/user.repo.ts index c7c7b2b2..2e2f5227 100644 --- a/apps/server/src/database/repos/user/user.repo.ts +++ b/apps/server/src/database/repos/user/user.repo.ts @@ -10,7 +10,7 @@ import { User, } from '@docmost/db/types/entity.types'; import { PaginationOptions } from '../../pagination/pagination-options'; -import { executeWithPagination } from '@docmost/db/pagination/pagination'; +import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination'; import { ExpressionBuilder, sql } from 'kysely'; import { jsonObjectFrom } from 'kysely/helpers/postgres'; @@ -145,8 +145,7 @@ export class UserRepo { .selectFrom('users') .select(this.baseFields) .where('workspaceId', '=', workspaceId) - .where('deletedAt', 'is', null) - .orderBy('createdAt', 'asc'); + .where('deletedAt', 'is', null); if (pagination.query) { query = query.where((eb) => @@ -162,12 +161,12 @@ export class UserRepo { ); } - const result = executeWithPagination(query, { - page: pagination.page, + return executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [{ expression: 'id', direction: 'asc' }], + parseCursor: (cursor) => ({ id: cursor.id }), }); - - return result; } async updatePreference( diff --git a/apps/server/src/ee b/apps/server/src/ee index b6844b01..22ab70c5 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit b6844b019c3778d51ff1bb236f30284a0bf8f403 +Subproject commit 22ab70c5444802eb01b94349da711aca22f04497 diff --git a/apps/server/src/integrations/import/file-task.controller.ts b/apps/server/src/integrations/import/file-task.controller.ts index 096cd5aa..e937ca72 100644 --- a/apps/server/src/integrations/import/file-task.controller.ts +++ b/apps/server/src/integrations/import/file-task.controller.ts @@ -27,7 +27,7 @@ import { AuthUser } from '../../common/decorators/auth-user.decorator'; import { FileTaskIdDto } from './dto/file-task-dto'; import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; -import { executeWithPagination } from '@docmost/db/pagination/pagination'; +import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination'; @Controller('file-tasks') export class FileTaskController { @@ -56,12 +56,13 @@ export class FileTaskController { const query = this.db .selectFrom('fileTasks') .selectAll() - .where('spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(user.id)) - .orderBy('createdAt', 'desc'); + .where('spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(user.id)); - return executeWithPagination(query, { - page: pagination.page, + return executeWithCursorPagination(query, { perPage: pagination.limit, + after: pagination.cursor, + fields: [{ expression: 'id', direction: 'desc' }], + parseCursor: (cursor) => ({ id: cursor.id }), }); }