feat: implement full CRUD functionality for projects with image upload support and admin dashboard management
This commit is contained in:
156
src/features/projects/actions.ts
Normal file
156
src/features/projects/actions.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
"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,
|
||||
};
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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
|
||||
},
|
||||
});
|
||||
|
||||
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" };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user