feat(journey): set a journey's status by hand (#762)

This commit is contained in:
Maurice
2026-10-01 13:03:46 +02:00
parent 3063606f56
commit 477f41693d
39 changed files with 212 additions and 7 deletions
+9
View File
@@ -5421,6 +5421,15 @@ function runMigrations(db: Database.Database): void {
if (!has('quantity')) db.exec('ALTER TABLE packing_template_items ADD COLUMN quantity INTEGER NOT NULL DEFAULT 1');
if (!has('bag_name')) db.exec('ALTER TABLE packing_template_items ADD COLUMN bag_name TEXT');
},
/**
* A journey's state as its owner sets it (#762): draft, live or completed.
* NULL keeps the state derived from the linked trips' dates, which is what
* every journey had so far; a journey with no trip could only ever be a draft.
*/
() => {
const cols = db.prepare("SELECT name FROM pragma_table_info('journeys')").all() as { name: string }[];
if (!cols.some(c => c.name === 'status_override')) db.exec('ALTER TABLE journeys ADD COLUMN status_override TEXT');
},
];
if (currentVersion < migrations.length) {
@@ -351,6 +351,7 @@ export class JourneyDomainService {
cover_gradient: string;
cover_image: string;
status: string;
status_override: string | null;
show_trip_tracks: boolean | number;
show_verdict: boolean | number;
show_mood: boolean | number;
@@ -362,12 +363,15 @@ export class JourneyDomainService {
if (!this.isOwner(journeyId, userId)) return null;
const ALLOWED_STATUSES = ['draft', 'active', 'completed', 'archived'];
// null hands the state back to the trip dates (#762).
const ALLOWED_OVERRIDES: (string | null)[] = [null, 'draft', 'live', 'completed'];
const allowed = [
'title',
'subtitle',
'cover_gradient',
'cover_image',
'status',
'status_override',
'show_trip_tracks',
'show_verdict',
'show_mood',
@@ -381,6 +385,7 @@ export class JourneyDomainService {
for (const [key, val] of Object.entries(data)) {
if (val !== undefined && allowed.includes(key)) {
if (key === 'status' && !ALLOWED_STATUSES.includes(val as string)) continue;
if (key === 'status_override' && !ALLOWED_OVERRIDES.includes(val as string | null)) continue;
fields.push(`${key} = ?`);
values.push(BOOLEAN_FIELDS.has(key) ? (val ? 1 : 0) : val);
}
+3 -2
View File
@@ -241,12 +241,13 @@ export class JourneyMcp {
@Tool({
name: 'update_journey',
description: "Update an existing journey's title, subtitle, cover, or status. Owner only.",
description: "Update an existing journey's title, subtitle, cover, or status. Owner only. status_override sets the state shown for the journey (draft, live, completed) instead of deriving it from the linked trips' dates; null goes back to the dates.",
inputSchema: {
journeyId: z.number().int().positive(),
title: z.string().min(1).max(200).optional(),
subtitle: z.string().max(300).optional(),
status: z.enum(['draft', 'active', 'completed', 'archived']).optional(),
status_override: z.enum(['draft', 'live', 'completed']).nullable().optional().describe('Shown state set by hand, or null to follow the trip dates'),
show_verdict: z.boolean().optional().describe('Whether entries in this journey offer a pros/cons list'),
show_mood: z.boolean().optional().describe('Whether entries in this journey offer a mood'),
show_weather: z.boolean().optional().describe('Whether entries in this journey offer a weather note'),
@@ -257,7 +258,7 @@ export class JourneyMcp {
})
updateJourney(
{ journeyId, ...data }: {
journeyId: number; title?: string; subtitle?: string; status?: string;
journeyId: number; title?: string; subtitle?: string; status?: string; status_override?: string | null;
show_verdict?: boolean; show_mood?: boolean; show_weather?: boolean;
},
ctx: McpContext,
@@ -337,6 +337,15 @@ describe('updateJourney', () => {
expect(result!.title).toBe('Same');
});
it('JOURNEY-SVC-762: the owner sets the shown state by hand, null hands it back, anything else is ignored', () => {
const { user } = createUser(testDb);
const journey = createJourney(testDb, user.id);
expect(svc.updateJourney(journey.id, user.id, { status_override: 'live' })!.status_override).toBe('live');
expect(svc.updateJourney(journey.id, user.id, { status_override: 'upcoming' })!.status_override).toBe('live');
expect(svc.updateJourney(journey.id, user.id, { status_override: null })!.status_override).toBeNull();
});
it('JOURNEY-SVC-021b: accepts archived status', () => {
const { user } = createUser(testDb);
const journey = createJourney(testDb, user.id, { title: 'To Archive' });