2026-01-08 05:41:31 +00:00
|
|
|
'use server';
|
|
|
|
|
|
|
|
|
|
import { auth } from "@/auth";
|
|
|
|
|
import { getFreshAccessToken } from "@/lib/auth-utils";
|
|
|
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
|
|
|
|
2026-01-09 06:58:44 +00:00
|
|
|
/**
|
|
|
|
|
* Creates a virtual folder in the database.
|
2026-01-11 13:41:54 +00:00
|
|
|
* We explicitly set the metadata type to "FOLDER" so the Dashboard icon
|
|
|
|
|
* and Location logic work immediately.
|
2026-01-09 06:58:44 +00:00
|
|
|
*/
|
|
|
|
|
export async function createFolderAction(name: string, parentId: string | null = null) {
|
|
|
|
|
const session = await auth();
|
|
|
|
|
if (!session?.user?.id) throw new Error("Unauthorized");
|
|
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
// We generate a manual UUID because the schema requires 'id' but has no default generator
|
2026-01-09 06:58:44 +00:00
|
|
|
const internalId = crypto.randomUUID();
|
|
|
|
|
|
|
|
|
|
await prisma.fileNode.create({
|
|
|
|
|
data: {
|
|
|
|
|
id: internalId,
|
|
|
|
|
name: name,
|
|
|
|
|
isFolder: true,
|
|
|
|
|
path: `/virtual/${name}`,
|
|
|
|
|
ownerId: session.user.id,
|
|
|
|
|
parentId: parentId || null,
|
2026-01-11 13:41:54 +00:00
|
|
|
metadata: {
|
|
|
|
|
type: "FOLDER",
|
|
|
|
|
mimeType: "inode/directory"
|
|
|
|
|
}
|
2026-01-09 06:58:44 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
revalidatePath("/upload");
|
|
|
|
|
revalidatePath("/dashboard");
|
|
|
|
|
return { success: true };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-11 13:41:54 +00:00
|
|
|
* Uploads a file to OneDrive into a unique UUID folder
|
|
|
|
|
* and links it to a virtual parent (Project/Folder) in the DB.
|
2026-01-09 06:58:44 +00:00
|
|
|
*/
|
2026-01-08 05:41:31 +00:00
|
|
|
export async function uploadFileAction(formData: FormData) {
|
|
|
|
|
const session = await auth();
|
|
|
|
|
if (!session?.user?.id) throw new Error("Unauthorized");
|
|
|
|
|
|
|
|
|
|
const file = formData.get("file") as File;
|
2026-01-09 06:58:44 +00:00
|
|
|
const description = formData.get("description") as string || "";
|
|
|
|
|
const parentId = formData.get("parentId") as string | null;
|
|
|
|
|
|
2026-01-08 05:41:31 +00:00
|
|
|
if (!file) throw new Error("No file selected");
|
|
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
// Use the utility to ensure we have a valid JWT (fixing the "no dots" error)
|
2026-01-08 05:41:31 +00:00
|
|
|
const accessToken = await getFreshAccessToken(session.user.id);
|
2026-01-09 06:58:44 +00:00
|
|
|
const rootFolder = "WebCalibre";
|
|
|
|
|
const internalId = crypto.randomUUID();
|
2026-01-08 05:41:31 +00:00
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
// 1. Create the unique UUID folder on OneDrive inside WebCalibre
|
2026-01-09 06:58:44 +00:00
|
|
|
const createSubFolderRes = await fetch(`https://graph.microsoft.com/v1.0/me/drive/root:/${rootFolder}:/children`, {
|
|
|
|
|
method: "POST",
|
2026-01-11 13:41:54 +00:00
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${accessToken}`,
|
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
name: internalId,
|
|
|
|
|
folder: {},
|
|
|
|
|
"@microsoft.graph.conflictBehavior": "fail"
|
|
|
|
|
})
|
2026-01-08 05:41:31 +00:00
|
|
|
});
|
|
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
if (!createSubFolderRes.ok) {
|
|
|
|
|
const err = await createSubFolderRes.json();
|
|
|
|
|
console.error("OneDrive Folder Creation Error:", err);
|
|
|
|
|
throw new Error("Storage directory creation failed");
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 06:58:44 +00:00
|
|
|
const subFolderData = await createSubFolderRes.json();
|
2026-01-08 05:41:31 +00:00
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
// 2. Create Upload Session for the file
|
2026-01-09 06:58:44 +00:00
|
|
|
const sessionUrl = `https://graph.microsoft.com/v1.0/me/drive/items/${subFolderData.id}:/${encodeURIComponent(file.name)}:/createUploadSession`;
|
2026-01-08 05:41:31 +00:00
|
|
|
const sessionRes = await fetch(sessionUrl, {
|
|
|
|
|
method: "POST",
|
2026-01-11 13:41:54 +00:00
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${accessToken}`,
|
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
|
},
|
2026-01-09 06:58:44 +00:00
|
|
|
body: JSON.stringify({ item: { "@microsoft.graph.conflictBehavior": "replace" } })
|
2026-01-08 05:41:31 +00:00
|
|
|
});
|
|
|
|
|
|
2026-01-09 06:58:44 +00:00
|
|
|
const { uploadUrl } = await sessionRes.json();
|
2026-01-08 05:41:31 +00:00
|
|
|
const buffer = Buffer.from(await file.arrayBuffer());
|
2026-01-11 13:41:54 +00:00
|
|
|
|
|
|
|
|
// 3. Perform the actual upload
|
2026-01-08 05:41:31 +00:00
|
|
|
const uploadRes = await fetch(uploadUrl, {
|
|
|
|
|
method: "PUT",
|
2026-01-11 13:41:54 +00:00
|
|
|
headers: {
|
|
|
|
|
"Content-Length": `${file.size}`,
|
|
|
|
|
"Content-Range": `bytes 0-${file.size - 1}/${file.size}`
|
|
|
|
|
},
|
2026-01-08 05:41:31 +00:00
|
|
|
body: buffer
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-09 06:58:44 +00:00
|
|
|
if (!uploadRes.ok) throw new Error("OneDrive stream failed");
|
2026-01-08 05:41:31 +00:00
|
|
|
const driveItem = await uploadRes.json();
|
|
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
// 4. Record in Database with full metadata and virtual hierarchy
|
|
|
|
|
const extension = file.name.split('.').pop()?.toUpperCase() || "UNKNOWN";
|
|
|
|
|
|
2026-01-08 05:41:31 +00:00
|
|
|
await prisma.fileNode.create({
|
|
|
|
|
data: {
|
2026-01-11 13:41:54 +00:00
|
|
|
id: internalId, // Matches the folder name on OneDrive
|
2026-01-08 05:41:31 +00:00
|
|
|
oneDriveId: driveItem.id,
|
|
|
|
|
name: file.name,
|
2026-01-09 06:58:44 +00:00
|
|
|
description: description,
|
2026-01-08 05:41:31 +00:00
|
|
|
size: BigInt(file.size),
|
|
|
|
|
isFolder: false,
|
2026-01-09 06:58:44 +00:00
|
|
|
path: `/${rootFolder}/${internalId}/${file.name}`,
|
2026-01-08 05:41:31 +00:00
|
|
|
ownerId: session.user.id,
|
2026-01-11 13:41:54 +00:00
|
|
|
parentId: parentId || null, // Virtual link to the Project folder
|
2026-01-08 05:41:31 +00:00
|
|
|
metadata: {
|
2026-01-11 13:41:54 +00:00
|
|
|
type: extension,
|
2026-01-08 05:41:31 +00:00
|
|
|
mimeType: file.type
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
revalidatePath("/dashboard");
|
|
|
|
|
return { success: true };
|
|
|
|
|
}
|