feat: Erinnerung zum Sichern von Story-Insights vor Ablauf

Ausgangspunkt: Instagram hält Story-Insights nach eigener Aussage nur
24 Stunden vor, auch der offizielle Datenexport enthält sie nicht mehr
danach. Der Standbild-Screenshot beim Prüfen entsteht direkt beim
Veröffentlichen, bevor nennenswerte Kennzahlen existieren — er kann
das strukturell nicht auffangen. Eine OAuth-Anbindung allein löst das
auch nicht: selbst mit API-Zugriff bräuchte es einen Abruf innerhalb
desselben 24h-Fensters.

- Migration 0007: asset.purpose ('initial' | 'insights', Default
  'initial' erhält die Bedeutung aller Bestandszeilen). Ein Beitrag
  kann jetzt mehrere Insights-Nachweise über die Zeit bekommen.
  GetLatestAssetForSubmission berücksichtigt weiterhin nur 'initial',
  damit ein späterer Insights-Upload nie den beim Archivieren
  referenzierten Original-Screenshot verdrängt.
- internal/web/insights_reminder.go: computeInsightsReminder — reine,
  ungetestete gegen echte Instagram-Daten, aber isoliert testbare
  Logik fürs Erinnerungs-Timing (Produktentscheidung, keine Rechtsnorm,
  daher nicht in rules/*.yaml).
- GET /beitraege/{id} zeigt die Erinnerung bei veröffentlichten
  "story"-Beiträgen ohne existierendes insights-Asset; POST
  /beitraege/{id}/insights speichert einen weiteren Screenshot (gleiche
  Validierung wie das initiale Standbild, wiederverwendet über
  readUploadedAsset/storeAsset mit purpose-Parameter).
- Bewusst nur In-App-Banner in dieser Ausbaustufe, kein Mail-/Push-
  Versand — dafür fehlt aktuell ein SMTP-Relay/Versanddienst, siehe
  CLAUDE.md-Hinweis dazu.

Volle Testsuite inkl. echter Postgres-Tests grün; End-to-End gegen
einen laufenden Server verifiziert (Story archivieren → Erinnerung
sichtbar → Insights-Upload → Erinnerung verschwindet, Nachweis
gelistet, Mandantentrennung beim Upload durchgesetzt).
This commit is contained in:
noroot
2026-08-28 10:10:44 +02:00
parent 5813e6209c
commit 6fd7831784
13 changed files with 512 additions and 56 deletions

View File

@@ -35,6 +35,28 @@
{{end}}
{{if .IsPublished}}
<p><a href="{{.DossierURL}}">Nachweis-Dossier (PDF) herunterladen</a></p>
{{if .InsightsReminder.Show}}
<div class="{{if .InsightsReminder.Expired}}fehler{{else}}rueckfrage{{end}}">
<p>{{.InsightsReminder.Message}}</p>
<form method="post" action="/beitraege/{{.SubmissionID}}/insights" enctype="multipart/form-data">
<label for="insights_standbild">Insights-Screenshot</label>
<input type="file" id="insights_standbild" name="insights_standbild" accept="image/*" required>
<button type="submit">Insights jetzt sichern</button>
</form>
</div>
{{end}}
{{if .InsightsAssets}}
<h2>Gesicherte Insights-Nachweise</h2>
<ul class="beteiligte">
{{range .InsightsAssets}}
<li class="beteiligter">
<div class="beteiligter-kopf">{{.CreatedAt}} — SHA-256: {{.SHA256}}</div>
</li>
{{end}}
</ul>
{{end}}
{{end}}
<h2>Verantwortungsmatrix</h2>