import { errAsync, ResultAsync } from "neverthrow"; import { nanoid } from "nanoid"; import { db } from "@pkg/db"; import { type Err } from "@pkg/result"; import { FlowExecCtx } from "@core/flow.execution.context"; import { CreateLink, Link, LinkStatus, LinkWithDevice, UpdateLink } from "./data"; import { LinkRepository } from "./repository"; import { linkErrors } from "./errors"; export class LinkController { constructor(private repo: LinkRepository) {} list(fctx: FlowExecCtx): ResultAsync { return this.repo.list(fctx); } getById(fctx: FlowExecCtx, id: number): ResultAsync { return this.repo.getById(fctx, id); } /** * Fetch a link by its URL token, including the joined device. * Used by apps/front to validate and resolve an incoming link. */ getByToken(fctx: FlowExecCtx, token: string): ResultAsync { return this.repo.getByToken(fctx, token); } /** * Validate a token: must exist, be active, and not be expired. * Returns the resolved link+device on success. */ validate(fctx: FlowExecCtx, token: string): ResultAsync { return this.repo.getByToken(fctx, token).andThen((l) => { if (l.status !== LinkStatus.ACTIVE) { return errAsync(linkErrors.linkNotActive(fctx, token)); } if (l.expiresAt && l.expiresAt < new Date()) { return errAsync(linkErrors.linkExpired(fctx, token)); } return this.repo.touch(fctx, token).map(() => l); }); } /** * Generate a new link. Token is auto-generated as a URL-safe nanoid. */ create( fctx: FlowExecCtx, data: Omit, ): ResultAsync { return this.repo.create(fctx, { ...data, token: nanoid(12), }); } update( fctx: FlowExecCtx, id: number, data: UpdateLink, ): ResultAsync { return this.repo.update(fctx, id, data); } assignDevice( fctx: FlowExecCtx, id: number, deviceId: number | null, ): ResultAsync { return this.repo.update(fctx, id, { linkedDeviceId: deviceId }); } revoke(fctx: FlowExecCtx, id: number): ResultAsync { return this.repo.update(fctx, id, { status: LinkStatus.REVOKED }); } delete(fctx: FlowExecCtx, id: number): ResultAsync { return this.repo.delete(fctx, id); } } export function getLinkController(): LinkController { return new LinkController(new LinkRepository(db)); }