1 Commits

Author SHA1 Message Date
9670da400e feat(guild): closed channel (discussion-complete support)
Channel.closed; POST /channels/:id/close (member-only); message/command
posts on closed channel -> 409 {error:channel_closed}; GET history still
allowed; listForUser carries closed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:52:43 +01:00
4 changed files with 27 additions and 0 deletions

View File

@@ -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);
}
}

View File

@@ -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');

View File

@@ -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;

View File

@@ -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');