* feat(gallery): per-event toggle to hide the logo on the password page (#894) * fix(admin): harden login_logo_visible coercion for SQLite + string booleans (#894) --------- Co-authored-by: Paul Nothaft <[email protected]>
This commit is contained in:
co-authored by
Paul Nothaft
parent
926a4a540d
commit
08ff9f20e7
@@ -201,6 +201,34 @@ describe('admin events CRUD endpoints (smoke)', () => {
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
// #894 — per-event password-page logo toggle: false hides, null
|
||||
// restores the default (show).
|
||||
it('stores login_logo_visible: false and clears it back to NULL', async () => {
|
||||
const id = await insertEvent(db, adminId);
|
||||
const hide = await auth(request(app).put(`/api/admin/events/${id}`)).send({
|
||||
login_logo_visible: false,
|
||||
});
|
||||
expect(hide.status).toBe(200);
|
||||
let row = await db('events').where({ id }).first();
|
||||
expect([false, 0]).toContain(row.login_logo_visible);
|
||||
|
||||
const clear = await auth(request(app).put(`/api/admin/events/${id}`)).send({
|
||||
login_logo_visible: null,
|
||||
});
|
||||
expect(clear.status).toBe(200);
|
||||
row = await db('events').where({ id }).first();
|
||||
expect(row.login_logo_visible).toBeNull();
|
||||
|
||||
// The string "false" passes isBoolean() validation — it must be
|
||||
// parsed, not treated as a truthy string (would store 1 = show).
|
||||
const hideStr = await auth(request(app).put(`/api/admin/events/${id}`)).send({
|
||||
login_logo_visible: 'false',
|
||||
});
|
||||
expect(hideStr.status).toBe(200);
|
||||
row = await db('events').where({ id }).first();
|
||||
expect([false, 0]).toContain(row.login_logo_visible);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /:id', () => {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Migration 166: per-event toggle to hide the branding logo on the
|
||||
* gallery password page (#894).
|
||||
*
|
||||
* NULL (the default) keeps today's behaviour — the global branding logo is
|
||||
* shown above the password form. Only an explicit `false` hides it for
|
||||
* that gallery; the admin login page and other surfaces are unaffected.
|
||||
*/
|
||||
exports.up = async function (knex) {
|
||||
if (await knex.schema.hasColumn('events', 'login_logo_visible')) return;
|
||||
await knex.schema.alterTable('events', (t) => {
|
||||
t.boolean('login_logo_visible').nullable();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = async function (knex) {
|
||||
if (!(await knex.schema.hasColumn('events', 'login_logo_visible'))) return;
|
||||
await knex.schema.alterTable('events', (t) => {
|
||||
t.dropColumn('login_logo_visible');
|
||||
});
|
||||
};
|
||||
@@ -1090,6 +1090,7 @@ module.exports = (router) => {
|
||||
hero_logo_visible: source.hero_logo_visible,
|
||||
hero_logo_size: source.hero_logo_size,
|
||||
hero_logo_position: source.hero_logo_position,
|
||||
login_logo_visible: source.login_logo_visible,
|
||||
header_style: source.header_style || 'standard',
|
||||
hero_divider_style: source.hero_divider_style || 'wave',
|
||||
hero_image_anchor: source.hero_image_anchor || 'center',
|
||||
@@ -1237,6 +1238,8 @@ module.exports = (router) => {
|
||||
body('hero_logo_visible').optional({ nullable: true }).isBoolean(),
|
||||
body('hero_logo_size').optional({ nullable: true }).isIn(['small', 'medium', 'large', 'xlarge']),
|
||||
body('hero_logo_position').optional().isIn(['top', 'center', 'bottom']),
|
||||
// Password-page logo toggle (#894). null = default (show).
|
||||
body('login_logo_visible').optional({ nullable: true }).isBoolean(),
|
||||
// Header style settings (decoupled from layout)
|
||||
body('header_style').optional().isIn(['hero', 'standard', 'banner', 'minimal', 'none']),
|
||||
body('hero_divider_style').optional().isIn(['wave', 'straight', 'angle', 'curve', 'none']),
|
||||
@@ -1452,6 +1455,16 @@ module.exports = (router) => {
|
||||
: formatBoolean(updates.hero_logo_visible);
|
||||
}
|
||||
|
||||
// Password-page logo toggle (#894): null passes through; other
|
||||
// accepted representations ("false", 0, …) are parsed before the
|
||||
// DB formatting — formatBoolean alone would store the truthy
|
||||
// string "false" as 1.
|
||||
if (Object.prototype.hasOwnProperty.call(updates, 'login_logo_visible')) {
|
||||
updates.login_logo_visible = updates.login_logo_visible === null
|
||||
? null
|
||||
: formatBoolean(parseBooleanInput(updates.login_logo_visible, true));
|
||||
}
|
||||
|
||||
// Per-event opt-in for hero-photo OG share image (#474). Coerce so
|
||||
// SQLite stores 0/1 and Postgres stores boolean true/false.
|
||||
if (Object.prototype.hasOwnProperty.call(updates, 'og_image_share_enabled')) {
|
||||
|
||||
@@ -221,6 +221,7 @@ router.get('/:slug/info', async (req, res) => {
|
||||
'hero_logo_size',
|
||||
'hero_logo_position',
|
||||
'hero_logo_url',
|
||||
'login_logo_visible',
|
||||
'header_style',
|
||||
'hero_divider_style',
|
||||
'hero_image_anchor',
|
||||
@@ -290,6 +291,9 @@ router.get('/:slug/info', async (req, res) => {
|
||||
enable_devtools_protection: event.enable_devtools_protection === true || event.enable_devtools_protection === 1 || event.enable_devtools_protection === '1',
|
||||
use_canvas_rendering: event.use_canvas_rendering === true || event.use_canvas_rendering === 1 || event.use_canvas_rendering === '1',
|
||||
hero_logo_visible: resolveHeroLogoVisible(event.hero_logo_visible, globalHeroLogoVisible),
|
||||
// #894: only an explicit false hides the logo on the password page;
|
||||
// NULL keeps the default (show).
|
||||
login_logo_visible: !(event.login_logo_visible === false || event.login_logo_visible === 0 || event.login_logo_visible === '0'),
|
||||
// #756: NULL per-event size inherits the global branding_logo_size.
|
||||
hero_logo_size: event.hero_logo_size || globalLogoSize || 'medium',
|
||||
hero_logo_position: event.hero_logo_position || 'top',
|
||||
|
||||
Reference in New Issue
Block a user