Files
mercadodevida/project/src/modules/store-settings/api/settings.routes.ts
2026-08-17 22:23:10 +02:00

110 lines
3.9 KiB
TypeScript

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<string, string> = {
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<void> {
// 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<string, string> = {};
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<string, string> = {};
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'] ?? '',
});
});
}