client and some admin new

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-08-05 04:32:43 +08:00
parent 8922f7f2f4
commit 0c7f5ccd0f
33 changed files with 937 additions and 770 deletions
+38 -8
View File
@@ -102,14 +102,12 @@ async function applyAdvertisementFields(advertisement, body) {
// status is intentionally NOT settable here — it's derived via deriveStatus()
// right before save, based on is_active + start_date/end_date.
if (body.content_mode !== undefined) {
if (!["image", "content"].includes(body.content_mode)) {
const err = new Error(`Invalid content_mode. Must be one of: image, content`);
err.status = 400;
throw err;
}
advertisement.content_mode = body.content_mode;
}
// content_mode is no longer an admin choice (the Image Only / Text with
// Image toggle was removed — every ad now carries the same mandatory
// badge/headline/description/image/link shape) — like type/format, it's
// fixed server-side rather than trusted from the request body. Legacy
// "image" mode rows keep that value until next edited.
advertisement.content_mode = "content";
if (body.badge_labels !== undefined) advertisement.badge_labels = normalizeBadgeLabels(body.badge_labels);
if (body.headline !== undefined) advertisement.headline = body.headline;
@@ -149,6 +147,38 @@ async function applyAdvertisementFields(advertisement, body) {
advertisement.size = body.size || null;
}
// Every ad now carries the same mandatory shape — badge label(s), headline,
// description, image, and a single link — enforced here as defense-in-depth
// alongside the frontend's Zod schema. The Admin Add/Edit Advertisement
// forms always submit the full shape, so this only ever fires on malformed
// requests — it does not retroactively touch existing incomplete rows,
// it just blocks saving one until it's brought up to the new shape.
if (!advertisement.badge_labels?.length) {
const err = new Error("At least one badge label is required.");
err.status = 400;
throw err;
}
if (!advertisement.headline?.trim()) {
const err = new Error("Headline is required.");
err.status = 400;
throw err;
}
if (!advertisement.description?.trim()) {
const err = new Error("Description is required.");
err.status = 400;
throw err;
}
if (!advertisement.image_asset_id) {
const err = new Error("Image is required.");
err.status = 400;
throw err;
}
if (!advertisement.redirect_link?.trim()) {
const err = new Error("Link is required.");
err.status = 400;
throw err;
}
// Recompute status now that is_active/start_date/end_date are all up to date
advertisement.status = deriveStatus(advertisement);
}