refactor: deduplicate formatters and improve test mocks (#81)

- Consolidate duplicate date formatting utilities
- Use shared formatters across backend and frontend
- Clean up test mocks to use consistent test data
- Remove redundant formatting functions
This commit is contained in:
Daniel Volz
2026-01-30 18:37:24 +01:00
committed by GitHub
parent fcd1b79c56
commit aed0b20875
13 changed files with 568 additions and 888 deletions
+1 -11
View File
@@ -9,6 +9,7 @@ import { doseTracking, medications, shareTokens, userSettings } from "../db/sche
import { getAnonymousUserId, requireAuth } from "../plugins/auth.js";
import { env } from "../plugins/env.js";
import type { AuthUser } from "../types/fastify.js";
import { parseTakenByJson } from "../utils/scheduler-utils.js";
const IMAGES_DIR = resolve(process.cwd(), "data/images");
@@ -125,17 +126,6 @@ async function getUserId(request: any, reply: any): Promise<number> {
return authUser.id;
}
// Parse takenByJson safely
function parseTakenByJson(takenByJson: string | null | undefined): string[] {
if (!takenByJson) return [];
try {
const parsed = JSON.parse(takenByJson);
return Array.isArray(parsed) ? parsed.filter((s: unknown) => typeof s === "string" && s.trim()) : [];
} catch {
return [];
}
}
// Parse blisters from DB format to export format
function parseBlistersForExport(
row: typeof medications.$inferSelect
+1 -31
View File
@@ -9,7 +9,7 @@ import { doseTracking, medications } from "../db/schema.js";
import { getAnonymousUserId, requireAuth } from "../plugins/auth.js";
import { env } from "../plugins/env.js";
import type { AuthUser } from "../types/fastify.js";
import { parseLocalDateTime } from "../utils/scheduler-utils.js";
import { parseBlisters, parseLocalDateTime, parseTakenByJson } from "../utils/scheduler-utils.js";
const IMAGES_DIR = resolve(process.cwd(), "data/images");
@@ -34,36 +34,6 @@ const medicationSchema = z.object({
blisters: z.array(blisterSchema).min(1).max(12),
});
function zipBlisters(usage: number[], every: number[], start: string[]) {
const len = Math.min(usage.length, every.length, start.length);
const blisters: Array<{ usage: number; every: number; start: string }> = [];
for (let i = 0; i < len; i++) {
blisters.push({ usage: usage[i], every: every[i], start: start[i] });
}
return blisters;
}
function parseBlisters(row: typeof medications.$inferSelect) {
try {
const usage = JSON.parse(row.usageJson) as number[];
const every = JSON.parse(row.everyJson) as number[];
const start = JSON.parse(row.startJson) as string[];
return zipBlisters(usage, every, start);
} catch (_err) {
return [];
}
}
function parseTakenByJson(takenByJson: string | null | undefined): string[] {
if (!takenByJson) return [];
try {
const parsed = JSON.parse(takenByJson);
return Array.isArray(parsed) ? parsed.filter((s: unknown) => typeof s === "string" && s.trim()) : [];
} catch {
return [];
}
}
export async function medicationRoutes(app: FastifyInstance) {
// All medication routes require auth
app.addHook("preHandler", requireAuth);
+1 -11
View File
@@ -7,6 +7,7 @@ import { medications, shareTokens, userSettings, users } from "../db/schema.js";
import { getAnonymousUserId, requireAuth } from "../plugins/auth.js";
import { env } from "../plugins/env.js";
import type { AuthUser } from "../types/fastify.js";
import { parseTakenByJson } from "../utils/scheduler-utils.js";
// Share token validity: 1 year in milliseconds
const SHARE_TOKEN_VALIDITY_MS = 365 * 24 * 60 * 60 * 1000;
@@ -35,17 +36,6 @@ async function getUserId(request: FastifyRequest, reply: FastifyReply): Promise<
return authUser.id;
}
// Helper to parse takenByJson
function parseTakenByJson(takenByJson: string | null | undefined): string[] {
if (!takenByJson) return [];
try {
const parsed = JSON.parse(takenByJson);
return Array.isArray(parsed) ? parsed.filter((s: unknown) => typeof s === "string" && s.trim()) : [];
} catch {
return [];
}
}
// =============================================================================
// Share Routes
// =============================================================================
@@ -42,10 +42,6 @@ function saveIntakeReminderState(state: IntakeReminderState): void {
writeFileSync(intakeReminderStateFile, JSON.stringify(state, null, 2));
}
function parseBlistersFromRow(row: { usageJson: string; everyJson: string; startJson: string }): Blister[] {
return parseBlisters(row);
}
async function sendIntakeReminderEmail(
email: string,
intakes: UpcomingIntake[],
@@ -295,7 +291,7 @@ async function checkAndSendIntakeRemindersForUser(
// Find intakes: upcoming ones in reminder window + past ones for repeat reminders
for (const med of medsWithReminders) {
const blisters = parseBlistersFromRow(med);
const blisters = parseBlisters(med);
const takenByArray = parseTakenByJson(med.takenByJson);
logger.info(