From 66f09ae92d7566ae81821a786fd7d927f3b02d72 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:50:20 -0800 Subject: [PATCH] filter trash --- apps/server/src/core/page/page.controller.ts | 2 +- .../src/core/page/services/page.service.ts | 129 ++++++++++-------- .../src/database/repos/page/page.repo.ts | 5 +- 3 files changed, 78 insertions(+), 58 deletions(-) diff --git a/apps/server/src/core/page/page.controller.ts b/apps/server/src/core/page/page.controller.ts index 46388a2b2..c27573b9d 100644 --- a/apps/server/src/core/page/page.controller.ts +++ b/apps/server/src/core/page/page.controller.ts @@ -213,7 +213,6 @@ export class PageController { @Body() pagination: PaginationOptions, @AuthUser() user: User, ) { - //TODO: should space admin see deleted pages they dont have access to? if (deletedPageDto.spaceId) { const ability = await this.spaceAbility.createForUser( user, @@ -226,6 +225,7 @@ export class PageController { return this.pageService.getDeletedSpacePages( deletedPageDto.spaceId, + user.id, pagination, ); } diff --git a/apps/server/src/core/page/services/page.service.ts b/apps/server/src/core/page/services/page.service.ts index 7b1adaaba..18586882b 100644 --- a/apps/server/src/core/page/services/page.service.ts +++ b/apps/server/src/core/page/services/page.service.ts @@ -57,61 +57,6 @@ export class PageService { private eventEmitter: EventEmitter2, ) {} - /** - * Filters a list of pages to only those accessible to the user while maintaining tree integrity. - * A page is included only if: - * 1. The user has access to it - * 2. Its parent is also included (or it's the root page) - * This ensures that if a middle page is inaccessible, its entire subtree is excluded. - */ - private async filterAccessibleTreePages( - pages: T[], - rootPageId: string, - userId: string, - ): Promise { - if (pages.length === 0) return []; - - const pageIds = pages.map((p) => p.id); - const accessiblePages = - await this.pagePermissionRepo.filterAccessiblePageIdsWithPermissions( - pageIds, - userId, - ); - const accessibleSet = new Set(accessiblePages.map((p) => p.id)); - - // Build a map for quick lookup - const pageMap = new Map(pages.map((p) => [p.id, p])); - - // Prune: include a page only if it's accessible AND its parent chain to root is included - const includedIds = new Set(); - - // Process pages in a way that ensures parents are processed before children - // We do this by iterating until no more pages can be added - let changed = true; - while (changed) { - changed = false; - for (const page of pages) { - if (includedIds.has(page.id)) continue; - if (!accessibleSet.has(page.id)) continue; - - // Root page: include if accessible - if (page.id === rootPageId) { - includedIds.add(page.id); - changed = true; - continue; - } - - // Non-root: include if parent is already included - if (page.parentPageId && includedIds.has(page.parentPageId)) { - includedIds.add(page.id); - changed = true; - } - } - } - - return pages.filter((p) => includedIds.has(p.id)); - } - async findById( pageId: string, includeContent?: boolean, @@ -760,9 +705,26 @@ export class PageService { async getDeletedSpacePages( spaceId: string, + userId: string, pagination: PaginationOptions, ): Promise> { - return this.pageRepo.getDeletedPagesInSpace(spaceId, pagination); + const result = await this.pageRepo.getDeletedPagesInSpace( + spaceId, + pagination, + ); + + if (result.items.length > 0) { + const pageIds = result.items.map((p) => p.id); + const accessiblePages = + await this.pagePermissionRepo.filterAccessiblePageIdsWithPermissions( + pageIds, + userId, + ); + const accessibleSet = new Set(accessiblePages.map((p) => p.id)); + result.items = result.items.filter((p) => accessibleSet.has(p.id)); + } + + return result; } async forceDelete(pageId: string, workspaceId: string): Promise { @@ -820,4 +782,59 @@ export class PageService { ): Promise { await this.pageRepo.removePage(pageId, userId, workspaceId); } + + /** + * Filters a list of pages to only those accessible to the user while maintaining tree integrity. + * A page is included only if: + * 1. The user has access to it + * 2. Its parent is also included (or it's the root page) + * This ensures that if a middle page is inaccessible, its entire subtree is excluded. + */ + private async filterAccessibleTreePages( + pages: T[], + rootPageId: string, + userId: string, + ): Promise { + if (pages.length === 0) return []; + + const pageIds = pages.map((p) => p.id); + const accessiblePages = + await this.pagePermissionRepo.filterAccessiblePageIdsWithPermissions( + pageIds, + userId, + ); + const accessibleSet = new Set(accessiblePages.map((p) => p.id)); + + // Build a map for quick lookup + const pageMap = new Map(pages.map((p) => [p.id, p])); + + // Prune: include a page only if it's accessible AND its parent chain to root is included + const includedIds = new Set(); + + // Process pages in a way that ensures parents are processed before children + // We do this by iterating until no more pages can be added + let changed = true; + while (changed) { + changed = false; + for (const page of pages) { + if (includedIds.has(page.id)) continue; + if (!accessibleSet.has(page.id)) continue; + + // Root page: include if accessible + if (page.id === rootPageId) { + includedIds.add(page.id); + changed = true; + continue; + } + + // Non-root: include if parent is already included + if (page.parentPageId && includedIds.has(page.parentPageId)) { + includedIds.add(page.id); + changed = true; + } + } + } + + return pages.filter((p) => includedIds.has(p.id)); + } } diff --git a/apps/server/src/database/repos/page/page.repo.ts b/apps/server/src/database/repos/page/page.repo.ts index 668c6b965..144903128 100644 --- a/apps/server/src/database/repos/page/page.repo.ts +++ b/apps/server/src/database/repos/page/page.repo.ts @@ -175,11 +175,13 @@ export class PageRepo { .selectFrom('pages') .select(['id']) .where('id', '=', pageId) + .where('deletedAt', 'is', null) .unionAll((exp) => exp .selectFrom('pages as p') .select(['p.id']) - .innerJoin('page_descendants as pd', 'pd.id', 'p.parentPageId'), + .innerJoin('page_descendants as pd', 'pd.id', 'p.parentPageId') + .where('p.deletedAt', 'is', null), ), ) .selectFrom('page_descendants') @@ -197,6 +199,7 @@ export class PageRepo { deletedAt: currentDate, }) .where('id', 'in', pageIds) + .where('deletedAt', 'is', null) .execute(); await trx.deleteFrom('shares').where('pageId', 'in', pageIds).execute();