Add link session preparation flow

- wire front link resolve/prepare routes
- add orchestrator session command handling
- update admin dashboards and device/link logic
This commit is contained in:
user
2026-03-28 17:47:03 +02:00
parent 5da61ed853
commit 0a11be5006
20 changed files with 1099 additions and 175 deletions

View File

@@ -1,6 +1,6 @@
import { errAsync, ResultAsync } from "neverthrow";
import { db } from "@pkg/db";
import { type Err } from "@pkg/result";
import { ERROR_CODES, type Err } from "@pkg/result";
import { FlowExecCtx } from "@core/flow.execution.context";
import { CreateDevice, Device, DeviceStatus, UpdateDevice } from "./data";
import { DeviceRepository } from "./repository";
@@ -46,14 +46,20 @@ export class DeviceController {
* Only succeeds if the device is currently online.
*/
allocate(fctx: FlowExecCtx, id: number): ResultAsync<Device, Err> {
return this.repo.getById(fctx, id).andThen((dev) => {
if (dev.status !== DeviceStatus.ONLINE || dev.inUse) {
return errAsync(deviceErrors.deviceNotAvailable(fctx, id));
return this.repo.allocateIfAvailable(fctx, id).orElse((error) => {
if (error.code === ERROR_CODES.NOT_ALLOWED) {
return this.repo
.getById(fctx, id)
.andThen((dev) => {
if (dev.status !== DeviceStatus.ONLINE || dev.inUse) {
return errAsync(deviceErrors.deviceNotAvailable(fctx, id));
}
return errAsync(error);
})
.orElse(() => errAsync(error));
}
return this.repo.update(fctx, id, {
status: DeviceStatus.BUSY,
inUse: true,
});
return errAsync(error);
});
}

View File

@@ -1,6 +1,6 @@
import { ResultAsync, errAsync, okAsync } from "neverthrow";
import { FlowExecCtx } from "@core/flow.execution.context";
import { Database, asc, eq } from "@pkg/db";
import { Database, and, asc, eq } from "@pkg/db";
import { device } from "@pkg/db/schema";
import { type Err } from "@pkg/result";
import { logger } from "@pkg/logger";
@@ -135,4 +135,40 @@ export class DeviceRepository {
): ResultAsync<Device, Err> {
return this.update(fctx, id, { status });
}
allocateIfAvailable(fctx: FlowExecCtx, id: number): ResultAsync<Device, Err> {
return traceResultAsync({
name: "device.allocateIfAvailable",
fctx,
fn: () =>
ResultAsync.fromPromise(
this.db
.update(device)
.set({
status: DeviceStatus.BUSY,
inUse: true,
updatedAt: new Date(),
})
.where(
and(
eq(device.id, id),
eq(device.status, DeviceStatus.ONLINE),
eq(device.inUse, false),
),
)
.returning()
.execute(),
(e) =>
deviceErrors.updateFailed(
fctx,
e instanceof Error ? e.message : String(e),
),
).andThen((rows) => {
if (!rows[0]) {
return errAsync(deviceErrors.deviceNotAvailable(fctx, id));
}
return okAsync(rows[0] as Device);
}),
});
}
}