TASK22102: slice6 blob crud and retrieval contract cleanup
This commit is contained in:
@@ -1472,3 +1472,39 @@ Validation:
|
|||||||
Follow-ups:
|
Follow-ups:
|
||||||
|
|
||||||
- Continue next larger slice on remaining file handlers with empty 400 responses to complete contract-consistency rollout.
|
- 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 { hashAPIPath } from "../../../actions/core/hash";
|
||||||
import { deleteBlob } from "../../../actions/azurestorage";
|
import { deleteBlob } from "../../../actions/azurestorage";
|
||||||
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
import nextConnect from "next-connect";
|
import nextConnect from "next-connect";
|
||||||
import middleware from "../middleware/middleware";
|
import middleware from "../middleware/middleware";
|
||||||
@@ -23,7 +24,11 @@ ApiProxy.get(async (req, res) => {
|
|||||||
typeof checkHash === "undefined" ||
|
typeof checkHash === "undefined" ||
|
||||||
checkHash.length === 0
|
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 =
|
var checkquerypath =
|
||||||
@@ -35,12 +40,16 @@ ApiProxy.get(async (req, res) => {
|
|||||||
blobName;
|
blobName;
|
||||||
|
|
||||||
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
|
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(
|
await deleteBlob(containerName, casefolderID + "/files/" + blobName).then(
|
||||||
(data) => {
|
(data) => {
|
||||||
return res.status(200).json({ data: data });
|
return respondSuccess(res, { data: data });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { hashAPIPath } from "../../../actions/core/hash";
|
import { hashAPIPath } from "../../../actions/core/hash";
|
||||||
import { deleteBlob } from "../../../actions/azurestorage";
|
import { deleteBlob } from "../../../actions/azurestorage";
|
||||||
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
import nextConnect from "next-connect";
|
import nextConnect from "next-connect";
|
||||||
import middleware from "../middleware/middleware";
|
import middleware from "../middleware/middleware";
|
||||||
@@ -23,7 +24,11 @@ ApiProxy.get(async (req, res) => {
|
|||||||
typeof checkHash === "undefined" ||
|
typeof checkHash === "undefined" ||
|
||||||
checkHash.length === 0
|
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 =
|
var checkquerypath =
|
||||||
@@ -35,12 +40,16 @@ ApiProxy.get(async (req, res) => {
|
|||||||
encodeURIComponent(blobName);
|
encodeURIComponent(blobName);
|
||||||
|
|
||||||
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
|
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(
|
await deleteBlob(containerName, casefolderID + "/files/" + blobName).then(
|
||||||
(data) => {
|
(data) => {
|
||||||
return res.status(200).json({ data: data });
|
return respondSuccess(res, { data: data });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { hashAPIPath } from "../../../actions/core/hash";
|
import { hashAPIPath } from "../../../actions/core/hash";
|
||||||
import { deleteBlobCase } from "../../../actions/azurestorage";
|
import { deleteBlobCase } from "../../../actions/azurestorage";
|
||||||
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
import nextConnect from "next-connect";
|
import nextConnect from "next-connect";
|
||||||
import middleware from "../middleware/middleware";
|
import middleware from "../middleware/middleware";
|
||||||
@@ -20,7 +21,11 @@ ApiProxy.get(async (req, res) => {
|
|||||||
typeof checkHash === "undefined" ||
|
typeof checkHash === "undefined" ||
|
||||||
checkHash.length === 0
|
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 =
|
var checkquerypath =
|
||||||
@@ -30,14 +35,16 @@ ApiProxy.get(async (req, res) => {
|
|||||||
casefolderID;
|
casefolderID;
|
||||||
|
|
||||||
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
|
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
|
||||||
return res.status(400).json();
|
return respondError(res, {
|
||||||
}
|
status: 400,
|
||||||
|
code: "INVALID_HASH",
|
||||||
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
|
message: "Invalid hash"
|
||||||
await deleteBlobCase(containerName, casefolderID).then((data) => {
|
|
||||||
return res.status(200).json({ data: data });
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await deleteBlobCase(containerName, casefolderID).then((data) => {
|
||||||
|
return respondSuccess(res, { data: data });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { hashAPIPath } from "../../../actions/core/hash";
|
import { hashAPIPath } from "../../../actions/core/hash";
|
||||||
import { deleteBlobRep } from "../../../actions/azurestorage";
|
import { deleteBlobRep } from "../../../actions/azurestorage";
|
||||||
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
import nextConnect from "next-connect";
|
import nextConnect from "next-connect";
|
||||||
import middleware from "../middleware/middleware";
|
import middleware from "../middleware/middleware";
|
||||||
@@ -23,7 +24,11 @@ ApiProxy.get(async (req, res) => {
|
|||||||
typeof checkHash === "undefined" ||
|
typeof checkHash === "undefined" ||
|
||||||
checkHash.length === 0
|
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 =
|
var checkquerypath =
|
||||||
@@ -35,16 +40,18 @@ ApiProxy.get(async (req, res) => {
|
|||||||
repfile;
|
repfile;
|
||||||
|
|
||||||
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
|
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
|
||||||
return res.status(400).json();
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "INVALID_HASH",
|
||||||
|
message: "Invalid hash"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
casefolderID = casefolderID + "/" + repfile;
|
casefolderID = casefolderID + "/" + repfile;
|
||||||
|
|
||||||
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
|
await deleteBlobRep(containerName, casefolderID).then((data) => {
|
||||||
await deleteBlobRep(containerName, casefolderID).then((data) => {
|
return respondSuccess(res, { data: data });
|
||||||
return res.status(200).json({ data: data });
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import _ from "lodash";
|
|||||||
import nextConnect from "next-connect";
|
import nextConnect from "next-connect";
|
||||||
import middleware from "../middleware/middleware";
|
import middleware from "../middleware/middleware";
|
||||||
import { hashAPIPath } from "../../../actions/core/hash";
|
import { hashAPIPath } from "../../../actions/core/hash";
|
||||||
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
const ApiProxy = nextConnect();
|
const ApiProxy = nextConnect();
|
||||||
ApiProxy.use(middleware);
|
ApiProxy.use(middleware);
|
||||||
@@ -20,25 +21,31 @@ ApiProxy.get(async (req, res) => {
|
|||||||
typeof checkHash === "undefined" ||
|
typeof checkHash === "undefined" ||
|
||||||
checkHash.length === 0
|
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 =
|
var checkquerypath =
|
||||||
"/api/file/getawaitingsubmissionfromblob?container=" + containerName;
|
"/api/file/getawaitingsubmissionfromblob?container=" + containerName;
|
||||||
|
|
||||||
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
|
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)
|
||||||
const blobObj = await getAllProgressBlobs(containerName)
|
.then((data) => {
|
||||||
.then((data) => {
|
return downloadAllProgressFiles(containerName, data);
|
||||||
return downloadAllProgressFiles(containerName, data);
|
})
|
||||||
})
|
.then((data) => {
|
||||||
.then((data) => {
|
return respondSuccess(res, data);
|
||||||
return res.status(200).json(data);
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import _ from "lodash";
|
|||||||
import nextConnect from "next-connect";
|
import nextConnect from "next-connect";
|
||||||
import middleware from "../middleware/middleware";
|
import middleware from "../middleware/middleware";
|
||||||
import { hashAPIPath } from "../../../actions/core/hash";
|
import { hashAPIPath } from "../../../actions/core/hash";
|
||||||
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
const ApiProxy = nextConnect();
|
const ApiProxy = nextConnect();
|
||||||
ApiProxy.use(middleware);
|
ApiProxy.use(middleware);
|
||||||
@@ -24,7 +25,11 @@ ApiProxy.get(async (req, res) => {
|
|||||||
typeof checkHash === "undefined" ||
|
typeof checkHash === "undefined" ||
|
||||||
checkHash.length === 0
|
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 =
|
var checkquerypath =
|
||||||
@@ -34,7 +39,11 @@ ApiProxy.get(async (req, res) => {
|
|||||||
casefolderID;
|
casefolderID;
|
||||||
|
|
||||||
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
|
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)
|
await getProgressBlobs(containerName, casefolderID)
|
||||||
@@ -42,7 +51,7 @@ ApiProxy.get(async (req, res) => {
|
|||||||
return downloadProgressFile(containerName, data.path, casefolderID);
|
return downloadProgressFile(containerName, data.path, casefolderID);
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
return res.status(200).json(data);
|
return respondSuccess(res, data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import nextConnect from "next-connect";
|
|||||||
import middleware from "../middleware/middleware";
|
import middleware from "../middleware/middleware";
|
||||||
import { hashAPIPath } from "../../../actions/core/hash";
|
import { hashAPIPath } from "../../../actions/core/hash";
|
||||||
import { consoleLogger } from "../../../actions/core/logger";
|
import { consoleLogger } from "../../../actions/core/logger";
|
||||||
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
const ApiProxy = nextConnect();
|
const ApiProxy = nextConnect();
|
||||||
ApiProxy.use(middleware);
|
ApiProxy.use(middleware);
|
||||||
@@ -54,32 +55,40 @@ ApiProxy.get(async (req, res) => {
|
|||||||
typeof checkHash === "undefined" ||
|
typeof checkHash === "undefined" ||
|
||||||
checkHash.length === 0
|
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;
|
var checkquerypath = "/api/file/getrepsblob?container=" + containerName;
|
||||||
|
|
||||||
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
|
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)
|
||||||
const blobObj = await getRepsBlobs(containerName)
|
.then(async (data) => {
|
||||||
.then(async (data) => {
|
return data;
|
||||||
return data;
|
})
|
||||||
})
|
.then(async (data) => {
|
||||||
.then(async (data) => {
|
let result = await downloadAllRepsFiles(containerName, data);
|
||||||
let result = await downloadAllRepsFiles(containerName, data);
|
res.setHeader("Cache-Control", "no-store");
|
||||||
|
return respondSuccess(res, result);
|
||||||
return res
|
})
|
||||||
.setHeader("Cache-Control", "no-store")
|
.catch((error) => {
|
||||||
.status(200)
|
consoleLogger(error);
|
||||||
.json(result);
|
return respondError(res, {
|
||||||
})
|
status: 400,
|
||||||
.catch((error) => {
|
code: "GET_REPS_BLOB_FAILED",
|
||||||
consoleLogger(error);
|
message: "Failed to retrieve representation blobs"
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ApiProxy;
|
export default ApiProxy;
|
||||||
|
|||||||
Reference in New Issue
Block a user