diff --git a/actions/azurestorage.js b/actions/azurestorage.js
index aa5617bc..a3c02dc0 100644
--- a/actions/azurestorage.js
+++ b/actions/azurestorage.js
@@ -1696,7 +1696,12 @@ export const listBlobHierarchical = async (
export async function listBlobHierarchicalForUser(containerClient) {
const blobNames = [];
for await (const blob of containerClient.listBlobsFlat()) {
- blobNames.push(blob.name);
+ // blobNames.push(blob.name);
+
+ blobNames.push({
+ name: blob.name,
+ size: blob.properties.contentLength || 0, // in bytes
+ });
}
return blobNames;
}
diff --git a/pages/api/file/downloadblob.js b/pages/api/file/downloadblob.js
index 6067189a..5ad78d18 100644
--- a/pages/api/file/downloadblob.js
+++ b/pages/api/file/downloadblob.js
@@ -24,10 +24,19 @@ ApiProxy.get(async (req, res) => {
"&blobname=" +
blobName.trim();
+ // console.log(checkquerypath);
+
+ // console.log(hashAPIPath(checkquerypath));
+
+ // console.log(req.query);
+
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
+ const bloblocation =
+ casefolderID + (blobName.indexOf(".json") > 0 ? "/" : "/files/");
+
const downloaded = await downloadFile(
containerName,
- casefolderID + "/files/" + decodeURI(blobName)
+ bloblocation + decodeURI(blobName)
);
res.setHeader(
diff --git a/pages/api/file/editRepJson.js b/pages/api/file/editRepJson.js
new file mode 100644
index 00000000..65080769
--- /dev/null
+++ b/pages/api/file/editRepJson.js
@@ -0,0 +1,83 @@
+import { DefaultAzureCredential } from "@azure/identity";
+import { BlobServiceClient } from "@azure/storage-blob";
+
+export default async function handler(req, res) {
+ if (req.method !== "POST") {
+ res.status(405).json({ error: "Method not allowed" });
+ return;
+ }
+
+ const { container, blobName } = req.body;
+
+ if (!container || !blobName) {
+ res.status(400).json({ error: "Missing container or blobName" });
+ return;
+ }
+
+ try {
+ const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
+ const creds = new DefaultAzureCredential();
+ const blobServiceClient = new BlobServiceClient(
+ `https://${accountName}.blob.core.windows.net`,
+ creds
+ );
+
+ const containerClient = blobServiceClient.getContainerClient(container);
+ const blockBlobClient = containerClient.getBlockBlobClient(blobName);
+
+ // Download blob content
+ const downloadResponse = await blockBlobClient.download(0);
+ const downloaded = await streamToString(
+ downloadResponse.readableStreamBody
+ );
+
+ // Parse JSON
+ const json = JSON.parse(downloaded);
+
+ // Remove repComplete element if it exists
+ if ("repComplete" in json) {
+ delete json.repComplete;
+ }
+
+ const tags = {
+ containerid: json.containerID,
+ caseID: json.ticketnumber,
+ blobType: "Representation",
+ };
+
+ // Convert back to string
+ const updatedContent = JSON.stringify(json, null, 2);
+
+ // Upload updated content (overwrite)
+ await blockBlobClient.upload(
+ updatedContent,
+ Buffer.byteLength(updatedContent),
+ {
+ blobHTTPHeaders: { blobContentType: "application/json" },
+ }
+ );
+
+ console.log("the tags:", tags);
+ const withTags = await blockBlobClient.setTags(tags);
+ const withMeta = await blockBlobClient.setMetadata(tags);
+
+ res.status(200).json({ message: "repComplete removed successfully" });
+ } catch (error) {
+ console.error("Error editing rep.json:", error);
+ res.status(500).json({ error: "Failed to edit rep.json" });
+ }
+}
+
+// Helper function to read stream to string
+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);
+ });
+}
diff --git a/pages/storageAdmin.js b/pages/storageAdmin.js
index 77cd088b..09eda695 100644
--- a/pages/storageAdmin.js
+++ b/pages/storageAdmin.js
@@ -7,10 +7,238 @@ import {
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";
-import { consoleLogger, getIP } from "../actions";
+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
@@ -54,41 +282,227 @@ export async function getServerSideProps({ req }) {
const users = await prisma.user.findMany();
- const data = await Promise.all(
+ 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
);
- return containers.length > 0
- ? { id: user.id, email: user.email, containers }
+
+ // 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) } };
}
-export default function StoragePage({ data }) {
- return (
-
-
Storage Account Contents
- {data.map((user) => (
-
- {user.containers.map((c) => (
-
-
- {c.container} - {user.email}
-
-
- {c.blobs.map((blob) => (
- - {blob}
- ))}
-
-
- ))}
-
- ))}
-
- );
-}