import { BadRequestException, Body, Controller, Get, Param, Patch, Post, Query, Req, UnauthorizedException } from '@nestjs/common'; import { ChannelsService } from './channels.service.js'; // ApiKeyGuard attaches the introspected Center user id onto the request. type AuthedRequest = { userId?: string }; @Controller('channels') export class ChannelsController { constructor(private readonly channelsService: ChannelsService) {} @Get() list(@Req() req: AuthedRequest, @Query('guildId') guildId?: string) { const userId = req.userId ?? ''; if (!userId) throw new UnauthorizedException('missing user'); return this.channelsService.listForUser(String(guildId ?? ''), userId); } @Post() create(@Req() req: AuthedRequest, @Body() body: Record) { const userId = req.userId ?? ''; if (!userId) throw new UnauthorizedException('missing user'); return this.channelsService.create( { guildId: body.guildId as string | undefined, name: body.name as string | undefined, kind: body.kind as string | undefined, xType: body.xType as string | undefined, isPublic: Boolean(body.isPublic), memberUserIds: Array.isArray(body.memberUserIds) ? (body.memberUserIds as string[]) : [], onDuty: body.onDuty as string | undefined, listeners: Array.isArray(body.listeners) ? (body.listeners as string[]) : [], bypassUserIds: Array.isArray(body.bypassUserIds) ? (body.bypassUserIds as string[]) : [], purpose: body.purpose as string | undefined, }, userId, ); } // Patch a channel's free-form purpose. Body: { purpose: string }. Pass // empty string to clear. Auth: channel member (or anyone for public // channels, mirroring close()). Frontend doesn't call this today — // intended for agent-side use (fabric-channel-set-purpose tool). @Patch(':id') patch( @Req() req: AuthedRequest, @Param('id') channelId: string, @Body() body: Record, ) { const userId = req.userId ?? ''; if (!userId) throw new UnauthorizedException('missing user'); // Only `purpose` is patchable today. Future patchable fields would // get their own typed branch; we explicitly NOT allow {} no-op patches // because that signals a caller bug. if (typeof body.purpose !== 'string') { throw new BadRequestException('purpose (string) is required'); } return this.channelsService.updatePurpose(channelId, userId, body.purpose); } // Move an order member into the bypass list (discuss/work only). @Post(':id/bypass') bypass( @Req() req: AuthedRequest, @Param('id') channelId: string, @Body() body: Record, ) { const userId = req.userId ?? ''; if (!userId) throw new UnauthorizedException('missing user'); return this.channelsService.moveToBypass( channelId, userId, String(body.userId ?? ''), ); } @Get(':id/members') members(@Req() req: AuthedRequest, @Param('id') channelId: string) { const userId = req.userId ?? ''; if (!userId) throw new UnauthorizedException('missing user'); return this.channelsService.channelMembers(channelId); } @Post(':id/join') join(@Req() req: AuthedRequest, @Param('id') channelId: string) { const userId = req.userId ?? ''; if (!userId) throw new UnauthorizedException('missing user'); return this.channelsService.joinChannel(channelId, userId); } @Post(':id/leave') leave(@Req() req: AuthedRequest, @Param('id') channelId: string) { const userId = req.userId ?? ''; 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); } }