demo 20-4-2026
This commit is contained in:
13
index.js
13
index.js
@@ -8,6 +8,7 @@ const geoRoutes = require("./routes/geometries");
|
|||||||
const entityRoutes = require("./routes/entities");
|
const entityRoutes = require("./routes/entities");
|
||||||
const sectionRoutes = require("./routes/sections");
|
const sectionRoutes = require("./routes/sections");
|
||||||
const { openApiSpec } = require("./swagger");
|
const { openApiSpec } = require("./swagger");
|
||||||
|
const { envelopeResponses } = require("./utils/apiEnvelope");
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
@@ -15,12 +16,12 @@ app.use(cors());
|
|||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
// serve MBTiles and geometry CRUD
|
// serve MBTiles and geometry CRUD
|
||||||
app.use("/tiles", tileRoutes);
|
app.use("/tiles", envelopeResponses, tileRoutes);
|
||||||
app.use("/raster-tiles", rasterTileRoutes);
|
app.use("/raster-tiles", envelopeResponses, rasterTileRoutes);
|
||||||
app.use("/geometries", geoRoutes);
|
app.use("/geometries", envelopeResponses, geoRoutes);
|
||||||
app.use("/entities", entityRoutes);
|
app.use("/entities", envelopeResponses, entityRoutes);
|
||||||
app.use("/sections", sectionRoutes);
|
app.use("/sections", envelopeResponses, sectionRoutes);
|
||||||
app.use("/submissions", (req, res, next) => {
|
app.use("/submissions", envelopeResponses, (req, res, next) => {
|
||||||
req.url = `/submissions${req.url}`;
|
req.url = `/submissions${req.url}`;
|
||||||
sectionRoutes(req, res, next);
|
sectionRoutes(req, res, next);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ router.get("/:z/:x/:y", (req, res) => {
|
|||||||
const y = Number(req.params.y);
|
const y = Number(req.params.y);
|
||||||
|
|
||||||
if (!Number.isInteger(z) || !Number.isInteger(x) || !Number.isInteger(y)) {
|
if (!Number.isInteger(z) || !Number.isInteger(x) || !Number.isInteger(y)) {
|
||||||
return res.status(400).send("Invalid tile coordinates");
|
return res.status(400).json({ error: "Invalid tile coordinates" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const tmsY = (1 << z) - 1 - y;
|
const tmsY = (1 << z) - 1 - y;
|
||||||
@@ -47,7 +47,7 @@ router.get("/:z/:x/:y", (req, res) => {
|
|||||||
`).get(z, x, tmsY);
|
`).get(z, x, tmsY);
|
||||||
|
|
||||||
if (!tile) {
|
if (!tile) {
|
||||||
return res.status(404).send("Tile not found");
|
return res.status(404).json({ error: "Tile not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
res.setHeader("Content-Type", contentType);
|
res.setHeader("Content-Type", contentType);
|
||||||
|
|||||||
@@ -289,9 +289,9 @@ router.post("/:sectionId/submit", (req, res) => {
|
|||||||
throw createHttpError("Section is not editable", 409);
|
throw createHttpError("Section is not editable", 409);
|
||||||
}
|
}
|
||||||
|
|
||||||
const commitId = normalizeId(req.body?.commit_id) || state.head_commit_id;
|
const commitId = state.head_commit_id;
|
||||||
if (!commitId) {
|
if (!commitId) {
|
||||||
throw createHttpError("Section has no commit to submit", 400);
|
throw createHttpError("Section has no head commit to submit", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const commit = getCommitForSection(section.id, commitId);
|
const commit = getCommitForSection(section.id, commitId);
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ router.get("/:z/:x/:y", (req, res) => {
|
|||||||
const y = Number(req.params.y);
|
const y = Number(req.params.y);
|
||||||
|
|
||||||
if (!Number.isInteger(z) || !Number.isInteger(x) || !Number.isInteger(y)) {
|
if (!Number.isInteger(z) || !Number.isInteger(x) || !Number.isInteger(y)) {
|
||||||
return res.status(400).send("Invalid tile coordinates");
|
return res.status(400).json({ error: "Invalid tile coordinates" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert XYZ → TMS
|
// convert XYZ → TMS
|
||||||
@@ -64,7 +64,7 @@ router.get("/:z/:x/:y", (req, res) => {
|
|||||||
const tile = stmt.get(z, x, tmsY);
|
const tile = stmt.get(z, x, tmsY);
|
||||||
|
|
||||||
if (!tile) {
|
if (!tile) {
|
||||||
return res.status(404).send("Tile not found");
|
return res.status(404).json({ error: "Tile not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
res.setHeader("Content-Type", contentType);
|
res.setHeader("Content-Type", contentType);
|
||||||
|
|||||||
32
swagger.js
32
swagger.js
@@ -24,9 +24,15 @@ const openApiSpec = {
|
|||||||
ErrorResponse: {
|
ErrorResponse: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
error: { type: "string" },
|
status: { type: "string", enum: ["error"] },
|
||||||
|
data: { nullable: true },
|
||||||
|
message: { type: "string" },
|
||||||
|
errors: {
|
||||||
|
type: "array",
|
||||||
|
items: {},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
required: ["error"],
|
required: ["status", "data", "message", "errors"],
|
||||||
},
|
},
|
||||||
Entity: {
|
Entity: {
|
||||||
type: "object",
|
type: "object",
|
||||||
@@ -366,7 +372,7 @@ function objectResponse(description, ref) {
|
|||||||
description,
|
description,
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: { $ref: ref },
|
schema: successEnvelopeSchema({ $ref: ref }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -377,15 +383,31 @@ function arrayResponse(description, itemRef) {
|
|||||||
description,
|
description,
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: {
|
schema: successEnvelopeSchema({
|
||||||
type: "array",
|
type: "array",
|
||||||
items: { $ref: itemRef },
|
items: { $ref: itemRef },
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function successEnvelopeSchema(dataSchema) {
|
||||||
|
return {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
status: { type: "string", enum: ["success"] },
|
||||||
|
data: dataSchema,
|
||||||
|
message: { type: "string" },
|
||||||
|
errors: {
|
||||||
|
type: "array",
|
||||||
|
items: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["status", "data", "message", "errors"],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function jsonBody(schema) {
|
function jsonBody(schema) {
|
||||||
return {
|
return {
|
||||||
required: true,
|
required: true,
|
||||||
|
|||||||
65
utils/apiEnvelope.js
Normal file
65
utils/apiEnvelope.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
function envelopeResponses(_req, res, next) {
|
||||||
|
const originalJson = res.json.bind(res);
|
||||||
|
|
||||||
|
res.json = (payload) => {
|
||||||
|
if (isApiEnvelope(payload)) {
|
||||||
|
return originalJson(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isError = res.statusCode >= 400;
|
||||||
|
const message = extractMessage(payload, isError);
|
||||||
|
const errors = extractErrors(payload, isError, message);
|
||||||
|
const data = isError ? null : payload;
|
||||||
|
|
||||||
|
return originalJson({
|
||||||
|
status: isError ? "error" : "success",
|
||||||
|
data,
|
||||||
|
message,
|
||||||
|
errors,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isApiEnvelope(payload) {
|
||||||
|
return Boolean(
|
||||||
|
payload &&
|
||||||
|
typeof payload === "object" &&
|
||||||
|
!Array.isArray(payload) &&
|
||||||
|
Object.prototype.hasOwnProperty.call(payload, "status") &&
|
||||||
|
Object.prototype.hasOwnProperty.call(payload, "data") &&
|
||||||
|
Object.prototype.hasOwnProperty.call(payload, "message") &&
|
||||||
|
Object.prototype.hasOwnProperty.call(payload, "errors")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractMessage(payload, isError) {
|
||||||
|
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
||||||
|
if (typeof payload.message === "string" && payload.message.length) {
|
||||||
|
return payload.message;
|
||||||
|
}
|
||||||
|
if (typeof payload.error === "string" && payload.error.length) {
|
||||||
|
return payload.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isError ? "Request failed" : "OK";
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractErrors(payload, isError, message) {
|
||||||
|
if (!isError) return [];
|
||||||
|
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
||||||
|
if (Array.isArray(payload.errors)) {
|
||||||
|
return payload.errors;
|
||||||
|
}
|
||||||
|
if (payload.error) {
|
||||||
|
return [payload.error];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message ? [message] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
envelopeResponses,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user