mirror of
https://github.com/docmost/docmost.git
synced 2025-11-19 10:31:12 +10:00
feat: delete space and edit space slug (#307)
* feat: make space slug editable * feat: delete space * client
This commit is contained in:
@ -4,10 +4,11 @@ import { AttachmentController } from './attachment.controller';
|
||||
import { StorageModule } from '../../integrations/storage/storage.module';
|
||||
import { UserModule } from '../user/user.module';
|
||||
import { WorkspaceModule } from '../workspace/workspace.module';
|
||||
import { AttachmentProcessor } from './processors/attachment.processor';
|
||||
|
||||
@Module({
|
||||
imports: [StorageModule, UserModule, WorkspaceModule],
|
||||
controllers: [AttachmentController],
|
||||
providers: [AttachmentService],
|
||||
providers: [AttachmentService, AttachmentProcessor],
|
||||
})
|
||||
export class AttachmentModule {}
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
import { Logger, OnModuleDestroy } from '@nestjs/common';
|
||||
import { OnWorkerEvent, Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Job } from 'bullmq';
|
||||
import { AttachmentService } from '../services/attachment.service';
|
||||
import { QueueJob, QueueName } from 'src/integrations/queue/constants';
|
||||
import { Space } from '@docmost/db/types/entity.types';
|
||||
|
||||
@Processor(QueueName.ATTACHEMENT_QUEUE)
|
||||
export class AttachmentProcessor extends WorkerHost implements OnModuleDestroy {
|
||||
private readonly logger = new Logger(AttachmentProcessor.name);
|
||||
constructor(private readonly attachmentService: AttachmentService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async process(job: Job<Space, void>): Promise<void> {
|
||||
try {
|
||||
if (job.name === QueueJob.DELETE_SPACE_ATTACHMENTS) {
|
||||
await this.attachmentService.handleDeleteSpaceAttachments(job.data.id);
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@OnWorkerEvent('active')
|
||||
onActive(job: Job) {
|
||||
this.logger.debug(`Processing ${job.name} job`);
|
||||
}
|
||||
|
||||
@OnWorkerEvent('failed')
|
||||
onError(job: Job) {
|
||||
this.logger.error(
|
||||
`Error processing ${job.name} job. Reason: ${job.failedReason}`,
|
||||
);
|
||||
}
|
||||
|
||||
@OnWorkerEvent('completed')
|
||||
onCompleted(job: Job) {
|
||||
this.logger.debug(`Completed ${job.name} job`);
|
||||
}
|
||||
|
||||
async onModuleDestroy(): Promise<void> {
|
||||
if (this.worker) {
|
||||
await this.worker.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -256,4 +256,37 @@ export class AttachmentService {
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
async handleDeleteSpaceAttachments(spaceId: string) {
|
||||
try {
|
||||
const attachments = await this.attachmentRepo.findBySpaceId(spaceId);
|
||||
if (!attachments || attachments.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const failedDeletions = [];
|
||||
|
||||
await Promise.all(
|
||||
attachments.map(async (attachment) => {
|
||||
try {
|
||||
await this.storageService.delete(attachment.filePath);
|
||||
await this.attachmentRepo.deleteAttachmentById(attachment.id);
|
||||
} catch (err) {
|
||||
failedDeletions.push(attachment.id);
|
||||
this.logger.log(
|
||||
`DeleteSpaceAttachments: failed to delete attachment ${attachment.id}:`,
|
||||
err,
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if(failedDeletions.length === attachments.length){
|
||||
throw new Error(`Failed to delete any attachments for spaceId: ${spaceId}`);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,9 @@ import { executeTx } from '@docmost/db/utils';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { SpaceMemberService } from './space-member.service';
|
||||
import { SpaceRole } from '../../../common/helpers/types/permission';
|
||||
import { QueueJob, QueueName } from 'src/integrations/queue/constants';
|
||||
import { Queue } from 'bullmq';
|
||||
import { InjectQueue } from '@nestjs/bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class SpaceService {
|
||||
@ -21,6 +24,7 @@ export class SpaceService {
|
||||
private spaceRepo: SpaceRepo,
|
||||
private spaceMemberService: SpaceMemberService,
|
||||
@InjectKysely() private readonly db: KyselyDB,
|
||||
@InjectQueue(QueueName.ATTACHEMENT_QUEUE) private attachmentQueue: Queue,
|
||||
) {}
|
||||
|
||||
async createSpace(
|
||||
@ -88,10 +92,24 @@ export class SpaceService {
|
||||
updateSpaceDto: UpdateSpaceDto,
|
||||
workspaceId: string,
|
||||
): Promise<Space> {
|
||||
if (updateSpaceDto?.slug) {
|
||||
const slugExists = await this.spaceRepo.slugExists(
|
||||
updateSpaceDto.slug,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (slugExists) {
|
||||
throw new BadRequestException(
|
||||
'Space slug exists. Please use a unique space slug',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return await this.spaceRepo.updateSpace(
|
||||
{
|
||||
name: updateSpaceDto.name,
|
||||
description: updateSpaceDto.description,
|
||||
slug: updateSpaceDto.slug,
|
||||
},
|
||||
updateSpaceDto.spaceId,
|
||||
workspaceId,
|
||||
@ -120,4 +138,14 @@ export class SpaceService {
|
||||
|
||||
return spaces;
|
||||
}
|
||||
|
||||
async deleteSpace(spaceId: string, workspaceId: string): Promise<void> {
|
||||
const space = await this.spaceRepo.findById(spaceId, workspaceId);
|
||||
if (!space) {
|
||||
throw new NotFoundException('Space not found');
|
||||
}
|
||||
|
||||
await this.spaceRepo.deleteSpace(spaceId, workspaceId);
|
||||
await this.attachmentQueue.add(QueueJob.DELETE_SPACE_ATTACHMENTS, space);
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ export class SpaceController {
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('create')
|
||||
createGroup(
|
||||
createSpace(
|
||||
@Body() createSpaceDto: CreateSpaceDto,
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@ -111,7 +111,7 @@ export class SpaceController {
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('update')
|
||||
async updateGroup(
|
||||
async updateSpace(
|
||||
@Body() updateSpaceDto: UpdateSpaceDto,
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@ -126,6 +126,23 @@ export class SpaceController {
|
||||
return this.spaceService.updateSpace(updateSpaceDto, workspace.id);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('delete')
|
||||
async deleteSpace(
|
||||
@Body() spaceIdDto: SpaceIdDto,
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
const ability = await this.spaceAbility.createForUser(
|
||||
user,
|
||||
spaceIdDto.spaceId,
|
||||
);
|
||||
if (ability.cannot(SpaceCaslAction.Manage, SpaceCaslSubject.Settings)) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
return this.spaceService.deleteSpace(spaceIdDto.spaceId, workspace.id);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('members')
|
||||
async getSpaceMembers(
|
||||
|
||||
Reference in New Issue
Block a user