mirror of
https://github.com/docmost/docmost.git
synced 2026-05-15 05:04:06 +08:00
Merge branch 'main' into perm-x
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<DB, 'shares'>) {
|
||||
|
||||
@@ -209,34 +209,33 @@ export class SpaceMemberRepo {
|
||||
return roles;
|
||||
}
|
||||
|
||||
async getUserSpaceIds(userId: string): Promise<string[]> {
|
||||
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<string[]> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
Submodule apps/server/src/ee updated: 075761c2d9...ed75f5071b
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user