import { sql } from "drizzle-orm"; import { db } from "../db/client.js"; import { settings } from "../db/schema.js"; export interface GotifySettings { enabled: boolean; url: string; token: string; priority: number; } export interface NtfySettings { enabled: boolean; url: string; topic: string; token: string; priority: number; } export interface SmtpSettings { enabled: boolean; host: string; port: number; secure: boolean; username: string; password: string; from: string; to: string; } export interface WebhookSettings { enabled: boolean; url: string; secret: string; } export interface NotificationEvents { dnsAdd: boolean; dnsUpdate: boolean; dnsDelete: boolean; secretCheck: boolean; secretCheckTime: string; // "HH:MM" timezone: string; } export type ProviderColors = Record; export type IntegrationColors = Record; export type DateFormat = "ymd" | "dmy" | "mdy"; export type TimeFormat = "24h" | "12h"; export interface DisplaySettings { dateFormat: DateFormat; timeFormat: TimeFormat; } export interface AppSettings { gotify: GotifySettings; ntfy: NtfySettings; smtp: SmtpSettings; webhook: WebhookSettings; notifications: NotificationEvents; providerColors: ProviderColors; integrationColors: IntegrationColors; display: DisplaySettings; } const DEFAULTS: AppSettings = { gotify: { enabled: false, url: "", token: "", priority: 5 }, ntfy: { enabled: false, url: "https://ntfy.sh", topic: "", token: "", priority: 3 }, smtp: { enabled: false, host: "", port: 587, secure: false, username: "", password: "", from: "", to: "" }, webhook: { enabled: false, url: "", secret: "" }, notifications: { dnsAdd: true, dnsUpdate: true, dnsDelete: true, secretCheck: true, secretCheckTime: "08:00", timezone: "UTC", }, providerColors: {}, integrationColors: {}, display: { dateFormat: "ymd", timeFormat: "24h" }, }; const KEYS = Object.keys(DEFAULTS) as (keyof AppSettings)[]; export async function getSettings(): Promise { const rows = await db.select().from(settings); const byKey = new Map(rows.map((r) => [r.key, r.value])); const result = {} as AppSettings; for (const key of KEYS) { const raw = byKey.get(key); let parsed: Record = {}; if (raw) { try { parsed = JSON.parse(raw); } catch { parsed = {}; } } (result[key] as Record) = { ...(DEFAULTS[key] as object), ...parsed }; } return result; } export type AppSettingsPatch = { [K in keyof AppSettings]?: Partial }; export async function updateSettings(partial: AppSettingsPatch): Promise { const current = await getSettings(); for (const key of Object.keys(partial) as (keyof AppSettings)[]) { if (!KEYS.includes(key)) continue; const merged = { ...(current[key] as object), ...(partial[key] as object) }; const value = JSON.stringify(merged); await db .insert(settings) .values({ key, value }) .onConflictDoUpdate({ target: settings.key, set: { value, updatedAt: sql`(current_timestamp)` } }); } return getSettings(); } /** Small internal key/value slot for scheduler bookkeeping, outside the AppSettings shape. */ export async function getInternalFlag(key: string): Promise { const [row] = await db.select().from(settings).where(sql`${settings.key} = ${`_internal:${key}`}`).limit(1); return row?.value ?? null; } export async function setInternalFlag(key: string, value: string): Promise { const fullKey = `_internal:${key}`; await db .insert(settings) .values({ key: fullKey, value }) .onConflictDoUpdate({ target: settings.key, set: { value, updatedAt: sql`(current_timestamp)` } }); }