diff --git a/apps/server/src/core/favorite/favorite.controller.ts b/apps/server/src/core/favorite/favorite.controller.ts index 47e63aace..2d6c19398 100644 --- a/apps/server/src/core/favorite/favorite.controller.ts +++ b/apps/server/src/core/favorite/favorite.controller.ts @@ -48,9 +48,9 @@ export class FavoriteController { await this.favoriteService.addFavorite(user.id, workspace.id, { type: dto.type, - pageId: dto.pageId, + pageId: dto.type === 'page' ? dto.pageId : undefined, spaceId: dto.type === 'space' ? resolved.spaceId : undefined, - templateId: dto.templateId, + templateId: dto.type === 'template' ? dto.templateId : undefined, }); } diff --git a/apps/server/src/core/favorite/services/favorite.service.ts b/apps/server/src/core/favorite/services/favorite.service.ts index 79902e645..ff10cb80a 100644 --- a/apps/server/src/core/favorite/services/favorite.service.ts +++ b/apps/server/src/core/favorite/services/favorite.service.ts @@ -6,14 +6,12 @@ import { import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; import { InsertableFavorite } from '@docmost/db/types/entity.types'; import { PagePermissionRepo } from '@docmost/db/repos/page/page-permission.repo'; -import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo'; @Injectable() export class FavoriteService { constructor( private readonly favoriteRepo: FavoriteRepo, private readonly pagePermissionRepo: PagePermissionRepo, - private readonly spaceMemberRepo: SpaceMemberRepo, ) {} async getFavoriteIds( @@ -43,12 +41,6 @@ export class FavoriteService { result.items = result.items.filter((id) => accessibleSet.has(id)); } - if (type === FavoriteType.SPACE) { - const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(userId); - const spaceSet = new Set(userSpaceIds); - result.items = result.items.filter((id) => spaceSet.has(id)); - } - return result; } @@ -111,9 +103,6 @@ export class FavoriteService { return result; } - const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(userId); - const spaceSet = new Set(userSpaceIds); - const pageFavorites = result.items.filter( (f) => f.type === FavoriteType.PAGE && f.pageId, ); @@ -129,19 +118,11 @@ export class FavoriteService { accessiblePageSet = new Set(accessibleIds); } - result.items = result.items.filter((f) => { - if (f.type === FavoriteType.PAGE) { - return f.pageId && accessiblePageSet?.has(f.pageId); - } - if (f.type === FavoriteType.SPACE) { - return f.spaceId && spaceSet.has(f.spaceId); - } - if (f.type === FavoriteType.TEMPLATE) { - const templateSpaceId = (f as any).template?.spaceId; - return !templateSpaceId || spaceSet.has(templateSpaceId); - } - return true; - }); + result.items = result.items.filter( + (f) => + f.type !== FavoriteType.PAGE || + (f.pageId && accessiblePageSet?.has(f.pageId)), + ); return result; } diff --git a/apps/server/src/database/repos/favorite/favorite.repo.ts b/apps/server/src/database/repos/favorite/favorite.repo.ts index 7b7aee302..5f6ed05d2 100644 --- a/apps/server/src/database/repos/favorite/favorite.repo.ts +++ b/apps/server/src/database/repos/favorite/favorite.repo.ts @@ -8,6 +8,7 @@ import { jsonObjectFrom } from 'kysely/helpers/postgres'; import { ExpressionBuilder, SelectQueryBuilder, sql } from 'kysely'; import { DB } from '@docmost/db/types/db'; import { dbOrTx } from '@docmost/db/utils'; +import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo'; export const FavoriteType = { PAGE: 'page', @@ -19,7 +20,10 @@ export type FavoriteType = (typeof FavoriteType)[keyof typeof FavoriteType]; @Injectable() export class FavoriteRepo { - constructor(@InjectKysely() private readonly db: KyselyDB) {} + constructor( + @InjectKysely() private readonly db: KyselyDB, + private readonly spaceMemberRepo: SpaceMemberRepo, + ) {} async insert(favorite: InsertableFavorite): Promise { try { @@ -82,6 +86,8 @@ export class FavoriteRepo { .where('favorites.workspaceId', '=', workspaceId) .where('favorites.type', '=', type); + query = this.applyMembershipFilter(query, userId); + if (spaceId) { query = this.applySpaceFilter(query, type, spaceId); } @@ -113,6 +119,8 @@ export class FavoriteRepo { .where('favorites.userId', '=', userId) .where('favorites.workspaceId', '=', workspaceId); + query = this.applyMembershipFilter(query, userId); + if (type) { query = query.where('favorites.type', '=', type); } @@ -155,7 +163,7 @@ export class FavoriteRepo { ): Promise { if (userIds.length === 0) return; - const { trx } = opts; + const { trx } = opts ?? {}; const db = dbOrTx(this.db, trx); const usersWithAccess = db @@ -174,7 +182,24 @@ export class FavoriteRepo { await db .deleteFrom('favorites') .where('userId', 'in', userIds) - .where('spaceId', '=', spaceId) + .where((eb) => + eb.or([ + eb('spaceId', '=', spaceId), + eb( + 'pageId', + 'in', + eb.selectFrom('pages').select('id').where('spaceId', '=', spaceId), + ), + eb( + 'templateId', + 'in', + eb + .selectFrom('templates') + .select('id') + .where('spaceId', '=', spaceId), + ), + ]), + ) .where('userId', 'not in', usersWithAccess) .execute(); } @@ -194,6 +219,46 @@ export class FavoriteRepo { .execute(); } + private applyMembershipFilter>( + query: Q, + userId: string, + ): Q { + const spaceIds = this.spaceMemberRepo.getUserSpaceIdsQuery(userId); + return query.where((eb: any) => + eb.or([ + eb.and([ + eb('favorites.type', '=', FavoriteType.SPACE), + eb('favorites.spaceId', 'in', spaceIds), + ]), + eb.and([ + eb('favorites.type', '=', FavoriteType.PAGE), + eb.exists( + eb + .selectFrom('pages') + .select(sql`1`.as('one')) + .whereRef('pages.id', '=', 'favorites.pageId') + .where('pages.spaceId', 'in', spaceIds), + ), + ]), + eb.and([ + eb('favorites.type', '=', FavoriteType.TEMPLATE), + eb.exists( + eb + .selectFrom('templates') + .select(sql`1`.as('one')) + .whereRef('templates.id', '=', 'favorites.templateId') + .where((e: any) => + e.or([ + e('templates.spaceId', 'is', null), + e('templates.spaceId', 'in', spaceIds), + ]), + ), + ), + ]), + ]), + ) as Q; + } + private applySpaceFilter>( query: Q, type: FavoriteType | undefined, @@ -239,7 +304,8 @@ export class FavoriteRepo { 'pages.isBase', 'pages.spaceId', ]) - .whereRef('pages.id', '=', 'favorites.pageId'), + .whereRef('pages.id', '=', 'favorites.pageId') + .where(sql.ref('favorites.type'), '=', FavoriteType.PAGE), ).as('page'); } @@ -269,8 +335,8 @@ export class FavoriteRepo { .select(['spaces.id', 'spaces.name', 'spaces.slug', 'spaces.logo']) .where(({ or, ref }) => or([ - sql`${ref('spaces.id')} = ${ref('favorites.spaceId')}`, - sql`${ref('spaces.id')} = (SELECT pages.space_id FROM pages WHERE pages.id = ${ref('favorites.pageId')})`, + sql`${ref('favorites.type')} = ${FavoriteType.SPACE} and ${ref('spaces.id')} = ${ref('favorites.spaceId')}`, + sql`${ref('favorites.type')} = ${FavoriteType.PAGE} and ${ref('spaces.id')} = (SELECT pages.space_id FROM pages WHERE pages.id = ${ref('favorites.pageId')})`, ]), ), ).as('space'); @@ -287,7 +353,8 @@ export class FavoriteRepo { 'templates.icon', 'templates.spaceId', ]) - .whereRef('templates.id', '=', 'favorites.templateId'), + .whereRef('templates.id', '=', 'favorites.templateId') + .where(sql.ref('favorites.type'), '=', FavoriteType.TEMPLATE), ).as('template'); } } diff --git a/apps/server/src/ee b/apps/server/src/ee index 039bd87f8..5b873a53c 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit 039bd87f8a8181ae2d0c2d0dd39f3aa2cfac9029 +Subproject commit 5b873a53c835f81bdd2f2e4ce827288417ffa26b