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_phone: { type: string, nullable: true, description: "Only persisted when the global phone-field setting is enabled." }
|
||||
* admin_email: { type: string, format: email, nullable: true }
|
||||
* require_password: { type: boolean, default: true }
|
||||
* password: { type: string, nullable: true, description: "Required when require_password is 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 resolves to 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." }
|
||||
* 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." }
|
||||
* 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:
|
||||
* 201:
|
||||
* description: Event created
|
||||
@@ -125,7 +128,10 @@ router.post(
|
||||
body('expires_at').optional({ nullable: true, checkFalsy: true }).isISO8601(),
|
||||
body('color_theme').optional({ nullable: true }).isString().trim(),
|
||||
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) => {
|
||||
try {
|
||||
@@ -134,11 +140,16 @@ router.post(
|
||||
const {
|
||||
event_name, event_type, event_date,
|
||||
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,
|
||||
color_theme = null,
|
||||
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;
|
||||
|
||||
// Issue #550 — mirror the admin POST path so API-created events
|
||||
@@ -174,6 +185,44 @@ router.post(
|
||||
}
|
||||
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)) {
|
||||
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
|
||||
// or global fallback) so the column default doesn't shadow it.
|
||||
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_email ? { customer_email } : {}),
|
||||
...(persistPhone ? { customer_phone: persistPhone } : {})
|
||||
|
||||
Reference in New Issue
Block a user