2026-01-08 05:41:31 +00:00
|
|
|
'use server';
|
2026-01-11 13:41:54 +00:00
|
|
|
|
2026-01-08 05:41:31 +00:00
|
|
|
import { auth } from "@/auth";
|
2026-01-11 13:41:54 +00:00
|
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
|
|
|
import { getFreshAccessToken } from "@/lib/auth-utils";
|
2026-01-08 05:41:31 +00:00
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
/**
|
2026-01-14 07:29:52 +00:00
|
|
|
* 1. FETCH: Get all file nodes for the Dashboard
|
2026-01-11 13:41:54 +00:00
|
|
|
*/
|
2026-01-08 05:41:31 +00:00
|
|
|
export async function getFileNodes() {
|
2026-01-11 13:41:54 +00:00
|
|
|
try {
|
|
|
|
|
const nodes = await prisma.fileNode.findMany({
|
|
|
|
|
orderBy: {
|
|
|
|
|
updatedAt: 'desc',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return nodes;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Error fetching file nodes:", error);
|
|
|
|
|
return [];
|
2026-01-08 05:41:31 +00:00
|
|
|
}
|
2026-01-11 13:41:54 +00:00
|
|
|
}
|
2026-01-08 05:41:31 +00:00
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
/**
|
2026-01-14 07:29:52 +00:00
|
|
|
* 2. DOWNLOAD: Generates the authenticated OneDrive URL
|
|
|
|
|
*/
|
|
|
|
|
export async function getDownloadUrlAction(id: string) {
|
|
|
|
|
const session = await auth();
|
|
|
|
|
if (!session?.user?.id) throw new Error("Unauthorized");
|
|
|
|
|
|
|
|
|
|
const file = await prisma.fileNode.findUnique({ where: { id } });
|
|
|
|
|
if (!file || !file.oneDriveId) throw new Error("File not found or missing cloud ID");
|
|
|
|
|
|
|
|
|
|
const accessToken = await getFreshAccessToken(session.user.id);
|
|
|
|
|
const res = await fetch(
|
|
|
|
|
`https://graph.microsoft.com/v1.0/me/drive/items/${file.oneDriveId}`,
|
|
|
|
|
{ headers: { Authorization: `Bearer ${accessToken}` } }
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!res.ok) throw new Error("Failed to contact OneDrive");
|
|
|
|
|
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
const downloadUrl = data["@microsoft.graph.downloadUrl"];
|
|
|
|
|
|
|
|
|
|
if (!downloadUrl) throw new Error("OneDrive did not provide a download link");
|
|
|
|
|
|
|
|
|
|
return { downloadUrl };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 3. DELETE: Remove from OneDrive (via ID) and Database
|
|
|
|
|
* Folders are virtual (DB only), so cloud deletion is skipped if oneDriveId is null.
|
2026-01-11 13:41:54 +00:00
|
|
|
*/
|
|
|
|
|
export async function deleteFileAction(fileId: string) {
|
|
|
|
|
const session = await auth();
|
|
|
|
|
if (!session?.user?.id) throw new Error("Unauthorized");
|
|
|
|
|
|
|
|
|
|
const node = await prisma.fileNode.findUnique({
|
|
|
|
|
where: { id: fileId },
|
2026-01-08 05:41:31 +00:00
|
|
|
});
|
|
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
if (!node) {
|
|
|
|
|
revalidatePath("/dashboard");
|
2026-01-14 07:29:52 +00:00
|
|
|
return { success: true };
|
2026-01-11 13:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-13 13:17:40 +00:00
|
|
|
// @ts-ignore
|
2026-01-11 13:41:54 +00:00
|
|
|
const isAdmin = session.user.role === "ADMIN";
|
|
|
|
|
const isOwner = node.ownerId === session.user.id;
|
|
|
|
|
|
|
|
|
|
if (!isAdmin && !isOwner) {
|
2026-01-13 13:17:40 +00:00
|
|
|
throw new Error("Permission Denied.");
|
2026-01-11 13:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const accessToken = await getFreshAccessToken(session.user.id);
|
2026-01-14 07:29:52 +00:00
|
|
|
|
|
|
|
|
// Only attempt cloud deletion if it's a file/storage with a oneDriveId.
|
|
|
|
|
// Virtual folders created in the DB have no oneDriveId and are skipped.
|
|
|
|
|
if (accessToken && node.oneDriveId) {
|
|
|
|
|
const onedriveRes = await fetch(
|
|
|
|
|
`https://graph.microsoft.com/v1.0/me/drive/items/${node.oneDriveId}`,
|
|
|
|
|
{
|
|
|
|
|
method: "DELETE",
|
|
|
|
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
|
|
|
}
|
|
|
|
|
);
|
2026-01-11 13:41:54 +00:00
|
|
|
|
|
|
|
|
if (!onedriveRes.ok && onedriveRes.status !== 404) {
|
2026-01-13 13:17:40 +00:00
|
|
|
console.warn("OneDrive Deletion Warning: Cloud record might still exist.");
|
2026-01-11 13:41:54 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (cloudError) {
|
2026-01-13 13:17:40 +00:00
|
|
|
console.error("Cloud cleanup failed:", cloudError);
|
2026-01-11 13:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2026-01-13 13:17:40 +00:00
|
|
|
await prisma.fileNode.delete({ where: { id: fileId } });
|
2026-01-11 13:41:54 +00:00
|
|
|
revalidatePath("/dashboard");
|
|
|
|
|
revalidatePath("/upload");
|
|
|
|
|
return { success: true };
|
2026-01-13 13:17:40 +00:00
|
|
|
} catch (dbError) {
|
2026-01-11 13:41:54 +00:00
|
|
|
throw new Error("Failed to remove the record from the database.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-14 07:29:52 +00:00
|
|
|
* 4. MOVE: Assign file to folder or folder to another folder (Virtual Move)
|
2026-01-11 13:41:54 +00:00
|
|
|
*/
|
|
|
|
|
export async function moveNodeAction(nodeId: string, newParentId: string | null) {
|
|
|
|
|
const session = await auth();
|
|
|
|
|
if (!session?.user?.id) throw new Error("Unauthorized");
|
|
|
|
|
if (nodeId === newParentId) throw new Error("Cannot move to self.");
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await prisma.fileNode.update({
|
|
|
|
|
where: { id: nodeId },
|
|
|
|
|
data: { parentId: newParentId }
|
|
|
|
|
});
|
|
|
|
|
revalidatePath("/dashboard");
|
|
|
|
|
return { success: true };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw new Error("Move failed.");
|
|
|
|
|
}
|
2026-01-13 13:17:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-14 07:29:52 +00:00
|
|
|
* 5. UPDATE & REPLACE: Full update of metadata and OneDrive content
|
2026-01-13 13:17:40 +00:00
|
|
|
*/
|
|
|
|
|
export async function updateFileFullAction(formData: FormData) {
|
|
|
|
|
const session = await auth();
|
|
|
|
|
if (!session?.user?.id) throw new Error("Unauthorized");
|
|
|
|
|
|
|
|
|
|
const id = formData.get("id") as string;
|
|
|
|
|
const name = formData.get("name") as string;
|
|
|
|
|
const description = formData.get("description") as string;
|
|
|
|
|
const parentIdRaw = formData.get("parentId") as string;
|
|
|
|
|
const metadataStr = formData.get("metadata") as string;
|
|
|
|
|
const newFile = formData.get("file") as File | null;
|
|
|
|
|
|
|
|
|
|
const parentId = parentIdRaw === "root" ? null : parentIdRaw;
|
|
|
|
|
let metadata = JSON.parse(metadataStr);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const accessToken = await getFreshAccessToken(session.user.id);
|
2026-01-14 07:29:52 +00:00
|
|
|
const node = await prisma.fileNode.findUnique({ where: { id } });
|
2026-01-13 13:17:40 +00:00
|
|
|
|
2026-01-14 07:29:52 +00:00
|
|
|
// Update physical file content only if a new file is uploaded and we have a target oneDriveId
|
|
|
|
|
if (newFile && newFile.size > 0 && node?.oneDriveId) {
|
|
|
|
|
const onedrivePath = `https://graph.microsoft.com/v1.0/me/drive/items/${node.oneDriveId}/content`;
|
2026-01-13 13:17:40 +00:00
|
|
|
|
|
|
|
|
const uploadRes = await fetch(onedrivePath, {
|
|
|
|
|
method: "PUT",
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${accessToken}`,
|
|
|
|
|
"Content-Type": newFile.type
|
|
|
|
|
},
|
|
|
|
|
body: Buffer.from(await newFile.arrayBuffer()),
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-14 07:29:52 +00:00
|
|
|
if (!uploadRes.ok) throw new Error("OneDrive content update failed");
|
2026-01-13 13:17:40 +00:00
|
|
|
|
|
|
|
|
metadata.type = newFile.name.split('.').pop()?.toUpperCase() || 'UNKNOWN';
|
|
|
|
|
metadata.mimeType = newFile.type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await prisma.fileNode.update({
|
|
|
|
|
where: { id },
|
|
|
|
|
data: {
|
|
|
|
|
name,
|
|
|
|
|
description,
|
|
|
|
|
parentId,
|
|
|
|
|
metadata,
|
|
|
|
|
size: newFile ? BigInt(newFile.size) : undefined,
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
revalidatePath("/dashboard");
|
|
|
|
|
return { success: true };
|
|
|
|
|
} catch (error: any) {
|
2026-01-14 07:29:52 +00:00
|
|
|
console.error("Full Update Failure:", error);
|
2026-01-13 13:17:40 +00:00
|
|
|
throw new Error(error.message || "Failed to update record.");
|
|
|
|
|
}
|
2026-01-08 05:41:31 +00:00
|
|
|
}
|