metis-desk/modules/resources/src/api/infrastructure/http/resource.routes.ts

196 lines
5.9 KiB
TypeScript
Raw Normal View History

2026-10-05 11:13:51 +00:00
import type { Application, RequestHandler, Response } from "express";
import { Router } from "express";
import { z } from "zod/v4";
import {
InventoryParamsSchema,
ResourceParamsSchema,
SaveResourceRequestSchema,
SetResourceActiveRequestSchema,
2026-10-05 11:13:51 +00:00
} from "../../../common/inventory";
import { diagnoseNetworkShares } from "../../application/network-share-diagnostics";
2026-10-05 11:13:51 +00:00
import type { ResourceUseCases } from "../../application/resource.use-cases";
import { buildResourceResponse } from "../../application/resource-response.builder";
const LifecycleRequestSchema = z.discriminatedUnion("action", [
z.strictObject({ action: z.literal("status"), body: SetResourceActiveRequestSchema }),
z.strictObject({ action: z.literal("delete"), body: z.strictObject({}) }),
]);
2026-10-05 11:13:51 +00:00
function fail(response: Response, code: string) {
response
.status(code === "INVALID_REQUEST" ? 400 : code.endsWith("NOT_FOUND") ? 404 : 409)
.json({ code, requestId: response.getHeader("x-request-id") });
}
2026-10-10 10:01:58 +00:00
function sendResource(
response: Response,
result: Awaited<ReturnType<ResourceUseCases["setActive"]>>
) {
if (!result.isSuccess) {
fail(response, result.error.code);
return;
}
2026-10-20 11:20:22 +00:00
response.json({
resource: buildResourceResponse(
result.data.resource,
result.data.actorNames,
result.data.directory,
result.data.networkShare
2026-10-20 11:20:22 +00:00
),
});
}
function sendDeleted(response: Response, result: Awaited<ReturnType<ResourceUseCases["remove"]>>) {
if (!result.isSuccess) {
fail(response, result.error.code);
return;
}
response.json({ deleted: true });
}
2026-10-05 11:13:51 +00:00
export function registerResourceRoutes(
app: Application,
useCases: ResourceUseCases,
authenticate: RequestHandler
) {
const router = Router();
2026-10-10 10:01:58 +00:00
for (const action of ["status", "delete"] as const) {
router.post(`/:companyId/:id/${action}`, (request, response, next) => {
const params = ResourceParamsSchema.safeParse(request.params);
const actor = z
.object({ userId: z.uuid() })
.safeParse("session" in request ? request.session : undefined);
const body = LifecycleRequestSchema.safeParse({ action, body: request.body });
if (
!(
params.success &&
actor.success &&
body.success &&
z.strictObject({}).safeParse(request.query).success
)
) {
fail(response, "INVALID_REQUEST");
return;
}
if (body.data.action === "delete") {
useCases
.remove(params.data.companyId, params.data.id)
.then((result) => sendDeleted(response, result))
.catch(next);
return;
}
useCases
.setActive(params.data.companyId, params.data.id, body.data.body.active, actor.data.userId)
.then((result) => sendResource(response, result))
.catch(next);
});
}
router.get("/:companyId/network-share-diagnostics", (request, response, next) => {
const params = InventoryParamsSchema.safeParse(request.params);
if (!(params.success && z.strictObject({}).safeParse(request.query).success)) {
fail(response, "INVALID_REQUEST");
return;
}
useCases
.get(params.data.companyId)
.then((result) => {
if (!result.isSuccess) {
fail(response, result.error.code);
return;
}
response.json({ items: diagnoseNetworkShares(result.data) });
})
.catch(next);
});
2026-10-05 11:13:51 +00:00
router.get("/:companyId", (request, response, next) => {
const params = InventoryParamsSchema.safeParse(request.params);
2026-10-10 10:01:58 +00:00
2026-10-20 11:20:22 +00:00
const query = z.strictObject({ environmentId: z.uuid().optional() }).safeParse(request.query);
if (!(params.success && query.success)) {
2026-10-05 11:13:51 +00:00
fail(response, "INVALID_REQUEST");
2026-10-10 10:01:58 +00:00
2026-10-05 11:13:51 +00:00
return;
}
useCases
.get(params.data.companyId)
.then((result) => {
if (!result.isSuccess) {
fail(response, result.error.code);
2026-10-10 10:01:58 +00:00
2026-10-05 11:13:51 +00:00
return;
}
response.json({
2026-10-05 15:01:32 +00:00
references: result.data.references,
2026-10-20 11:20:22 +00:00
resources: result.data.resources
.filter(
(resource) =>
!query.data.environmentId ||
result.data.directoryDomains?.get(resource.id.toPrimitive())?.environmentId ===
query.data.environmentId.toLowerCase()
)
.map((resource) =>
buildResourceResponse(
resource,
result.data.actorNames,
result.data.directoryDomains?.get(resource.id.toPrimitive()),
result.data.networkShares?.get(resource.id.toPrimitive())
2026-10-20 11:20:22 +00:00
)
),
2026-10-05 11:13:51 +00:00
});
})
.catch(next);
});
for (const path of ["/:companyId", "/:companyId/:id/update"]) {
router.post(path, (request, response, next) => {
const params = (
path.endsWith("update") ? ResourceParamsSchema : InventoryParamsSchema
).safeParse(request.params);
const body = SaveResourceRequestSchema.safeParse(request.body);
const actor = z
.object({ userId: z.uuid() })
.safeParse("session" in request ? request.session : undefined);
2026-10-10 10:01:58 +00:00
2026-10-05 11:13:51 +00:00
if (!(params.success && body.success && actor.success)) {
fail(response, "INVALID_REQUEST");
2026-10-10 10:01:58 +00:00
2026-10-05 11:13:51 +00:00
return;
}
const id =
"id" in params.data && typeof params.data.id === "string" ? params.data.id : undefined;
2026-10-10 10:01:58 +00:00
2026-10-05 11:13:51 +00:00
useCases
.save(params.data.companyId, id, body.data.values, actor.data.userId)
.then((result) => {
if (!result.isSuccess) {
fail(response, result.error.code);
2026-10-10 10:01:58 +00:00
2026-10-05 11:13:51 +00:00
return;
}
response.status(id ? 200 : 201).json({
2026-10-20 11:20:22 +00:00
resource: buildResourceResponse(
result.data.resource,
result.data.actorNames,
result.data.directory,
result.data.networkShare
2026-10-20 11:20:22 +00:00
),
2026-10-05 11:13:51 +00:00
});
})
.catch(next);
});
}
app.use("/api/resources", authenticate, router);
}