fix(demo): the demo's addon set and the demo account's settings are written without awaiting in a loop

One UPDATE per shelf and state and one upsert for the switches, inside the same transaction, and the demo account's settings in one upsert. The two new log lines go through the logger.
This commit is contained in:
Maurice
2026-10-24 23:22:09 +00:00
parent 2ac333f842
commit 7ada99acfc
3 changed files with 34 additions and 17 deletions
+26 -14
View File
@@ -420,29 +420,41 @@ export class DemoRepository {
/**
* Writes the demo's addon states in one transaction, so a reset that fails
* halfway does not leave half the shelf switched. A named row the database
* does not hold is an update that matches nothing, never an insert: the
* seeders own which rows exist.
* halfway does not leave half the shelf switched. One UPDATE per shelf and
* state (generic inherited `nativeUpdate` over `id IN (...)`, the statement
* `setEnabled` issues for one row) and one upsert for all the switches
* (`AppSettingsRepository#setValue`'s), however long the lists are. A named
* row the database does not hold, like an empty list, is an update that
* matches nothing, never an insert: the seeders own which rows exist.
*/
async applyAddonStates(states: DemoAddonStates): Promise<void> {
this.validateRequestContext();
const switches = Object.entries(states.appSettings).map(([key, value]) => ({ key, value }));
await this.em.transactional(async () => {
const addons = this.em.getRepository(Addons);
for (const id of states.addons.on) await addons.setEnabled(id, true);
for (const id of states.addons.off) await addons.setEnabled(id, false);
await addons.nativeUpdate({ id: { $in: [...states.addons.on] } }, { enabled: true });
await addons.nativeUpdate({ id: { $in: [...states.addons.off] } }, { enabled: false });
const photos = this.em.getRepository(PhotoProviders);
for (const id of states.photoProviders.on) await photos.setEnabled(id, 1);
for (const id of states.photoProviders.off) await photos.setEnabled(id, 0);
await photos.nativeUpdate({ id: { $in: [...states.photoProviders.on] } }, { enabled: 1 });
await photos.nativeUpdate({ id: { $in: [...states.photoProviders.off] } }, { enabled: 0 });
const documents = this.em.getRepository(DocumentProviders);
for (const id of states.documentProviders.on) await documents.setEnabled(id, 1);
for (const id of states.documentProviders.off) await documents.setEnabled(id, 0);
const settings = this.em.getRepository(AppSettings);
for (const [key, value] of Object.entries(states.appSettings)) await settings.setValue(key, value);
await documents.nativeUpdate({ id: { $in: [...states.documentProviders.on] } }, { enabled: 1 });
await documents.nativeUpdate({ id: { $in: [...states.documentProviders.off] } }, { enabled: 0 });
await this.em
.getRepository(AppSettings)
.upsertMany(switches, { onConflictFields: ['key'], onConflictAction: 'merge' });
});
}
/** A user's own settings row, written as given (the demo account's fixed language). Composes `SettingsRepository#upsertForUser`. */
async setUserSetting(userId: number, key: string, value: string): Promise<void> {
await this.em.getRepository(Settings).upsertForUser(userId, key, value);
/**
* A user's own settings rows, written as given in one statement (the demo
* account's fixed language). The upsert `SettingsRepository#upsertForUser`
* issues for one key, for every row at once.
*/
async setUserSettings(userId: number, rows: readonly { key: string; value: string }[]): Promise<void> {
await this.em.getRepository(Settings).upsertMany(
rows.map(({ key, value }) => ({ user: userId, key, value })),
{ onConflictFields: ['user', 'key'], onConflictAction: 'merge' },
);
}
}
+2 -1
View File
@@ -8,6 +8,7 @@ import {
} from '../db/repositories/DemoRepository';
import type { CarriedUserSessionRow } from '../db/repositories/UserSessions.repository';
import { dbNow } from '../db/types';
import { logError } from '../nest/audit/audit-log.logger';
import { DatabaseConnectionLostError, type DatabaseBackupStrategy } from '../nest/database/database-backup.interface';
import { applyDemoAddonPolicy } from './demo-addon-policy';
import { RequestContext, type EntityManager } from '@mikro-orm/core';
@@ -163,7 +164,7 @@ async function resetDemoUser(database: DatabaseBackupStrategy): Promise<void> {
try {
await applyDemoAddonPolicy(demoFresh);
} catch (e: unknown) {
console.error('[Demo Reset] Failed to apply the demo addon set:', e instanceof Error ? e.message : e);
logError(`[Demo Reset] Failed to apply the demo addon set: ${e instanceof Error ? e.message : String(e)}`);
}
});
+6 -2
View File
@@ -1,5 +1,6 @@
import { readEnv } from '../app-config';
import { DemoRepository, type NewDemoPlaceRow } from '../db/repositories/DemoRepository';
import { logInfo } from '../nest/audit/audit-log.logger';
import { DEMO_ACCOUNT_SETTINGS, DEMO_PASS } from '../nest/common/demo';
import { applyDemoAddonPolicy } from './demo-addon-policy';
// Static like in demo-reset.job.ts: the module top is inert, everything that
@@ -59,7 +60,10 @@ async function seedDemoData(): Promise<{ adminId: number; demoId: number }> {
// (the hourly reset included), so neither an admin's toggle nor an old
// baseline outlasts the hour.
await applyDemoAddonPolicy(demo);
for (const [key, value] of Object.entries(DEMO_ACCOUNT_SETTINGS)) await demo.setUserSetting(demoId, key, value);
await demo.setUserSettings(
demoId,
Object.entries(DEMO_ACCOUNT_SETTINGS).map(([key, value]) => ({ key, value })),
);
// Check if admin already has example trips
const adminTripCount = await demo.countTripsByUser(adminId);
@@ -75,7 +79,7 @@ async function seedDemoData(): Promise<{ adminId: number; demoId: number }> {
// and the mark below would save them into the next baseline.
const demoTripCount = await demo.countTripsByUser(demoId);
if (demoTripCount > 0) {
console.log('[Demo] The demo account owns its own trips, skipping the example trips');
logInfo('[Demo] The demo account owns its own trips, skipping the example trips');
return { adminId, demoId };
}