Files
chronus/src/pages/api/time-entries/manual.ts
Atridad Lahiji 12d59bb42f
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m57s
Refactored a bunch of shit
2026-02-09 01:49:19 -07:00

134 lines
3.4 KiB
TypeScript

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