// @ts-check
/**
* AP 30 — Projekt-/Share-Zielmodell.
*
* Alt (Ist):
* - `ObjectBase.UIDBelongsTo` trug die **Organisation** (Org-Gruppe)
* - der Projekt-Link zeigte vom Projekt zum Share
* (`Links.UID` = Projekt, `UIDTarget` = Share, Type `memberA`/`member`/`projectShare`)
* - die Lesestufe steckte im Link-Typ (`memberA` = schreiben, `member` = lesen)
*
* Neu (Ziel, normativ seit 2026-09-24):
* - `UIDBelongsTo` = eigene UID. Es transportiert **keine** Organisation mehr;
* die Org wird ueber die `memberA`-Kette abgeleitet
* (`getOrganizationForObject`, siehe 080-Workspaces/051-Migrationsplan-Komponenten).
* Ein Ableger zeigt auf seinen Basis-Share (bleibt unveraendert).
* - der Projekt-Link zeigt vom Share zum Projekt (`UID` = Share, `UIDTarget` = Projekt)
* - die Lesestufe ist eine Eigenschaft des Shares: `metadata.mode`
* (`readOnly` sperrt, alles andere ist editierbar)
*
* Die Migration ist idempotent und laeuft ueber den normalen Migrationslauf
* (`dbVersion`). Sie aendert ausschliesslich Basisobjekte: ein Share/Projekt,
* dessen Elternobjekt selbst ein Share/Projekt ist, bleibt ein Ableger.
*/
export const id = '20260924-project-share-target-model';
export const name = 'Projekt/Share: Selbstreferenz und Share->Projekt-Link';
/** Enum-Werte der Share-Objekte in `ObjectBase.Type`. */
const SHARE_TYPES_SQL = `('repositoryShare','directoryShare')`;
/**
* Fuehrt die Datenmigration aus.
*
* @param {{ query: Function, UUID2hex: Function, HEX2uuid: Function }} api
* @returns {Promise<void>}
*/
export const migrate = async ({ query, HEX2uuid }) => {
// Kandidaten **vor** dem Umbau einsammeln: nur fuer diese Objekte aendert sich
// die Projektion (`organization_id` / `root_uid`), nicht fuer den ganzen Bestand.
const affectedShares = await query(
`SELECT s.UID, s.Type FROM ObjectBase s
JOIN ObjectBase o ON o.UID = s.UIDBelongsTo
WHERE s.Type IN ${SHARE_TYPES_SQL} AND o.Type = 'group'`,
[],
);
const affectedProjects = await query(
`SELECT p.UID FROM ObjectBase p
JOIN ObjectBase o ON o.UID = p.UIDBelongsTo
WHERE p.Type = 'project' AND o.Type = 'group'`,
[],
);
// ── 1. Lesestufe wandert vom Link auf das Objekt ────────────────────────
// Ein `member`-Link hiess "nur lesen". Im Zielmodell ist das
// `metadata.mode = 'readOnly'` am Share. Vorhandene Modi bleiben unberuehrt.
await query(
`UPDATE ObjectBase s
JOIN Links l ON l.UIDTarget = s.UID AND l.Type = 'member'
JOIN ObjectBase p ON p.UID = l.UID AND p.Type = 'project'
SET s.Data = JSON_SET(s.Data, '$.metadata.mode', 'readOnly')
WHERE s.Type IN ${SHARE_TYPES_SQL}
AND JSON_VALID(s.Data)
AND JSON_VALUE(s.Data, '$.metadata.mode') IS NULL`,
[],
);
// ── 2. Projekt-Link umdrehen: Projekt -> Share ==> Share -> Projekt ───
// Erst die neue Richtung anlegen (idempotent), dann die alte entfernen.
await query(
`INSERT IGNORE INTO Links (UID, Type, UIDTarget, UIDuser)
SELECT l.UIDTarget, 'memberA', l.UID, l.UIDuser
FROM Links l
JOIN ObjectBase s ON s.UID = l.UIDTarget AND s.Type IN ${SHARE_TYPES_SQL}
JOIN ObjectBase p ON p.UID = l.UID AND p.Type = 'project'
WHERE l.Type IN ('memberA','member','projectShare')`,
[],
);
await query(
`DELETE l FROM Links l
JOIN ObjectBase s ON s.UID = l.UIDTarget AND s.Type IN ${SHARE_TYPES_SQL}
JOIN ObjectBase p ON p.UID = l.UID AND p.Type = 'project'
WHERE l.Type IN ('memberA','member','projectShare')`,
[],
);
// ── 3. Organisation aus dem Objekt entfernen ────────────────────────────
// Basisobjekte zeigen auf sich selbst. Ableger (Eltern = anderes
// Projekt/Share) behalten ihren Zeiger auf den Basis-Share.
await query(
`UPDATE ObjectBase s
JOIN ObjectBase o ON o.UID = s.UIDBelongsTo
SET s.UIDBelongsTo = s.UID
WHERE s.Type IN ${SHARE_TYPES_SQL} AND o.Type = 'group'`,
[],
);
await query(
`UPDATE ObjectBase p
JOIN ObjectBase o ON o.UID = p.UIDBelongsTo
SET p.UIDBelongsTo = p.UID
WHERE p.Type = 'project' AND o.Type = 'group'`,
[],
);
// ── 4. Projektion nachziehen ────────────────────────────────────────────
// `rag-sync` (rightsSync) projiziert `organization_id` und `root_uid` und
// liest dafuer den `eventLog`-Cursor. Ohne diese Events blieben die
// projizierten Werte auf dem alten Stand (root_uid = Org-Gruppe).
// Nur die tatsaechlich betroffenen Objekte werden gemeldet.
const { writeEventLog, buildEventPayload } = await import('../../utils/events.js');
const { getOrganizationForObject, getOrganizationForShare } = await import('../../utils/organizationUtils.js');
for (const project of affectedProjects) {
const uid = HEX2uuid(project.UID);
const organization = await getOrganizationForObject(project.UID);
await writeEventLog(`/change/project/${uid}`, buildEventPayload({ organization, data: [uid] }));
}
for (const share of affectedShares) {
const uid = HEX2uuid(share.UID);
const organization = await getOrganizationForShare(share.UID);
await writeEventLog(`/change/${share.Type}/${uid}`, buildEventPayload({ organization, data: [uid] }));
}
};