TASK22102: slice5 larger file handler contract cleanup

This commit is contained in:
2026-03-17 13:16:48 +00:00
parent e68319f16c
commit 3048df3cd9
5 changed files with 112 additions and 20 deletions
+35
View File
@@ -1437,3 +1437,38 @@ Validation:
Follow-ups:
- Continue next larger slice on remaining non-proxy file handlers still returning raw error payloads.
---
### CL-039: TASK22102 API contract consistency — Slice 5 (larger file-handler batch)
date: 2026-03-17
author: Cline
scope: `pages/api/file/{uploadsinglefile,setupcontainer,createrepcompletemessage_api,editRepJson}.js`
type: change
rationale: Continue with a larger efficiency-focused slice by migrating remaining high-signal file handlers that still returned raw/empty error responses.
impact: Improves response-contract consistency and avoids leaking raw error payloads in selected file handlers while preserving success behavior and endpoint semantics.
status: completed
Summary:
- Migrated additional file handlers to helper-based responses:
- `uploadsinglefile`
- `setupcontainer`
- `createrepcompletemessage_api`
- `editRepJson`
- Replaced direct `res.status(...).json(...)` error branches (including raw `error` payload and empty responses) with structured:
- `respondError(...)`
- `respondSuccess(...)`
- Preserved existing success semantics:
- `uploadsinglefile` still returns uploaded result + `invalidFiles`
- `setupcontainer` still returns `{ data: "success", output }`
- `editRepJson` still returns success message on completion
Validation:
- `npx next lint --file pages/api/file/uploadsinglefile.js --file pages/api/file/setupcontainer.js --file pages/api/file/createrepcompletemessage_api.js --file pages/api/file/editRepJson.js` -> pass (no warnings/errors)
Follow-ups:
- Continue next larger slice on remaining file handlers with empty 400 responses to complete contract-consistency rollout.
+17 -4
View File
@@ -4,6 +4,7 @@ import {
} from "../../../actions/azurestorage";
import { hashAPIPath } from "../../../actions/core/hash";
import { consoleLogger } from "../../../actions/core/logger";
import { respondError, respondSuccess } from "../middleware/apiResponse";
import nextConnect from "next-connect";
import middleware from "../middleware/middleware";
@@ -27,7 +28,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, tempcaseref, repid and hash are required"
});
}
var checkquerypath =
@@ -39,16 +44,24 @@ ApiProxy.get(async (req, res) => {
filename;
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "INVALID_HASH",
message: "Invalid hash"
});
}
await createRepCompleteMessage(containerName, tempCaseRef, filename)
.then((data) => {
return res.status(200).json(data);
return respondSuccess(res, data);
})
.catch((error) => {
consoleLogger(error);
return res.status(400).json();
return respondError(res, {
status: 400,
code: "CREATE_REP_COMPLETE_MESSAGE_FAILED",
message: "Failed to create representation complete message"
});
});
});
+21 -7
View File
@@ -1,17 +1,25 @@
import { DefaultAzureCredential } from "@azure/identity";
import { BlobServiceClient } from "@azure/storage-blob";
import { consoleLogger } from "../../../actions/core/logger";
import { respondError, respondSuccess } from "../middleware/apiResponse";
export default async function handler(req, res) {
if (req.method !== "POST") {
res.status(405).json({ error: "Method not allowed" });
return;
return respondError(res, {
status: 405,
code: "METHOD_NOT_ALLOWED",
message: "Method not allowed"
});
}
const { container, blobName } = req.body;
if (!container || !blobName) {
res.status(400).json({ error: "Missing container or blobName" });
return;
return respondError(res, {
status: 400,
code: "MISSING_REQUIRED_BODY",
message: "Missing container or blobName"
});
}
try {
@@ -60,10 +68,16 @@ export default async function handler(req, res) {
const withTags = await blockBlobClient.setTags(tags);
const withMeta = await blockBlobClient.setMetadata(tags);
res.status(200).json({ message: "repComplete removed successfully" });
return respondSuccess(res, {
message: "repComplete removed successfully"
});
} catch (error) {
console.error("Error editing rep.json:", error);
res.status(500).json({ error: "Failed to edit rep.json" });
consoleLogger(error);
return respondError(res, {
status: 500,
code: "EDIT_REP_JSON_FAILED",
message: "Failed to edit rep.json"
});
}
}
+17 -4
View File
@@ -7,6 +7,7 @@ import {
} from "../../../actions/azurestorage";
import { hashAPIPath } from "../../../actions/core/hash";
import { consoleLogger } from "../../../actions/core/logger";
import { respondError, respondSuccess } from "../middleware/apiResponse";
import middleware from "../middleware/middleware";
import nextConnect from "next-connect";
@@ -24,22 +25,34 @@ 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: "ident and hash are required"
});
}
var checkquerypath = "/api/file/setupcontainer?ident=" + containerName;
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "INVALID_HASH",
message: "Invalid hash"
});
}
await createContainer(containerName)
.then((data) => {
return res.status(200).json({ data: "success", output: data });
return respondSuccess(res, { data: "success", output: data });
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
return respondError(res, {
status: 400,
code: "SETUP_CONTAINER_FAILED",
message: "Failed to setup container"
});
});
});
+22 -5
View File
@@ -8,6 +8,7 @@ import nextConnect from "next-connect";
import middleware from "../middleware/middleware";
import { consoleLogger } from "../../../actions/core/logger";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
import { fileTypeFromBuffer } from "file-type";
import fs from "fs";
import path from "path";
@@ -39,13 +40,21 @@ ApiProxy.post(async (req, res) => {
var checkHash = req.query.hash;
if (typeof checkHash === "undefined" || checkHash.length === 0) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "HASH_REQUIRED",
message: "hash is required"
});
}
var checkquerypath = "/api/file/uploadsinglefile";
if (hashAPIPath(checkquerypath) != "?hash=" + checkHash) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "INVALID_HASH",
message: "Invalid hash"
});
}
const containerID = req.body?.containerID?.[0];
@@ -57,7 +66,11 @@ ApiProxy.post(async (req, res) => {
typeof casefolderID === "undefined" ||
casefolderID.length === 0
) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "MISSING_REQUIRED_BODY",
message: "containerID and casefolderID are required"
});
}
const uploadedFiles = req.files || {};
@@ -126,10 +139,14 @@ ApiProxy.post(async (req, res) => {
containerID,
casefolderID
);
return res.status(200).json({ data, invalidFiles });
return respondSuccess(res, { data, invalidFiles });
} catch (error) {
consoleLogger(error);
return res.status(500).json({ error: "Failed to upload files" });
return respondError(res, {
status: 500,
code: "UPLOAD_SINGLE_FILE_FAILED",
message: "Failed to upload files"
});
}
});