fix(demo): save the first baseline only after a real seed again

Moving the first baseline save out of the boot-time seed into DemoResetJob
also changed when it happened. The seed saved it only on the boot that put
the example trips in; a later boot returned early and saved nothing, so an
instance with data but no baseline logged "No baseline found, skipping" on
every hourly reset. The job instead saved one on any demo boot that found
no travel-baseline.db, freezing whatever the database held at that moment,
user edits included, as the target the hourly reset rolls back to.

The seed now leaves a mark when it seeds the example trips, and the job
saves the first baseline only when that mark is set and no baseline
exists, which is the old trigger. As before, the save does not depend on
the cron registrar being enabled. The demo mode wiki page says so.
This commit is contained in:
Maurice
2026-10-08 15:36:53 +02:00
parent 0aa5b36661
commit fd28ba7fd3
6 changed files with 74 additions and 13 deletions
+23 -1
View File
@@ -153,4 +153,26 @@ function hasBaseline(): boolean {
return fs.existsSync(baselinePath);
}
export { resetDemoUser, saveBaseline, hasBaseline };
/**
* Whether the boot-time seed put the example trips in during this process.
*
* The first baseline is saved only after a real seed, as it always was: a demo
* database that already holds data but has no baseline keeps it that way, and
* the hourly reset stays a logged no-op until the admin saves one. The seed
* runs before the container exists, so it leaves this mark and DemoResetJob,
* which has the backup port, saves the baseline once the app is up.
*/
let seededExampleTrips = false;
function markExampleTripsSeeded(): void {
seededExampleTrips = true;
}
/** Reads the mark and clears it, so one seed saves at most one baseline. */
function takeExampleTripsSeeded(): boolean {
const seeded = seededExampleTrips;
seededExampleTrips = false;
return seeded;
}
export { resetDemoUser, saveBaseline, hasBaseline, markExampleTripsSeeded, takeExampleTripsSeeded };
+5 -3
View File
@@ -4,7 +4,7 @@ import { DEMO_PASS } from '../nest/common/demo';
import { DemoRepository, type NewDemoPlaceRow } from '../db/repositories/DemoRepository';
// Static like in demo-reset.job.ts: the module top is inert, everything that
// touches the database happens inside the functions.
import { requireEntityManager } from './demo-reset';
import { markExampleTripsSeeded, requireEntityManager } from './demo-reset';
// D6 (task-2-review.md's controller ruling / non-HTTP caller table): this runs
// from `runSchemaBootstrap` before `app.init()`, outside any request context, but
@@ -63,8 +63,10 @@ async function seedDemoData(): Promise<{ adminId: number; demoId: number }> {
console.log('[Demo] Seeding example trips...');
await seedExampleTrips(demo, adminId, demoId);
// The first baseline is saved once the app is up, by DemoResetJob, through
// the injected backup port this boot-time seed has no access to.
// The first baseline follows this seed and only this seed. DemoResetJob saves
// it once the app is up, through the injected backup port this boot-time
// seed has no access to.
markExampleTripsSeeded();
return { adminId, demoId };
}
+8 -5
View File
@@ -5,7 +5,7 @@ import { RuntimeEnvService } from '../app-config/runtime-env.service';
import { CronRegistrarService } from '../scheduling/cron-registrar.service';
import { DATABASE_BACKUP, type DatabaseBackupStrategy } from '../database/database-backup.interface';
import { withRequestContext } from '../database/request-context';
import { hasBaseline, resetDemoUser, saveBaseline } from '../../demo/demo-reset';
import { hasBaseline, resetDemoUser, saveBaseline, takeExampleTripsSeeded } from '../../demo/demo-reset';
/**
* Demo mode: hourly reset of demo user data (moved from src/scheduler.ts).
@@ -15,9 +15,12 @@ import { hasBaseline, resetDemoUser, saveBaseline } from '../../demo/demo-reset'
* side-effect-free, and the close/swap/reopen sequence runs inside
* resetDemoUser at tick time, through the injected backup port.
*
* It also saves the first baseline, on a demo boot that has none. That used to
* happen inside the boot-time demo seed, which runs before the container can
* hand anything in; here the port is injected like everywhere else.
* It also saves the first baseline, on the boot whose seed has just put the
* example trips in and only when no baseline exists yet: the same trigger the
* save had inside the boot-time demo seed, which runs before the container can
* hand the port in. A demo database that already holds data without a baseline
* is left alone, so its hourly reset stays a logged no-op as before. Like the
* old save, it does not depend on the cron registrar being enabled.
*/
@Injectable()
export class DemoResetJob implements OnApplicationBootstrap {
@@ -36,7 +39,7 @@ export class DemoResetJob implements OnApplicationBootstrap {
this.registrar.register('demo-reset', '0 * * * *', () => void this.tick(), { timezone: 'none' });
logInfo('Demo hourly reset scheduled');
}
if (!hasBaseline()) await this.saveFirstBaseline();
if (takeExampleTripsSeeded() && !hasBaseline()) await this.saveFirstBaseline();
}
async tick(): Promise<void> {