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>
This commit is contained in:
@@ -54,4 +54,11 @@ export class ChannelsController {
|
|||||||
if (!userId) throw new UnauthorizedException('missing user');
|
if (!userId) throw new UnauthorizedException('missing user');
|
||||||
return this.channelsService.leaveChannel(channelId, userId);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,18 @@ export class ChannelsService {
|
|||||||
return rows.map((r) => ({ userId: r.userId }));
|
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) {
|
async joinChannel(channelId: string, userId: string) {
|
||||||
const channel = await this.channelRepo.findOne({ where: { id: channelId } });
|
const channel = await this.channelRepo.findOne({ where: { id: channelId } });
|
||||||
if (!channel) throw new NotFoundException('channel not found');
|
if (!channel) throw new NotFoundException('channel not found');
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ export class Channel {
|
|||||||
@Column({ type: 'boolean', default: false })
|
@Column({ type: 'boolean', default: false })
|
||||||
isPublic!: boolean;
|
isPublic!: boolean;
|
||||||
|
|
||||||
|
// closed (e.g. discussion-complete): history readable, new posts rejected
|
||||||
|
@Column({ type: 'boolean', default: false })
|
||||||
|
closed!: boolean;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ default: 0 })
|
@Column({ default: 0 })
|
||||||
lastSeq!: number;
|
lastSeq!: number;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
|
ConflictException,
|
||||||
Controller,
|
Controller,
|
||||||
Delete,
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
@@ -143,6 +144,9 @@ export class MessagingController {
|
|||||||
|
|
||||||
const channel = await this.channelRepo.findOne({ where: { id: channelId } });
|
const channel = await this.channelRepo.findOne({ where: { id: channelId } });
|
||||||
if (!channel) throw new NotFoundException('channel not found');
|
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 xType = channel.xType ?? 'general';
|
||||||
const isRotating = xType === 'discuss' || xType === 'work';
|
const isRotating = xType === 'discuss' || xType === 'work';
|
||||||
const authorUserId = String(body.authorUserId ?? 'anonymous');
|
const authorUserId = String(body.authorUserId ?? 'anonymous');
|
||||||
|
|||||||
Reference in New Issue
Block a user