src/api/v2/projects/projectfiles/projectfiles.controller.tsmodifiedimport crypto from "node:crypto";
import fs from "node:fs/promises";
import { Request, Response } from "express";
import { v4 } from "uuid";
import * as blobService from "../../../../services/azure.blob.service";
import * as service from "../../../../services/projectfiles.service";
import { LoggerFactory } from "../../../../util/logger";
import { createApiErrorResponse } from "../../../../util/errorFormatter";
import { getLoggedInUsername } from "../../../../util/username";
import {
generateTokenisedBlobDownloadUrl,
getFullyQualifiedBlobStoragePath,
} from "../../../../util/azure.util";
const logger = LoggerFactory("ProjectFilesController");
export const getFiles = async (req: Request, res: Response) => {
const { projectId } = req.params;
try {
const rows = await service.getProjectFiles(projectId);
return res.status(200).json(
rows.map((row) => ({
fileReferenceId: row.FileReferenceId,
fileName: row.FileName,
fileExtension: row.FileExtension,
xyzDisplayName: row.XyzDisplayName,
fileSizeBytes: Number(row.FileSizeBytes),
fileHash: row.FileHash,
createdBy: row.CreatedBy,
insertedOn: row.InsertedOn,
lastModifiedBy: row.LastModifiedBy,
lastModifiedOn: row.LastModifiedOn,
})),
);
} catch (err) {
logger.error("Error fetching project files.", err);
return createApiErrorResponse(err, res);
}
};
export const getBiggestFile = async (req: Request, res: Response) => {
const { projectId } = req.params;
try {
const row = await service.getBiggestProjectFile(projectId);
if (!row) {
return res.status(200).json(null);
}
return res.status(200).json({
fileReferenceId: row.FileReferenceId,
fileName: row.FileName,
fileExtension: row.FileExtension,
xyzDisplayName: row.XyzDisplayName,
fileSizeBytes: Number(row.FileSizeBytes),
fileHash: row.FileHash,
createdBy: row.CreatedBy,
insertedOn: row.InsertedOn,
lastModifiedBy: row.LastModifiedBy,
lastModifiedOn: row.LastModifiedOn,
});
} catch (err) {
logger.error("Error fetching biggest project file.", err);
return createApiErrorResponse(err, res);
}
};
export const getFile = async (req: Request, res: Response) => {
const { projectId, fileReferenceId } = req.params;
try {
const row = await service.getProjectFile(projectId, fileReferenceId);
const downloadUrl = await generateTokenisedBlobDownloadUrl(
row.CloudStoragePath as string,
);
return res.status(200).json({
fileReferenceId: row.FileReferenceId,
fileName: row.FileName,
fileExtension: row.FileExtension,
xyzDisplayName: row.XyzDisplayName,
fileSizeBytes: Number(row.FileSizeBytes),
fileHash: row.FileHash,
createdBy: row.CreatedBy,
insertedOn: row.InsertedOn,
lastModifiedBy: row.LastModifiedBy,
lastModifiedOn: row.LastModifiedOn,
downloadUrl,
});
} catch (err) {
logger.error("Error fetching project file.", err);
return createApiErrorResponse(err, res);
}
};
export const uploadFile = async (req: Request, res: Response) => {
if (!req.file) {
return res
.status(400)
.json({
code: "InvalidRequest",
message: "No file provided for upload.",
});
}
const { projectId } = req.params;
const chunkIndex = Number.parseInt(req.body.chunkIndex ?? "0");
const totalChunks = Number.parseInt(req.body.totalChunks ?? "1");
const fileReferenceId = chunkIndex === 0 ? v4() : req.body.fileReferenceId;
const { fileName, xyzDisplayName } = req.body;
const fileExtension = fileName.split(".").pop()?.toLowerCase() ?? "";
const cloudStoragePath = `RawData/files/ProjectId=${projectId}/${fileReferenceId}`;
logger.info(
`Uploading project file chunk ${chunkIndex + 1} of ${totalChunks} for fileReferenceId: ${fileReferenceId}, projectId: ${projectId}`,
);
try {
const chunkBuffer = await fs.readFile(req.file.path);
const chunkHash = crypto.createHash("sha256").update(chunkBuffer).digest("hex");
await blobService.uploadChunk(cloudStoragePath, req.file, chunkIndex, totalChunks);
await service.accumulateFileHash(fileReferenceId, chunkHash, chunkIndex === 0);
if (chunkIndex === 0) {
await service.createProjectFile(
projectId,
fileReferenceId,
fileName,
fileExtension,
getFullyQualifiedBlobStoragePath(cloudStoragePath),
getLoggedInUsername(),
xyzDisplayName
);
}
if (chunkIndex + 1 === totalChunks) {
const fileSizeBytes = chunkIndex === 0 ? req.file.size : 0;
const fileHash = await service.popFileHash(fileReferenceId);
await service.updateProjectFileSize(
projectId,
fileReferenceId,
fileSizeBytes,
fileHash,
);
return res.status(201).json({
fileReferenceId,
message: "Project file uploaded successfully",
});
}
return res.status(200).json({
fileReferenceId,
message: "Project file chunk uploaded successfully",
});
} catch (err) {
logger.error("Error uploading project file.", err);
return createApiErrorResponse(err, res);
}
};