kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 39e2dfae265f26c8d6d888a560f50ab2d5d58b3f 87 lines 2.2 kB view raw
1import * as v from "valibot"; 2 3export const discordEventKeys = [ 4 "taskCreated", 5 "taskStatusChanged", 6 "taskPriorityChanged", 7 "taskTitleChanged", 8 "taskDescriptionChanged", 9 "taskCommentCreated", 10] as const; 11 12export type DiscordEventKey = (typeof discordEventKeys)[number]; 13 14const discordWebhookSchema = v.pipe( 15 v.string(), 16 v.url(), 17 v.regex( 18 /^https:\/\/(?:discord\.com|discordapp\.com)\/api\/webhooks\/[^/]+\/[^/\s]+$/i, 19 "Enter a valid Discord webhook URL", 20 ), 21); 22 23export const discordConfigSchema = v.object({ 24 webhookUrl: discordWebhookSchema, 25 channelName: v.optional(v.string()), 26 events: v.optional( 27 v.object({ 28 taskCreated: v.optional(v.boolean()), 29 taskStatusChanged: v.optional(v.boolean()), 30 taskPriorityChanged: v.optional(v.boolean()), 31 taskTitleChanged: v.optional(v.boolean()), 32 taskDescriptionChanged: v.optional(v.boolean()), 33 taskCommentCreated: v.optional(v.boolean()), 34 }), 35 ), 36}); 37 38export type DiscordConfig = v.InferOutput<typeof discordConfigSchema>; 39 40export const defaultDiscordEvents: Record<DiscordEventKey, boolean> = { 41 taskCreated: true, 42 taskStatusChanged: true, 43 taskPriorityChanged: false, 44 taskTitleChanged: false, 45 taskDescriptionChanged: false, 46 taskCommentCreated: true, 47}; 48 49export function getDefaultDiscordConfig(webhookUrl: string): DiscordConfig { 50 return { 51 webhookUrl, 52 events: { ...defaultDiscordEvents }, 53 }; 54} 55 56export function normalizeDiscordConfig(config: DiscordConfig): DiscordConfig { 57 return { 58 ...config, 59 channelName: config.channelName?.trim() || undefined, 60 events: { 61 ...defaultDiscordEvents, 62 ...(config.events ?? {}), 63 }, 64 }; 65} 66 67export async function validateDiscordConfig( 68 config: unknown, 69): Promise<{ valid: boolean; errors?: string[] }> { 70 try { 71 const parsed = v.parse(discordConfigSchema, config); 72 normalizeDiscordConfig(parsed); 73 return { valid: true }; 74 } catch (error) { 75 if (error instanceof v.ValiError) { 76 return { 77 valid: false, 78 errors: error.issues.map((issue) => issue.message), 79 }; 80 } 81 82 return { 83 valid: false, 84 errors: [error instanceof Error ? error.message : "Invalid config"], 85 }; 86 } 87}