Source: RouterNode/node/controller.js

// @ts-check
/**
 * @import {ExpressRequestAuthorized, ExpressResponse} from '../../types.js'
 */

import { sendOk, sendErrorFrom } from '../../utils/apiEnvelope.js';
import * as service from './service.js';

/**
 * GET /api/nodes?role=&group_uid=
 * @param {ExpressRequestAuthorized} req
 * @param {ExpressResponse} res
 */
export const listNodes = async (req, res) => {
    try {
        const role = typeof req.query?.role === 'string' && req.query.role ? req.query.role : undefined;
        const groupUid =
            typeof req.query?.group_uid === 'string' && req.query.group_uid
                ? req.query.group_uid
                : undefined;
        sendOk(res, await service.listNodes(req, { role, groupUid }));
    } catch (e) {
        sendErrorFrom(res, e);
    }
};

/**
 * POST /api/nodes — neuen Node anlegen (role=runner|gateway|dev-pc).
 * Runner verlangen `publicKey`; Gateways/Dev-PCs nicht.
 */
export const createNode = async (req, res) => {
    try {
        const result = await service.createNode(req, req.body || {});
        sendOk(res, result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};

/**
 * POST /api/nodes/upsert — idempotenter Sync aus dem project-bot.
 *
 * Legt Gateway/Dev-PC/Runner als Node-Objekt an bzw. zieht Tailnet-Fakten nach
 * (Match über `tailnetNodeId` → `hostname` → `tailnetIp`). Die Rolle eines
 * bestehenden Objekts wird nie überschrieben; ein neues erbt die Sichtbarkeit
 * seines Netzes. Bewusst kein `checkObjectAdmin`: das Objekt entsteht ggf.
 * erst — der Service prüft die Sichtbarkeit des Netzes.
 */
export const upsertNode = async (req, res) => {
    try {
        const result = await service.upsertNodeByHostname(req, req.body || {});
        sendOk(res, result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};

/**
 * GET /api/nodes/:UID
 */
export const getNode = async (req, res) => {
    try {
        const result = await service.getNode(req, req.params.UID);
        sendOk(res, result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};

/**
 * POST /api/nodes/:UID — Data/Netzbindung ändern.
 */
export const updateNode = async (req, res) => {
    try {
        const result = await service.updateNode(req, req.params.UID, req.body || {});
        sendOk(res, result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};

/**
 * POST /api/nodes/:UID/revoke
 */
export const revokeNode = async (req, res) => {
    try {
        const result = await service.revokeNode(req, req.params.UID);
        sendOk(res, result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};

/**
 * POST /api/nodes/:UID/drain
 */
export const drainNode = async (req, res) => {
    try {
        const result = await service.drainNode(req, req.params.UID);
        sendOk(res, result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};

/**
 * DELETE /api/nodes/:UID
 * Hard-delete (cleanup; `revoke` ist der Soft-Pfad).
 */
export const deleteNode = async (req, res) => {
    try {
        const result = await service.deleteNode(req, req.params.UID);
        sendOk(res, result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};