70 lines
2.4 KiB
TypeScript
70 lines
2.4 KiB
TypeScript
import { Router, Request } from 'express';
|
|
import { eq } from 'drizzle-orm';
|
|
import { db } from '../db/client';
|
|
import { settings, users } from '../db/schema';
|
|
import { AuthedRequest } from '../auth';
|
|
|
|
const router = Router();
|
|
|
|
// The API shape is unchanged from the single-user days — one flat object with
|
|
// these keys — but storage is now split (FEATURE_User_Accounts.md): `theme` and
|
|
// `weight_unit` are per-user preferences on the caller's `users` row, the rest
|
|
// stay global in the settings table. A NULL user pref falls back to the global
|
|
// value, so a fresh account inherits the rabbitry-wide defaults until the user
|
|
// picks their own. Client consumers (ThemeSync, useWeightUnit, Settings page)
|
|
// are agnostic to the split.
|
|
const GLOBAL_KEYS = ['rabbitry_name', 'kit_prefix', 'default_breed'] as const;
|
|
const USER_KEYS = ['theme', 'weight_unit'] as const;
|
|
const ALLOWED_KEYS = [...GLOBAL_KEYS, ...USER_KEYS] as string[];
|
|
|
|
function mergedSettings(userId: number): Record<string, string | null> {
|
|
const result: Record<string, string | null> = {};
|
|
for (const row of db.select().from(settings).all()) {
|
|
result[row.key] = row.value;
|
|
}
|
|
const prefs = db
|
|
.select({ theme: users.theme, weightUnit: users.weightUnit })
|
|
.from(users)
|
|
.where(eq(users.id, userId))
|
|
.get();
|
|
if (prefs?.theme != null) result.theme = prefs.theme;
|
|
if (prefs?.weightUnit != null) result.weight_unit = prefs.weightUnit;
|
|
return result;
|
|
}
|
|
|
|
function currentUserId(req: Request): number {
|
|
return (req as AuthedRequest).user!.id;
|
|
}
|
|
|
|
router.get('/', (req, res) => {
|
|
res.json(mergedSettings(currentUserId(req)));
|
|
});
|
|
|
|
router.patch('/', (req, res) => {
|
|
const keys = Object.keys(req.body);
|
|
const unknownKey = keys.find((key) => !ALLOWED_KEYS.includes(key));
|
|
|
|
if (unknownKey) {
|
|
res.status(400).json({ error: `Unknown setting: ${unknownKey}` });
|
|
return;
|
|
}
|
|
|
|
const userId = currentUserId(req);
|
|
|
|
const userPatch: { theme?: string; weightUnit?: string } = {};
|
|
if ('theme' in req.body) userPatch.theme = req.body.theme;
|
|
if ('weight_unit' in req.body) userPatch.weightUnit = req.body.weight_unit;
|
|
if (Object.keys(userPatch).length > 0) {
|
|
db.update(users).set(userPatch).where(eq(users.id, userId)).run();
|
|
}
|
|
|
|
for (const key of keys) {
|
|
if ((USER_KEYS as readonly string[]).includes(key)) continue;
|
|
db.update(settings).set({ value: req.body[key] }).where(eq(settings.key, key)).run();
|
|
}
|
|
|
|
res.json(mergedSettings(userId));
|
|
});
|
|
|
|
export default router;
|