feat(board): redesign classifieds + Slack channel creation

Adds AdminGhost bot token for admin-only Slack channel creation, refreshes
BoardPostCard/Form layouts, and expands admin board-channels management.
This commit is contained in:
Jennie Robinson Faber 2026-04-14 20:20:17 +01:00
parent 6f3d088763
commit 9a560f2a3b
14 changed files with 544 additions and 158 deletions

View file

@ -400,7 +400,7 @@ export const boardPostUpdateSchema = z.object({
export const boardChannelCreateSchema = z.object({
name: z.string().trim().min(1).max(200),
slackChannelId: z.string().trim().min(1).max(50).regex(/^[A-Z0-9]+$/, 'Invalid Slack channel ID'),
slackChannelId: z.string().trim().min(1).max(50).regex(/^[A-Z0-9]+$/, 'Invalid Slack channel ID').optional(),
tagSlugs: z.array(z.string().max(100)).optional().default([])
})

View file

@ -262,6 +262,33 @@ export class SlackService {
}
}
/**
* Create a new Slack channel. Returns the new channel id and normalized name.
*/
async createChannel(
name: string,
isPrivate: boolean = false,
): Promise<{ id: string; name: string }> {
const normalized = name
.trim()
.replace(/^#/, '')
.toLowerCase()
.replace(/[^a-z0-9_-]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 80)
const response = await this.client.conversations.create({
name: normalized,
is_private: isPrivate,
})
if (!response.ok || !response.channel?.id || !response.channel?.name) {
throw new Error(`Slack create failed: ${response.error || 'unknown'}`)
}
return { id: response.channel.id, name: response.channel.name }
}
/**
* Verify the Slack channel exists and bot has access
*/
@ -292,3 +319,36 @@ export function getSlackService(): SlackService | null {
return new SlackService(config.slackBotToken, config.slackVettingChannelId);
}
/**
* Get a SlackService for operations that don't need the vetting channel.
*/
export function getSlackServiceNoVetting(): SlackService | null {
const config = useRuntimeConfig();
if (!config.slackBotToken) {
console.warn("Slack integration not configured - missing bot token");
return null;
}
return new SlackService(config.slackBotToken, "");
}
/**
* Get a SlackService backed by the AdminGhost app token for admin-only
* operations like channel creation. Falls back to the main bot token if
* AdminGhost isn't configured.
*/
export function getSlackAdminService(): SlackService | null {
const config = useRuntimeConfig();
const token = config.slackAdminBotToken || config.slackBotToken;
if (!token) {
console.warn(
"Slack admin integration not configured - missing admin bot token",
);
return null;
}
return new SlackService(token, "");
}