import { eq } from "drizzle-orm"; import { guildSettings } from "@db/schema"; import { DrizzleClient } from "@shared/db/DrizzleClient"; import { UserError } from "@shared/lib/errors"; export interface GuildSettingsData { guildId: string; studentRoleId?: string; visitorRoleId?: string; colorRoleIds?: string[]; welcomeChannelId?: string; welcomeMessage?: string; feedbackChannelId?: string; terminalChannelId?: string; terminalMessageId?: string; moderationLogChannelId?: string; moderationDmOnWarn?: boolean; moderationAutoTimeoutThreshold?: number; featureOverrides?: Record; } export const guildSettingsService = { getSettings: async (guildId: string): Promise => { const settings = await DrizzleClient.query.guildSettings.findFirst({ where: eq(guildSettings.guildId, BigInt(guildId)), }); if (!settings) return null; return { guildId: settings.guildId.toString(), studentRoleId: settings.studentRoleId?.toString(), visitorRoleId: settings.visitorRoleId?.toString(), colorRoleIds: settings.colorRoleIds ?? [], welcomeChannelId: settings.welcomeChannelId?.toString(), welcomeMessage: settings.welcomeMessage ?? undefined, feedbackChannelId: settings.feedbackChannelId?.toString(), terminalChannelId: settings.terminalChannelId?.toString(), terminalMessageId: settings.terminalMessageId?.toString(), moderationLogChannelId: settings.moderationLogChannelId?.toString(), moderationDmOnWarn: settings.moderationDmOnWarn ?? true, moderationAutoTimeoutThreshold: settings.moderationAutoTimeoutThreshold ?? undefined, featureOverrides: settings.featureOverrides ?? {}, }; }, upsertSettings: async (data: Partial & { guildId: string }) => { const values: typeof guildSettings.$inferInsert = { guildId: BigInt(data.guildId), studentRoleId: data.studentRoleId ? BigInt(data.studentRoleId) : null, visitorRoleId: data.visitorRoleId ? BigInt(data.visitorRoleId) : null, colorRoleIds: data.colorRoleIds ?? [], welcomeChannelId: data.welcomeChannelId ? BigInt(data.welcomeChannelId) : null, welcomeMessage: data.welcomeMessage ?? null, feedbackChannelId: data.feedbackChannelId ? BigInt(data.feedbackChannelId) : null, terminalChannelId: data.terminalChannelId ? BigInt(data.terminalChannelId) : null, terminalMessageId: data.terminalMessageId ? BigInt(data.terminalMessageId) : null, moderationLogChannelId: data.moderationLogChannelId ? BigInt(data.moderationLogChannelId) : null, moderationDmOnWarn: data.moderationDmOnWarn ?? true, moderationAutoTimeoutThreshold: data.moderationAutoTimeoutThreshold ?? null, featureOverrides: data.featureOverrides ?? {}, updatedAt: new Date(), }; const [result] = await DrizzleClient.insert(guildSettings) .values(values) .onConflictDoUpdate({ target: guildSettings.guildId, set: values, }) .returning(); return result; }, updateSetting: async ( guildId: string, key: string, value: string | string[] | boolean | number | Record | null ) => { const keyMap: Record = { studentRole: "studentRoleId", visitorRole: "visitorRoleId", colorRoles: "colorRoleIds", welcomeChannel: "welcomeChannelId", welcomeMessage: "welcomeMessage", feedbackChannel: "feedbackChannelId", terminalChannel: "terminalChannelId", terminalMessage: "terminalMessageId", moderationLogChannel: "moderationLogChannelId", moderationDmOnWarn: "moderationDmOnWarn", moderationAutoTimeoutThreshold: "moderationAutoTimeoutThreshold", featureOverrides: "featureOverrides", }; const column = keyMap[key]; if (!column) { throw new UserError(`Unknown setting: ${key}`); } const updates: Record = { updatedAt: new Date() }; if (column === "colorRoleIds" && Array.isArray(value)) { updates[column] = value; } else if (column === "featureOverrides" && typeof value === "object" && value !== null) { updates[column] = value; } else if (column === "moderationDmOnWarn" && typeof value === "boolean") { updates[column] = value; } else if (column === "moderationAutoTimeoutThreshold" && typeof value === "number") { updates[column] = value; } else if (typeof value === "string") { updates[column] = BigInt(value); } else if (value === null) { updates[column] = null; } else { updates[column] = value; } const [result] = await DrizzleClient.update(guildSettings) .set(updates) .where(eq(guildSettings.guildId, BigInt(guildId))) .returning(); if (!result) { throw new UserError(`No settings found for guild ${guildId}. Use /settings set to create settings first.`); } return result; }, deleteSettings: async (guildId: string) => { const [result] = await DrizzleClient.delete(guildSettings) .where(eq(guildSettings.guildId, BigInt(guildId))) .returning(); return result; }, addColorRole: async (guildId: string, roleId: string) => { const settings = await guildSettingsService.getSettings(guildId); const colorRoleIds = settings?.colorRoleIds ?? []; if (colorRoleIds.includes(roleId)) { return settings; } colorRoleIds.push(roleId); return await guildSettingsService.upsertSettings({ guildId, colorRoleIds }); }, removeColorRole: async (guildId: string, roleId: string) => { const settings = await guildSettingsService.getSettings(guildId); if (!settings) return null; const colorRoleIds = (settings.colorRoleIds ?? []).filter(id => id !== roleId); return await guildSettingsService.upsertSettings({ guildId, colorRoleIds }); }, };