From e1bee3b5626ee2ee91fe705d6af5348136307de0 Mon Sep 17 00:00:00 2001 From: Maurice Date: Mon, 12 Oct 2026 19:56:44 +0000 Subject: [PATCH] refactor(settings): a bulk save prepares the planner views once, before the writes --- server/src/nest/settings/settings.service.ts | 23 +++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/server/src/nest/settings/settings.service.ts b/server/src/nest/settings/settings.service.ts index 8af4c956f..80212915c 100644 --- a/server/src/nest/settings/settings.service.ts +++ b/server/src/nest/settings/settings.service.ts @@ -336,12 +336,11 @@ export class SettingsService { async bulkUpsertSettings(userId: number, settings: Record) { let written = 0; await this.uow.transactional(async () => { - for (const [key, value] of Object.entries(settings)) { - // The client echoes redacted secrets back unchanged — skip them so a - // bulk save can never overwrite a stored secret with the mask (the - // single-upsert route has the same no-op in the controller). - if (value === MASKED_SETTING_VALUE) continue; - const prepared = await this.prepareValue(userId, key, value); + // The client echoes redacted secrets back unchanged — skip them so a + // bulk save can never overwrite a stored secret with the mask (the + // single-upsert route has the same no-op in the controller). + const entries = Object.entries(settings).filter(([, value]) => value !== MASKED_SETTING_VALUE); + for (const [key, prepared] of await this.prepareValues(userId, entries)) { await this.settings.upsertForUser(userId, key, serializeValue(key, prepared)); written++; } @@ -349,6 +348,18 @@ export class SettingsService { return written; } + /** + * {@link prepareValue} over the entries of a bulk save. Only the planner's + * view switches are read against the database, so that read happens once, + * before the writes, rather than once per key. + */ + private async prepareValues(userId: number, entries: [string, unknown][]): Promise<[string, unknown][]> { + const planner = entries.find(([key]) => key === PLANNER_TRIP_VIEWS_KEY); + if (!planner) return entries; + const views = await this.prepareValue(userId, PLANNER_TRIP_VIEWS_KEY, planner[1]); + return entries.map(([key, value]) => [key, key === PLANNER_TRIP_VIEWS_KEY ? views : value]); + } + /** * Read a single per-user setting, decrypting it if it's an encrypted key. * Unlike getUserSettings (which MASKS encrypted keys for the client), this