# Share SDK

<Lead>
**`platform.share`** ist der eine Teilen-Service der Plattform: ein System für Token-Erzeugung, Ablauf, Passwort-Gate, Widerruf und View-Zählung. Tools bringen nur ihren Renderer für die öffentliche Seite mit; die Link-Mechanik bauen sie nie selbst.
</Lead>

Der Service ist live und wird vom zentralen Dateispeicher genutzt; weitere Tools hängen ihre Freigaben an dieselbe Mechanik. Die Tool-Perspektive (Datei teilen) zeigt die [Dateispeicher-Seite](/dev/sdk/files); hier steht die Service-API.

## Link anlegen

```typescript
import { createShareService } from "@reosa/sdk";

const share = createShareService(db);

const link = await share.create(ctx, {
  resourceType: "file_node",     // frei wählbarer, stabiler Typ-String
  resourceId: node.id,
  expiresInDays: 30,             // 1..90, Default 7
  passwordHash,                  // optional: vorberechneter Hash, nie Klartext
  allowDownload: true,           // Default true; false = nur ansehen
  maxViews: 100,                 // optional: Ansichts-Limit
});
// link: { id, token, expiresAt }
// link.token ist NUR hier verfügbar; öffentlicher Pfad: /share/<token>
```

- **Token-Sicherheit:** Der Token ist 256 Bit aus dem CSPRNG, URL-safe. Gespeichert wird ausschließlich sein SHA-256-Hash; der Klartext existiert nur in dieser einen Antwort. Zeige ihn der Nutzer:in genau einmal.
- **Ablauf:** `expiresInDays` erlaubt ganze Tage von 1 bis 90 (die Datei-Freigabe-UI bietet daraus feste Stufen an). Alternativ setzt `expiresAt` einen absoluten Zeitpunkt, für interne Consumer mit Sub-Tages-Ablauf (z.B. 1-Stunden-Links); er muss in der Zukunft und innerhalb von 366 Tagen liegen. `noExpiry: true` (Link läuft nie ab) existiert nur für Alt-Consumer; neue Flächen setzen es nicht.
- **Passwort:** Das Hashing bleibt beim Aufrufer (App-Utility); der Service bekommt nur den fertigen Hash und speichert nie Klartext.

## Widerrufen

```typescript
await share.revoke(ctx, link.id);                          // ein Link, sofort wirksam
await share.revokeForResource(ctx, "file_node", node.id);  // alle Links einer Ressource
await share.revokeAll(ctx);                                // alle aktiven Links des Tenants
```

Widerruf wirkt sofort; die öffentliche Seite zeigt danach dieselbe neutrale Meldung wie für nie existierende Links. Beim endgültigen Löschen einer Ressource gehört `revokeForResource` in deinen Lösch-Pfad.

## Übersichten und Badges

```typescript
await share.listForResource(ctx, "file_node", node.id);       // alle Links, neueste zuerst
await share.listByResourceTypes(ctx, ["hs_generation"]);      // tool-eigene Teilen-Liste
await share.listActiveResourceIds(ctx, "file_node", nodeIds); // welche IDs haben aktive Links?
```

`listActiveResourceIds` ist gebatcht und gedacht für Link-Badges in Listen ("dieses Element ist geteilt"), ohne pro Zeile eine Abfrage zu machen.

## Öffentliche Auflösung und Zähler

Die öffentliche Seite (der Empfänger hat keine Session) löst über `resolveByToken(token)` auf und verbucht Ansichten mit `recordView(share)`: `view_count` und `last_viewed_at` zählen atomar hoch, das Ansichts-Limit wird race-sicher durchgesetzt, und der **erste** View schreibt ein Audit-Event (`share_viewed`, Empfänger bleibt anonym). Gültigkeit (widerrufen, abgelaufen, Limit erreicht) und das Passwort-Gate prüft die aufrufende Seite. Diese beiden Pfade laufen über die Service-Connection der Plattform; Tool-Code ruft sie nicht direkt auf.

## Regeln

- **Token nie speichern oder loggen.** Nur der Hash liegt in der Datenbank; auch dein Tool hält den Klartext nirgends fest.
- **Kein eigener Link-Mechanismus.** Ein Tool, das teilt, nutzt diesen Service, keine selbstgebauten Token-Tabellen.
- **Widerruf in Lösch-Pfade einbauen.** Eine gelöschte Ressource darf keinen lebenden Link zurücklassen.
- **Neutrale Fehlerseite respektieren:** ungültig, abgelaufen und widerrufen sind für Außenstehende nicht unterscheidbar.
