114 lines
3.0 KiB
JavaScript
114 lines
3.0 KiB
JavaScript
import { Router } from 'express';
|
|
import { AppError } from '../lib/errors.js';
|
|
import { requireAuth, requirePolicy } from '../middleware/auth.js';
|
|
import { requireIdempotencyKey } from '../middleware/idempotency.js';
|
|
import { buildIdempotencyKey, readIdempotentResult, writeIdempotentResult } from '../services/idempotency-store.js';
|
|
import { commandBaseSchema } from '../contracts/commands/command-base.js';
|
|
|
|
function parseBody(body) {
|
|
const parsed = commandBaseSchema.safeParse(body || {});
|
|
if (!parsed.success) {
|
|
throw new AppError('VALIDATION_ERROR', 'Invalid command payload', 400, {
|
|
issues: parsed.error.issues,
|
|
});
|
|
}
|
|
return parsed.data;
|
|
}
|
|
|
|
function createCommandResponse(route, requestId, idempotencyKey) {
|
|
return {
|
|
accepted: true,
|
|
route,
|
|
commandId: `${route}:${Date.now()}`,
|
|
idempotencyKey,
|
|
requestId,
|
|
};
|
|
}
|
|
|
|
function buildCommandHandler(policyAction, policyResource) {
|
|
return async (req, res, next) => {
|
|
try {
|
|
parseBody(req.body);
|
|
|
|
const route = `${req.baseUrl}${req.route.path}`;
|
|
const compositeKey = buildIdempotencyKey({
|
|
userId: req.actor.uid,
|
|
route,
|
|
idempotencyKey: req.idempotencyKey,
|
|
});
|
|
|
|
const existing = await readIdempotentResult(compositeKey);
|
|
if (existing) {
|
|
return res.status(existing.statusCode).json(existing.payload);
|
|
}
|
|
|
|
const payload = createCommandResponse(route, req.requestId, req.idempotencyKey);
|
|
const persisted = await writeIdempotentResult({
|
|
compositeKey,
|
|
userId: req.actor.uid,
|
|
route,
|
|
idempotencyKey: req.idempotencyKey,
|
|
payload,
|
|
statusCode: 200,
|
|
});
|
|
return res.status(persisted.statusCode).json(persisted.payload);
|
|
} catch (error) {
|
|
return next(error);
|
|
}
|
|
};
|
|
}
|
|
|
|
export function createCommandsRouter() {
|
|
const router = Router();
|
|
|
|
router.post(
|
|
'/orders/create',
|
|
requireAuth,
|
|
requireIdempotencyKey,
|
|
requirePolicy('orders.create', 'order'),
|
|
buildCommandHandler('orders.create', 'order')
|
|
);
|
|
|
|
router.post(
|
|
'/orders/:orderId/update',
|
|
requireAuth,
|
|
requireIdempotencyKey,
|
|
requirePolicy('orders.update', 'order'),
|
|
buildCommandHandler('orders.update', 'order')
|
|
);
|
|
|
|
router.post(
|
|
'/orders/:orderId/cancel',
|
|
requireAuth,
|
|
requireIdempotencyKey,
|
|
requirePolicy('orders.cancel', 'order'),
|
|
buildCommandHandler('orders.cancel', 'order')
|
|
);
|
|
|
|
router.post(
|
|
'/shifts/:shiftId/change-status',
|
|
requireAuth,
|
|
requireIdempotencyKey,
|
|
requirePolicy('shifts.change-status', 'shift'),
|
|
buildCommandHandler('shifts.change-status', 'shift')
|
|
);
|
|
|
|
router.post(
|
|
'/shifts/:shiftId/assign-staff',
|
|
requireAuth,
|
|
requireIdempotencyKey,
|
|
requirePolicy('shifts.assign-staff', 'shift'),
|
|
buildCommandHandler('shifts.assign-staff', 'shift')
|
|
);
|
|
|
|
router.post(
|
|
'/shifts/:shiftId/accept',
|
|
requireAuth,
|
|
requireIdempotencyKey,
|
|
requirePolicy('shifts.accept', 'shift'),
|
|
buildCommandHandler('shifts.accept', 'shift')
|
|
);
|
|
|
|
return router;
|
|
}
|