import { DefaultAzureCredential } from "@azure/identity"; import { BlobServiceClient, ContainerClient } from "@azure/storage-blob"; import { v4 as uuidv4 } from "uuid"; import { createContainerSas, listContainers, listBlobHierarchical, listContainersForUser, } from "../actions/azurestorage"; import Link from "next/link"; import { PrismaClient } from "@prisma/client"; import { bytesToSize, getThumbnailIconByExtension, updateLinks, } from "../components/utils"; import { consoleLogger, getIP, sendRepCompleteMessage, deleteMyRepresentationsFromBlob, } from "../actions"; import CryptoJS from "crypto-js"; function getUpToLastSlash(url) { let lastSlash = url.lastIndexOf("/"); url = url.substring(0, lastSlash); url = url.lastIndexOf("/files") > 0 ? url.substring(0, url.lastIndexOf("/files")) : url; return url; } async function streamToString(readableStream) { return new Promise((resolve, reject) => { const chunks = []; readableStream.on("data", (data) => { chunks.push(data.toString()); }); readableStream.on("end", () => { resolve(chunks.join("")); }); readableStream.on("error", reject); }); } const deleteRepItem = (containerID, caseID, repfile) => { deleteMyRepresentationsFromBlob(containerID, caseID, repfile).then( (data) => { console.log("======== rep deleted ========="); return data; } ); }; export default function StoragePage({ data }) { console.log(data); return (

Storage Account Contents

{data.map((user) => (
{user.containers.map((c) => (

{c.container} - {user.email}

{c.folders.map((folder) => (

{folder.folderPath || "Root"}

{/* {folder.hasRepJson && (

rep.json file found:{" "} {folder.repJsonBlob.name}

)} */} {folder.hasRepJson && (
{" "} {folder.repComplete && ( )} {folder.repComplete && ( )}
)}
    {folder.blobs.map((blob) => (
  • {blob.name} ( {bytesToSize( blob.contentLength || blob.size )} )
  • ))}
))}
))}
))}
); } export async function getServerSideProps({ req }) { // Load allowed IPs from env and trim spaces const ALLOWED_IPS = process.env.ALLOWED_IPS ? process.env.ALLOWED_IPS.split(",").map((ip) => ip.trim()) : []; // Always allow localhost addresses const LOCALHOST_IPS = ["127.0.0.1", "::1"]; // Get IP address from headers or socket const forwarded = req.headers["x-forwarded-for"]; const ip = typeof forwarded === "string" ? forwarded.split(",")[0] : req.socket.remoteAddress; console.log("Visitor IP:", ip); // Check whitelist + localhost if (![...ALLOWED_IPS, ...LOCALHOST_IPS].includes(ip)) { return { redirect: { destination: "/403", // custom "Access Denied" page permanent: false, }, }; } const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME; const creds = new DefaultAzureCredential(); const globalForPrisma = global; const prisma = globalForPrisma.prisma || new PrismaClient(); const blobServiceClient = new BlobServiceClient( `https://${accountName}.blob.core.windows.net`, creds ); const users = await prisma.user.findMany(); const hashAPIPath = (queryPath, WORDKEY) => { var hashlink = CryptoJS.HmacSHA256( queryPath, CryptoJS.enc.Hex.parse(WORDKEY) ); hashlink = hashlink.toString(CryptoJS.enc.Hex); return hashlink; }; let data = await Promise.all( users.map(async (user) => { const containers = await listContainersForUser( blobServiceClient, `${user.id}`, user.email ); // Add hashedName for each blob // const containersWithHashes = containers.map((container) => { // console.log("eee "); // const groupedByFolder = container.blobs.reduce((acc, blob) => { // // Get folder path - everything before last slash, or "" if no slash // const folderPath = getUpToLastSlash(blob.name.trim()) || ""; // const hashedfilepath = hashAPIPath( // "/api/file/downloadblob?container=" + // container.container + // "&casefolderID=" + // folderPath + // "&blobname=" + // blob.name // .substring(blob.name.lastIndexOf("/") + 1) // .trim(), // process.env.HASHKEY // ); // const blobWithHash = { ...blob, hashedfilepath }; // // Find or create folder group // let folderGroup = acc.find( // (f) => f.folderPath === folderPath // ); // if (!folderGroup) { // folderGroup = { folderPath, blobs: [] }; // acc.push(folderGroup); // } // folderGroup.blobs.push(blobWithHash); // return acc; // }, []); // console.log(groupedByFolder); // return { // ...container, // blobs: container.blobs.map((blob) => ({ // ...blob, // hashedfilepath: hashAPIPath( // "/api/file/downloadblob?container=" + // container.container + // "&casefolderID=" + // getUpToLastSlash(blob.name.trim()) + // "&blobname=" + // blob.name // .substring(blob.name.lastIndexOf("/") + 1) // .trim(), // process.env.HASHKEY // ), // })), // }; // }); // const containersWithHashes = containers.map((container) => { // const groupedByFolder = container.blobs.reduce((acc, blob) => { // const folderPath = getUpToLastSlash(blob.name.trim()) || ""; // const hashedfilepath = hashAPIPath( // "/api/file/downloadblob?container=" + // container.container + // "&casefolderID=" + // folderPath + // "&blobname=" + // blob.name // .substring(blob.name.lastIndexOf("/") + 1) // .trim(), // process.env.HASHKEY // ); // const blobWithHash = { ...blob, hashedfilepath }; // let folderGroup = acc.find( // (f) => f.folderPath === folderPath // ); // if (!folderGroup) { // folderGroup = { // folderPath, // blobs: [], // hasRepJson: false, // flag // repJsonBlob: null, // save the rep.json blob if found // }; // acc.push(folderGroup); // } // folderGroup.blobs.push(blobWithHash); // // Check if this blob ends with "rep.json" // if (blob.name.toLowerCase().endsWith("rep.json")) { // folderGroup.hasRepJson = true; // folderGroup.repJsonBlob = blobWithHash; // // You can also do some processing here if you want // } // return acc; // }, []); // return { // ...container, // folders: groupedByFolder, // // Optionally remove flat blobs if you don't want them // // blobs: undefined, // }; // }); const containersWithHashes = await Promise.all( containers.map(async (container) => { const groupedByFolder = await container.blobs.reduce( async (accP, blob) => { // Because we'll do async read, reduce needs to be async, so accP is a Promise const acc = await accP; const folderPath = getUpToLastSlash(blob.name.trim()) || ""; const hashedfilepath = hashAPIPath( "/api/file/downloadblob?container=" + container.container + "&casefolderID=" + folderPath + "&blobname=" + blob.name .substring( blob.name.lastIndexOf("/") + 1 ) .trim(), process.env.HASHKEY ); const blobWithHash = { ...blob, hashedfilepath }; let folderGroup = acc.find( (f) => f.folderPath === folderPath ); if (!folderGroup) { folderGroup = { folderPath, blobs: [], hasRepJson: false, repJsonBlob: null, repComplete: false, // new flag }; acc.push(folderGroup); } folderGroup.blobs.push(blobWithHash); // If this blob ends with rep.json, fetch and parse JSON if (blob.name.toLowerCase().endsWith("rep.json")) { folderGroup.hasRepJson = true; folderGroup.repJsonBlob = blobWithHash; // Read blob content from Azure Storage const containerClient = blobServiceClient.getContainerClient( container.container ); const blockBlobClient = containerClient.getBlockBlobClient( blob.name ); try { const downloadResponse = await blockBlobClient.download(0); const downloaded = await streamToString( downloadResponse.readableStreamBody ); const json = JSON.parse(downloaded); folderGroup.repComplete = Boolean( json.repComplete ); } catch (err) { console.error( "Error reading or parsing rep.json", err ); folderGroup.repComplete = false; } } return acc; }, Promise.resolve([]) ); return { ...container, folders: groupedByFolder, }; }) ); return containersWithHashes.length > 0 ? { id: user.id, email: user.email, containers: containersWithHashes, } : null; }) ); return { props: { data: data.filter(Boolean) } }; }