Source: config/migrations/20261003-node-object-types.js

export const id = '20261003-node-object-types';
export const name = "Node als generischer Tailnet-Objekttyp: ObjectBase.Type 'node', Links 'tailnetNode'; Runner werden Nodes mit role='runner'";

/**
 * Ein Tailscale-Node war bisher **kein** Objekt — nur Runner hatten eine
 * ObjectBase-Zeile (`Type='runner'`). Gateways und Dev-PCs existierten
 * ausschließlich in Headscale: ohne Sichtbarkeit, ohne Gruppenzuordnung, ohne
 * Möglichkeit, sie im UI einem Team/Netz zuzuordnen.
 *
 * Diese Migration macht den Node zum **ersten Objekttyp** für jede Maschine im
 * Tailnet; der Runner ist ab jetzt ein Node mit `Data.role='runner'`:
 *
 *   Type='node'
 *     Data.role        'runner' | 'gateway' | 'dev-pc'
 *     Data.hostname    Tailnet-Hostname (TS_HOSTNAME)
 *     Data.tailnetNodeId / tailnetIp
 *     Data.mode        personal|team|shared (Eigentum, wie bisher beim Runner)
 *     + runner-spezifisch: publicKey, architecture, operatingSystem,
 *       capabilities, status, revokedAt
 *
 * Sichtbarkeit und Eigentum bleiben unverändert: `Links` (`member`/`memberA`)
 * und `Visible` wie bei Projekten/Lists/Runnern. Die Companion-Zeile für den
 * heißen Zustand ist die generische `Member`-Tabelle (Member-Muster, seit
 * 20260926-runner-companion-into-member) — sie ist nur für `role='runner'`
 * sinnvoll (Credential-`lookup_key`, Heartbeat) und wird für andere Rollen
 * nicht angelegt.
 *
 * Bestehende Daten:
 *   - `runner`              → `node` + `role='runner'`
 *   - Links `tailnetRunner` → `tailnetNode` (UID=Netz, UIDTarget=Node)
 *
 * Enum-Superset bewusst vollständig (Vereinigung aus ausgerollten DBs und dem
 * Schema-Snapshot, inkl. bereits entfernter Literale wie `deployment`/`workspace`)
 * — ein zu kurzes Enum würde beim MODIFY bestehende Zeilen abschneiden
 * („Data truncated"). `system_versioning_alter_history=1` erlaubt das MODIFY
 * trotz History-Partitionen mit alten Literalen (Muster aus
 * 20260827-tailnet-object-types). 'runner' bleibt im Enum (History/Rollback).
 */

const OBJECTBASE_TYPE_ENUM = [
    'account',
    'accountingChangeable',
    'accountingVisible',
    'achievement',
    'achievementT',
    'action',
    'actionT',
    'app',
    'appAsset',
    'appDomain',
    'changeable',
    'costcenter',
    'deployment',
    'deploymentEndpoint',
    'directoryShare',
    'dlist',
    'document',
    'email',
    'entry',
    'event',
    'eventFunctionT',
    'eventJob',
    'eventJobT',
    'eventT',
    'exclude',
    'extern',
    'family',
    'fiscalyear',
    'function',
    'gentry',
    'ggroup',
    'group',
    'guest',
    'historic',
    'include',
    'intersect',
    'job',
    'list',
    'location',
    'locationC',
    'locationT',
    // Neu: generischer Node (Tailnet-Maschine, Rolle in Data.role)
    'node',
    'person',
    'project',
    'repositoryShare',
    'runner',
    'tailnet',
    'user',
    'visible',
    'workspace',
];

const LINKS_TYPE_ENUM = [
    'achievement',
    'action',
    'actionT',
    'admin',
    'appAsset',
    'appDomain',
    'botRepo',
    'config',
    'deploymentRunner',
    'dynamic',
    'event',
    'family',
    'familyFees',
    'filter',
    'function',
    'identifyer',
    'list',
    'location',
    'locationC',
    'locationT',
    'member',
    'member0',
    'memberA',
    'memberGA',
    'memberS',
    'memberSys',
    'portal_pending',
    'projectShare',
    // Neu: Node → Netz (verdrängt tailnetRunner; alt bleibt im Enum)
    'tailnetNode',
    'tailnetRunner',
    'transaction',
    'transactionLine',
    'txReference',
    'workspaceLease',
    'workspaceRunner',
];

const renderEnum = (values) => values.map((v) => `'${v}'`).join(',');

export const migrate = async ({ query }) => {
    await query('SET @@system_versioning_alter_history = 1', []);
    await query(
        `ALTER TABLE ObjectBase
         MODIFY COLUMN \`Type\` enum(${renderEnum(OBJECTBASE_TYPE_ENUM)}) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL`,
        [],
    );
    await query(
        `ALTER TABLE Links
         MODIFY COLUMN \`Type\` enum(${renderEnum(LINKS_TYPE_ENUM)}) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL`,
        [],
    );
    await query('SET @@system_versioning_alter_history = 0', []);

    // Runner → Node mit Rolle. `COALESCE`/`JSON_SET` auch für (theoretisch)
    // leere Data-Spalten; idempotent, weil nur `Type='runner'` betroffen ist.
    await query(
        `UPDATE ObjectBase
         SET \`Type\`='node',
             \`Data\`=JSON_SET(COALESCE(\`Data\`, JSON_OBJECT()), '$.role', 'runner')
         WHERE \`Type\`='runner'`,
        [],
    );

    // Netz-Link umbenennen (Node → Tailnet).
    await query(
        `UPDATE Links SET \`Type\`='tailnetNode' WHERE \`Type\`='tailnetRunner'`,
        [],
    );
};