Files
Vadim Sobinin fc886320e3 first commit
2026-02-02 16:14:57 +03:00

143 lines
4.7 KiB
JavaScript

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 };
});
}