diff --git a/src/channels/channels.controller.ts b/src/channels/channels.controller.ts index 19f82c6..f131d09 100644 --- a/src/channels/channels.controller.ts +++ b/src/channels/channels.controller.ts @@ -54,4 +54,11 @@ export class ChannelsController { if (!userId) throw new UnauthorizedException('missing user'); return this.channelsService.leaveChannel(channelId, userId); } + + @Post(':id/close') + close(@Req() req: AuthedRequest, @Param('id') channelId: string) { + const userId = req.userId ?? ''; + if (!userId) throw new UnauthorizedException('missing user'); + return this.channelsService.closeChannel(channelId, userId); + } } diff --git a/src/channels/channels.service.ts b/src/channels/channels.service.ts index 63e02ea..1d66861 100644 --- a/src/channels/channels.service.ts +++ b/src/channels/channels.service.ts @@ -64,6 +64,18 @@ export class ChannelsService { return rows.map((r) => ({ userId: r.userId })); } + async closeChannel(channelId: string, userId: string) { + const channel = await this.channelRepo.findOne({ where: { id: channelId } }); + if (!channel) throw new NotFoundException('channel not found'); + const member = await this.memberRepo.findOne({ where: { channelId, userId } }); + if (!member && !channel.isPublic) { + throw new ForbiddenException('not a channel member'); + } + channel.closed = true; + await this.channelRepo.save(channel); + return { status: 'ok', channelId, closed: true }; + } + async joinChannel(channelId: string, userId: string) { const channel = await this.channelRepo.findOne({ where: { id: channelId } }); if (!channel) throw new NotFoundException('channel not found'); diff --git a/src/entities/channel.entity.ts b/src/entities/channel.entity.ts index 127a199..c21ee6b 100644 --- a/src/entities/channel.entity.ts +++ b/src/entities/channel.entity.ts @@ -31,6 +31,10 @@ export class Channel { @Column({ type: 'boolean', default: false }) isPublic!: boolean; + // closed (e.g. discussion-complete): history readable, new posts rejected + @Column({ type: 'boolean', default: false }) + closed!: boolean; + @Index() @Column({ default: 0 }) lastSeq!: number; diff --git a/src/messaging/messaging.controller.ts b/src/messaging/messaging.controller.ts index 72a97f3..f38693f 100644 --- a/src/messaging/messaging.controller.ts +++ b/src/messaging/messaging.controller.ts @@ -1,5 +1,6 @@ import { Body, + ConflictException, Controller, Delete, Get, @@ -143,6 +144,9 @@ export class MessagingController { const channel = await this.channelRepo.findOne({ where: { id: channelId } }); if (!channel) throw new NotFoundException('channel not found'); + if (channel.closed) { + throw new ConflictException({ error: 'channel_closed', message: 'channel is closed' }); + } const xType = channel.xType ?? 'general'; const isRotating = xType === 'discuss' || xType === 'work'; const authorUserId = String(body.authorUserId ?? 'anonymous');