feat(03-02): server actions + getAllServices query for service catalog

- Create src/app/admin/catalog/actions.ts with createService, updateService, toggleServiceActive
- Each action includes requireAdmin() guard via getServerSession
- Zod validation: name (min 1), unit_price (coerce.number min 0.01)
- Add getAllServices() to src/lib/admin-queries.ts ordered by name
- Import service_catalog and ServiceCatalog types in admin-queries.ts
This commit is contained in:
Simone Cavalli
2026-05-17 11:41:55 +02:00
parent 29bfd88255
commit efbc235c6e
2 changed files with 73 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
"use server";
import { db } from "@/db";
import { service_catalog } from "@/db/schema";
import { revalidatePath } from "next/cache";
import { eq } from "drizzle-orm";
import { z } from "zod";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
const serviceSchema = z.object({
name: z.string().min(1, "Nome richiesto"),
description: z.string().optional(),
unit_price: z.coerce.number().min(0.01, "Prezzo deve essere maggiore di 0"),
});
async function requireAdmin() {
const session = await getServerSession(authOptions);
if (!session) throw new Error("Non autorizzato");
}
export async function createService(formData: FormData) {
await requireAdmin();
const parsed = serviceSchema.safeParse({
name: formData.get("name"),
description: formData.get("description") ?? "",
unit_price: formData.get("unit_price"),
});
if (!parsed.success) throw new Error(parsed.error.issues[0].message);
await db.insert(service_catalog).values({
name: parsed.data.name,
description: parsed.data.description ?? null,
unit_price: parsed.data.unit_price.toFixed(2),
});
revalidatePath("/admin/catalog");
}
export async function updateService(serviceId: string, formData: FormData) {
await requireAdmin();
const parsed = serviceSchema.safeParse({
name: formData.get("name"),
description: formData.get("description") ?? "",
unit_price: formData.get("unit_price"),
});
if (!parsed.success) throw new Error(parsed.error.issues[0].message);
await db
.update(service_catalog)
.set({
name: parsed.data.name,
description: parsed.data.description ?? null,
unit_price: parsed.data.unit_price.toFixed(2),
})
.where(eq(service_catalog.id, serviceId));
revalidatePath("/admin/catalog");
}
export async function toggleServiceActive(serviceId: string, active: boolean) {
await requireAdmin();
await db
.update(service_catalog)
.set({ active })
.where(eq(service_catalog.id, serviceId));
revalidatePath("/admin/catalog");
}
+9
View File
@@ -9,6 +9,7 @@ import {
documents, documents,
notes, notes,
time_entries, time_entries,
service_catalog,
} from "@/db/schema"; } from "@/db/schema";
import { eq, inArray, asc, isNull, sql } from "drizzle-orm"; import { eq, inArray, asc, isNull, sql } from "drizzle-orm";
import type { import type {
@@ -20,6 +21,7 @@ import type {
Document, Document,
Note, Note,
Comment, Comment,
ServiceCatalog,
} from "@/db/schema"; } from "@/db/schema";
export type ClientWithPayments = { export type ClientWithPayments = {
@@ -189,3 +191,10 @@ export async function getClientFullDetail(id: string): Promise<ClientFullDetail
comments: commentsRows, comments: commentsRows,
}; };
} }
export async function getAllServices(): Promise<ServiceCatalog[]> {
return db
.select()
.from(service_catalog)
.orderBy(asc(service_catalog.name));
}