import { z } from 'zod'; import { prisma } from '../index.js'; function parseTimeToMinutes(timeStr) { const str = timeStr.trim(); // Format: "8,5" or "8.5" (decimal hours) if (/^\d+[,\.]\d+$/.test(str)) { const hours = parseFloat(str.replace(',', '.')); return Math.round(hours * 60); } // Format: "8:30" (hours:minutes) if (/^\d+:\d{1,2}$/.test(str)) { const [hours, minutes] = str.split(':').map(Number); return hours * 60 + minutes; } // Format: "8" (just hours) if (/^\d+$/.test(str)) { return parseInt(str, 10) * 60; } throw new Error('Invalid time format'); } const createLogSchema = z.object({ date: z.string(), time: z.string(), description: z.string().min(1).max(1000), }); const updateLogSchema = z.object({ date: z.string().optional(), time: z.string().optional(), description: z.string().min(1).max(1000).optional(), }); const querySchema = z.object({ startDate: z.string().optional(), endDate: z.string().optional(), }); export async function logsRoutes(fastify) { fastify.addHook('onRequest', fastify.authenticate); // Get logs (default: current month) fastify.get('/', async (request) => { const user = request.user; const query = querySchema.parse(request.query); const now = new Date(); const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59); const startDate = query.startDate ? new Date(query.startDate) : startOfMonth; const endDate = query.endDate ? new Date(query.endDate + 'T23:59:59') : endOfMonth; const logs = await prisma.timeLog.findMany({ where: { userId: user.id, date: { gte: startDate, lte: endDate, }, }, orderBy: { date: 'desc' }, }); return logs.map(log => ({ ...log, hours: Math.floor(log.minutes / 60), mins: log.minutes % 60, })); }); // Create log fastify.post('/', async (request, reply) => { const user = request.user; const result = createLogSchema.safeParse(request.body); if (!result.success) { return reply.status(400).send({ error: 'Invalid input', details: result.error.errors }); } const { date, time, description } = result.data; let minutes; try { minutes = parseTimeToMinutes(time); } catch { return reply.status(400).send({ error: 'Invalid time format' }); } const log = await prisma.timeLog.create({ data: { date: new Date(date), minutes, description, userId: user.id, }, }); return { ...log, hours: Math.floor(log.minutes / 60), mins: log.minutes % 60, }; }); // Update log fastify.put('/:id', async (request, reply) => { const user = request.user; const { id } = request.params; const result = updateLogSchema.safeParse(request.body); if (!result.success) { return reply.status(400).send({ error: 'Invalid input', details: result.error.errors }); } const existing = await prisma.timeLog.findFirst({ where: { id, userId: user.id }, }); if (!existing) { return reply.status(404).send({ error: 'Log not found' }); } const { date, time, description } = result.data; let minutes; if (time) { try { minutes = parseTimeToMinutes(time); } catch { return reply.status(400).send({ error: 'Invalid time format' }); } } const log = await prisma.timeLog.update({ where: { id }, data: { ...(date && { date: new Date(date) }), ...(minutes !== undefined && { minutes }), ...(description && { description }), }, }); return { ...log, hours: Math.floor(log.minutes / 60), mins: log.minutes % 60, }; }); // Delete log fastify.delete('/:id', async (request, reply) => { const user = request.user; const { id } = request.params; const existing = await prisma.timeLog.findFirst({ where: { id, userId: user.id }, }); if (!existing) { return reply.status(404).send({ error: 'Log not found' }); } await prisma.timeLog.delete({ where: { id } }); return { success: true }; }); }