aboutsummaryrefslogtreecommitdiff
path: root/src/lib/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/server')
-rw-r--r--src/lib/server/db/schema.ts2
-rw-r--r--src/lib/server/services/routine-actions.ts15
-rw-r--r--src/lib/server/services/routines.test.ts46
-rw-r--r--src/lib/server/services/routines.ts12
-rw-r--r--src/lib/server/services/validation.ts10
5 files changed, 40 insertions, 45 deletions
diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts
index a7fcd8f..b6404ce 100644
--- a/src/lib/server/db/schema.ts
+++ b/src/lib/server/db/schema.ts
@@ -50,7 +50,7 @@ export const routines = sqliteTable(
.notNull()
.references(() => projects.id, { onDelete: 'cascade' }),
name: text('name').notNull(),
- frequencyDays: integer('frequency_days').notNull(),
+ frequency: text('frequency').notNull(),
description: text('description').notNull().default(''),
archived: integer('archived', { mode: 'boolean' }).notNull().default(false),
archivedAt: text('archived_at'),
diff --git a/src/lib/server/services/routine-actions.ts b/src/lib/server/services/routine-actions.ts
index 44ba2b8..1d7f8b5 100644
--- a/src/lib/server/services/routine-actions.ts
+++ b/src/lib/server/services/routine-actions.ts
@@ -1,7 +1,8 @@
import { fail, type ActionFailure } from '@sveltejs/kit';
import { archive, complete, edit, loadCurrent } from './routines';
-import { parseFormString, parseFrequencyDays } from './validation';
+import { parseFormString } from './validation';
import { instantFromDay } from '$lib/shared/temporal';
+import { Frequency } from '$lib/shared/frequency';
/** Shared routine edit/complete actions; rejects missing/archived routines. */
@@ -26,16 +27,20 @@ export async function editRoutineAction(
return fail(400, { error: 'Le nom est requis' });
}
- let frequencyDays: number;
+ let frequency: string;
try {
- frequencyDays = parseFrequencyDays(data.get('frequencyDays'));
+ frequency = Frequency.parse(
+ data.get('frequency')?.toString() ?? '',
+ ).toSpec();
} catch {
return fail(400, { error: 'Fréquence invalide' });
}
// Only touch description when the form sends one; list edits never wipe it.
- const fields: { name: string; frequencyDays: number; description?: string } =
- { name, frequencyDays };
+ const fields: { name: string; frequency: string; description?: string } = {
+ name,
+ frequency,
+ };
const description = data.get('description');
if (description !== null) {
fields.description = description.toString();
diff --git a/src/lib/server/services/routines.test.ts b/src/lib/server/services/routines.test.ts
index eda8824..2ad409a 100644
--- a/src/lib/server/services/routines.test.ts
+++ b/src/lib/server/services/routines.test.ts
@@ -24,46 +24,46 @@ describe('routines service', () => {
});
it('creates a routine with version 1', async () => {
- const r = await add(projectId, 'clean kitchen', 7, db);
+ const r = await add(projectId, 'clean kitchen', '7d', db);
expect(r.id).toBe(1);
expect(r.version).toBe(1);
- expect(r.frequencyDays).toBe(7);
+ expect(r.frequency).toBe('7d');
});
it('edit inserts a new snapshot version and keeps history', async () => {
- const r = await add(projectId, 'clean kitchen', 7, db);
+ const r = await add(projectId, 'clean kitchen', '7d', db);
const updated = await edit(
r.id,
- { name: 'clean kitchen deeply', frequencyDays: 3 },
+ { name: 'clean kitchen deeply', frequency: '3d' },
db,
);
expect(updated.version).toBe(2);
- expect(updated.frequencyDays).toBe(3);
+ expect(updated.frequency).toBe('3d');
const versions = await loadAllVersions(r.id, db);
expect(versions).toHaveLength(2);
- expect(versions[0].frequencyDays).toBe(7);
- expect(versions[1].frequencyDays).toBe(3);
+ expect(versions[0].frequency).toBe('7d');
+ expect(versions[1].frequency).toBe('3d');
});
it('new routines start with an empty description', async () => {
- const r = await add(projectId, 'clean kitchen', 7, db);
+ const r = await add(projectId, 'clean kitchen', '7d', db);
expect(r.description).toBe('');
});
it('edit is partial: name/frequency edits keep the description and vice-versa', async () => {
- const r = await add(projectId, 'clean kitchen', 7, db);
+ const r = await add(projectId, 'clean kitchen', '7d', db);
const described = await edit(r.id, { description: 'wax on, wax off' }, db);
expect(described.name).toBe('clean kitchen');
- expect(described.frequencyDays).toBe(7);
+ expect(described.frequency).toBe('7d');
expect(described.description).toBe('wax on, wax off');
const renamed = await edit(r.id, { name: 'clean the whole kitchen' }, db);
expect(renamed.version).toBe(3);
expect(renamed.description).toBe('wax on, wax off');
- expect(renamed.frequencyDays).toBe(7);
+ expect(renamed.frequency).toBe('7d');
- const refreq = await edit(r.id, { frequencyDays: 14 }, db);
+ const refreq = await edit(r.id, { frequency: '14d' }, db);
expect(refreq.version).toBe(4);
expect(refreq.name).toBe('clean the whole kitchen');
expect(refreq.description).toBe('wax on, wax off');
@@ -75,7 +75,7 @@ describe('routines service', () => {
});
it('archive inserts an archived snapshot without removing history', async () => {
- const r = await add(projectId, 'clean kitchen', 7, db);
+ const r = await add(projectId, 'clean kitchen', '7d', db);
await archive(r.id, db);
const versions = await loadAllVersions(r.id, db);
expect(versions).toHaveLength(2);
@@ -92,8 +92,8 @@ describe('routines service', () => {
});
it('loadArchived lists archived routines; edits keep the archive date', async () => {
- const a = await add(projectId, 'clean kitchen', 7, db);
- await add(projectId, 'water plants', 3, db);
+ const a = await add(projectId, 'clean kitchen', '7d', db);
+ await add(projectId, 'water plants', '3d', db);
await archive(a.id, db);
const stamped = (await loadAllVersions(a.id, db))[1].archivedAt;
expect(stamped).toBeTruthy();
@@ -105,13 +105,13 @@ describe('routines service', () => {
const archived = await loadArchived(projectId, db);
expect(archived.map((r) => r.id)).toEqual([a.id]);
expect(archived[0].name).toBe('clean kitchen deeply');
- expect(archived[0].frequencyDays).toBe(7);
+ expect(archived[0].frequency).toBe('7d');
expect(archived[0].archivedAt).toBe(stamped);
});
it('loadArchived sorts most recently archived first', async () => {
- const a = await add(projectId, 'first', 1, db);
- const b = await add(projectId, 'second', 1, db);
+ const a = await add(projectId, 'first', '1d', db);
+ const b = await add(projectId, 'second', '1d', db);
await archive(a.id, db);
await archive(b.id, db);
@@ -126,8 +126,8 @@ describe('routines service', () => {
});
it('complete records the snapshot version it was made against', async () => {
- const r = await add(projectId, 'clean kitchen', 7, db);
- await edit(r.id, { frequencyDays: 5 }, db);
+ const r = await add(projectId, 'clean kitchen', '7d', db);
+ await edit(r.id, { frequency: '5d' }, db);
const doneAt = Temporal.Now.instant().toString({
smallestUnit: 'millisecond',
});
@@ -137,7 +137,7 @@ describe('routines service', () => {
});
it('dashboard computes urgency and health from completions', async () => {
- const r = await add(projectId, 'clean kitchen', 7, db);
+ const r = await add(projectId, 'clean kitchen', '7d', db);
const created = Temporal.Instant.from(r.createdAt);
await complete(
r.id,
@@ -157,9 +157,9 @@ describe('routines service', () => {
});
it('overview aggregates per-project health and a global score', async () => {
- await add(projectId, 'clean kitchen', 7, db);
+ await add(projectId, 'clean kitchen', '7d', db);
const growth = await addProject('Personal growth', db);
- await add(growth.id, 'read 30 minutes', 1, db);
+ await add(growth.id, 'read 30 minutes', '1d', db);
const now = Temporal.Now.instant();
const overview = await loadOverview(now, db);
diff --git a/src/lib/server/services/routines.ts b/src/lib/server/services/routines.ts
index 57611d6..b1e210b 100644
--- a/src/lib/server/services/routines.ts
+++ b/src/lib/server/services/routines.ts
@@ -23,7 +23,7 @@ export async function nextRoutineId(db: AppDatabase): Promise<number> {
export async function add(
projectId: number,
name: string,
- frequencyDays: number,
+ frequency: string,
db = defaultDb,
): Promise<Routine> {
const id = await nextRoutineId(db);
@@ -34,7 +34,7 @@ export async function add(
version: 1,
projectId,
name,
- frequencyDays,
+ frequency,
createdAt: Temporal.Now.instant().toString({
smallestUnit: 'millisecond',
}),
@@ -46,7 +46,7 @@ export async function add(
/** Edit = insert a version+1 snapshot, copying unchanged fields forward. */
export async function edit(
id: number,
- fields: Partial<Pick<Routine, 'name' | 'frequencyDays' | 'description'>>,
+ fields: Partial<Pick<Routine, 'name' | 'frequency' | 'description'>>,
db = defaultDb,
): Promise<Routine> {
const [current] = await db
@@ -64,7 +64,7 @@ export async function edit(
version: current.version + 1,
projectId: current.projectId,
name: fields.name ?? current.name,
- frequencyDays: fields.frequencyDays ?? current.frequencyDays,
+ frequency: fields.frequency ?? current.frequency,
description: fields.description ?? current.description,
archived: current.archived,
archivedAt: current.archivedAt,
@@ -96,7 +96,7 @@ export async function archive(id: number, db = defaultDb): Promise<void> {
export interface ArchivedRoutine {
id: number;
name: string;
- frequencyDays: number;
+ frequency: string;
createdAt: string;
archivedAt: string;
}
@@ -125,7 +125,7 @@ export async function loadArchived(
.map((r) => ({
id: r.id,
name: r.name,
- frequencyDays: r.frequencyDays,
+ frequency: r.frequency,
createdAt: r.createdAt,
archivedAt: r.archivedAt ?? r.createdAt,
}))
diff --git a/src/lib/server/services/validation.ts b/src/lib/server/services/validation.ts
index b0a515c..a8cebd9 100644
--- a/src/lib/server/services/validation.ts
+++ b/src/lib/server/services/validation.ts
@@ -1,4 +1,3 @@
-import { parseIntervalToDays } from '$lib/shared/routine-metrics';
import * as v from 'valibot';
export const IdSchema = v.pipe(v.number(), v.integer(), v.minValue(1));
@@ -44,12 +43,3 @@ export function parseOptionalNumber(
if (!Number.isFinite(n)) return null;
return n;
}
-
-/** Parses an interval like '2w' into days; throws on invalid format. */
-export function parseFrequencyDays(value: FormDataEntryValue | null): number {
- const days = parseIntervalToDays(value?.toString() ?? '');
- if (days === null) {
- throw new Error('Invalid interval');
- }
- return days;
-}