Files
harbor-net/src/server/http/routes/deviceInventoryRoute.ts
T
dokril 116686a138
Build and Deploy Gateway / build-and-push (push) Successful in 26s
Build and Deploy Gateway / deploy (push) Successful in 14s
Implement Harbor gateway device ecosystem support
2026-08-31 02:06:22 +03:00

121 lines
4.2 KiB
TypeScript

import type { IncomingMessage, ServerResponse } from 'node:http';
import { HarborError } from '../../../shared/errors.js';
import { sendJson } from '../response.js';
interface DeviceInventoryPort {
snapshot(): unknown;
refresh(): Promise<unknown>;
update(deviceId: string, patch: Record<string, unknown>, expectedRevision: unknown): unknown;
createTag(name: unknown, expectedRevision: unknown): unknown;
renameTag(tagId: string, name: unknown, expectedRevision: unknown): unknown;
deleteTag(tagId: string, expectedRevision: unknown): unknown;
resetTraffic(expectedRevision: unknown): Promise<unknown>;
setPolicy(deviceId: string, mode: unknown, expectedRevision: unknown): Promise<unknown>;
}
interface DeviceInventoryRouteDependencies {
deviceInventory: DeviceInventoryPort | null;
readBody(req: IncomingMessage): Promise<Record<string, unknown>>;
}
const DEVICE_PATH = /^\/api\/devices\/(dev_[a-f0-9]{16})$/;
const DEVICE_POLICY_PATH = /^\/api\/devices\/(dev_[a-f0-9]{16})\/policy$/;
const DEVICE_TAG_PATH = /^\/api\/device-tags\/(tag_[a-f0-9]{16})$/;
export function createDeviceInventoryRoute(dependencies: DeviceInventoryRouteDependencies) {
return {
async handle(req: IncomingMessage, res: ServerResponse) {
const pathname = new URL(req.url || '/', 'http://localhost').pathname;
if (pathname === '/api/devices') {
if (!dependencies.deviceInventory || req.method !== 'GET') {
throw new HarborError('ENDPOINT_NOT_FOUND');
}
sendJson(res, 200, dependencies.deviceInventory.snapshot());
return true;
}
if (pathname === '/api/devices/refresh') {
if (!dependencies.deviceInventory || req.method !== 'POST') {
throw new HarborError('ENDPOINT_NOT_FOUND');
}
sendJson(res, 200, await dependencies.deviceInventory.refresh());
return true;
}
if (pathname === '/api/devices/traffic') {
if (!dependencies.deviceInventory || req.method !== 'DELETE') {
throw new HarborError('ENDPOINT_NOT_FOUND');
}
const body = await dependencies.readBody(req);
sendJson(res, 200, await dependencies.deviceInventory.resetTraffic(body.expectedRevision));
return true;
}
if (pathname === '/api/device-tags') {
if (!dependencies.deviceInventory || req.method !== 'POST') {
throw new HarborError('ENDPOINT_NOT_FOUND');
}
const body = await dependencies.readBody(req);
sendJson(
res,
200,
dependencies.deviceInventory.createTag(body.name, body.expectedRevision),
);
return true;
}
const tagMatch = pathname.match(DEVICE_TAG_PATH);
if (tagMatch) {
if (!dependencies.deviceInventory || !['PUT', 'DELETE'].includes(req.method || '')) {
throw new HarborError('ENDPOINT_NOT_FOUND');
}
const body = await dependencies.readBody(req);
sendJson(
res,
200,
req.method === 'PUT'
? dependencies.deviceInventory.renameTag(tagMatch[1], body.name, body.expectedRevision)
: dependencies.deviceInventory.deleteTag(tagMatch[1], body.expectedRevision),
);
return true;
}
const deviceMatch = pathname.match(DEVICE_PATH);
if (deviceMatch) {
if (!dependencies.deviceInventory || req.method !== 'PUT') {
throw new HarborError('ENDPOINT_NOT_FOUND');
}
const { expectedRevision, ...patch } = await dependencies.readBody(req);
sendJson(
res,
200,
dependencies.deviceInventory.update(deviceMatch[1], patch, expectedRevision),
);
return true;
}
const policyMatch = pathname.match(DEVICE_POLICY_PATH);
if (policyMatch) {
if (!dependencies.deviceInventory || req.method !== 'PUT') {
throw new HarborError('ENDPOINT_NOT_FOUND');
}
const body = await dependencies.readBody(req);
sendJson(
res,
200,
await dependencies.deviceInventory.setPolicy(
policyMatch[1],
body.mode,
body.expectedRevision,
),
);
return true;
}
return false;
},
};
}