From 649c324f37c53e5ec1744f1397489b152f5c5c32 Mon Sep 17 00:00:00 2001 From: Justin Reiners Date: Thu, 26 Mar 2026 11:56:51 -0500 Subject: [PATCH] feat: add org_config loader with caching and defaults Co-Authored-By: Claude Sonnet 4.6 --- src/lib/server/config.ts | 128 ++++++++++++++++++++++++++++++++++++++ tests/unit/config.test.ts | 24 +++++++ 2 files changed, 152 insertions(+) create mode 100644 src/lib/server/config.ts create mode 100644 tests/unit/config.test.ts diff --git a/src/lib/server/config.ts b/src/lib/server/config.ts new file mode 100644 index 0000000..bff30ca --- /dev/null +++ b/src/lib/server/config.ts @@ -0,0 +1,128 @@ +import { db } from './db'; +import { orgConfig } from './schema'; + +export interface OrgBranding { + primaryColor: string; + primaryColorLight: string; + primaryColorDark: string; + accentColor: string; + accentColorLight: string; + accentColorDark: string; + logoUrl: string; +} + +export interface OrgInfo { + name: string; + tagline: string; + city: string; + state: string; + phone: string; + email: string; + facebookUrl: string; + instagramUrl: string; +} + +export interface OrgLocale { + language: string; + currency: string; + dateFormat: string; +} + +export interface OrgApplications { + adoptionEnabled: boolean; + fosterEnabled: boolean; + volunteerEnabled: boolean; + surrenderEnabled: boolean; + adoptionFee: number; +} + +export interface OrgConfig { + branding: OrgBranding; + orgInfo: OrgInfo; + locale: OrgLocale; + applications: OrgApplications; + setupComplete: boolean; +} + +const DEFAULTS: OrgConfig = { + branding: { + primaryColor: '#0d9488', + primaryColorLight: '#14b8a6', + primaryColorDark: '#0f766e', + accentColor: '#f59e0b', + accentColorLight: '#fbbf24', + accentColorDark: '#d97706', + logoUrl: '' + }, + orgInfo: { + name: 'My Rescue', + tagline: '', + city: '', + state: '', + phone: '', + email: '', + facebookUrl: '', + instagramUrl: '' + }, + locale: { + language: 'en', + currency: 'USD', + dateFormat: 'MM/DD/YYYY' + }, + applications: { + adoptionEnabled: true, + fosterEnabled: true, + volunteerEnabled: true, + surrenderEnabled: true, + adoptionFee: 250 + }, + setupComplete: false +}; + +const KEY_MAP: Record = { + branding: 'branding', + org_info: 'orgInfo', + locale: 'locale', + applications: 'applications', + setup_complete: 'setupComplete' +}; + +export function parseOrgConfig(rows: { key: string; value: unknown }[]): OrgConfig { + const config = structuredClone(DEFAULTS); + + for (const row of rows) { + const configKey = KEY_MAP[row.key]; + if (!configKey) continue; + + if (configKey === 'setupComplete') { + config.setupComplete = Boolean(row.value); + } else { + config[configKey] = { + ...config[configKey], + ...(row.value as Record) + } as any; + } + } + + return config; +} + +let cachedConfig: OrgConfig | null = null; +let cacheTime = 0; +const CACHE_TTL = 5 * 60 * 1000; // 5 minutes + +export async function getOrgConfig(): Promise { + if (cachedConfig && Date.now() - cacheTime < CACHE_TTL) { + return cachedConfig; + } + + const rows = await db.select().from(orgConfig); + cachedConfig = parseOrgConfig(rows); + cacheTime = Date.now(); + return cachedConfig; +} + +export function invalidateConfigCache(): void { + cachedConfig = null; + cacheTime = 0; +} diff --git a/tests/unit/config.test.ts b/tests/unit/config.test.ts new file mode 100644 index 0000000..2e989d7 --- /dev/null +++ b/tests/unit/config.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from 'vitest'; +import { parseOrgConfig, type OrgConfig } from '$lib/server/config'; + +describe('config', () => { + it('parses org config rows into structured object', () => { + const rows = [ + { key: 'branding', value: { primaryColor: '#0d9488', accentColor: '#f59e0b', logoUrl: '' } }, + { key: 'org_info', value: { name: 'Test Rescue', city: 'Sioux Falls', state: 'SD' } }, + { key: 'locale', value: { language: 'en', currency: 'USD', dateFormat: 'MM/DD/YYYY' } } + ]; + + const config = parseOrgConfig(rows); + expect(config.branding.primaryColor).toBe('#0d9488'); + expect(config.orgInfo.name).toBe('Test Rescue'); + expect(config.locale.language).toBe('en'); + }); + + it('provides defaults for missing keys', () => { + const config = parseOrgConfig([]); + expect(config.branding.primaryColor).toBe('#0d9488'); + expect(config.orgInfo.name).toBe('My Rescue'); + expect(config.locale.language).toBe('en'); + }); +});