/**
* Migration: `memberA`-Owner-Links von `node`/`tailnet` reparieren.
*
* ## Der Bug
*
* `createNode` und `createTailnet` schrieben die Owner-Gruppe aus
* `resolveNodeGroup`/`resolveTailnetGroup` **direkt** in `Links.UIDTarget`. Diese
* Gruppe kommt aber aus `query(..., { cast: ['UUID'] })` — also als präfixierter
* **String** `UUID-xxxxxxxx-…` (41 Zeichen, Codebase-Konvention). In die binäre
* `UIDTarget`-Spalte geschrieben, schneidet MariaDB den String auf 16 Bytes ab:
*
* 'UUID-bb73955a-94' statt 0x11F194B7BB73955AB16F426041107549
*
* Der Owner-Link zeigt danach auf eine nicht existierende UID — die Gruppe ist
* für das Objekt verloren (`isListAdmin`, Gruppenzuordnung im UI).
* `createProject` macht es richtig (`groupUID = UUID2hex(group.UID)`); der
* Node-/Tailnet-Pfad wurde analog korrigiert.
*
* ## Die Reparatur
*
* Das Ziel ist aus `Data.groupUID` (dieselbe präfixierte Form) über
* `U_UUID2BIN()` rekonstruierbar — die SQL-Entsprechung von `UUID2hex()`.
* Ein Node ohne eigenes `groupUID` erbt das seines Netzes (`tailnetNode`).
*
* Zusätzlich werden **verwaiste** Links entfernt (`UIDTarget` zeigt auf keine
* ObjectBase-Zeile) — Reste aus früherem Umbenennen/Löschen, die sonst als
* Geister-Nodes in Listen auftauchen.
*
* Idempotent: der `<>`-Vergleich lässt bereits korrekte Links unberührt.
*/
export const id = '20261003-repair-membera-link-targets';
export const name = 'Owner-Links (memberA) von node/tailnet aus Data.groupUID reparieren';
/**
* @param {{ query: Function }} api
*/
export const migrate = async ({ query }) => {
// 1. Tailnets: Ziel aus dem eigenen `Data.groupUID`.
await query(
`UPDATE Links l
INNER JOIN ObjectBase o ON (o.UID = l.UID)
SET l.UIDTarget = U_UUID2BIN(JSON_UNQUOTE(JSON_VALUE(o.Data, '$.groupUID')))
WHERE l.Type = 'memberA'
AND o.Type = 'tailnet'
AND JSON_VALUE(o.Data, '$.groupUID') IS NOT NULL
AND l.UIDTarget <> U_UUID2BIN(JSON_UNQUOTE(JSON_VALUE(o.Data, '$.groupUID')))`,
[],
);
// 2. Nodes mit eigenem `Data.groupUID` (team/shared).
await query(
`UPDATE Links l
INNER JOIN ObjectBase o ON (o.UID = l.UID)
SET l.UIDTarget = U_UUID2BIN(JSON_UNQUOTE(JSON_VALUE(o.Data, '$.groupUID')))
WHERE l.Type = 'memberA'
AND o.Type = 'node'
AND JSON_VALUE(o.Data, '$.groupUID') IS NOT NULL
AND l.UIDTarget <> U_UUID2BIN(JSON_UNQUOTE(JSON_VALUE(o.Data, '$.groupUID')))`,
[],
);
// 3. Nodes ohne eigenes groupUID: Owner-Gruppe des Netzes übernehmen
// (`tailnetNode`, UID=Netz, UIDTarget=Node).
await query(
`UPDATE Links l
INNER JOIN ObjectBase o ON (o.UID = l.UID)
INNER JOIN Links tl ON (tl.UIDTarget = o.UID AND tl.Type = 'tailnetNode')
INNER JOIN ObjectBase net ON (net.UID = tl.UID AND net.Type = 'tailnet')
SET l.UIDTarget = U_UUID2BIN(JSON_UNQUOTE(JSON_VALUE(net.Data, '$.groupUID')))
WHERE l.Type = 'memberA'
AND o.Type = 'node'
AND JSON_VALUE(o.Data, '$.groupUID') IS NULL
AND JSON_VALUE(net.Data, '$.groupUID') IS NOT NULL
AND l.UIDTarget <> U_UUID2BIN(JSON_UNQUOTE(JSON_VALUE(net.Data, '$.groupUID')))`,
[],
);
// 4. Verwaiste Links der node/tailnet-Objekte entfernen (Ziel existiert
// nicht mehr) — Reste aus Umbenennen/Löschen, die sonst als
// Geister-Zuordnung in Listen auftauchen.
await query(
`DELETE l FROM Links l
INNER JOIN ObjectBase o ON (o.UID = l.UID)
WHERE l.Type IN ('memberA', 'tailnetNode')
AND o.Type IN ('node', 'tailnet')
AND NOT EXISTS (SELECT 1 FROM ObjectBase t WHERE t.UID = l.UIDTarget)`,
[],
);
};