From 7ff8caf9d7e601dff2472b1ad57b3c83ba9d3cdc Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Sat, 5 Sep 2026 23:41:17 +0200 Subject: [PATCH] fix: remove the fragmentation handling stranded by #1303 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1298 and #1303 merged together. #1298 taught the creation paths to resolve a fragmentation_level default; #1303 removed everything that consumed it. Neither conflicted textually, so main ended up validating the field on create and update, copying it on duplicate, resolving default_fragmentation_level for it, and advertising it in the v1 API docs — for a value nothing reads and a setting the Image Security tab no longer exposes. Inert rather than broken, which is exactly why it needed removing on purpose: dead code that contradicts the PR that just deleted the feature is how the next reader concludes fragmentation still works. The events.fragmentation_level column and the app_settings row stay, as #1303 decided — dropping a column is irreversible and the stored values are harmless once nothing reads them. Refs #1300 --- .../integration/imageSecurityDefaults.test.js | 12 +++--------- backend/__tests__/routes/adminEvents.smoke.test.js | 1 - backend/src/routes/adminEvents/crud.js | 2 -- backend/src/routes/adminEvents/helpers.js | 8 -------- backend/src/routes/v1/events.js | 2 -- 5 files changed, 3 insertions(+), 22 deletions(-) diff --git a/backend/__tests__/integration/imageSecurityDefaults.test.js b/backend/__tests__/integration/imageSecurityDefaults.test.js index b2998705..b923fac1 100644 --- a/backend/__tests__/integration/imageSecurityDefaults.test.js +++ b/backend/__tests__/integration/imageSecurityDefaults.test.js @@ -5,7 +5,7 @@ * rendered as toggles, and read by nothing: * * default_protection_level, default_image_quality, - * enable_canvas_rendering, default_fragmentation_level + * enable_canvas_rendering * * Each maps onto an `events` column migration 038 already created, and each * is labelled "… by default". `enable_devtools_protection` was the only one @@ -46,7 +46,7 @@ describe('image-security creation defaults', () => { beforeEach(async () => { await db('app_settings').whereIn('setting_key', [ 'default_protection_level', 'default_image_quality', - 'enable_canvas_rendering', 'default_fragmentation_level', + 'enable_canvas_rendering', ]).del(); }); @@ -60,13 +60,11 @@ describe('image-security creation defaults', () => { await setSetting('default_protection_level', 'enhanced'); await setSetting('default_image_quality', 72); await setSetting('enable_canvas_rendering', true); - await setSetting('default_fragmentation_level', 5); expect(await getImageSecurityDefaults()).toEqual({ protection_level: 'enhanced', image_quality: 72, use_canvas_rendering: true, - fragmentation_level: 5, }); }); @@ -83,7 +81,6 @@ describe('image-security creation defaults', () => { ['image quality above 100', 'default_image_quality', 250], ['image quality of zero', 'default_image_quality', 0], ['a non-numeric quality', 'default_image_quality', 'high'], - ['fragmentation above the range', 'default_fragmentation_level', 99], ['a non-boolean canvas value', 'enable_canvas_rendering', 'yes'], // parseInt would have rescued each of these into a valid-looking // integer. The settings PUT stores values without validating them, so @@ -91,8 +88,6 @@ describe('image-security creation defaults', () => { ['a numeric prefix with trailing junk', 'default_image_quality', '72oops'], ['a fractional quality', 'default_image_quality', 72.5], ['a single-element array', 'default_image_quality', [72]], - ['a fractional fragmentation level', 'default_fragmentation_level', 3.7], - ['a fragmentation level with trailing junk', 'default_fragmentation_level', '3x'], ])('ignores %s and falls through to the column default', async (_label, key, value) => { await setSetting(key, value); expect(await getImageSecurityDefaults()).toEqual({}); @@ -210,11 +205,10 @@ describe('image-security creation defaults', () => { it('resolves each column independently', () => { expect(resolveImageSecurityColumns( { image_quality: 60 }, - { protection_level: 'enhanced', fragmentation_level: 4 }, + { protection_level: 'enhanced' }, )).toEqual({ protection_level: 'enhanced', image_quality: 60, - fragmentation_level: 4, }); }); diff --git a/backend/__tests__/routes/adminEvents.smoke.test.js b/backend/__tests__/routes/adminEvents.smoke.test.js index 4f857406..e15bf3b3 100644 --- a/backend/__tests__/routes/adminEvents.smoke.test.js +++ b/backend/__tests__/routes/adminEvents.smoke.test.js @@ -224,7 +224,6 @@ describe('admin events CRUD endpoints (smoke)', () => { ['image_quality', [72]], ['protection_level', ['basic']], ['use_canvas_rendering', [false]], - ['fragmentation_level', [3]], // Not a protection field: the guard is not scoped to that block. ['event_name', ['Arrayed']], ['allow_downloads', [false]], diff --git a/backend/src/routes/adminEvents/crud.js b/backend/src/routes/adminEvents/crud.js index a9ed22d8..c80e96ba 100644 --- a/backend/src/routes/adminEvents/crud.js +++ b/backend/src/routes/adminEvents/crud.js @@ -235,7 +235,6 @@ module.exports = (router) => { body('protection_level').optional().not().isArray().isIn(['basic', 'standard', 'enhanced', 'maximum']), body('use_canvas_rendering').optional().not().isArray().isBoolean().toBoolean(), body('image_quality').optional().not().isArray().isInt({ min: 1, max: 100 }).toInt(), - body('fragmentation_level').optional().not().isArray().isInt({ min: 1, max: 10 }).toInt(), body('watermark_downloads').optional().isBoolean(), body('watermark_text').optional().trim(), // #328 follow-up: per-event opt-in for presigned-URL "Download All". @@ -1427,7 +1426,6 @@ module.exports = (router) => { protection_level: source.protection_level, image_quality: source.image_quality, use_canvas_rendering: source.use_canvas_rendering, - fragmentation_level: source.fragmentation_level, watermark_downloads: source.watermark_downloads, watermark_text: source.watermark_text, allow_presigned_download: source.allow_presigned_download, diff --git a/backend/src/routes/adminEvents/helpers.js b/backend/src/routes/adminEvents/helpers.js index 82e1af16..98fe9a9a 100644 --- a/backend/src/routes/adminEvents/helpers.js +++ b/backend/src/routes/adminEvents/helpers.js @@ -127,7 +127,6 @@ const getDownloadProtectionDefaults = async () => { * default_protection_level → events.protection_level * default_image_quality → events.image_quality * enable_canvas_rendering → events.use_canvas_rendering - * default_fragmentation_level → events.fragmentation_level * * Each maps onto a column migration 038 already created, and each is * labelled "… by default", so applying them at creation is what the panel @@ -172,7 +171,6 @@ const getImageSecurityDefaults = async (trx = null) => { 'default_protection_level', 'default_image_quality', 'enable_canvas_rendering', - 'default_fragmentation_level', ]) .select('setting_key', 'setting_value'); @@ -214,10 +212,6 @@ const getImageSecurityDefaults = async (trx = null) => { defaults.use_canvas_rendering = canvas; } - const fragmentation = toInteger(read('default_fragmentation_level')); - if (fragmentation !== undefined && fragmentation >= 1 && fragmentation <= 10) { - defaults.fragmentation_level = fragmentation; - } } catch (error) { // A settings read must never block event creation; the column defaults // are a correct fallback. @@ -262,8 +256,6 @@ const resolveImageSecurityColumns = (body = {}, defaults = {}) => { const canvas = pick('use_canvas_rendering'); if (canvas !== undefined) columns.use_canvas_rendering = formatBoolean(canvas); - const fragmentation = pick('fragmentation_level'); - if (fragmentation !== undefined) columns.fragmentation_level = fragmentation; return columns; }; diff --git a/backend/src/routes/v1/events.js b/backend/src/routes/v1/events.js index 121c22ea..982c5bc4 100644 --- a/backend/src/routes/v1/events.js +++ b/backend/src/routes/v1/events.js @@ -138,7 +138,6 @@ const photoUpload = async (req, res, next) => { * protection_level: { type: string, nullable: true, enum: [basic, standard, enhanced, maximum], description: "Image protection level. When omitted, falls back to the global default_protection_level setting." } * use_canvas_rendering: { type: boolean, nullable: true, description: "Render gallery images to a canvas instead of an img tag. When omitted, falls back to the global enable_canvas_rendering setting." } * image_quality: { type: integer, minimum: 1, maximum: 100, nullable: true, description: "Served image quality percentage. When omitted, falls back to the global default_image_quality setting." } - * fragmentation_level: { type: integer, minimum: 1, maximum: 10, nullable: true, description: "Stored for future use; no renderer consumes it yet. When omitted, falls back to the global default_fragmentation_level 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)." } @@ -187,7 +186,6 @@ router.post( body('protection_level').optional().not().isArray().isIn(['basic', 'standard', 'enhanced', 'maximum']), body('use_canvas_rendering').optional().not().isArray().isBoolean().toBoolean(), body('image_quality').optional().not().isArray().isInt({ min: 1, max: 100 }).toInt(), - body('fragmentation_level').optional().not().isArray().isInt({ min: 1, max: 10 }).toInt(), 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'])