9d353ede69
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.
43 lines
1.2 KiB
TypeScript
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;
|