+
{space.name}
diff --git a/apps/server/src/core/group/dto/create-group.dto.ts b/apps/server/src/core/group/dto/create-group.dto.ts
index 2efdad35..34a759cb 100644
--- a/apps/server/src/core/group/dto/create-group.dto.ts
+++ b/apps/server/src/core/group/dto/create-group.dto.ts
@@ -11,7 +11,7 @@ import {Transform, TransformFnParams} from "class-transformer";
export class CreateGroupDto {
@MinLength(2)
- @MaxLength(50)
+ @MaxLength(100)
@IsString()
@Transform(({ value }: TransformFnParams) => value?.trim())
name: string;
diff --git a/apps/server/src/core/search/search.service.ts b/apps/server/src/core/search/search.service.ts
index 515580bf..9561ed99 100644
--- a/apps/server/src/core/search/search.service.ts
+++ b/apps/server/src/core/search/search.service.ts
@@ -76,16 +76,13 @@ export class SearchService {
queryResults = queryResults.where('spaceId', '=', searchParams.spaceId);
} else if (opts.userId && !searchParams.spaceId) {
// only search spaces the user is a member of
- const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(
- opts.userId,
- );
- if (userSpaceIds.length > 0) {
- queryResults = queryResults
- .where('spaceId', 'in', userSpaceIds)
- .where('workspaceId', '=', opts.workspaceId);
- } else {
- return [];
- }
+ queryResults = queryResults
+ .where(
+ 'spaceId',
+ 'in',
+ this.spaceMemberRepo.getUserSpaceIdsQuery(opts.userId),
+ )
+ .where('workspaceId', '=', opts.workspaceId);
} else if (searchParams.shareId && !searchParams.spaceId && !opts.userId) {
// search in shares
const shareId = searchParams.shareId;
diff --git a/apps/server/src/core/share/share.service.ts b/apps/server/src/core/share/share.service.ts
index 5933ad8d..ac339c21 100644
--- a/apps/server/src/core/share/share.service.ts
+++ b/apps/server/src/core/share/share.service.ts
@@ -140,80 +140,82 @@ export class ShareService {
.withRecursive('page_hierarchy', (cte) =>
cte
.selectFrom('pages')
+ .leftJoin('shares', 'shares.pageId', 'pages.id')
.select([
- 'id',
- 'slugId',
+ 'pages.id',
+ 'pages.slugId',
'pages.title',
'pages.icon',
- 'parentPageId',
+ 'pages.parentPageId',
sql`0`.as('level'),
+ 'shares.id as shareId',
+ 'shares.key as shareKey',
+ 'shares.includeSubPages',
+ 'shares.searchIndexing',
+ 'shares.creatorId',
+ 'shares.spaceId',
+ 'shares.workspaceId',
+ 'shares.createdAt',
])
- .where(isValidUUID(pageId) ? 'id' : 'slugId', '=', pageId)
- .where('deletedAt', 'is', null)
- .unionAll((union) =>
- union
- .selectFrom('pages as p')
- .select([
- 'p.id',
- 'p.slugId',
- 'p.title',
- 'p.icon',
- 'p.parentPageId',
- // Increase the level by 1 for each ancestor.
- sql`ph.level + 1`.as('level'),
- ])
- .innerJoin('page_hierarchy as ph', 'ph.parentPageId', 'p.id')
- .where('p.deletedAt', 'is', null),
+ .where(isValidUUID(pageId) ? 'pages.id' : 'pages.slugId', '=', pageId)
+ .where('pages.deletedAt', 'is', null)
+ .unionAll(
+ (union) =>
+ union
+ .selectFrom('pages as p')
+ .innerJoin('page_hierarchy as ph', 'ph.parentPageId', 'p.id')
+ .leftJoin('shares as s', 's.pageId', 'p.id')
+ .select([
+ 'p.id',
+ 'p.slugId',
+ 'p.title',
+ 'p.icon',
+ 'p.parentPageId',
+ sql`ph.level + 1`.as('level'),
+ 's.id as shareId',
+ 's.key as shareKey',
+ 's.includeSubPages',
+ 's.searchIndexing',
+ 's.creatorId',
+ 's.spaceId',
+ 's.workspaceId',
+ 's.createdAt',
+ ])
+ .where('p.deletedAt', 'is', null)
+ .where(sql`ph.share_id`, 'is', null) // stop if share found
+ .where(sql`ph.level`, '<', sql`25`), // prevent loop
),
)
.selectFrom('page_hierarchy')
- .leftJoin('shares', 'shares.pageId', 'page_hierarchy.id')
- .select([
- 'page_hierarchy.id as sharedPageId',
- 'page_hierarchy.slugId as sharedPageSlugId',
- 'page_hierarchy.title as sharedPageTitle',
- 'page_hierarchy.icon as sharedPageIcon',
- 'page_hierarchy.level as level',
- 'shares.id',
- 'shares.key',
- 'shares.pageId',
- 'shares.includeSubPages',
- 'shares.searchIndexing',
- 'shares.creatorId',
- 'shares.spaceId',
- 'shares.workspaceId',
- 'shares.createdAt',
- 'shares.updatedAt',
- ])
- .where('shares.id', 'is not', null)
- .orderBy('page_hierarchy.level', 'asc')
+ .selectAll()
+ .where('shareId', 'is not', null)
+ .limit(1)
.executeTakeFirst();
if (!share || share.workspaceId !== workspaceId) {
return undefined;
}
- if (share.level === 1 && !share.includeSubPages) {
- // we can only show a page if its shared ancestor permits it
+ if ((share.level as number) > 0 && !share.includeSubPages) {
return undefined;
}
return {
- id: share.id,
- key: share.key,
+ id: share.shareId,
+ key: share.shareKey,
includeSubPages: share.includeSubPages,
searchIndexing: share.searchIndexing,
- pageId: share.pageId,
+ pageId: share.id,
creatorId: share.creatorId,
spaceId: share.spaceId,
workspaceId: share.workspaceId,
createdAt: share.createdAt,
level: share.level,
sharedPage: {
- id: share.sharedPageId,
- slugId: share.sharedPageSlugId,
- title: share.sharedPageTitle,
- icon: share.sharedPageIcon,
+ id: share.id,
+ slugId: share.slugId,
+ title: share.title,
+ icon: share.icon,
},
};
}
diff --git a/apps/server/src/core/space/dto/create-space.dto.ts b/apps/server/src/core/space/dto/create-space.dto.ts
index bd7e6689..310bdcf2 100644
--- a/apps/server/src/core/space/dto/create-space.dto.ts
+++ b/apps/server/src/core/space/dto/create-space.dto.ts
@@ -9,7 +9,7 @@ import {Transform, TransformFnParams} from "class-transformer";
export class CreateSpaceDto {
@MinLength(2)
- @MaxLength(50)
+ @MaxLength(100)
@IsString()
@Transform(({ value }: TransformFnParams) => value?.trim())
name: string;
@@ -19,7 +19,7 @@ export class CreateSpaceDto {
description?: string;
@MinLength(2)
- @MaxLength(50)
+ @MaxLength(100)
@IsAlphanumeric()
slug: string;
}
diff --git a/apps/server/src/database/repos/page/page.repo.ts b/apps/server/src/database/repos/page/page.repo.ts
index 82449e20..fdf2a3a4 100644
--- a/apps/server/src/database/repos/page/page.repo.ts
+++ b/apps/server/src/database/repos/page/page.repo.ts
@@ -293,24 +293,18 @@ export class PageRepo {
}
async getRecentPages(userId: string, pagination: PaginationOptions) {
- const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(userId);
-
const query = this.db
.selectFrom('pages')
.select(this.baseFields)
.select((eb) => this.withSpace(eb))
- .where('spaceId', 'in', userSpaceIds)
+ .where('spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(userId))
.where('deletedAt', 'is', null)
.orderBy('updatedAt', 'desc');
- const hasEmptyIds = userSpaceIds.length === 0;
- const result = executeWithPagination(query, {
+ return executeWithPagination(query, {
page: pagination.page,
perPage: pagination.limit,
- hasEmptyIds,
});
-
- return result;
}
async getDeletedPagesInSpace(spaceId: string, pagination: PaginationOptions) {
diff --git a/apps/server/src/database/repos/share/share.repo.ts b/apps/server/src/database/repos/share/share.repo.ts
index c2943c07..3cf4ab3b 100644
--- a/apps/server/src/database/repos/share/share.repo.ts
+++ b/apps/server/src/database/repos/share/share.repo.ts
@@ -137,25 +137,19 @@ export class ShareRepo {
}
async getShares(userId: string, pagination: PaginationOptions) {
- const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(userId);
-
const query = this.db
.selectFrom('shares')
.select(this.baseFields)
.select((eb) => this.withPage(eb))
.select((eb) => this.withSpace(eb, userId))
.select((eb) => this.withCreator(eb))
- .where('spaceId', 'in', userSpaceIds)
+ .where('spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(userId))
.orderBy('updatedAt', 'desc');
- const hasEmptyIds = userSpaceIds.length === 0;
- const result = executeWithPagination(query, {
+ return executeWithPagination(query, {
page: pagination.page,
perPage: pagination.limit,
- hasEmptyIds,
});
-
- return result;
}
withPage(eb: ExpressionBuilder) {
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 0850c5e1..64e4ba2c 100644
--- a/apps/server/src/database/repos/space/space-member.repo.ts
+++ b/apps/server/src/database/repos/space/space-member.repo.ts
@@ -209,34 +209,33 @@ export class SpaceMemberRepo {
return roles;
}
- async getUserSpaceIds(userId: string): Promise {
- const membership = await this.db
+ getUserSpaceIdsQuery(userId: string) {
+ return this.db
.selectFrom('spaceMembers')
.innerJoin('spaces', 'spaces.id', 'spaceMembers.spaceId')
- .select(['spaces.id'])
+ .select('spaces.id')
.where('userId', '=', userId)
.union(
this.db
.selectFrom('spaceMembers')
.innerJoin('groupUsers', 'groupUsers.groupId', 'spaceMembers.groupId')
.innerJoin('spaces', 'spaces.id', 'spaceMembers.spaceId')
- .select(['spaces.id'])
+ .select('spaces.id')
.where('groupUsers.userId', '=', userId),
- )
- .execute();
+ );
+ }
+ async getUserSpaceIds(userId: string): Promise {
+ const membership = await this.getUserSpaceIdsQuery(userId).execute();
return membership.map((space) => space.id);
}
async getUserSpaces(userId: string, pagination: PaginationOptions) {
- const userSpaceIds = await this.getUserSpaceIds(userId);
-
let query = this.db
.selectFrom('spaces')
.selectAll()
.select((eb) => [this.spaceRepo.withMemberCount(eb)])
- //.where('workspaceId', '=', workspaceId)
- .where('id', 'in', userSpaceIds)
+ .where('id', 'in', this.getUserSpaceIdsQuery(userId))
.orderBy('createdAt', 'asc');
if (pagination.query) {
@@ -253,14 +252,9 @@ export class SpaceMemberRepo {
);
}
- const hasEmptyIds = userSpaceIds.length === 0;
-
- const result = executeWithPagination(query, {
+ return executeWithPagination(query, {
page: pagination.page,
perPage: pagination.limit,
- hasEmptyIds,
});
-
- return result;
}
}
diff --git a/apps/server/src/ee b/apps/server/src/ee
index 075761c2..ed75f507 160000
--- a/apps/server/src/ee
+++ b/apps/server/src/ee
@@ -1 +1 @@
-Subproject commit 075761c2d9bcae7adcc3de4b1c5b8f8c3b315878
+Subproject commit ed75f5071b3c8d628fb4fac9300818525ca20f52
diff --git a/apps/server/src/integrations/import/file-task.controller.ts b/apps/server/src/integrations/import/file-task.controller.ts
index 305779b4..096cd5aa 100644
--- a/apps/server/src/integrations/import/file-task.controller.ts
+++ b/apps/server/src/integrations/import/file-task.controller.ts
@@ -10,46 +10,59 @@ import {
} from '@nestjs/common';
import SpaceAbilityFactory from '../../core/casl/abilities/space-ability.factory';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
-import { User } from '@docmost/db/types/entity.types';
+import { User, Workspace } from '@docmost/db/types/entity.types';
import {
SpaceCaslAction,
SpaceCaslSubject,
} from '../../core/casl/interfaces/space-ability.type';
+import {
+ WorkspaceCaslAction,
+ WorkspaceCaslSubject,
+} from '../../core/casl/interfaces/workspace-ability.type';
+import WorkspaceAbilityFactory from '../../core/casl/abilities/workspace-ability.factory';
+import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator';
import { InjectKysely } from 'nestjs-kysely';
import { KyselyDB } from '@docmost/db/types/kysely.types';
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';
@Controller('file-tasks')
export class FileTaskController {
constructor(
- private readonly spaceMemberRepo: SpaceMemberRepo,
private readonly spaceAbility: SpaceAbilityFactory,
+ private readonly workspaceAbility: WorkspaceAbilityFactory,
+ private readonly spaceMemberRepo: SpaceMemberRepo,
@InjectKysely() private readonly db: KyselyDB,
) {}
@UseGuards(JwtAuthGuard)
@HttpCode(HttpStatus.OK)
@Post()
- async getFileTasks(@AuthUser() user: User) {
- const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(user.id);
-
- if (!userSpaceIds || userSpaceIds.length === 0) {
- return [];
+ async getFileTasks(
+ @Body() pagination: PaginationOptions,
+ @AuthUser() user: User,
+ @AuthWorkspace() workspace: Workspace,
+ ) {
+ const ability = this.workspaceAbility.createForUser(user, workspace);
+ if (
+ ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Settings)
+ ) {
+ throw new ForbiddenException();
}
- const fileTasks = await this.db
+ const query = this.db
.selectFrom('fileTasks')
.selectAll()
- .where('spaceId', 'in', userSpaceIds)
- .execute();
+ .where('spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(user.id))
+ .orderBy('createdAt', 'desc');
- if (!fileTasks) {
- throw new NotFoundException('File task not found');
- }
-
- return fileTasks;
+ return executeWithPagination(query, {
+ page: pagination.page,
+ perPage: pagination.limit,
+ });
}
@UseGuards(JwtAuthGuard)