diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index 44922c9c..79e65e3d 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -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. diff --git a/pages/api/file/createrepcompletemessage_api.js b/pages/api/file/createrepcompletemessage_api.js index 1f30911a..d682d651 100644 --- a/pages/api/file/createrepcompletemessage_api.js +++ b/pages/api/file/createrepcompletemessage_api.js @@ -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" + }); }); }); diff --git a/pages/api/file/editRepJson.js b/pages/api/file/editRepJson.js index 20a19c4f..e301ade4 100644 --- a/pages/api/file/editRepJson.js +++ b/pages/api/file/editRepJson.js @@ -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" + }); } } diff --git a/pages/api/file/setupcontainer.js b/pages/api/file/setupcontainer.js index f02ba816..ca2c5d00 100644 --- a/pages/api/file/setupcontainer.js +++ b/pages/api/file/setupcontainer.js @@ -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" + }); }); }); diff --git a/pages/api/file/uploadsinglefile.js b/pages/api/file/uploadsinglefile.js index ffbf222e..d5c0a99c 100644 --- a/pages/api/file/uploadsinglefile.js +++ b/pages/api/file/uploadsinglefile.js @@ -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" + }); } });