Files
website-portofolio/src/features/projects/actions.ts

169 lines
5.9 KiB
TypeScript

"use server";
import { prisma } from "@/core/db/prisma";
import { uploadFileToMinio } from "@/core/storage/minio";
import { verifySession } from "@/core/security/session";
import { projectSchema } from "./project-schema";
import { revalidatePath } from "next/cache";
export async function createProjectAction(prevState: any, formData: FormData) {
// 1. Verify Authentication
const session = await verifySession();
if (!session) return { success: false, message: "Unauthorized" };
// 2. Extract and Validate Form Data
const data = {
title: formData.get("title") as string,
slug: formData.get("slug") as string,
description: formData.get("description") as string,
category: formData.get("category") as string,
repoUrl: formData.get("repoUrl") as string,
liveUrl: formData.get("liveUrl") as string,
isPublished: formData.get("isPublished") === "on",
image: formData.get("image") as File | null,
techStack: formData.get("techStack") as string,
year: formData.get("year") as string,
highlights: formData.get("highlights") as string,
};
const validation = projectSchema.safeParse(data);
if (!validation.success) {
return { success: false, message: validation.error.issues[0].message };
}
// 3. Process Image Upload
let imageUrl: string | undefined = undefined;
if (data.image && data.image.size > 0 && data.image.name) {
try {
imageUrl = await uploadFileToMinio(data.image);
} catch (e) {
console.error("Image upload failed:", e);
return { success: false, message: "Failed to upload image. Ensure MinIO is running and bucket exists." };
}
}
// 4. Save to Database
try {
const project = await prisma.project.create({
data: {
title: validation.data.title,
slug: validation.data.slug,
description: validation.data.description,
category: validation.data.category,
repoUrl: validation.data.repoUrl || null,
liveUrl: validation.data.liveUrl || null,
isPublished: validation.data.isPublished,
imageUrl: imageUrl,
techStack: validation.data.techStack ? validation.data.techStack.split(",").map(s => s.trim()).filter(s => s) : [],
year: validation.data.year || null,
highlights: validation.data.highlights ? validation.data.highlights.split(",").map(s => s.trim()).filter(s => s) : [],
},
});
revalidatePath("/admin/dashboard");
revalidatePath("/");
return { success: true };
} catch (error: any) {
console.error("DB Error:", error);
if (error?.code === "P2002") {
return { success: false, message: "Project slug already exists" };
}
return { success: false, message: "Failed to create project" };
}
}
export async function updateProjectAction(id: string, prevState: any, formData: FormData) {
const session = await verifySession();
if (!session) return { success: false, message: "Unauthorized" };
const data = {
title: formData.get("title") as string,
slug: formData.get("slug") as string,
description: formData.get("description") as string,
category: formData.get("category") as string,
repoUrl: formData.get("repoUrl") as string,
liveUrl: formData.get("liveUrl") as string,
isPublished: formData.get("isPublished") === "on",
image: formData.get("image") as File | null,
techStack: formData.get("techStack") as string,
year: formData.get("year") as string,
highlights: formData.get("highlights") as string,
};
const validation = projectSchema.safeParse(data);
if (!validation.success) {
return { success: false, message: validation.error.issues[0].message };
}
let imageUrl: string | undefined = undefined;
if (data.image && data.image.size > 0 && data.image.name) {
try {
imageUrl = await uploadFileToMinio(data.image);
} catch (e) {
return { success: false, message: "Failed to upload new image." };
}
}
try {
await prisma.project.update({
where: { id },
data: {
title: validation.data.title,
slug: validation.data.slug,
description: validation.data.description,
category: validation.data.category,
repoUrl: validation.data.repoUrl || null,
liveUrl: validation.data.liveUrl || null,
isPublished: validation.data.isPublished,
...(imageUrl && { imageUrl }), // only update image if a new one was uploaded
techStack: validation.data.techStack ? validation.data.techStack.split(",").map(s => s.trim()).filter(s => s) : [],
year: validation.data.year || null,
highlights: validation.data.highlights ? validation.data.highlights.split(",").map(s => s.trim()).filter(s => s) : [],
},
});
revalidatePath("/admin/dashboard");
revalidatePath("/admin/dashboard/projects");
revalidatePath("/");
return { success: true };
} catch (error: any) {
if (error?.code === "P2002") {
return { success: false, message: "Project slug already exists" };
}
return { success: false, message: "Failed to update project" };
}
}
export async function deleteProjectAction(id: string) {
const session = await verifySession();
if (!session) return { success: false, message: "Unauthorized" };
try {
await prisma.project.delete({ where: { id } });
revalidatePath("/admin/dashboard");
revalidatePath("/");
return { success: true };
} catch (error) {
return { success: false, message: "Failed to delete project" };
}
}
export async function toggleProjectPublishAction(id: string, isPublished: boolean) {
const session = await verifySession();
if (!session) return { success: false, message: "Unauthorized" };
try {
await prisma.project.update({
where: { id },
data: { isPublished: !isPublished },
});
revalidatePath("/admin/dashboard");
revalidatePath("/");
return { success: true };
} catch (error) {
return { success: false, message: "Failed to update project status" };
}
}