diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-08 08:57:08 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-08 08:57:08 +0200 |
| commit | 4b2b964283075712de98743720ff3e31370ddc8e (patch) | |
| tree | 7200e83869dbd42d5f20318a07a6ca328e5ac59f /src/lib/server | |
| parent | 992e40835af1600a0961ec2454fb78ff43aec3c6 (diff) | |
Deduplicate routine actions and badges across project routes
- Share edit/complete/archive form actions in routine-actions.ts; route
servers become thin wrappers (strictest guard semantics in one place)
- Move parseFrequencyDays into validation.ts
- Extract ScoreBadge/StatusBadge components; use on project pages and
the home page instead of duplicated scoreClass/statusClass helpers
- Fix archiveProject calling the routine archive with the project id
Diffstat (limited to 'src/lib/server')
| -rw-r--r-- | src/lib/server/services/routine-actions.ts | 86 | ||||
| -rw-r--r-- | src/lib/server/services/validation.ts | 10 |
2 files changed, 96 insertions, 0 deletions
diff --git a/src/lib/server/services/routine-actions.ts b/src/lib/server/services/routine-actions.ts new file mode 100644 index 0000000..b546ec7 --- /dev/null +++ b/src/lib/server/services/routine-actions.ts @@ -0,0 +1,86 @@ +import { fail, type ActionFailure } from '@sveltejs/kit'; +import { archive, complete, edit, loadCurrent } from './routines'; +import { parseFormString, parseFrequencyDays } from './validation'; + +/** + * Shared implementations of the routine edit/complete form actions, used by + * both the project dashboard (/projects/[id]) and the routine page + * (/projects/[projectId]/routine/[routineId]). Guarding and failure messages + * are the strictest common denominator: a missing or archived routine is + * rejected before any write. + */ + +export type EditRoutineResult = + ActionFailure<{ error: string }> | { success: boolean; type?: 'edit' }; + +export async function editRoutineAction( + data: FormData, + routineId: number, + extra?: { type: 'edit' }, +): Promise<EditRoutineResult> { + const current = await loadCurrent(routineId); + if (!current) { + return fail(400, { error: 'Routine introuvable' }); + } + if (current.archived) { + return fail(400, { error: 'Routine archivée' }); + } + + const name = parseFormString(data.get('name')); + if (!name) { + return fail(400, { error: 'Le nom est requis' }); + } + + let frequencyDays: number; + try { + frequencyDays = parseFrequencyDays(data.get('frequencyDays')); + } catch { + return fail(400, { error: 'Fréquence invalide' }); + } + + // The dashboard row form has no description field; only touch it when the + // form actually sends one, so list edits never wipe a description. + const fields: { name: string; frequencyDays: number; description?: string } = + { name, frequencyDays }; + const description = data.get('description'); + if (description !== null) { + fields.description = description.toString(); + } + + try { + await edit(routineId, fields); + } catch { + return fail(400, { error: 'Routine introuvable' }); + } + return { success: true, type: extra?.type }; +} + +export async function completeRoutineAction(data: FormData, routineId: number) { + const current = await loadCurrent(routineId); + if (!current) { + return fail(400, { error: 'Routine introuvable' }); + } + if (current.archived) { + return fail(400, { error: 'Routine archivée' }); + } + + try { + await complete( + routineId, + parseFormString(data.get('note')), + new Date().toISOString(), + ); + } catch { + return fail(400, { error: 'Routine introuvable' }); + } + return { success: true }; +} + +export async function archiveRoutineAction(routineId: number) { + try { + await archive(routineId); + } catch { + return fail(400, { error: 'Routine introuvable' }); + } + return { success: true }; +} diff --git a/src/lib/server/services/validation.ts b/src/lib/server/services/validation.ts index a8cebd9..69820ea 100644 --- a/src/lib/server/services/validation.ts +++ b/src/lib/server/services/validation.ts @@ -1,3 +1,4 @@ +import { parseIntervalToDays } from '$lib/shared/routine-metrics'; import * as v from 'valibot'; export const IdSchema = v.pipe(v.number(), v.integer(), v.minValue(1)); @@ -43,3 +44,12 @@ export function parseOptionalNumber( if (!Number.isFinite(n)) return null; return n; } + +/** Parses an interval like '2w' into days; throws when the format is invalid. */ +export function parseFrequencyDays(value: FormDataEntryValue | null): number { + const days = parseIntervalToDays(value?.toString() ?? ''); + if (days === null) { + throw new Error('Invalid interval'); + } + return days; +} |
