"use server"; import { prisma } from "@/core/db/prisma"; import { verifySession } from "@/core/security/session"; import { revalidatePath } from "next/cache"; import { z } from "zod"; const optionalTextSchema = z.preprocess((value) => { if (typeof value !== "string") return undefined; const trimmed = value.trim(); return trimmed === "" ? undefined : trimmed; }, z.string().optional()); const optionalUrlSchema = z.preprocess((value) => { if (typeof value !== "string") return undefined; const trimmed = value.trim(); return trimmed === "" ? undefined : trimmed; }, z.string().url("Final project URL must be a valid URL").optional()); const yearSchema = z .coerce .number() .int("Year must be a whole number") .min(1900, "Year must be 1900 or later") .max(2100, "Year must be 2100 or earlier"); const optionalYearSchema = z.preprocess((value) => { if (value === "" || value === null || value === undefined) return undefined; return value; }, yearSchema.optional()); const educationSchema = z .object({ institution: z.string().trim().min(1, "Institution is required"), degree: z.string().trim().min(1, "Degree is required"), fieldOfStudy: z.string().trim().min(1, "Field of study is required"), location: optionalTextSchema, startYear: yearSchema, endYear: optionalYearSchema, isOngoing: z.boolean().default(false), description: optionalTextSchema, gpa: optionalTextSchema, finalProjectTitle: optionalTextSchema, finalProjectUrl: optionalUrlSchema, order: z.coerce.number().int().default(0), }) .superRefine((data, ctx) => { if (!data.isOngoing && typeof data.endYear !== "number") { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["endYear"], message: "End year is required unless this education is ongoing", }); } if ( typeof data.endYear === "number" && data.endYear < data.startYear ) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["endYear"], message: "End year must be greater than or equal to start year", }); } }); function extractEducationFormData(formData: FormData) { return { institution: formData.get("institution") as string, degree: formData.get("degree") as string, fieldOfStudy: formData.get("fieldOfStudy") as string, location: formData.get("location") as string, startYear: formData.get("startYear") as string, endYear: formData.get("endYear") as string, isOngoing: formData.get("isOngoing") === "on", description: formData.get("description") as string, gpa: formData.get("gpa") as string, finalProjectTitle: formData.get("finalProjectTitle") as string, finalProjectUrl: formData.get("finalProjectUrl") as string, order: formData.get("order") as string, }; } export async function createEducationAction(prevState: unknown, formData: FormData) { const session = await verifySession(); if (!session) return { success: false, message: "Unauthorized" }; const validation = educationSchema.safeParse(extractEducationFormData(formData)); if (!validation.success) { return { success: false, message: validation.error.issues[0].message }; } try { await prisma.education.create({ data: validation.data, }); revalidatePath("/admin/dashboard/education"); revalidatePath("/admin/dashboard"); revalidatePath("/"); return { success: true }; } catch (error) { console.error(error); return { success: false, message: "Failed to create education entry" }; } } export async function updateEducationAction( id: string, prevState: unknown, formData: FormData ) { const session = await verifySession(); if (!session) return { success: false, message: "Unauthorized" }; const validation = educationSchema.safeParse(extractEducationFormData(formData)); if (!validation.success) { return { success: false, message: validation.error.issues[0].message }; } try { await prisma.education.update({ where: { id }, data: validation.data, }); revalidatePath("/admin/dashboard/education"); revalidatePath("/admin/dashboard"); revalidatePath("/"); return { success: true }; } catch (error) { console.error(error); return { success: false, message: "Failed to update education entry" }; } } export async function deleteEducationAction(id: string) { const session = await verifySession(); if (!session) return { success: false, message: "Unauthorized" }; try { await prisma.education.delete({ where: { id } }); revalidatePath("/admin/dashboard/education"); revalidatePath("/admin/dashboard"); revalidatePath("/"); return { success: true }; } catch (error) { console.error(error); return { success: false, message: "Failed to delete education entry" }; } }