feat(gateway): Discord channel auto-creation on project bootstrap
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful

When a project is bootstrapped, the gateway now calls onProjectCreated on all loaded plugins. The Discord plugin creates a text channel in the configured guild (mosaic-{slug}) and the channel ID is stored in the project metadata jsonb. Gracefully skips if Discord is not configured or guild is unavailable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 21:28:59 -05:00
parent 12653477d6
commit 1fb761f0b0
4 changed files with 67 additions and 1 deletions

View File

@@ -1,4 +1,4 @@
import { Client, GatewayIntentBits, type Message as DiscordMessage } from 'discord.js';
import { ChannelType, Client, GatewayIntentBits, type Message as DiscordMessage } from 'discord.js';
import { io, type Socket } from 'socket.io-client';
export interface DiscordPluginConfig {
@@ -86,6 +86,34 @@ export class DiscordPlugin {
await this.client.destroy();
}
async createProjectChannel(project: {
id: string;
name: string;
description?: string;
}): Promise<{ channelId: string } | null> {
if (!this.config.guildId) return null;
const guild = this.client.guilds.cache.get(this.config.guildId);
if (!guild) return null;
// Slugify project name for channel: lowercase, replace spaces/special chars with hyphens
const channelName = `mosaic-${project.name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '')}`;
const channel = await guild.channels.create({
name: channelName,
type: ChannelType.GuildText,
topic: project.description ?? `Mosaic project: ${project.name}`,
});
// Register the channel mapping so messages route correctly
this.channelConversations.set(channel.id, `discord-${channel.id}`);
return { channelId: channel.id };
}
private handleDiscordMessage(message: DiscordMessage): void {
// Ignore bot messages
if (message.author.bot) return;