mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
fix(planner): road trip mode, the dawarich trail and the route overview follow the account to another browser
The three planner view switches lived in sessionStorage only, so a new tab, another browser or another device opened every trip on the plain view again. They are now kept per trip in one user setting, planner_trip_views, validated against a contract in @trek/shared. The server keeps only switches that are on and only trips the user can still open, so a deleted trip or one the user left drops out on the next write. The tab copy in sessionStorage stays and answers until the settings have loaded. Refs #2639
This commit is contained in:
@@ -32,6 +32,12 @@ const { db } = vi.hoisted(() => {
|
||||
tmp.exec(`CREATE TABLE settings (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL, value TEXT, UNIQUE(user_id, key));`);
|
||||
tmp.exec('CREATE TABLE app_settings (key TEXT PRIMARY KEY, value TEXT);');
|
||||
// Only what the planner view row (#2639) asks: which trips the user can open.
|
||||
tmp.exec(`CREATE TABLE trips (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL,
|
||||
title TEXT NOT NULL DEFAULT 'Trip', created_at DATETIME DEFAULT CURRENT_TIMESTAMP);`);
|
||||
tmp.exec(
|
||||
'CREATE TABLE trip_members (id INTEGER PRIMARY KEY AUTOINCREMENT, trip_id INTEGER NOT NULL, user_id INTEGER NOT NULL);',
|
||||
);
|
||||
return { db: tmp };
|
||||
});
|
||||
|
||||
@@ -327,4 +333,57 @@ describe('Settings e2e (real auth guard + temp SQLite)', () => {
|
||||
expect(await findRow(orm, AppSettings, { key: 'default_user_setting_week_start' })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('planner_trip_views (#2639)', () => {
|
||||
beforeEach(() => {
|
||||
db.exec('DELETE FROM trip_members; DELETE FROM trips;');
|
||||
db.prepare('INSERT INTO trips (id, user_id) VALUES (?, ?)').run(41, 1);
|
||||
db.prepare('INSERT INTO trips (id, user_id) VALUES (?, ?)').run(42, 2);
|
||||
db.prepare('INSERT INTO trips (id, user_id) VALUES (?, ?)').run(43, 2);
|
||||
db.prepare('INSERT INTO trip_members (trip_id, user_id) VALUES (?, ?)').run(43, 1);
|
||||
});
|
||||
|
||||
it('PUT keeps the switches of trips the user can open and GET hands them back', async () => {
|
||||
const put = await request(server)
|
||||
.put('/api/settings')
|
||||
.set('Cookie', sessionCookie(1))
|
||||
.send({
|
||||
key: 'planner_trip_views',
|
||||
value: {
|
||||
'41': { roadtrip: true },
|
||||
'42': { roadtrip: true },
|
||||
'43': { dawarich: true, overview: false },
|
||||
'99': { overview: true },
|
||||
},
|
||||
});
|
||||
expect(put.status).toBe(200);
|
||||
const res = await request(server).get('/api/settings').set('Cookie', sessionCookie(1));
|
||||
expect(res.body.settings.planner_trip_views).toEqual({ '41': { roadtrip: true }, '43': { dawarich: true } });
|
||||
});
|
||||
|
||||
it('bulk prunes the same way', async () => {
|
||||
const res = await request(server)
|
||||
.post('/api/settings/bulk')
|
||||
.set('Cookie', sessionCookie(1))
|
||||
.send({ settings: { planner_trip_views: { '41': { overview: true }, '42': { overview: true } } } });
|
||||
expect(res.status).toBe(200);
|
||||
expect(JSON.parse(String(await userSetting(1, 'planner_trip_views')))).toEqual({ '41': { overview: true } });
|
||||
});
|
||||
|
||||
it('400 for a value outside the contract, on both routes, and nothing is stored', async () => {
|
||||
const put = await request(server)
|
||||
.put('/api/settings')
|
||||
.set('Cookie', sessionCookie(1))
|
||||
.send({ key: 'planner_trip_views', value: { '41': { roadtrip: 'on' } } });
|
||||
expect(put.status).toBe(400);
|
||||
expect(put.body.error).toMatch(/planner_trip_views/);
|
||||
const bulk = await request(server)
|
||||
.post('/api/settings/bulk')
|
||||
.set('Cookie', sessionCookie(1))
|
||||
.send({ settings: { theme: 'dark', planner_trip_views: ['41'] } });
|
||||
expect(bulk.status).toBe(400);
|
||||
expect(await userSetting(1, 'planner_trip_views')).toBeNull();
|
||||
expect(await userSetting(1, 'theme')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -413,7 +413,7 @@ export async function createPluginRpcHostParts(
|
||||
// Plan 4 Task 1 constructor-ripple: LlmConfigResolver's addon-row read
|
||||
// moved off DatabaseService onto AddonsRepository.
|
||||
const llmConfig = new LlmConfigResolver(
|
||||
new SettingsService(await createTestUnitOfWork(db), appSettings, await createTestSettingsRepo(db)),
|
||||
new SettingsService(await createTestUnitOfWork(db), appSettings, await createTestSettingsRepo(db), await createTestTripsRepo(db)),
|
||||
(await sharedTestOrm(db)).repo(Addons),
|
||||
addons,
|
||||
);
|
||||
@@ -507,7 +507,7 @@ export async function createPluginRpcHostParts(
|
||||
generalStorage,
|
||||
await createTestUnitOfWork(db),
|
||||
(await sharedTestOrm(db)).em,
|
||||
new SettingsService(await createTestUnitOfWork(db), appSettings, await createTestSettingsRepo(db)),
|
||||
new SettingsService(await createTestUnitOfWork(db), appSettings, await createTestSettingsRepo(db), await createTestTripsRepo(db)),
|
||||
);
|
||||
// Plan 4 Task 4: UserCleanupService's UC1 now goes through a directly-injected
|
||||
// EntityManager (MaintenanceRepository.deletePluginUserData), not DatabaseService.
|
||||
|
||||
@@ -95,7 +95,12 @@ beforeAll(async () => {
|
||||
await createTestJourneyEntryPhotosRepo(testDb),
|
||||
await createTestPlacesRepo(testDb),
|
||||
),
|
||||
new SettingsService(uow, await createTestAppSettingsRepo(testDb), await createTestSettingsRepo(testDb)),
|
||||
new SettingsService(
|
||||
uow,
|
||||
await createTestAppSettingsRepo(testDb),
|
||||
await createTestSettingsRepo(testDb),
|
||||
await createTestTripsRepo(testDb),
|
||||
),
|
||||
// Plan 3g Task 3: JourneyShareTokensRepository (JS1-JS15) + the
|
||||
// already-built JourneysRepository (JS8/JS12), same instance the domain
|
||||
// service above uses.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import { db as testDb } from '../../../src/db/database';
|
||||
import { Settings } from '../../../src/db/entities/Settings.entity';
|
||||
import { SettingsService } from '../../../src/nest/settings/settings.service';
|
||||
import { createUser } from '../../helpers/factories';
|
||||
import { addTripMember, createTrip, createUser } from '../../helpers/factories';
|
||||
import { findRows } from '../../helpers/factories/rows';
|
||||
import { readUserSetting, setAppSetting, setUserSetting } from '../../helpers/factories/settings';
|
||||
import { resetTestDb } from '../../helpers/test-db';
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
createTestUnitOfWork,
|
||||
createTestAppSettingsRepo,
|
||||
createTestSettingsRepo,
|
||||
createTestTripsRepo,
|
||||
} from '../../helpers/test-uow';
|
||||
|
||||
import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest';
|
||||
@@ -61,6 +62,7 @@ beforeAll(async () => {
|
||||
await createTestUnitOfWork(testDb),
|
||||
await createTestAppSettingsRepo(testDb),
|
||||
await createTestSettingsRepo(testDb),
|
||||
await createTestTripsRepo(testDb),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -406,3 +408,61 @@ describe('legacy quirk fixes', () => {
|
||||
expect(await svc.bulkUpsertSettings(user.id, { a: '1', b: '••••••••' })).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// #2639: road trip mode, the Dawarich trail and the route overview follow the
|
||||
// user to another browser through one settings row, and that row must not keep
|
||||
// trips the user can no longer open.
|
||||
describe('planner_trip_views', () => {
|
||||
it('SET-SVC-037 — keeps the switches of trips the user owns or is a member of', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const { user: other } = createUser(testDb);
|
||||
const own = createTrip(testDb, user.id);
|
||||
const shared = createTrip(testDb, other.id);
|
||||
addTripMember(testDb, shared.id, user.id);
|
||||
await svc.upsertSetting(user.id, 'planner_trip_views', {
|
||||
[own.id]: { roadtrip: true },
|
||||
[shared.id]: { dawarich: true, overview: true },
|
||||
});
|
||||
expect((await svc.getUserSettings(user.id)).planner_trip_views).toEqual({
|
||||
[own.id]: { roadtrip: true },
|
||||
[shared.id]: { dawarich: true, overview: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('SET-SVC-038 — drops trips the user cannot open, deleted ones included, on every write', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const { user: other } = createUser(testDb);
|
||||
const own = createTrip(testDb, user.id);
|
||||
const foreign = createTrip(testDb, other.id);
|
||||
const gone = createTrip(testDb, user.id);
|
||||
testDb.prepare('DELETE FROM trips WHERE id = ?').run(gone.id);
|
||||
await svc.upsertSetting(user.id, 'planner_trip_views', {
|
||||
[own.id]: { roadtrip: true },
|
||||
[foreign.id]: { roadtrip: true },
|
||||
[gone.id]: { overview: true },
|
||||
});
|
||||
expect(JSON.parse(String(await readUserSetting(t, user.id, 'planner_trip_views')))).toEqual({
|
||||
[own.id]: { roadtrip: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('SET-SVC-039 — stores only switches that are on, and the bulk path does the same', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const a = createTrip(testDb, user.id);
|
||||
const b = createTrip(testDb, user.id);
|
||||
await svc.bulkUpsertSettings(user.id, {
|
||||
planner_trip_views: { [a.id]: { roadtrip: false, overview: true }, [b.id]: { dawarich: false } },
|
||||
});
|
||||
expect(JSON.parse(String(await readUserSetting(t, user.id, 'planner_trip_views')))).toEqual({
|
||||
[a.id]: { overview: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('SET-SVC-040 — a value outside the contract is stored as an empty row, null clears it', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
await svc.upsertSetting(user.id, 'planner_trip_views', { abc: { roadtrip: 'yes' } });
|
||||
expect(await readUserSetting(t, user.id, 'planner_trip_views')).toBe('{}');
|
||||
await svc.upsertSetting(user.id, 'planner_trip_views', null);
|
||||
expect(await readUserSetting(t, user.id, 'planner_trip_views')).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
createTestUnitOfWork,
|
||||
createTestAppSettingsRepo,
|
||||
createTestSettingsRepo,
|
||||
createTestTripsRepo,
|
||||
createTestTagsRepo,
|
||||
createTestPlaceRatingsRepo,
|
||||
createTestAssignmentParticipantsRepo,
|
||||
@@ -82,6 +83,7 @@ beforeAll(async () => {
|
||||
await createTestUnitOfWork(testDb),
|
||||
await createTestAppSettingsRepo(testDb),
|
||||
await createTestSettingsRepo(testDb),
|
||||
await createTestTripsRepo(testDb),
|
||||
),
|
||||
permissionsStub,
|
||||
new QueryHelpersService(
|
||||
|
||||
@@ -296,7 +296,7 @@ beforeAll(async () => {
|
||||
coversFx.storage,
|
||||
await createTestUnitOfWork(testDb),
|
||||
(await sharedTestOrm(testDb)).em,
|
||||
new SettingsService(await createTestUnitOfWork(testDb), await createTestAppSettingsRepo(testDb), await createTestSettingsRepo(testDb)),
|
||||
new SettingsService(await createTestUnitOfWork(testDb), await createTestAppSettingsRepo(testDb), await createTestSettingsRepo(testDb), await createTestTripsRepo(testDb)),
|
||||
);
|
||||
membersSvc = new TripMembersService(budgetSvc, new UserCleanupService(new MaintenanceRepository(dbsEm!), budgetSvc, await createTestUnitOfWork(testDb), await createTestUsersRepo(testDb), await createTestTripMembersRepo(testDb), await createTestBudgetItemsRepo(testDb), await createTestBudgetSettlementsRepo(testDb), await createTestJourneyShareTokensRepo(testDb), await createTestJourneysRepo(testDb), await createTestJourneyEntriesRepo(testDb), await createTestJourneyContributorsRepo(testDb), await createTestShareTokensRepo(testDb), await createTestPluginsRepo(testDb), await createTestPluginUserErasureQueueRepo(testDb)), new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)), realtime, notificationsStub(), await createTestUnitOfWork(testDb), await createTestTripsRepo(testDb), await createTestTripMembersRepo(testDb), await createTestUsersRepo(testDb));
|
||||
readModelSvc = new TripReadModelService(
|
||||
|
||||
Reference in New Issue
Block a user