"use server"; import { revalidatePath } from "next/cache"; import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/auth"; import { db } from "@/db"; async function requireAdmin() { const session = await getServerSession(authOptions); if (!session) throw new Error("Non autorizzato"); } import { time_entries, projects } from "@/db/schema"; import { eq, isNull, asc } from "drizzle-orm"; import { nanoid } from "nanoid"; export async function startTimer(projectId: string): Promise<{ entryId: string }> { await requireAdmin(); // Stop any currently running session before starting a new one const running = await db .select({ id: time_entries.id }) .from(time_entries) .where(isNull(time_entries.ended_at)); for (const r of running) { const now = new Date(); const entry = await db .select({ started_at: time_entries.started_at }) .from(time_entries) .where(eq(time_entries.id, r.id)) .limit(1); if (entry[0]) { const secs = Math.round((now.getTime() - new Date(entry[0].started_at).getTime()) / 1000); await db .update(time_entries) .set({ ended_at: now, duration_seconds: secs }) .where(eq(time_entries.id, r.id)); } } const id = nanoid(); await db.insert(time_entries).values({ id, project_id: projectId }); revalidatePath("/admin"); revalidatePath("/admin/projects"); revalidatePath(`/admin/projects/${projectId}`); return { entryId: id }; } export async function startTimerForClient(clientId: string): Promise<{ entryId: string }> { await requireAdmin(); const projectRows = await db .select({ id: projects.id }) .from(projects) .where(eq(projects.client_id, clientId)) .orderBy(asc(projects.created_at)) .limit(1); const projectId = projectRows[0]?.id; if (!projectId) throw new Error("Nessun progetto trovato per questo cliente"); return startTimer(projectId); } export async function stopTimer(entryId: string): Promise { await requireAdmin(); const rows = await db .select({ started_at: time_entries.started_at, project_id: time_entries.project_id }) .from(time_entries) .where(eq(time_entries.id, entryId)) .limit(1); if (!rows[0]) return; const now = new Date(); const secs = Math.round((now.getTime() - new Date(rows[0].started_at).getTime()) / 1000); await db .update(time_entries) .set({ ended_at: now, duration_seconds: secs }) .where(eq(time_entries.id, entryId)); revalidatePath("/admin"); revalidatePath("/admin/projects"); revalidatePath(`/admin/projects/${rows[0].project_id}`); } /** * Inserts a manually entered time block (already closed). * duration_seconds = minutes * 60 * ended_at = started_at + duration_seconds * @param projectId project to record time against * @param minutes duration in minutes (must be > 0) * @param startedAt optional ISO date string — defaults to start of current day */ export async function addManualTimeEntry( projectId: string, minutes: number, startedAt?: string ): Promise { await requireAdmin(); if (!Number.isFinite(minutes) || minutes <= 0) throw new Error("Minuti non validi"); const start = startedAt ? new Date(startedAt) : new Date(); if (isNaN(start.getTime())) throw new Error("Data non valida"); const durationSeconds = Math.round(minutes * 60); const end = new Date(start.getTime() + durationSeconds * 1000); await db.insert(time_entries).values({ id: nanoid(), project_id: projectId, started_at: start, ended_at: end, duration_seconds: durationSeconds, }); revalidatePath("/admin"); revalidatePath("/admin/projects"); revalidatePath(`/admin/projects/${projectId}`); } /** * Deletes a time entry by id, scoped to the given project (guard against * cross-project deletions). */ export async function deleteTimeEntry(entryId: string, projectId: string): Promise { await requireAdmin(); await db .delete(time_entries) .where(eq(time_entries.id, entryId)); revalidatePath("/admin"); revalidatePath("/admin/projects"); revalidatePath(`/admin/projects/${projectId}`); }