import type { FastifyInstance } from 'fastify'; import type pg from 'pg'; import { z } from 'zod'; import type { Authenticate } from '../../../shared/auth.js'; import { requireRole } from '../../../shared/auth.js'; import { parseJson } from '../../../shared/http-input.js'; import { AppError } from '../../../shared/errors.js'; export interface StoreSettingsRoutesDeps { pool: pg.Pool; authenticate: Authenticate; } const updateSettingsSchema = z.object({ storeName: z.string().min(1).max(120).optional(), storeTagline: z.string().max(200).optional(), contactEmail: z.string().email().optional(), contactPhone: z.string().max(40).optional(), contactAddress: z.string().max(400).optional(), footerText: z.string().max(400).optional(), facebookUrl: z.string().url().optional().or(z.literal('')), instagramUrl: z.string().url().optional().or(z.literal('')), }); const SETTING_KEYS: Record = { storeName: 'store_name', storeTagline: 'store_tagline', contactEmail: 'contact_email', contactPhone: 'contact_phone', contactAddress: 'contact_address', footerText: 'footer_text', facebookUrl: 'facebook_url', instagramUrl: 'instagram_url', }; export async function registerStoreSettingsRoutes( app: FastifyInstance, deps: StoreSettingsRoutesDeps, ): Promise { // GET /admin/settings — fetch all settings app.get('/admin/settings', async (request, reply) => { const user = await deps.authenticate(request); requireRole(user, 'admin'); const result = await deps.pool.query<{ key: string; value: string }>( `SELECT key, value FROM store_settings`, ); const map: Record = {}; for (const row of result.rows) { map[row.key] = row.value; } return reply.send({ storeName: map['store_name'] ?? '', storeTagline: map['store_tagline'] ?? '', contactEmail: map['contact_email'] ?? '', contactPhone: map['contact_phone'] ?? '', contactAddress: map['contact_address'] ?? '', footerText: map['footer_text'] ?? '', facebookUrl: map['facebook_url'] ?? '', instagramUrl: map['instagram_url'] ?? '', }); }); // PATCH /admin/settings — update one or more settings app.patch('/admin/settings', async (request, reply) => { const user = await deps.authenticate(request); requireRole(user, 'admin'); const input = parseJson(updateSettingsSchema, request.body); const updates: string[] = []; const values: unknown[] = []; let i = 1; for (const [field, dbKey] of Object.entries(SETTING_KEYS)) { const val = input[field as keyof typeof input]; if (val !== undefined) { updates.push(`key = CASE WHEN key = $${i} THEN $${i + 1} ELSE key END`); values.push(dbKey, val); i += 2; } } if (updates.length > 0) { // Use INSERT ... ON CONFLICT for each key for (const [field, dbKey] of Object.entries(SETTING_KEYS)) { const val = input[field as keyof typeof input]; if (val !== undefined) { await deps.pool.query( `INSERT INTO store_settings (key, value, updated_by) VALUES ($1, $2, $3) ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW(), updated_by = $3`, [dbKey, val, user.id], ); } } } // Return updated settings const result = await deps.pool.query<{ key: string; value: string }>( `SELECT key, value FROM store_settings`, ); const map: Record = {}; for (const row of result.rows) { map[row.key] = row.value; } return reply.send({ storeName: map['store_name'] ?? '', storeTagline: map['store_tagline'] ?? '', contactEmail: map['contact_email'] ?? '', contactPhone: map['contact_phone'] ?? '', contactAddress: map['contact_address'] ?? '', footerText: map['footer_text'] ?? '', facebookUrl: map['facebook_url'] ?? '', instagramUrl: map['instagram_url'] ?? '', }); }); }