import type { APIRoute } from "astro"; import { db } from "../../../db"; import { timeEntries, members } from "../../../db/schema"; import { eq } from "drizzle-orm"; import { nanoid } from "nanoid"; import { validateTimeEntryResources, validateTimeRange, MAX_LENGTHS, } from "../../../lib/validation"; export const POST: APIRoute = async ({ request, locals }) => { if (!locals.user) { return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" }, }); } const body = await request.json(); const { description, clientId, startTime, endTime, tagId } = body; // Validation if (!clientId) { return new Response(JSON.stringify({ error: "Client is required" }), { status: 400, headers: { "Content-Type": "application/json" }, }); } if (description && description.length > MAX_LENGTHS.description) { return new Response( JSON.stringify({ error: `Description must be ${MAX_LENGTHS.description} characters or fewer` }), { status: 400, headers: { "Content-Type": "application/json" } }, ); } if (!startTime) { return new Response(JSON.stringify({ error: "Start time is required" }), { status: 400, headers: { "Content-Type": "application/json" }, }); } if (!endTime) { return new Response(JSON.stringify({ error: "End time is required" }), { status: 400, headers: { "Content-Type": "application/json" }, }); } const timeValidation = validateTimeRange(startTime, endTime); if ( !timeValidation.valid || !timeValidation.startDate || !timeValidation.endDate ) { return new Response(JSON.stringify({ error: timeValidation.error }), { status: 400, headers: { "Content-Type": "application/json" }, }); } const { startDate, endDate } = timeValidation; // Get user's organization const member = await db .select() .from(members) .where(eq(members.userId, locals.user.id)) .limit(1) .get(); if (!member) { return new Response(JSON.stringify({ error: "No organization found" }), { status: 400, headers: { "Content-Type": "application/json" }, }); } const resourceValidation = await validateTimeEntryResources({ organizationId: member.organizationId, clientId, tagId: tagId || null, }); if (!resourceValidation.valid) { return new Response(JSON.stringify({ error: resourceValidation.error }), { status: 400, headers: { "Content-Type": "application/json" }, }); } const id = nanoid(); try { // Insert the manual time entry await db.insert(timeEntries).values({ id, userId: locals.user.id, organizationId: member.organizationId, clientId, tagId: tagId || null, startTime: startDate, endTime: endDate, description: description || null, isManual: true, }); return new Response( JSON.stringify({ success: true, id, startTime: startDate.toISOString(), endTime: endDate.toISOString(), }), { status: 201, headers: { "Content-Type": "application/json" }, }, ); } catch (error) { console.error("Error creating manual time entry:", error); return new Response( JSON.stringify({ error: "Failed to create time entry" }), { status: 500, headers: { "Content-Type": "application/json" }, }, ); } };