2026-01-16 05:22:00 +00:00
|
|
|
// src/data-access/file-nodes.ts
|
|
|
|
|
import "server-only";
|
2026-01-21 01:34:02 +00:00
|
|
|
|
|
|
|
|
import { getOneDriveFileBuffer } from "@/services/onedrive";
|
|
|
|
|
import { extractMetadata } from "@/lib/metadata-extractor";
|
2026-01-16 05:22:00 +00:00
|
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
|
|
2026-01-21 01:34:02 +00:00
|
|
|
|
2026-01-16 05:22:00 +00:00
|
|
|
/**
|
|
|
|
|
* FETCH: Retrieve all nodes for the dashboard.
|
|
|
|
|
* Centralizing this here allows us to change sort order or filters
|
|
|
|
|
* in one place for the entire application.
|
|
|
|
|
*/
|
|
|
|
|
export async function getAllFileNodes() {
|
|
|
|
|
return await prisma.fileNode.findMany({
|
|
|
|
|
orderBy: {
|
|
|
|
|
updatedAt: 'desc',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* FETCH: Get a single node by ID.
|
|
|
|
|
* Used by the Download route and Update pages to verify a file exists.
|
|
|
|
|
*/
|
|
|
|
|
export async function getFileNodeById(id: string) {
|
|
|
|
|
return await prisma.fileNode.findUnique({
|
|
|
|
|
where: { id },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* UPDATE: Modify metadata, name, or virtual location.
|
|
|
|
|
* This function accepts the data object to keep the DAL flexible.
|
|
|
|
|
*/
|
|
|
|
|
export async function updateFileNode(id: string, data: any) {
|
|
|
|
|
return await prisma.fileNode.update({
|
|
|
|
|
where: { id },
|
|
|
|
|
data: {
|
|
|
|
|
...data,
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* DELETE: Remove the record from the database.
|
|
|
|
|
* Cloud deletion should be handled by the Service Layer before calling this.
|
|
|
|
|
*/
|
|
|
|
|
export async function deleteFileNode(id: string) {
|
|
|
|
|
return await prisma.fileNode.delete({
|
|
|
|
|
where: { id },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* MASTER CREATE: Handles both standard uploads and virtual folders.
|
|
|
|
|
* If no ID is provided, it generates a fresh UUID.
|
|
|
|
|
*/
|
|
|
|
|
export async function createFileNode(data: {
|
|
|
|
|
id?: string; // Optional: used for virtual folders/UUID storage
|
|
|
|
|
oneDriveId: string | null;
|
|
|
|
|
name: string;
|
2026-02-15 04:10:58 +00:00
|
|
|
hash?: string | null; // ✅ ADDED: For SHA-256 duplicate prevention
|
2026-01-16 05:22:00 +00:00
|
|
|
description?: string;
|
|
|
|
|
isFolder: boolean;
|
|
|
|
|
path: string;
|
|
|
|
|
ownerId: string;
|
|
|
|
|
parentId?: string | null; // Optional: for nested structures
|
|
|
|
|
size?: bigint;
|
|
|
|
|
metadata: any;
|
|
|
|
|
}) {
|
|
|
|
|
return await prisma.fileNode.create({
|
|
|
|
|
data: {
|
|
|
|
|
...data,
|
|
|
|
|
id: data.id ?? crypto.randomUUID(), // Use provided ID or generate new one
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ... other functions (getAllFileNodes, etc)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* UPSERT: Create or Update a file node based on OneDrive ID
|
|
|
|
|
* Moved here because it interacts with the Database.
|
|
|
|
|
*/
|
2026-02-15 04:10:58 +00:00
|
|
|
export async function upsertFileNode(oneDriveId: string, data: {
|
|
|
|
|
name: string;
|
|
|
|
|
size: bigint;
|
|
|
|
|
isFolder: boolean;
|
|
|
|
|
path: string;
|
|
|
|
|
ownerId: string;
|
|
|
|
|
metadata: any;
|
|
|
|
|
hash?: string | null; // ✅ ADDED: Keep hash in sync during upserts
|
|
|
|
|
}) {
|
2026-01-16 05:22:00 +00:00
|
|
|
return await prisma.fileNode.upsert({
|
|
|
|
|
where: { oneDriveId },
|
|
|
|
|
update: {
|
|
|
|
|
name: data.name,
|
|
|
|
|
size: data.size,
|
|
|
|
|
isFolder: data.isFolder,
|
|
|
|
|
path: data.path,
|
2026-02-15 04:10:58 +00:00
|
|
|
hash: data.hash, // ✅ ADDED
|
2026-01-16 05:22:00 +00:00
|
|
|
updatedAt: new Date(),
|
|
|
|
|
},
|
|
|
|
|
create: {
|
|
|
|
|
id: crypto.randomUUID(),
|
|
|
|
|
oneDriveId: oneDriveId,
|
|
|
|
|
name: data.name,
|
|
|
|
|
size: data.size,
|
|
|
|
|
isFolder: data.isFolder,
|
|
|
|
|
path: data.path,
|
|
|
|
|
ownerId: data.ownerId,
|
|
|
|
|
metadata: data.metadata,
|
2026-02-15 04:10:58 +00:00
|
|
|
hash: data.hash, // ✅ ADDED
|
2026-01-16 05:22:00 +00:00
|
|
|
}
|
|
|
|
|
});
|
2026-01-21 01:34:02 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 11:38:41 +00:00
|
|
|
/**
|
|
|
|
|
* FETCH: Get all virtual folders for selection in dropdowns.
|
|
|
|
|
* Used by the BulkUpload page to set destinations.
|
|
|
|
|
*/
|
|
|
|
|
export async function getAllFolders() {
|
|
|
|
|
return await prisma.fileNode.findMany({
|
|
|
|
|
where: {
|
|
|
|
|
isFolder: true
|
|
|
|
|
},
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
name: true
|
|
|
|
|
},
|
|
|
|
|
orderBy: {
|
|
|
|
|
name: 'asc'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-01-21 01:34:02 +00:00
|
|
|
|
2026-05-28 11:38:41 +00:00
|
|
|
/**
|
|
|
|
|
* UPSERT BY HASH: The core of the Disaster Recovery process.
|
|
|
|
|
* If a hash exists, we update the OneDrive ID (Restoring the link).
|
|
|
|
|
* If not, we create a new entry.
|
|
|
|
|
*/
|
|
|
|
|
export async function upsertFileNodeByHash(data: {
|
|
|
|
|
name: string;
|
|
|
|
|
hash: string;
|
|
|
|
|
oneDriveId: string;
|
|
|
|
|
parentId?: string | null;
|
|
|
|
|
size: bigint;
|
|
|
|
|
ownerId: string;
|
|
|
|
|
mimeType?: string;
|
|
|
|
|
}) {
|
|
|
|
|
// First, check if we have a record with this hash
|
|
|
|
|
const existing = await prisma.fileNode.findFirst({
|
|
|
|
|
where: { hash: data.hash }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (existing) {
|
|
|
|
|
// 🛡️ DISASTER RECOVERY MODE
|
|
|
|
|
// The database knows about this file, but the OneDrive link is old.
|
|
|
|
|
// We update the existing record with the NEW cloud ID.
|
|
|
|
|
return await prisma.fileNode.update({
|
|
|
|
|
where: { id: existing.id },
|
|
|
|
|
data: {
|
|
|
|
|
oneDriveId: data.oneDriveId,
|
|
|
|
|
// Optional: Update parent if the user chose a new folder during restore
|
|
|
|
|
parentId: data.parentId || existing.parentId,
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ✨ NEW UPLOAD MODE
|
|
|
|
|
return await prisma.fileNode.create({
|
|
|
|
|
data: {
|
|
|
|
|
id: crypto.randomUUID(),
|
|
|
|
|
oneDriveId: data.oneDriveId,
|
|
|
|
|
name: data.name,
|
|
|
|
|
hash: data.hash,
|
|
|
|
|
size: data.size,
|
|
|
|
|
isFolder: false,
|
|
|
|
|
ownerId: data.ownerId,
|
|
|
|
|
parentId: data.parentId || null,
|
|
|
|
|
path: data.name, // Simplified for now
|
|
|
|
|
metadata: {}, // Placeholder for extractMetadata logic
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-01-21 01:34:02 +00:00
|
|
|
|