Files
TX/artifacts/api-server/src/routes/settings.ts
T
Riyadh 9d353ede69 Add editable site name and finish service image upload
User asked to remove the hardcoded "TeaBoy" branding and let admins
change the system name. Also completes the in-progress service image
upload work via App Storage.

Changes:
- New app_settings table (single row id=1) with siteNameAr/siteNameEn
- New /settings endpoints: GET (public) + PATCH (admin)
- Atomic ensureSettingsRow via INSERT ... ON CONFLICT DO NOTHING
  to avoid race conditions
- New SiteSettingsPanel tab in admin page (Arabic + English inputs)
- New useAppName hook reads settings, updates document.title, falls
  back to defaults while loading
- Login + register pages now display the dynamic site name
- Service image upload (App Storage) wired via useUpload + presigned
  GCS URL flow; admin component ServiceImageUploader
- Storage routes: /storage/uploads/request-url and /storage/objects/*
  now require auth (closes previously-open endpoints flagged by review)
- Added AppSettings/UpdateAppSettingsBody + storage schemas to
  openapi.yaml; regenerated client and zod
- Exposed UploadResponse from @workspace/object-storage-web; added
  composite:true so it can be referenced by teaboy-os tsconfig

Validation: typechecks pass for api-server and teaboy-os; settings GET
returns row; upload URL endpoint returns 401 without auth.
2026-04-20 11:06:15 +00:00

43 lines
1.2 KiB
TypeScript

import { Router, type IRouter } from "express";
import { eq } from "drizzle-orm";
import { db } from "@workspace/db";
import { appSettingsTable } from "@workspace/db";
import { requireAdmin } from "../middlewares/auth";
import { UpdateAppSettingsBody } from "@workspace/api-zod";
const router: IRouter = Router();
async function ensureSettingsRow() {
await db
.insert(appSettingsTable)
.values({ id: 1 })
.onConflictDoNothing({ target: appSettingsTable.id });
}
async function readSettings() {
await ensureSettingsRow();
const [row] = await db.select().from(appSettingsTable).where(eq(appSettingsTable.id, 1));
return row;
}
router.get("/settings", async (_req, res): Promise<void> => {
res.json(await readSettings());
});
router.patch("/settings", requireAdmin, async (req, res): Promise<void> => {
const parsed = UpdateAppSettingsBody.safeParse(req.body);
if (!parsed.success) {
res.status(400).json({ error: parsed.error.message });
return;
}
await ensureSettingsRow();
const [updated] = await db
.update(appSettingsTable)
.set(parsed.data)
.where(eq(appSettingsTable.id, 1))
.returning();
res.json(updated);
});
export default router;