This commit is contained in:
Your Name committed 2026-07-25 20:05:46 -04:00
1 parent 48aebb2ec9
commit b693c7188c
8 files changed
+267 -4

No files matched your search

+94 -4
View File
@@ -3,22 +3,112 @@ import prisma from "../lib/prisma";
import { adminOnly } from "../middleware/auth";
export const settingsRoute = new Elysia({ prefix: "/settings" })
.get("/", async ({ set }) => {
.get("/", async ({ set, query }) => {
try {
let settings = await prisma.storeSettings.findFirst();
let settings = await prisma.storeSettings.findFirst({ include: { translations: true } });
if (!settings) {
settings = await prisma.storeSettings.create({
data: {}
data: {},
include: { translations: true }
});
}
return settings;
const { translations, ...settingsData } = settings;
const requestedLang = typeof query?.lang === "string" ? query.lang : undefined;
const localizedDescription = requestedLang
? translations.find((translation) => translation.langCode === requestedLang)?.footerDescription
: undefined;
return localizedDescription ? { ...settingsData, footerDescription: localizedDescription } : settingsData;
} catch (err) {
console.error("[Settings] Error:", err);
set.status = 500;
return { error: "Failed to fetch settings" };
}
})
/**
* Dedicated Footer Brand contract. This keeps the editable Brand block
* separate from footer link menus while reusing the existing StoreSettings
* columns and StoreSettingsTranslation rows.
*/
.get("/footer-brand", async ({ set }) => {
try {
const settings = await prisma.storeSettings.findFirst({ include: { translations: true } });
const base = settings ?? await prisma.storeSettings.create({ data: {}, include: { translations: true } });
const descriptions: Record<string, string> = {};
for (const translation of base.translations ?? []) {
if (translation.footerDescription) descriptions[translation.langCode] = translation.footerDescription;
}
if (base.footerDescription && !descriptions.th) descriptions.th = base.footerDescription;
return {
version: base.updatedAt.toISOString(),
brand: {
brandName: base.brandName,
logoUrl: base.logoUrl ?? null,
description: descriptions,
socialLinks: {
facebookUrl: base.facebookUrl ?? null,
instagramUrl: base.instagramUrl ?? null,
tiktokUrl: base.tiktokUrl ?? null,
},
},
};
} catch (err) {
console.error("[Settings] Footer brand fetch error:", err);
set.status = 500;
return { error: "Failed to fetch footer brand" };
}
})
.use(adminOnly)
.patch("/footer-brand", async ({ body, set }) => {
try {
const payload = body as any;
const settings = await prisma.storeSettings.findFirst({ include: { translations: true } });
const current = settings ?? await prisma.storeSettings.create({ data: {}, include: { translations: true } });
const version = current.updatedAt.toISOString();
if (payload.expectedVersion && payload.expectedVersion !== version) {
set.status = 409;
return { error: "Footer brand has changed since it was read", code: "VERSION_CONFLICT", expectedVersion: payload.expectedVersion, currentVersion: version };
}
const updates: any = {};
if (payload.brandName !== undefined) {
if (typeof payload.brandName !== "string" || !payload.brandName.trim()) throw new Error("brandName must be a non-empty string");
updates.brandName = payload.brandName.trim();
}
for (const field of ["logoUrl", "facebookUrl", "instagramUrl", "tiktokUrl"]) {
if (payload[field] !== undefined) {
if (payload[field] !== null && typeof payload[field] !== "string") throw new Error(`${field} must be a string or null`);
updates[field] = payload[field];
}
}
const description = payload.description ?? payload.footerDescription;
if (description !== undefined && (typeof description !== "object" || Array.isArray(description))) throw new Error("description must be an object keyed by locale");
if (payload.dryRun) {
return { dryRun: true, version, wouldUpdate: { ...updates, description: description ?? undefined } };
}
const updated = Object.keys(updates).length
? await prisma.storeSettings.update({ where: { id: current.id }, data: updates })
: current;
if (description) {
for (const [langCode, value] of Object.entries(description)) {
if (typeof langCode !== "string" || typeof value !== "string") throw new Error("description values must be strings");
const existing = await prisma.storeSettingsTranslation.findUnique({ where: { storeSettingsId_langCode: { storeSettingsId: current.id, langCode } } });
if (existing) await prisma.storeSettingsTranslation.update({ where: { id: existing.id }, data: { footerDescription: value } });
else await prisma.storeSettingsTranslation.create({ data: { storeSettingsId: current.id, langCode, footerDescription: value } });
if (langCode === "th") await prisma.storeSettings.update({ where: { id: current.id }, data: { footerDescription: value } });
}
}
const result = await prisma.storeSettings.findUnique({ where: { id: current.id }, include: { translations: true } });
const descriptions: Record<string, string> = {};
for (const translation of result?.translations ?? []) if (translation.footerDescription) descriptions[translation.langCode] = translation.footerDescription;
if (result?.footerDescription && !descriptions.th) descriptions.th = result.footerDescription;
return { success: true, version: result?.updatedAt.toISOString(), brand: { brandName: result?.brandName, logoUrl: result?.logoUrl ?? null, description: descriptions, socialLinks: { facebookUrl: result?.facebookUrl ?? null, instagramUrl: result?.instagramUrl ?? null, tiktokUrl: result?.tiktokUrl ?? null } } };
} catch (err: any) {
console.error("[Settings] Footer brand update error:", err);
set.status = 400;
return { error: err?.message || "Failed to update footer brand" };
}
}, { body: t.Any() })
.put("/", async ({ body, set }) => {
try {
const data = body as any;