mirror of
https://github.com/docmost/docmost.git
synced 2026-05-15 13:14:11 +08:00
feat: favorites (#2103)
* feat: favorites and templates(ee) * rename migrations * fix sidebar * cleanup tabs * fix * turn off templates * cleanup * uuid validation
This commit is contained in:
@@ -20,6 +20,7 @@ import { AuditContextMiddleware } from '../common/middlewares/audit-context.midd
|
||||
import { ShareModule } from './share/share.module';
|
||||
import { NotificationModule } from './notification/notification.module';
|
||||
import { WatcherModule } from './watcher/watcher.module';
|
||||
import { FavoriteModule } from './favorite/favorite.module';
|
||||
import { SessionModule } from './session/session.module';
|
||||
import { ClsMiddleware } from 'nestjs-cls';
|
||||
|
||||
@@ -31,6 +32,7 @@ import { ClsMiddleware } from 'nestjs-cls';
|
||||
PageModule,
|
||||
AttachmentModule,
|
||||
CommentModule,
|
||||
FavoriteModule,
|
||||
SearchModule,
|
||||
SpaceModule,
|
||||
GroupModule,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
IsIn,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
export class AddFavoriteDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsIn(['page', 'space', 'template'])
|
||||
type: 'page' | 'space' | 'template';
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
pageId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
spaceId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
templateId?: string;
|
||||
}
|
||||
|
||||
export class RemoveFavoriteDto extends AddFavoriteDto {}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { IsIn, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class ListFavoritesDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsIn(['page', 'space', 'template'])
|
||||
type?: 'page' | 'space' | 'template';
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
ForbiddenException,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
NotFoundException,
|
||||
Post,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { FavoriteService } from './services/favorite.service';
|
||||
import { AddFavoriteDto, RemoveFavoriteDto } from './dto/favorite.dto';
|
||||
import { ListFavoritesDto } from './dto/list-favorites.dto';
|
||||
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { AuthUser } from '../../common/decorators/auth-user.decorator';
|
||||
import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator';
|
||||
import { Page, User, Workspace } from '@docmost/db/types/entity.types';
|
||||
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
||||
import { SpaceRepo } from '@docmost/db/repos/space/space.repo';
|
||||
import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo';
|
||||
import { PageAccessService } from '../page/page-access/page-access.service';
|
||||
import { TemplateRepo } from '@docmost/db/repos/template/template.repo';
|
||||
import { FavoriteType } from '@docmost/db/repos/favorite/favorite.repo';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('favorites')
|
||||
export class FavoriteController {
|
||||
constructor(
|
||||
private readonly favoriteService: FavoriteService,
|
||||
private readonly pageRepo: PageRepo,
|
||||
private readonly spaceRepo: SpaceRepo,
|
||||
private readonly spaceMemberRepo: SpaceMemberRepo,
|
||||
private readonly pageAccessService: PageAccessService,
|
||||
private readonly templateRepo: TemplateRepo,
|
||||
) {}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('add')
|
||||
async addFavorite(
|
||||
@Body() dto: AddFavoriteDto,
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
const resolved = await this.resolveAndValidate(dto, user, workspace.id);
|
||||
|
||||
await this.favoriteService.addFavorite(user.id, workspace.id, {
|
||||
type: dto.type,
|
||||
pageId: dto.pageId,
|
||||
spaceId: dto.type === 'space' ? resolved.spaceId : undefined,
|
||||
templateId: dto.templateId,
|
||||
});
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('remove')
|
||||
async removeFavorite(
|
||||
@Body() dto: RemoveFavoriteDto,
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
await this.resolveAndValidate(dto, user, workspace.id);
|
||||
|
||||
await this.favoriteService.removeFavorite(user.id, {
|
||||
type: dto.type,
|
||||
pageId: dto.pageId,
|
||||
spaceId: dto.spaceId,
|
||||
templateId: dto.templateId,
|
||||
});
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post()
|
||||
async getUserFavorites(
|
||||
@Body() dto: ListFavoritesDto,
|
||||
@Body() pagination: PaginationOptions,
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.favoriteService.getUserFavorites(
|
||||
user.id,
|
||||
workspace.id,
|
||||
pagination,
|
||||
dto.type as FavoriteType | undefined,
|
||||
);
|
||||
}
|
||||
|
||||
private async resolveAndValidate(
|
||||
dto: AddFavoriteDto | RemoveFavoriteDto,
|
||||
user: User,
|
||||
workspaceId: string,
|
||||
): Promise<{ spaceId: string; page?: Page }> {
|
||||
if (dto.type === 'page') {
|
||||
if (!dto.pageId) throw new BadRequestException('pageId is required');
|
||||
const page = await this.pageRepo.findById(dto.pageId);
|
||||
if (!page) throw new NotFoundException('Page not found');
|
||||
await this.pageAccessService.validateCanView(page, user);
|
||||
return { spaceId: page.spaceId, page };
|
||||
}
|
||||
|
||||
if (dto.type === 'space') {
|
||||
if (!dto.spaceId) throw new BadRequestException('spaceId is required');
|
||||
const space = await this.spaceRepo.findById(dto.spaceId, workspaceId);
|
||||
if (!space) throw new NotFoundException('Space not found');
|
||||
await this.validateSpaceAccess(user.id, space.id);
|
||||
return { spaceId: space.id };
|
||||
}
|
||||
|
||||
if (dto.type === 'template') {
|
||||
if (!dto.templateId)
|
||||
throw new BadRequestException('templateId is required');
|
||||
const template = await this.templateRepo.findById(
|
||||
dto.templateId,
|
||||
workspaceId,
|
||||
);
|
||||
if (!template) throw new NotFoundException('Template not found');
|
||||
if (template.spaceId) {
|
||||
await this.validateSpaceAccess(user.id, template.spaceId);
|
||||
}
|
||||
return { spaceId: template.spaceId };
|
||||
}
|
||||
|
||||
throw new BadRequestException('Invalid favorite type');
|
||||
}
|
||||
|
||||
private async validateSpaceAccess(
|
||||
userId: string,
|
||||
spaceId: string,
|
||||
): Promise<void> {
|
||||
const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(userId);
|
||||
if (!userSpaceIds.includes(spaceId)) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { FavoriteService } from './services/favorite.service';
|
||||
import { FavoriteController } from './favorite.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [FavoriteController],
|
||||
providers: [FavoriteService],
|
||||
exports: [FavoriteService],
|
||||
})
|
||||
export class FavoriteModule {}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
FavoriteRepo,
|
||||
FavoriteType,
|
||||
} from '@docmost/db/repos/favorite/favorite.repo';
|
||||
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 addFavorite(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
opts: {
|
||||
type: FavoriteType;
|
||||
pageId?: string;
|
||||
spaceId?: string;
|
||||
templateId?: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
const favorite: InsertableFavorite = {
|
||||
userId,
|
||||
pageId: opts.pageId ?? null,
|
||||
spaceId: opts.spaceId ?? null,
|
||||
templateId: opts.templateId ?? null,
|
||||
type: opts.type,
|
||||
workspaceId,
|
||||
};
|
||||
|
||||
await this.favoriteRepo.insert(favorite);
|
||||
}
|
||||
|
||||
async removeFavorite(
|
||||
userId: string,
|
||||
opts: {
|
||||
type: FavoriteType;
|
||||
pageId?: string;
|
||||
spaceId?: string;
|
||||
templateId?: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
if (opts.type === FavoriteType.PAGE && opts.pageId) {
|
||||
await this.favoriteRepo.deleteByUserAndPage(userId, opts.pageId);
|
||||
} else if (opts.type === FavoriteType.SPACE && opts.spaceId) {
|
||||
await this.favoriteRepo.deleteByUserAndSpace(userId, opts.spaceId);
|
||||
} else if (opts.type === FavoriteType.TEMPLATE && opts.templateId) {
|
||||
await this.favoriteRepo.deleteByUserAndTemplate(userId, opts.templateId);
|
||||
}
|
||||
}
|
||||
|
||||
async getUserFavorites(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
pagination: PaginationOptions,
|
||||
type?: FavoriteType,
|
||||
) {
|
||||
const result = await this.favoriteRepo.findUserFavorites(
|
||||
userId,
|
||||
workspaceId,
|
||||
pagination,
|
||||
type,
|
||||
);
|
||||
|
||||
if (result.items.length === 0) {
|
||||
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,
|
||||
);
|
||||
|
||||
let accessiblePageSet: Set<string> | undefined;
|
||||
if (pageFavorites.length > 0) {
|
||||
const pageIds = pageFavorites.map((f) => f.pageId as string);
|
||||
const accessibleIds =
|
||||
await this.pagePermissionRepo.filterAccessiblePageIds({
|
||||
pageIds,
|
||||
userId,
|
||||
});
|
||||
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;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo';
|
||||
import { UserRepo } from '@docmost/db/repos/user/user.repo';
|
||||
import { executeTx } from '@docmost/db/utils';
|
||||
import { WatcherRepo } from '@docmost/db/repos/watcher/watcher.repo';
|
||||
import { FavoriteRepo } from '@docmost/db/repos/favorite/favorite.repo';
|
||||
import { AuditEvent, AuditResource } from '../../../common/events/audit-events';
|
||||
import {
|
||||
AUDIT_SERVICE,
|
||||
@@ -29,6 +30,7 @@ export class GroupUserService {
|
||||
@Inject(forwardRef(() => GroupService))
|
||||
private groupService: GroupService,
|
||||
private readonly watcherRepo: WatcherRepo,
|
||||
private readonly favoriteRepo: FavoriteRepo,
|
||||
@InjectKysely() private readonly db: KyselyDB,
|
||||
@Inject(AUDIT_SERVICE) private readonly auditService: IAuditService,
|
||||
) {}
|
||||
@@ -137,6 +139,12 @@ export class GroupUserService {
|
||||
spaceId,
|
||||
{ trx },
|
||||
);
|
||||
|
||||
await this.favoriteRepo.deleteByUsersWithoutSpaceAccess(
|
||||
[userId],
|
||||
spaceId,
|
||||
{ trx },
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Group, InsertableGroup, User } from '@docmost/db/types/entity.types';
|
||||
import { CursorPaginationResult } from '@docmost/db/pagination/cursor-pagination';
|
||||
import { GroupUserService } from './group-user.service';
|
||||
import { WatcherRepo } from '@docmost/db/repos/watcher/watcher.repo';
|
||||
import { FavoriteRepo } from '@docmost/db/repos/favorite/favorite.repo';
|
||||
import { executeTx } from '@docmost/db/utils';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { AuditEvent, AuditResource } from '../../../common/events/audit-events';
|
||||
@@ -34,6 +35,7 @@ export class GroupService {
|
||||
@Inject(forwardRef(() => GroupUserService))
|
||||
private groupUserService: GroupUserService,
|
||||
private readonly watcherRepo: WatcherRepo,
|
||||
private readonly favoriteRepo: FavoriteRepo,
|
||||
@InjectKysely() private readonly db: KyselyDB,
|
||||
@Inject(AUDIT_SERVICE) private readonly auditService: IAuditService,
|
||||
) {}
|
||||
@@ -189,6 +191,12 @@ export class GroupService {
|
||||
spaceId,
|
||||
{ trx },
|
||||
);
|
||||
|
||||
await this.favoriteRepo.deleteByUsersWithoutSpaceAccess(
|
||||
userIds,
|
||||
spaceId,
|
||||
{ trx },
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { IsOptional, IsUUID } from 'class-validator';
|
||||
|
||||
export class CreatedByUserDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
userId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
spaceId?: string;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { IsOptional, IsString, IsUUID } from 'class-validator';
|
||||
import { SpaceIdDto } from './page.dto';
|
||||
|
||||
export class SidebarPageDto {
|
||||
@IsOptional()
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
import SpaceAbilityFactory from '../casl/abilities/space-ability.factory';
|
||||
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
||||
import { RecentPageDto } from './dto/recent-page.dto';
|
||||
import { CreatedByUserDto } from './dto/created-by-user.dto';
|
||||
import { DuplicatePageDto } from './dto/duplicate-page.dto';
|
||||
import { DeletedPageDto } from './dto/deleted-page.dto';
|
||||
import {
|
||||
@@ -336,6 +337,29 @@ export class PageController {
|
||||
return this.pageService.getRecentPages(user.id, pagination);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('created-by-user')
|
||||
async getCreatedByPages(
|
||||
@Body() dto: CreatedByUserDto,
|
||||
@Body() pagination: PaginationOptions,
|
||||
@AuthUser() user: User,
|
||||
) {
|
||||
const targetUserId = dto.userId ?? user.id;
|
||||
|
||||
if (dto.spaceId) {
|
||||
const ability = await this.spaceAbility.createForUser(
|
||||
user,
|
||||
dto.spaceId,
|
||||
);
|
||||
|
||||
if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
}
|
||||
|
||||
return this.pageService.getCreatedByPages(targetUserId, user.id, pagination, dto.spaceId);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('trash')
|
||||
async getDeletedPages(
|
||||
|
||||
@@ -300,7 +300,7 @@ export class PageService {
|
||||
}
|
||||
|
||||
const result = await executeWithCursorPagination(query, {
|
||||
perPage: 200,
|
||||
perPage: pagination.limit,
|
||||
cursor: pagination.cursor,
|
||||
beforeCursor: pagination.beforeCursor,
|
||||
fields: [
|
||||
@@ -856,6 +856,33 @@ export class PageService {
|
||||
return result;
|
||||
}
|
||||
|
||||
async getCreatedByPages(
|
||||
creatorId: string,
|
||||
requestingUserId: string,
|
||||
pagination: PaginationOptions,
|
||||
spaceId?: string,
|
||||
): Promise<CursorPaginationResult<Page>> {
|
||||
const result = await this.pageRepo.getCreatedByPages(
|
||||
creatorId,
|
||||
requestingUserId,
|
||||
pagination,
|
||||
spaceId,
|
||||
);
|
||||
|
||||
if (result.items.length > 0) {
|
||||
const pageIds = result.items.map((p) => p.id);
|
||||
const accessibleIds =
|
||||
await this.pagePermissionRepo.filterAccessiblePageIds({
|
||||
pageIds,
|
||||
userId: requestingUserId,
|
||||
});
|
||||
const accessibleSet = new Set(accessibleIds);
|
||||
result.items = result.items.filter((p) => accessibleSet.has(p.id));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async getDeletedSpacePages(
|
||||
spaceId: string,
|
||||
userId: string,
|
||||
|
||||
@@ -17,6 +17,7 @@ import { UpdateSpaceMemberRoleDto } from '../dto/update-space-member-role.dto';
|
||||
import { SpaceRole } from '../../../common/helpers/types/permission';
|
||||
import { CursorPaginationResult } from '@docmost/db/pagination/cursor-pagination';
|
||||
import { WatcherRepo } from '@docmost/db/repos/watcher/watcher.repo';
|
||||
import { FavoriteRepo } from '@docmost/db/repos/favorite/favorite.repo';
|
||||
import { executeTx } from '@docmost/db/utils';
|
||||
import { AuditEvent, AuditResource } from '../../../common/events/audit-events';
|
||||
import {
|
||||
@@ -31,6 +32,7 @@ export class SpaceMemberService {
|
||||
private groupUserRepo: GroupUserRepo,
|
||||
private spaceRepo: SpaceRepo,
|
||||
private watcherRepo: WatcherRepo,
|
||||
private favoriteRepo: FavoriteRepo,
|
||||
@InjectKysely() private readonly db: KyselyDB,
|
||||
@Inject(AUDIT_SERVICE) private readonly auditService: IAuditService,
|
||||
) {}
|
||||
@@ -272,6 +274,12 @@ export class SpaceMemberService {
|
||||
dto.spaceId,
|
||||
{ trx },
|
||||
);
|
||||
|
||||
await this.favoriteRepo.deleteByUsersWithoutSpaceAccess(
|
||||
affectedUserIds,
|
||||
dto.spaceId,
|
||||
{ trx },
|
||||
);
|
||||
});
|
||||
|
||||
this.auditService.log({
|
||||
|
||||
@@ -53,7 +53,41 @@ export class SpaceController {
|
||||
pagination: PaginationOptions,
|
||||
@AuthUser() user: User,
|
||||
) {
|
||||
return this.spaceMemberService.getUserSpaces(user.id, pagination);
|
||||
const result = await this.spaceMemberService.getUserSpaces(
|
||||
user.id,
|
||||
pagination,
|
||||
);
|
||||
|
||||
if (result.items.length > 0) {
|
||||
const spaceIds = result.items.map((s) => s.id);
|
||||
const roles = await this.spaceMemberRepo.getUserRolesForSpaces(
|
||||
user.id,
|
||||
spaceIds,
|
||||
);
|
||||
|
||||
const roleMap = new Map<string, string[]>();
|
||||
for (const row of roles) {
|
||||
const existing = roleMap.get(row.spaceId) || [];
|
||||
existing.push(row.role);
|
||||
roleMap.set(row.spaceId, existing);
|
||||
}
|
||||
|
||||
result.items = result.items.map((space) => {
|
||||
const spaceRoles = roleMap.get(space.id);
|
||||
const role = spaceRoles
|
||||
? findHighestUserSpaceRole(
|
||||
spaceRoles.map((r) => ({ userId: user.id, role: r })),
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...space,
|
||||
membership: { userId: user.id, role },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
||||
@@ -54,4 +54,8 @@ export class UpdateWorkspaceDto extends PartialType(CreateWorkspaceDto) {
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
trashRetentionDays: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
allowMemberTemplates: boolean;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import { isPageEmbeddingsTableExists } from '@docmost/db/helpers/helpers';
|
||||
import { CursorPaginationResult } from '@docmost/db/pagination/cursor-pagination';
|
||||
import { ShareRepo } from '@docmost/db/repos/share/share.repo';
|
||||
import { WatcherRepo } from '@docmost/db/repos/watcher/watcher.repo';
|
||||
import { FavoriteRepo } from '@docmost/db/repos/favorite/favorite.repo';
|
||||
import { AuditEvent, AuditResource } from '../../../common/events/audit-events';
|
||||
import {
|
||||
AUDIT_SERVICE,
|
||||
@@ -64,6 +65,7 @@ export class WorkspaceService {
|
||||
private licenseCheckService: LicenseCheckService,
|
||||
private shareRepo: ShareRepo,
|
||||
private watcherRepo: WatcherRepo,
|
||||
private favoriteRepo: FavoriteRepo,
|
||||
@InjectKysely() private readonly db: KyselyDB,
|
||||
@InjectQueue(QueueName.ATTACHMENT_QUEUE) private attachmentQueue: Queue,
|
||||
@InjectQueue(QueueName.BILLING_QUEUE) private billingQueue: Queue,
|
||||
@@ -328,7 +330,8 @@ export class WorkspaceService {
|
||||
typeof updateWorkspaceDto.disablePublicSharing !== 'undefined' ||
|
||||
typeof updateWorkspaceDto.trashRetentionDays !== 'undefined' ||
|
||||
typeof updateWorkspaceDto.mcpEnabled !== 'undefined' ||
|
||||
typeof updateWorkspaceDto.restrictApiToAdmins !== 'undefined'
|
||||
typeof updateWorkspaceDto.restrictApiToAdmins !== 'undefined' ||
|
||||
typeof updateWorkspaceDto.allowMemberTemplates !== 'undefined'
|
||||
) {
|
||||
const ws = await this.db
|
||||
.selectFrom('workspaces')
|
||||
@@ -351,7 +354,8 @@ export class WorkspaceService {
|
||||
if (
|
||||
typeof updateWorkspaceDto.disablePublicSharing !== 'undefined' ||
|
||||
typeof updateWorkspaceDto.trashRetentionDays !== 'undefined' ||
|
||||
typeof updateWorkspaceDto.restrictApiToAdmins !== 'undefined'
|
||||
typeof updateWorkspaceDto.restrictApiToAdmins !== 'undefined' ||
|
||||
typeof updateWorkspaceDto.allowMemberTemplates !== 'undefined'
|
||||
) {
|
||||
if (!this.licenseCheckService.hasFeature(ws.licenseKey, Feature.SECURITY_SETTINGS, ws.plan)) {
|
||||
throw new ForbiddenException(
|
||||
@@ -458,6 +462,20 @@ export class WorkspaceService {
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof updateWorkspaceDto.allowMemberTemplates !== 'undefined') {
|
||||
const prev = settingsBefore?.templates?.allowMemberTemplates ?? false;
|
||||
if (prev !== updateWorkspaceDto.allowMemberTemplates) {
|
||||
before.allowMemberTemplates = prev;
|
||||
after.allowMemberTemplates = updateWorkspaceDto.allowMemberTemplates;
|
||||
}
|
||||
await this.workspaceRepo.updateTemplateSettings(
|
||||
workspaceId,
|
||||
'allowMemberTemplates',
|
||||
updateWorkspaceDto.allowMemberTemplates,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof updateWorkspaceDto.aiChat !== 'undefined') {
|
||||
const prev = settingsBefore?.ai?.chat ?? false;
|
||||
if (prev !== updateWorkspaceDto.aiChat) {
|
||||
@@ -477,6 +495,7 @@ export class WorkspaceService {
|
||||
delete updateWorkspaceDto.generativeAi;
|
||||
delete updateWorkspaceDto.disablePublicSharing;
|
||||
delete updateWorkspaceDto.mcpEnabled;
|
||||
delete updateWorkspaceDto.allowMemberTemplates;
|
||||
delete updateWorkspaceDto.aiChat;
|
||||
|
||||
await this.workspaceRepo.updateWorkspace(
|
||||
@@ -808,6 +827,10 @@ export class WorkspaceService {
|
||||
trx,
|
||||
});
|
||||
|
||||
await this.favoriteRepo.deleteByUserAndWorkspace(userId, workspaceId, {
|
||||
trx,
|
||||
});
|
||||
|
||||
await this.userSessionRepo.revokeByUserId(userId, workspaceId, trx);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user