fix(api/v1/events): also honour require_password + branding defaults (#592 follow-up)
Same class of bug as the devtools-detection gap landed in 2304b25.
v1 POST /events was hardcoding require_password=true in the destructure
default and skipping getBrandingDefaults entirely, so:
- Admins who disabled "require password by default" globally still
got password-required galleries through the API.
- API-created events ignored the global branding_logo_display_hero
and branding_logo_size toggles, defaulting to visible/medium
regardless of the admin's preferred branding chrome.
Mirror the readBooleanSetting + getBrandingDefaults pattern from
adminEvents.js inline (helpers aren't exported, and pulling them out
is out-of-scope for this fix). Adds validators, fallback resolution,
and the three resolved values to the events insert. hero_logo_position
stays at 'top' since #357 / migration 084 explicitly disconnected it
from the header-bar branding_logo_position setting. OpenAPI updated.
This commit is contained in:
@@ -86,12 +86,15 @@ const photoUpload = multer({
|
|||||||
* customer_email: { type: string, format: email, nullable: true }
|
* customer_email: { type: string, format: email, nullable: true }
|
||||||
* customer_phone: { type: string, nullable: true, description: "Only persisted when the global phone-field setting is enabled." }
|
* customer_phone: { type: string, nullable: true, description: "Only persisted when the global phone-field setting is enabled." }
|
||||||
* admin_email: { type: string, format: email, nullable: true }
|
* admin_email: { type: string, format: email, nullable: true }
|
||||||
* require_password: { type: boolean, default: true }
|
* require_password: { type: boolean, nullable: true, description: "When omitted, falls back to the global event_default_require_password setting." }
|
||||||
* password: { type: string, nullable: true, description: "Required when require_password is true." }
|
* password: { type: string, nullable: true, description: "Required when require_password resolves to true." }
|
||||||
* expires_at: { type: string, format: date-time, nullable: true }
|
* expires_at: { type: string, format: date-time, nullable: true }
|
||||||
* color_theme: { type: string, nullable: true, description: "Preset name (e.g. 'default') or JSON-encoded ThemeConfig. Persisted as-is on the event row." }
|
* color_theme: { type: string, nullable: true, description: "Preset name (e.g. 'default') or JSON-encoded ThemeConfig. Persisted as-is on the event row." }
|
||||||
* feedback_enabled: { type: boolean, nullable: true, description: "Enable guest feedback for this gallery. When omitted, falls back to the global event_default_feedback_enabled setting." }
|
* feedback_enabled: { type: boolean, nullable: true, description: "Enable guest feedback for this gallery. When omitted, falls back to the global event_default_feedback_enabled setting." }
|
||||||
* enable_devtools_protection: { type: boolean, nullable: true, description: "Block right-click / devtools shortcuts in the gallery. When omitted, falls back to the global enable_devtools_protection setting." }
|
* enable_devtools_protection: { type: boolean, nullable: true, description: "Block right-click / devtools shortcuts in the gallery. When omitted, falls back to the global enable_devtools_protection setting." }
|
||||||
|
* hero_logo_visible: { type: boolean, nullable: true, description: "Show event logo in the hero block. When omitted, falls back to the global branding_logo_display_hero setting." }
|
||||||
|
* hero_logo_size: { type: string, nullable: true, enum: [small, medium, large, xlarge], description: "Hero logo size. When omitted, falls back to the global branding_logo_size setting." }
|
||||||
|
* hero_logo_position: { type: string, nullable: true, enum: [top, center, bottom], description: "Hero logo position. Defaults to 'top' (not settings-backed — see migration 084)." }
|
||||||
* responses:
|
* responses:
|
||||||
* 201:
|
* 201:
|
||||||
* description: Event created
|
* description: Event created
|
||||||
@@ -125,7 +128,10 @@ router.post(
|
|||||||
body('expires_at').optional({ nullable: true, checkFalsy: true }).isISO8601(),
|
body('expires_at').optional({ nullable: true, checkFalsy: true }).isISO8601(),
|
||||||
body('color_theme').optional({ nullable: true }).isString().trim(),
|
body('color_theme').optional({ nullable: true }).isString().trim(),
|
||||||
body('feedback_enabled').optional().isBoolean(),
|
body('feedback_enabled').optional().isBoolean(),
|
||||||
body('enable_devtools_protection').optional().isBoolean()
|
body('enable_devtools_protection').optional().isBoolean(),
|
||||||
|
body('hero_logo_visible').optional().isBoolean(),
|
||||||
|
body('hero_logo_size').optional().isIn(['small', 'medium', 'large', 'xlarge']),
|
||||||
|
body('hero_logo_position').optional().isIn(['top', 'center', 'bottom'])
|
||||||
],
|
],
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -134,11 +140,16 @@ router.post(
|
|||||||
const {
|
const {
|
||||||
event_name, event_type, event_date,
|
event_name, event_type, event_date,
|
||||||
customer_name = null, customer_email = null, customer_phone = null,
|
customer_name = null, customer_email = null, customer_phone = null,
|
||||||
admin_email = null, require_password = true, password,
|
admin_email = null,
|
||||||
|
require_password: requirePasswordInput,
|
||||||
|
password,
|
||||||
expires_at = null,
|
expires_at = null,
|
||||||
color_theme = null,
|
color_theme = null,
|
||||||
feedback_enabled: feedbackEnabledInput,
|
feedback_enabled: feedbackEnabledInput,
|
||||||
enable_devtools_protection: devtoolsInput
|
enable_devtools_protection: devtoolsInput,
|
||||||
|
hero_logo_visible: heroLogoVisibleInput,
|
||||||
|
hero_logo_size: heroLogoSizeInput,
|
||||||
|
hero_logo_position: heroLogoPositionInput
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
// Issue #550 — mirror the admin POST path so API-created events
|
// Issue #550 — mirror the admin POST path so API-created events
|
||||||
@@ -174,6 +185,44 @@ router.post(
|
|||||||
}
|
}
|
||||||
const enable_devtools_protection = parseBooleanInput(devtoolsInput, devtoolsFallback);
|
const enable_devtools_protection = parseBooleanInput(devtoolsInput, devtoolsFallback);
|
||||||
|
|
||||||
|
// Same shape as the feedback / devtools fallbacks: honour the global
|
||||||
|
// event_default_require_password toggle (#317). Without this an admin
|
||||||
|
// who disabled "require password by default" globally still got
|
||||||
|
// password-required galleries through the API.
|
||||||
|
let requirePasswordFallback = true;
|
||||||
|
if (requirePasswordInput === undefined) {
|
||||||
|
const setting = await db('app_settings').where('setting_key', 'event_default_require_password').first();
|
||||||
|
if (setting) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(setting.setting_value);
|
||||||
|
if (typeof parsed === 'boolean') requirePasswordFallback = parsed;
|
||||||
|
} catch { /* keep true */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const require_password = parseBooleanInput(requirePasswordInput, requirePasswordFallback);
|
||||||
|
|
||||||
|
// Branding inheritance (Feature 7) — mirror adminEvents.js
|
||||||
|
// getBrandingDefaults so API-created events inherit the global
|
||||||
|
// hero logo visibility + size. hero_logo_position is intentionally
|
||||||
|
// NOT settings-backed (see migration 084 / #357 — branding_logo_position
|
||||||
|
// is the *header bar*, a different concept than the hero block).
|
||||||
|
let heroLogoVisibleFallback = true;
|
||||||
|
let heroLogoSizeFallback = 'medium';
|
||||||
|
const brandingRows = await db('app_settings')
|
||||||
|
.whereIn('setting_key', ['branding_logo_display_hero', 'branding_logo_size'])
|
||||||
|
.select('setting_key', 'setting_value');
|
||||||
|
for (const row of brandingRows) {
|
||||||
|
let value = row.setting_value;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try { value = JSON.parse(value); } catch { /* keep raw */ }
|
||||||
|
}
|
||||||
|
if (row.setting_key === 'branding_logo_display_hero') heroLogoVisibleFallback = value !== false;
|
||||||
|
if (row.setting_key === 'branding_logo_size' && value) heroLogoSizeFallback = value;
|
||||||
|
}
|
||||||
|
const hero_logo_visible = heroLogoVisibleInput !== undefined ? heroLogoVisibleInput : heroLogoVisibleFallback;
|
||||||
|
const hero_logo_size = heroLogoSizeInput || heroLogoSizeFallback;
|
||||||
|
const hero_logo_position = heroLogoPositionInput || 'top';
|
||||||
|
|
||||||
if (require_password && (!password || password.length < 6)) {
|
if (require_password && (!password || password.length < 6)) {
|
||||||
return res.status(400).json({ error: 'Password is required when require_password is true (min 6 chars)' });
|
return res.status(400).json({ error: 'Password is required when require_password is true (min 6 chars)' });
|
||||||
}
|
}
|
||||||
@@ -225,6 +274,10 @@ router.post(
|
|||||||
// Issue #592 — write the resolved devtools setting (input value
|
// Issue #592 — write the resolved devtools setting (input value
|
||||||
// or global fallback) so the column default doesn't shadow it.
|
// or global fallback) so the column default doesn't shadow it.
|
||||||
enable_devtools_protection: formatBoolean(enable_devtools_protection),
|
enable_devtools_protection: formatBoolean(enable_devtools_protection),
|
||||||
|
// Branding inheritance — resolved value from body or app_settings.
|
||||||
|
hero_logo_visible: formatBoolean(hero_logo_visible),
|
||||||
|
hero_logo_size,
|
||||||
|
hero_logo_position,
|
||||||
...(customer_name ? { customer_name } : {}),
|
...(customer_name ? { customer_name } : {}),
|
||||||
...(customer_email ? { customer_email } : {}),
|
...(customer_email ? { customer_email } : {}),
|
||||||
...(persistPhone ? { customer_phone: persistPhone } : {})
|
...(persistPhone ? { customer_phone: persistPhone } : {})
|
||||||
|
|||||||
Reference in New Issue
Block a user