TASK22102: slice6 blob crud and retrieval contract cleanup

This commit is contained in:
2026-03-17 13:23:01 +00:00
parent 3048df3cd9
commit 98184b65df
8 changed files with 145 additions and 52 deletions
+36
View File
@@ -1472,3 +1472,39 @@ Validation:
Follow-ups:
- Continue next larger slice on remaining file handlers with empty 400 responses to complete contract-consistency rollout.
---
### CL-040: TASK22102 API contract consistency — Slice 6 (CRUD/blob retrieval group)
date: 2026-03-17
author: Cline
scope: `pages/api/file/{deleteblob,deleteblobcase,deleteblobrep,deleteawaitingsubmissionfromblob,getprogressobjblob,getawaitingsubmissionfromblob,getrepsblob}.js`
type: change
rationale: Deliver the first of two remaining larger slices by standardizing response contracts across blob CRUD and retrieval handlers still using direct/empty response patterns.
impact: Improves consistency and safety of error responses in selected blob handlers while preserving existing success payload behavior and cache-control semantics.
status: completed
Summary:
- Migrated 7 blob CRUD/retrieval handlers to `respondSuccess`/`respondError`:
- `deleteblob`
- `deleteblobcase`
- `deleteblobrep`
- `deleteawaitingsubmissionfromblob`
- `getprogressobjblob`
- `getawaitingsubmissionfromblob`
- `getrepsblob`
- Replaced empty `400` and direct status responses with structured error envelopes including explicit codes/messages.
- Removed redundant post-hash equality branches where equivalent logic was already guaranteed after early guard checks.
- Preserved response behavior:
- existing success data payload shapes retained (`{ data: ... }` or direct data payloads)
- `getrepsblob` retains `Cache-Control: no-store` header.
Validation:
- `npx next lint --file pages/api/file/deleteblob.js --file pages/api/file/deleteblobcase.js --file pages/api/file/deleteblobrep.js --file pages/api/file/deleteawaitingsubmissionfromblob.js --file pages/api/file/getprogressobjblob.js --file pages/api/file/getawaitingsubmissionfromblob.js --file pages/api/file/getrepsblob.js` -> pass (no warnings/errors)
Follow-ups:
- Execute final Slice 7 for remaining complex handlers (`upload`, `getbloblist`, `downloadblob`, `generateappealpdf`) to complete the 2-slice finish plan.
@@ -1,5 +1,6 @@
import { hashAPIPath } from "../../../actions/core/hash";
import { deleteBlob } from "../../../actions/azurestorage";
import { respondError, respondSuccess } from "../middleware/apiResponse";
import nextConnect from "next-connect";
import middleware from "../middleware/middleware";
@@ -23,7 +24,11 @@ ApiProxy.get(async (req, res) => {
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "MISSING_REQUIRED_QUERY",
message: "container, casefolderID, blobname and hash are required"
});
}
var checkquerypath =
@@ -35,12 +40,16 @@ ApiProxy.get(async (req, res) => {
blobName;
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "INVALID_HASH",
message: "Invalid hash"
});
}
await deleteBlob(containerName, casefolderID + "/files/" + blobName).then(
(data) => {
return res.status(200).json({ data: data });
return respondSuccess(res, { data: data });
}
);
});
+12 -3
View File
@@ -1,5 +1,6 @@
import { hashAPIPath } from "../../../actions/core/hash";
import { deleteBlob } from "../../../actions/azurestorage";
import { respondError, respondSuccess } from "../middleware/apiResponse";
import nextConnect from "next-connect";
import middleware from "../middleware/middleware";
@@ -23,7 +24,11 @@ ApiProxy.get(async (req, res) => {
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "MISSING_REQUIRED_QUERY",
message: "container, casefolderID, blobname and hash are required"
});
}
var checkquerypath =
@@ -35,12 +40,16 @@ ApiProxy.get(async (req, res) => {
encodeURIComponent(blobName);
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "INVALID_HASH",
message: "Invalid hash"
});
}
await deleteBlob(containerName, casefolderID + "/files/" + blobName).then(
(data) => {
return res.status(200).json({ data: data });
return respondSuccess(res, { data: data });
}
);
});
+14 -7
View File
@@ -1,5 +1,6 @@
import { hashAPIPath } from "../../../actions/core/hash";
import { deleteBlobCase } from "../../../actions/azurestorage";
import { respondError, respondSuccess } from "../middleware/apiResponse";
import nextConnect from "next-connect";
import middleware from "../middleware/middleware";
@@ -20,7 +21,11 @@ ApiProxy.get(async (req, res) => {
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "MISSING_REQUIRED_QUERY",
message: "container, casefolderID and hash are required"
});
}
var checkquerypath =
@@ -30,14 +35,16 @@ ApiProxy.get(async (req, res) => {
casefolderID;
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
}
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
await deleteBlobCase(containerName, casefolderID).then((data) => {
return res.status(200).json({ data: data });
return respondError(res, {
status: 400,
code: "INVALID_HASH",
message: "Invalid hash"
});
}
await deleteBlobCase(containerName, casefolderID).then((data) => {
return respondSuccess(res, { data: data });
});
});
export const config = {
+14 -7
View File
@@ -1,5 +1,6 @@
import { hashAPIPath } from "../../../actions/core/hash";
import { deleteBlobRep } from "../../../actions/azurestorage";
import { respondError, respondSuccess } from "../middleware/apiResponse";
import nextConnect from "next-connect";
import middleware from "../middleware/middleware";
@@ -23,7 +24,11 @@ ApiProxy.get(async (req, res) => {
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "MISSING_REQUIRED_QUERY",
message: "container, casefolderID, repfile and hash are required"
});
}
var checkquerypath =
@@ -35,16 +40,18 @@ ApiProxy.get(async (req, res) => {
repfile;
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "INVALID_HASH",
message: "Invalid hash"
});
}
casefolderID = casefolderID + "/" + repfile;
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
await deleteBlobRep(containerName, casefolderID).then((data) => {
return res.status(200).json({ data: data });
});
}
await deleteBlobRep(containerName, casefolderID).then((data) => {
return respondSuccess(res, { data: data });
});
});
export const config = {
+18 -11
View File
@@ -6,6 +6,7 @@ import _ from "lodash";
import nextConnect from "next-connect";
import middleware from "../middleware/middleware";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const ApiProxy = nextConnect();
ApiProxy.use(middleware);
@@ -20,25 +21,31 @@ ApiProxy.get(async (req, res) => {
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "MISSING_REQUIRED_QUERY",
message: "container and hash are required"
});
}
var checkquerypath =
"/api/file/getawaitingsubmissionfromblob?container=" + containerName;
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "INVALID_HASH",
message: "Invalid hash"
});
}
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
const blobObj = await getAllProgressBlobs(containerName)
.then((data) => {
return downloadAllProgressFiles(containerName, data);
})
.then((data) => {
return res.status(200).json(data);
});
}
const blobObj = await getAllProgressBlobs(containerName)
.then((data) => {
return downloadAllProgressFiles(containerName, data);
})
.then((data) => {
return respondSuccess(res, data);
});
});
export const config = {
+12 -3
View File
@@ -6,6 +6,7 @@ import _ from "lodash";
import nextConnect from "next-connect";
import middleware from "../middleware/middleware";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const ApiProxy = nextConnect();
ApiProxy.use(middleware);
@@ -24,7 +25,11 @@ ApiProxy.get(async (req, res) => {
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "MISSING_REQUIRED_QUERY",
message: "container, casefolderID and hash are required"
});
}
var checkquerypath =
@@ -34,7 +39,11 @@ ApiProxy.get(async (req, res) => {
casefolderID;
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "INVALID_HASH",
message: "Invalid hash"
});
}
await getProgressBlobs(containerName, casefolderID)
@@ -42,7 +51,7 @@ ApiProxy.get(async (req, res) => {
return downloadProgressFile(containerName, data.path, casefolderID);
})
.then((data) => {
return res.status(200).json(data);
return respondSuccess(res, data);
});
});
+27 -18
View File
@@ -38,6 +38,7 @@ import nextConnect from "next-connect";
import middleware from "../middleware/middleware";
import { hashAPIPath } from "../../../actions/core/hash";
import { consoleLogger } from "../../../actions/core/logger";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const ApiProxy = nextConnect();
ApiProxy.use(middleware);
@@ -54,32 +55,40 @@ ApiProxy.get(async (req, res) => {
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "MISSING_REQUIRED_QUERY",
message: "container and hash are required"
});
}
var checkquerypath = "/api/file/getrepsblob?container=" + containerName;
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "INVALID_HASH",
message: "Invalid hash"
});
}
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
const blobObj = await getRepsBlobs(containerName)
.then(async (data) => {
return data;
})
.then(async (data) => {
let result = await downloadAllRepsFiles(containerName, data);
return res
.setHeader("Cache-Control", "no-store")
.status(200)
.json(result);
})
.catch((error) => {
consoleLogger(error);
const blobObj = await getRepsBlobs(containerName)
.then(async (data) => {
return data;
})
.then(async (data) => {
let result = await downloadAllRepsFiles(containerName, data);
res.setHeader("Cache-Control", "no-store");
return respondSuccess(res, result);
})
.catch((error) => {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "GET_REPS_BLOB_FAILED",
message: "Failed to retrieve representation blobs"
});
}
});
});
export default ApiProxy;