feat: add marketplace overview audit and injection success metrics endpoint

This commit is contained in:
MOHAN 2026-03-29 17:33:44 +05:30
parent 7de04ab4e0
commit 156f7cdd58
7 changed files with 204 additions and 3 deletions

View File

@ -0,0 +1,54 @@
# 00 Marketplace Overview Audit
Source checked: Uber Eats Marketplace APIs Overview section shared by you.
## Status Matrix (Doc vs Current Code)
- Integration Configuration:
Implemented partly. Merchant and OAuth connection setup exists.
Files: `src/modules/connections`, `src/modules/auth`.
Pending: typed onboarding endpoints from official Integration Configuration docs.
- Menu:
Implemented partly.
Implemented: menu upsert/get wrappers.
Files: `src/modules/proxy/proxy.service.js`, `src/routes/proxy.routes.js`.
Pending: full endpoint set from official Menu docs.
- Store:
Implemented partly.
Implemented: update store hours shortcut.
Pending: full store details/status/holiday hours typed routes.
- Order:
Implemented partly.
Implemented: list orders and order actions (accept/deny/ready/cancel).
Pending: all order lifecycle endpoints from official Order docs, including detailed states and BYOC-specific flows.
- Promotions:
Not implemented as typed APIs yet.
Temporary coverage: generic passthrough endpoint.
- Reporting:
Not implemented as typed APIs yet.
Temporary coverage: generic passthrough endpoint.
- Webhooks:
Implemented basic ingestion and persistence.
Pending: signature verification, idempotency key enforcement, async processing pipeline.
## Production Requirement Alignment
- 99% injection success tracking:
Implemented initial metric endpoint:
`GET /api/v1/metrics/injection-success`
based on logged responses for `accept_pos_order`.
## Important Current Constraint
Until endpoint-specific docs are provided, the wrapper supports full coverage through:
- `POST /api/v1/uber/request`
Typed endpoint expansion will continue section-by-section as you send official docs.

View File

@ -6,4 +6,4 @@ Go-live readiness:
- Webhook URL reachable over HTTPS - Webhook URL reachable over HTTPS
- Alerting and log monitoring enabled - Alerting and log monitoring enabled
- Token refresh and failure runbooks ready - Token refresh and failure runbooks ready
- Injection success rate monitoring enabled (`target >= 99%`)

View File

@ -145,6 +145,37 @@
} }
} }
}, },
"/api/v1/metrics/injection-success": {
"get": {
"summary": "Get order injection success metric",
"tags": [
"Metrics"
],
"parameters": [
{
"in": "query",
"name": "merchantId",
"required": false,
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "windowDays",
"required": false,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Injection metrics"
}
}
}
},
"/api/v1/uber/request": { "/api/v1/uber/request": {
"post": { "post": {
"summary": "Generic Uber passthrough for any Uber endpoint", "summary": "Generic Uber passthrough for any Uber endpoint",

View File

@ -12,6 +12,7 @@ const authRoutes = require("./routes/auth.routes");
const connectionRoutes = require("./routes/connections.routes"); const connectionRoutes = require("./routes/connections.routes");
const proxyRoutes = require("./routes/proxy.routes"); const proxyRoutes = require("./routes/proxy.routes");
const webhookRoutes = require("./routes/webhooks.routes"); const webhookRoutes = require("./routes/webhooks.routes");
const metricsRoutes = require("./routes/metrics.routes");
const app = express(); const app = express();
@ -25,10 +26,10 @@ app.use("/health", healthRoutes);
app.use("/api/v1/auth", requireWrapperApiKey, authRoutes); app.use("/api/v1/auth", requireWrapperApiKey, authRoutes);
app.use("/api/v1", requireWrapperApiKey, connectionRoutes); app.use("/api/v1", requireWrapperApiKey, connectionRoutes);
app.use("/api/v1", requireWrapperApiKey, proxyRoutes); app.use("/api/v1", requireWrapperApiKey, proxyRoutes);
app.use("/api/v1", requireWrapperApiKey, metricsRoutes);
app.use("/api/v1", webhookRoutes); app.use("/api/v1", webhookRoutes);
app.use("/docs", swaggerUi.serve, swaggerUi.setup(spec)); app.use("/docs", swaggerUi.serve, swaggerUi.setup(spec));
app.use(errorHandler); app.use(errorHandler);
module.exports = app; module.exports = app;

View File

@ -148,6 +148,46 @@ const apiLogRepository = {
`); `);
stmt.run(row); stmt.run(row);
return row; return row;
},
getInjectionSuccessStats({ merchantId, sinceIso }) {
const params = [];
const filters = ["uber_path LIKE '%/accept_pos_order%'"];
if (merchantId) {
filters.push("merchant_id = ?");
params.push(merchantId);
}
if (sinceIso) {
filters.push("created_at >= ?");
params.push(sinceIso);
}
const whereSql = filters.length ? `WHERE ${filters.join(" AND ")}` : "";
const totals = db
.prepare(
`
SELECT
COUNT(*) AS total,
SUM(CASE WHEN response_status >= 200 AND response_status < 300 THEN 1 ELSE 0 END) AS success
FROM api_logs
${whereSql}
`
)
.get(...params);
const total = totals?.total || 0;
const success = totals?.success || 0;
const successRate = total === 0 ? 0 : Number(((success / total) * 100).toFixed(2));
return {
total,
success,
failed: total - success,
successRate
};
} }
}; };
@ -157,4 +197,3 @@ module.exports = {
webhookRepository, webhookRepository,
apiLogRepository apiLogRepository
}; };

View File

@ -0,0 +1,44 @@
const { z } = require("zod");
const { apiLogRepository } = require("../../db/adapter");
function toSinceIso(windowDays) {
if (!windowDays) {
return null;
}
const days = Number(windowDays);
if (Number.isNaN(days) || days <= 0) {
return null;
}
const ms = days * 24 * 60 * 60 * 1000;
return new Date(Date.now() - ms).toISOString();
}
async function getInjectionSuccess(req, res) {
const schema = z.object({
merchantId: z.string().optional(),
windowDays: z.string().optional()
});
const query = schema.parse(req.query);
const sinceIso = toSinceIso(query.windowDays);
const stats = apiLogRepository.getInjectionSuccessStats({
merchantId: query.merchantId,
sinceIso
});
return res.json({
success: true,
data: {
metric: "order_injection_success_rate",
merchantId: query.merchantId || "all",
windowDays: query.windowDays ? Number(query.windowDays) : "all_time",
targetPercent: 99,
...stats
}
});
}
module.exports = {
getInjectionSuccess
};

View File

@ -0,0 +1,32 @@
const express = require("express");
const asyncHandler = require("../middleware/asyncHandler");
const { getInjectionSuccess } = require("../modules/metrics/metrics.controller");
const router = express.Router();
/**
* @openapi
* /api/v1/metrics/injection-success:
* get:
* summary: Get order injection success metric
* tags:
* - Metrics
* parameters:
* - in: query
* name: merchantId
* required: false
* schema:
* type: string
* - in: query
* name: windowDays
* required: false
* schema:
* type: integer
* responses:
* 200:
* description: Injection metrics
*/
router.get("/metrics/injection-success", asyncHandler(getInjectionSuccess));
module.exports = router;