Working again
This commit is contained in:
parent
86cdffdd1c
commit
fe9eb915e1
2 changed files with 87 additions and 63 deletions
|
|
@ -6,7 +6,7 @@ import { revalidatePath } from "next/cache";
|
||||||
import { getFreshAccessToken } from "@/lib/auth-utils";
|
import { getFreshAccessToken } from "@/lib/auth-utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. FETCH: Get all file nodes
|
* 1. FETCH: Get all file nodes for the Dashboard
|
||||||
*/
|
*/
|
||||||
export async function getFileNodes() {
|
export async function getFileNodes() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -23,7 +23,34 @@ export async function getFileNodes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 2. DELETE: Remove from OneDrive and Database
|
* 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.
|
||||||
*/
|
*/
|
||||||
export async function deleteFileAction(fileId: string) {
|
export async function deleteFileAction(fileId: string) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
@ -35,7 +62,7 @@ export async function deleteFileAction(fileId: string) {
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
revalidatePath("/dashboard");
|
revalidatePath("/dashboard");
|
||||||
return { success: true, message: "Item already removed from database" };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -48,14 +75,17 @@ export async function deleteFileAction(fileId: string) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const accessToken = await getFreshAccessToken(session.user.id);
|
const accessToken = await getFreshAccessToken(session.user.id);
|
||||||
if (accessToken) {
|
|
||||||
const rootFolder = "WebCalibre";
|
|
||||||
const onedrivePath = `https://graph.microsoft.com/v1.0/me/drive/root:/${rootFolder}/${node.id}`;
|
|
||||||
|
|
||||||
const onedriveRes = await fetch(onedrivePath, {
|
// 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",
|
method: "DELETE",
|
||||||
headers: { Authorization: `Bearer ${accessToken}` },
|
headers: { Authorization: `Bearer ${accessToken}` },
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (!onedriveRes.ok && onedriveRes.status !== 404) {
|
if (!onedriveRes.ok && onedriveRes.status !== 404) {
|
||||||
console.warn("OneDrive Deletion Warning: Cloud record might still exist.");
|
console.warn("OneDrive Deletion Warning: Cloud record might still exist.");
|
||||||
|
|
@ -76,7 +106,7 @@ export async function deleteFileAction(fileId: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 3. MOVE: Assign file to folder or folder to another folder
|
* 4. MOVE: Assign file to folder or folder to another folder (Virtual Move)
|
||||||
*/
|
*/
|
||||||
export async function moveNodeAction(nodeId: string, newParentId: string | null) {
|
export async function moveNodeAction(nodeId: string, newParentId: string | null) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
@ -96,8 +126,7 @@ export async function moveNodeAction(nodeId: string, newParentId: string | null)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 4. UPDATE & REPLACE: Full update of metadata and OneDrive content
|
* 5. UPDATE & REPLACE: Full update of metadata and OneDrive content
|
||||||
* Uses FormData to handle the binary file upload and text fields.
|
|
||||||
*/
|
*/
|
||||||
export async function updateFileFullAction(formData: FormData) {
|
export async function updateFileFullAction(formData: FormData) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
@ -110,19 +139,16 @@ export async function updateFileFullAction(formData: FormData) {
|
||||||
const metadataStr = formData.get("metadata") as string;
|
const metadataStr = formData.get("metadata") as string;
|
||||||
const newFile = formData.get("file") as File | null;
|
const newFile = formData.get("file") as File | null;
|
||||||
|
|
||||||
// Handle the "root" placeholder back to null for Prisma
|
|
||||||
const parentId = parentIdRaw === "root" ? null : parentIdRaw;
|
const parentId = parentIdRaw === "root" ? null : parentIdRaw;
|
||||||
let metadata = JSON.parse(metadataStr);
|
let metadata = JSON.parse(metadataStr);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const accessToken = await getFreshAccessToken(session.user.id);
|
const accessToken = await getFreshAccessToken(session.user.id);
|
||||||
if (!accessToken) throw new Error("Access token expired.");
|
const node = await prisma.fileNode.findUnique({ where: { id } });
|
||||||
|
|
||||||
// 1. OneDrive Overwrite (if file is provided)
|
// Update physical file content only if a new file is uploaded and we have a target oneDriveId
|
||||||
if (newFile && newFile.size > 0) {
|
if (newFile && newFile.size > 0 && node?.oneDriveId) {
|
||||||
const rootFolder = "WebCalibre";
|
const onedrivePath = `https://graph.microsoft.com/v1.0/me/drive/items/${node.oneDriveId}/content`;
|
||||||
// PUT request to the specific file ID path overwrites content
|
|
||||||
const onedrivePath = `https://graph.microsoft.com/v1.0/me/drive/root:/${rootFolder}/${id}:/content`;
|
|
||||||
|
|
||||||
const uploadRes = await fetch(onedrivePath, {
|
const uploadRes = await fetch(onedrivePath, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
|
|
@ -133,17 +159,12 @@ export async function updateFileFullAction(formData: FormData) {
|
||||||
body: Buffer.from(await newFile.arrayBuffer()),
|
body: Buffer.from(await newFile.arrayBuffer()),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!uploadRes.ok) {
|
if (!uploadRes.ok) throw new Error("OneDrive content update failed");
|
||||||
const err = await uploadRes.json();
|
|
||||||
throw new Error(`OneDrive error: ${err.error?.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update metadata with new file properties
|
|
||||||
metadata.type = newFile.name.split('.').pop()?.toUpperCase() || 'UNKNOWN';
|
metadata.type = newFile.name.split('.').pop()?.toUpperCase() || 'UNKNOWN';
|
||||||
metadata.mimeType = newFile.type;
|
metadata.mimeType = newFile.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Database Update
|
|
||||||
await prisma.fileNode.update({
|
await prisma.fileNode.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -151,7 +172,6 @@ export async function updateFileFullAction(formData: FormData) {
|
||||||
description,
|
description,
|
||||||
parentId,
|
parentId,
|
||||||
metadata,
|
metadata,
|
||||||
// If file changed, update size; otherwise keep existing
|
|
||||||
size: newFile ? BigInt(newFile.size) : undefined,
|
size: newFile ? BigInt(newFile.size) : undefined,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
}
|
}
|
||||||
|
|
@ -160,7 +180,7 @@ export async function updateFileFullAction(formData: FormData) {
|
||||||
revalidatePath("/dashboard");
|
revalidatePath("/dashboard");
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("Full Update Action Failure:", error);
|
console.error("Full Update Failure:", error);
|
||||||
throw new Error(error.message || "Failed to update record.");
|
throw new Error(error.message || "Failed to update record.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,46 +1,29 @@
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { auth } from "@/auth";
|
import { auth } from "@/auth";
|
||||||
import { getFreshAccessToken } from "@/lib/auth-utils";
|
import { getFreshAccessToken } from "@/lib/auth-utils";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. CREATE FOLDER: Virtual Only
|
||||||
|
* Logic: User-created organizational folders exist ONLY in the database.
|
||||||
|
* No call to OneDrive is made here.
|
||||||
|
*/
|
||||||
export async function createFolderAction(name: string, parentId?: string | null) {
|
export async function createFolderAction(name: string, parentId?: string | null) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
if (!session?.user?.id) throw new Error("Unauthorized");
|
if (!session?.user?.id) throw new Error("Unauthorized");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const accessToken = await getFreshAccessToken(session.user.id);
|
|
||||||
const rootFolder = "WebCalibre";
|
|
||||||
const internalId = crypto.randomUUID();
|
const internalId = crypto.randomUUID();
|
||||||
|
|
||||||
const onedriveRes = await fetch(`https://graph.microsoft.com/v1.0/me/drive/root:/${rootFolder}:/children`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: internalId,
|
|
||||||
folder: {},
|
|
||||||
"@microsoft.graph.conflictBehavior": "rename"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!onedriveRes.ok) {
|
|
||||||
const errorData = await onedriveRes.json();
|
|
||||||
throw new Error(errorData.error?.message || "OneDrive folder creation failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// CAPTURE Microsoft's ID
|
|
||||||
const onedriveData = await onedriveRes.json();
|
|
||||||
|
|
||||||
const newNode = await prisma.fileNode.create({
|
const newNode = await prisma.fileNode.create({
|
||||||
data: {
|
data: {
|
||||||
id: internalId,
|
id: internalId,
|
||||||
oneDriveId: onedriveData.id, // SAVED HERE
|
oneDriveId: null, // Virtual folders do not have a cloud ID
|
||||||
name: name,
|
name: name,
|
||||||
isFolder: true,
|
isFolder: true,
|
||||||
path: `/${rootFolder}/${internalId}`,
|
path: `virtual:/${name}`,
|
||||||
ownerId: session.user.id,
|
ownerId: session.user.id,
|
||||||
parentId: parentId || null,
|
parentId: parentId || null,
|
||||||
metadata: { type: "FOLDER" }
|
metadata: { type: "FOLDER" }
|
||||||
|
|
@ -49,12 +32,19 @@ export async function createFolderAction(name: string, parentId?: string | null)
|
||||||
|
|
||||||
revalidatePath("/upload");
|
revalidatePath("/upload");
|
||||||
revalidatePath("/dashboard");
|
revalidatePath("/dashboard");
|
||||||
|
|
||||||
return { success: true, node: newNode };
|
return { success: true, node: newNode };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw new Error(error.message || "Failed to create folder");
|
console.error("Folder creation error:", error);
|
||||||
|
throw new Error(error.message || "Failed to create virtual folder");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2. UPLOAD FILE: Physical Container
|
||||||
|
* Logic: Creates a physical folder (UUID) on OneDrive to hold the file.
|
||||||
|
* This ensures every file has a unique storage space in the cloud.
|
||||||
|
*/
|
||||||
export async function uploadFileAction(formData: FormData) {
|
export async function uploadFileAction(formData: FormData) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
if (!session?.user?.id) throw new Error("Unauthorized");
|
if (!session?.user?.id) throw new Error("Unauthorized");
|
||||||
|
|
@ -63,6 +53,7 @@ export async function uploadFileAction(formData: FormData) {
|
||||||
const description = formData.get("description") as string || "";
|
const description = formData.get("description") as string || "";
|
||||||
const parentIdRaw = formData.get("parentId") as string | null;
|
const parentIdRaw = formData.get("parentId") as string | null;
|
||||||
const parentId = (parentIdRaw === "" || parentIdRaw === "root") ? null : parentIdRaw;
|
const parentId = (parentIdRaw === "" || parentIdRaw === "root") ? null : parentIdRaw;
|
||||||
|
|
||||||
const customMetadataRaw = formData.get("customMetadata") as string;
|
const customMetadataRaw = formData.get("customMetadata") as string;
|
||||||
const customMetadata = customMetadataRaw ? JSON.parse(customMetadataRaw) : {};
|
const customMetadata = customMetadataRaw ? JSON.parse(customMetadataRaw) : {};
|
||||||
|
|
||||||
|
|
@ -70,17 +61,29 @@ export async function uploadFileAction(formData: FormData) {
|
||||||
|
|
||||||
const accessToken = await getFreshAccessToken(session.user.id);
|
const accessToken = await getFreshAccessToken(session.user.id);
|
||||||
const rootFolder = "WebCalibre";
|
const rootFolder = "WebCalibre";
|
||||||
const internalId = crypto.randomUUID();
|
const internalId = crypto.randomUUID(); // This UUID will be the OneDrive folder name
|
||||||
|
|
||||||
// 1. Create Storage Container
|
// 1. Create the Physical Storage Folder on OneDrive
|
||||||
const createSubFolderRes = await fetch(`https://graph.microsoft.com/v1.0/me/drive/root:/${rootFolder}:/children`, {
|
const createSubFolderRes = await fetch(`https://graph.microsoft.com/v1.0/me/drive/root:/${rootFolder}:/children`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
|
headers: {
|
||||||
body: JSON.stringify({ name: internalId, folder: {} })
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: internalId,
|
||||||
|
folder: {},
|
||||||
|
"@microsoft.graph.conflictBehavior": "fail"
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!createSubFolderRes.ok) {
|
||||||
|
const errorData = await createSubFolderRes.json();
|
||||||
|
throw new Error(errorData.error?.message || "Storage directory creation failed");
|
||||||
|
}
|
||||||
const subFolderData = await createSubFolderRes.json();
|
const subFolderData = await createSubFolderRes.json();
|
||||||
|
|
||||||
// 2. Upload File
|
// 2. Create Upload Session inside the new Physical Folder
|
||||||
const sessionUrl = `https://graph.microsoft.com/v1.0/me/drive/items/${subFolderData.id}:/${encodeURIComponent(file.name)}:/createUploadSession`;
|
const sessionUrl = `https://graph.microsoft.com/v1.0/me/drive/items/${subFolderData.id}:/${encodeURIComponent(file.name)}:/createUploadSession`;
|
||||||
const sessionRes = await fetch(sessionUrl, {
|
const sessionRes = await fetch(sessionUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -91,6 +94,7 @@ export async function uploadFileAction(formData: FormData) {
|
||||||
const { uploadUrl } = await sessionRes.json();
|
const { uploadUrl } = await sessionRes.json();
|
||||||
const buffer = Buffer.from(await file.arrayBuffer());
|
const buffer = Buffer.from(await file.arrayBuffer());
|
||||||
|
|
||||||
|
// 3. PUT the file binary
|
||||||
const uploadRes = await fetch(uploadUrl, {
|
const uploadRes = await fetch(uploadUrl, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -100,16 +104,16 @@ export async function uploadFileAction(formData: FormData) {
|
||||||
body: buffer
|
body: buffer
|
||||||
});
|
});
|
||||||
|
|
||||||
// 3. GET THE FINAL FILE ID FROM MICROSOFT
|
|
||||||
const uploadedFileData = await uploadRes.json();
|
const uploadedFileData = await uploadRes.json();
|
||||||
const oneDriveId = uploadedFileData.id;
|
const oneDriveId = uploadedFileData.id;
|
||||||
|
|
||||||
const extension = file.name.split('.').pop()?.toUpperCase() || "UNKNOWN";
|
const extension = file.name.split('.').pop()?.toUpperCase() || "UNKNOWN";
|
||||||
|
|
||||||
|
// 4. Create record in Database
|
||||||
|
// Link it to the VIRTUAL folder via parentId
|
||||||
await prisma.fileNode.create({
|
await prisma.fileNode.create({
|
||||||
data: {
|
data: {
|
||||||
id: internalId,
|
id: internalId,
|
||||||
oneDriveId: oneDriveId, // SAVED HERE
|
oneDriveId: oneDriveId,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
description: description,
|
description: description,
|
||||||
size: BigInt(file.size),
|
size: BigInt(file.size),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue