Source: config/migrations/20260924-project-share-access-backfill.js

// @ts-check
/**
 * AP 30 — Rechte-Vererbung fuer Bestands-Shares materialisieren.
 *
 * Neue Shares ziehen ihren Rechte-Satz beim Anlegen/Verlinken nach
 * (`./20260924-project-share-target-model` nur strukturell, die Vererbung selbst
 * in `RouterProject/projectShare/access.js`). Der **Bestand** hat nur die
 * Owner-Zeile: die Projekt-Rechte sind nirgends am Share materialisiert, und es
 * gibt kein Rechte-Event, aus dem eine Projektion sie gewinnen koennte.
 *
 * Dieses Modul holt das nach — je Share:
 *  1. den Filter-Satz des Projekts spiegeln (dieselbe Struktur wie bei Listen),
 *  2. `Visible` neu materialisieren und die `/add|/remove/{shareType}/{level}/{uid}`-
 *     Events melden (macht `listRebuildAccess` selbst).
 *
 * Damit wird `share_grants` in der RAG-Projektion **ableitbar** statt gerechnet:
 * die Kette Members (`Visible`) → Event → Projektion steht dann lueckenlos.
 *
 * Idempotent. Die Organisation kommt ueber die Link-Kette
 * (`getOrganizationForShare`) — das Modul enthaelt keine UID.
 */

export const id = '20260924-project-share-access-backfill';
export const name = 'Rechte-Vererbung: Visible/Events fuer Bestands-Shares materialisieren';

/** Enum-Werte der Share-Objekte in `ObjectBase.Type`. */
const SHARE_TYPES_SQL = `('repositoryShare','directoryShare')`;

/**
 * Fuehrt den Backfill aus.
 *
 * @param {{ query: Function, HEX2uuid: Function }} api
 * @returns {Promise<void>}
 */
export const migrate = async ({ query, HEX2uuid }) => {
    // Nur Shares, die an (mindestens) einem Projekt haengen — alle anderen haben
    // nichts zu erben. Beide Link-Richtungen (alt: Projekt -> Share, neu: umgekehrt).
    const shares = await query(
        `SELECT DISTINCT s.UID, s.Type FROM ObjectBase AS s
           JOIN Links AS l ON (l.UID = s.UID AND l.Type = 'memberA'
                               AND l.UIDTarget IN (SELECT UID FROM ObjectBase WHERE Type = 'project'))
          WHERE s.Type IN ${SHARE_TYPES_SQL}
         UNION
         SELECT DISTINCT s.UID, s.Type FROM ObjectBase AS s
           JOIN Links AS l ON (l.UIDTarget = s.UID AND l.Type IN ('memberA','member','projectShare')
                               AND l.UID IN (SELECT UID FROM ObjectBase WHERE Type = 'project'))
          WHERE s.Type IN ${SHARE_TYPES_SQL}`,
        [],
    );
    if (shares.length === 0) return;

    // Beide Helfer erst hier laden: die Migration laeuft beim Start, und der
    // Baum-/Queue-Code soll das Modul nicht bei jedem Import mitziehen.
    const { syncShareFilters } = await import('../../RouterProject/projectShare/access.js');
    const { listRebuildAccess } = await import('../../tree/rebuildList.js');
    const { getOrganizationForShare } = await import('../../utils/organizationUtils.js');

    const seen = new Set();
    for (const share of shares) {
        const uid = HEX2uuid(share.UID);
        if (!uid || seen.has(uid)) continue;
        seen.add(uid);

        const organization = await getOrganizationForShare(share.UID);
        if (!organization) continue;

        // 1. Struktur: Projekt-Filter auf den Share spiegeln.
        await syncShareFilters(share.UID);

        // 2. Materialisieren + Events. `listRebuildAccess` loescht die Zeilen
        //    des Targets und leitet sie aus den (jetzt gespiegelten) Filtern
        //    neu ab; die `member`-Verbindung des Shares sorgt fuer die
        //    Owner-Zeile, `memberSys` fuer den Org-Superadmin.
        await listRebuildAccess(share.UID, organization);
    }
};