feat(service): add guild settings service layer
Implement service for managing per-guild configuration with methods for getting, upserting, updating, and deleting settings. Includes helpers for color role management.
This commit is contained in:
158
shared/modules/guild-settings/guild-settings.service.ts
Normal file
158
shared/modules/guild-settings/guild-settings.service.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
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<string, boolean>;
|
||||
}
|
||||
|
||||
export const guildSettingsService = {
|
||||
getSettings: async (guildId: string): Promise<GuildSettingsData | null> => {
|
||||
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<GuildSettingsData> & { 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<string, boolean> | null
|
||||
) => {
|
||||
const keyMap: Record<string, keyof typeof guildSettings.$inferSelect> = {
|
||||
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<string, unknown> = { 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 });
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user