112 lines
3.5 KiB
TypeScript
112 lines
3.5 KiB
TypeScript
|
|
'use server';
|
||
|
|
|
||
|
|
import { auth } from "@/auth";
|
||
|
|
import { getFreshAccessToken } from "@/lib/auth-utils";
|
||
|
|
import { prisma } from "@/lib/prisma";
|
||
|
|
import { revalidatePath } from "next/cache";
|
||
|
|
|
||
|
|
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;
|
||
|
|
if (!file) throw new Error("No file selected");
|
||
|
|
|
||
|
|
const accessToken = await getFreshAccessToken(session.user.id);
|
||
|
|
const folderName = "WebCalibre";
|
||
|
|
|
||
|
|
// --- 1. CHECK/CREATE THE WEBCALIBRE FOLDER ---
|
||
|
|
// We check if the folder exists at the root of the user's OneDrive
|
||
|
|
const folderCheckUrl = `https://graph.microsoft.com/v1.0/me/drive/root:/${folderName}`;
|
||
|
|
const folderCheck = await fetch(folderCheckUrl, {
|
||
|
|
headers: { Authorization: `Bearer ${accessToken}` }
|
||
|
|
});
|
||
|
|
|
||
|
|
if (folderCheck.status === 404) {
|
||
|
|
console.log(`📂 Folder '${folderName}' not found. Creating it...`);
|
||
|
|
const createFolderRes = await fetch(`https://graph.microsoft.com/v1.0/me/drive/root/children`, {
|
||
|
|
method: "POST",
|
||
|
|
headers: {
|
||
|
|
Authorization: `Bearer ${accessToken}`,
|
||
|
|
"Content-Type": "application/json"
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
name: folderName,
|
||
|
|
folder: {}, // Empty object tells Graph to create a folder
|
||
|
|
"@microsoft.graph.conflictBehavior": "fail"
|
||
|
|
})
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!createFolderRes.ok) {
|
||
|
|
const errorData = await createFolderRes.json();
|
||
|
|
console.error("❌ Folder Creation Error:", errorData);
|
||
|
|
throw new Error("Could not create WebCalibre folder on OneDrive.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- 2. CREATE UPLOAD SESSION ---
|
||
|
|
// encodeURIComponent is vital for filenames with spaces or special characters
|
||
|
|
const sessionUrl = `https://graph.microsoft.com/v1.0/me/drive/root:/${folderName}/${encodeURIComponent(file.name)}:/createUploadSession`;
|
||
|
|
|
||
|
|
const sessionRes = await fetch(sessionUrl, {
|
||
|
|
method: "POST",
|
||
|
|
headers: {
|
||
|
|
Authorization: `Bearer ${accessToken}`,
|
||
|
|
"Content-Type": "application/json"
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
item: {
|
||
|
|
"@microsoft.graph.conflictBehavior": "rename", // If file exists, name it "Book 1.pdf"
|
||
|
|
name: file.name
|
||
|
|
}
|
||
|
|
})
|
||
|
|
});
|
||
|
|
|
||
|
|
const sessionData = await sessionRes.json();
|
||
|
|
if (!sessionRes.ok) {
|
||
|
|
console.error("❌ Session Error:", sessionData);
|
||
|
|
throw new Error(sessionData.error?.message || "OneDrive session failed");
|
||
|
|
}
|
||
|
|
|
||
|
|
const { uploadUrl } = sessionData;
|
||
|
|
|
||
|
|
// --- 3. UPLOAD THE DATA BYTES ---
|
||
|
|
const buffer = Buffer.from(await file.arrayBuffer());
|
||
|
|
const uploadRes = await fetch(uploadUrl, {
|
||
|
|
method: "PUT",
|
||
|
|
headers: {
|
||
|
|
"Content-Length": `${file.size}`,
|
||
|
|
"Content-Range": `bytes 0-${file.size - 1}/${file.size}`
|
||
|
|
},
|
||
|
|
body: buffer
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!uploadRes.ok) {
|
||
|
|
const uploadError = await uploadRes.json();
|
||
|
|
console.error("❌ Upload Error:", uploadError);
|
||
|
|
throw new Error("Chunk upload failed");
|
||
|
|
}
|
||
|
|
|
||
|
|
const driveItem = await uploadRes.json();
|
||
|
|
|
||
|
|
// --- 4. RECORD IN POSTGRESQL (PRISMA) ---
|
||
|
|
await prisma.fileNode.create({
|
||
|
|
data: {
|
||
|
|
oneDriveId: driveItem.id,
|
||
|
|
name: file.name,
|
||
|
|
size: BigInt(file.size),
|
||
|
|
isFolder: false,
|
||
|
|
path: `/${folderName}/${file.name}`,
|
||
|
|
ownerId: session.user.id,
|
||
|
|
metadata: {
|
||
|
|
type: file.name.split('.').pop()?.toUpperCase() || "UNKNOWN",
|
||
|
|
mimeType: file.type
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Revalidate ensures the dashboard list updates immediately
|
||
|
|
revalidatePath("/dashboard");
|
||
|
|
|
||
|
|
return { success: true };
|
||
|
|
}
|