mirror of
https://github.com/docmost/docmost.git
synced 2026-08-30 02:25:01 +08:00
optimize share tree filtering for restricted pages
- Add getPageAndDescendantsExcludingRestricted to PageRepo that filters
restricted subtrees in a single query using recursive CTE
- Block share tree access when shared page inherits restriction from ancestor
This commit is contained in:
@@ -43,18 +43,20 @@ export class ShareService {
|
|||||||
throw new NotFoundException('Share not found');
|
throw new NotFoundException('Share not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isRestricted =
|
||||||
|
await this.pagePermissionRepo.hasRestrictedAncestor(share.pageId);
|
||||||
|
if (isRestricted) {
|
||||||
|
throw new NotFoundException('Share not found');
|
||||||
|
}
|
||||||
|
|
||||||
if (share.includeSubPages) {
|
if (share.includeSubPages) {
|
||||||
const pageList = await this.pageRepo.getPageAndDescendants(share.pageId, {
|
const pageTree =
|
||||||
includeContent: false,
|
await this.pageRepo.getPageAndDescendantsExcludingRestricted(
|
||||||
});
|
share.pageId,
|
||||||
|
{ includeContent: false },
|
||||||
|
);
|
||||||
|
|
||||||
// Filter out restricted pages and their descendants
|
return { share, pageTree };
|
||||||
const restrictedIds =
|
|
||||||
await this.pagePermissionRepo.getRestrictedSubtreeIds(share.pageId);
|
|
||||||
const restrictedSet = new Set(restrictedIds);
|
|
||||||
const filteredPages = pageList.filter((page) => !restrictedSet.has(page.id));
|
|
||||||
|
|
||||||
return { share, pageTree: filteredPages };
|
|
||||||
} else {
|
} else {
|
||||||
return { share, pageTree: [] };
|
return { share, pageTree: [] };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -454,4 +454,73 @@ export class PageRepo {
|
|||||||
.selectAll()
|
.selectAll()
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get page and all descendants, excluding restricted pages and their subtrees.
|
||||||
|
* More efficient than getPageAndDescendants + filtering because:
|
||||||
|
* 1. Single DB query (no separate restricted IDs query)
|
||||||
|
* 2. Stops traversing at restricted pages (doesn't fetch data to discard)
|
||||||
|
* 3. No in-memory filtering needed
|
||||||
|
*/
|
||||||
|
async getPageAndDescendantsExcludingRestricted(
|
||||||
|
parentPageId: string,
|
||||||
|
opts: { includeContent: boolean },
|
||||||
|
) {
|
||||||
|
return this.db
|
||||||
|
.withRecursive('page_hierarchy', (db) =>
|
||||||
|
db
|
||||||
|
.selectFrom('pages')
|
||||||
|
.leftJoin('pageAccess', 'pageAccess.pageId', 'pages.id')
|
||||||
|
.select([
|
||||||
|
'pages.id',
|
||||||
|
'pages.slugId',
|
||||||
|
'pages.title',
|
||||||
|
'pages.icon',
|
||||||
|
'pages.position',
|
||||||
|
'pages.parentPageId',
|
||||||
|
'pages.spaceId',
|
||||||
|
'pages.workspaceId',
|
||||||
|
sql<boolean>`page_access.id IS NOT NULL`.as('isRestricted'),
|
||||||
|
])
|
||||||
|
.$if(opts?.includeContent, (qb) => qb.select('pages.content'))
|
||||||
|
.where('pages.id', '=', parentPageId)
|
||||||
|
.where('pages.deletedAt', 'is', null)
|
||||||
|
.unionAll((exp) =>
|
||||||
|
exp
|
||||||
|
.selectFrom('pages as p')
|
||||||
|
.innerJoin('page_hierarchy as ph', 'p.parentPageId', 'ph.id')
|
||||||
|
.leftJoin('pageAccess', 'pageAccess.pageId', 'p.id')
|
||||||
|
.select([
|
||||||
|
'p.id',
|
||||||
|
'p.slugId',
|
||||||
|
'p.title',
|
||||||
|
'p.icon',
|
||||||
|
'p.position',
|
||||||
|
'p.parentPageId',
|
||||||
|
'p.spaceId',
|
||||||
|
'p.workspaceId',
|
||||||
|
sql<boolean>`page_access.id IS NOT NULL`.as('isRestricted'),
|
||||||
|
])
|
||||||
|
.$if(opts?.includeContent, (qb) => qb.select('p.content'))
|
||||||
|
.where('p.deletedAt', 'is', null)
|
||||||
|
// Only recurse into children of non-restricted pages
|
||||||
|
.where('ph.isRestricted', '=', false),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.selectFrom('page_hierarchy')
|
||||||
|
.select([
|
||||||
|
'id',
|
||||||
|
'slugId',
|
||||||
|
'title',
|
||||||
|
'icon',
|
||||||
|
'position',
|
||||||
|
'parentPageId',
|
||||||
|
'spaceId',
|
||||||
|
'workspaceId',
|
||||||
|
])
|
||||||
|
.$if(opts?.includeContent, (qb) => qb.select('content'))
|
||||||
|
// Filter out restricted pages from the result
|
||||||
|
.where('isRestricted', '=', false)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user