images: Lebenszyklus — Veröffentlichungsgate, published_ever_at, Snapshot der veröffentlichten Fassung, Sofortlöschung, Legacy-Bereinigung, Beweiszugriff, Korrekturweg (Bildkennzeichnung Block E)

- publish()/changeStatusFromAdmin(): kein Bild aus Formular v2 ohne gültige Bestätigung (ImageRightsConfirmationRequiredException, Admin-Seite meldet es); published_ever_at an PM und Bildern; PublishedImageSnapshot (Cover + JSON mit Nachweis/Kennzeichnung)
- deleteFromAdmin() und press-releases:purge-drafts löschen Bilder nie veröffentlichter Meldungen samt Original und Bestätigung sofort (E4, O1)
- press-releases:backfill-published-ever (Status-Log), press-release-images:purge-inactive-legacy (E-B11, Trockenlauf Standard)
- Admin-Route für Beweisoriginal und veröffentlichte Fassung nur mit Zweck, ActivityType EvidenceAccess; Architekturtest: original_path nur in vier Dateien
- ImageCorrectionService + image_declaration_corrections (insert-only), withheld_at blendet das Bild öffentlich aus (publiclyVisible-Scope), Korrekturpanel auf der Admin-Seite
- Systemstatus: fehlgeschlagene Herkunftsprüfungen der letzten 7 Tage

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-22 09:53:49 +00:00
parent 9f44546082
commit da167b713b
24 changed files with 1202 additions and 12 deletions

View file

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Bildkennzeichnung E.1 (Decision-Update S8, E5): „veröffentlicht" heißt,
* die Meldung hatte irgendwann eine öffentlich abrufbare URL — auch kurz.
* Der Zeitpunkt entscheidet, ob Bilder beim Entfernen ins Beweisarchiv
* gehen (drei Jahre) oder sofort gelöscht werden (nie veröffentlicht).
*
* `published_at` reicht dafür nicht: Es wird bei Terminierung überschrieben
* und bleibt nach Depublikation stehen, sagt aber nichts über Bilder, die
* später hinzukamen. Backfill über press-releases:backfill-published-ever.
*
* press_releases hat keinen FULLTEXT-Index (Suchtabelle getrennt), ADD
* COLUMN läuft inplace.
*/
return new class extends Migration
{
public function up(): void
{
Schema::table('press_releases', function (Blueprint $table) {
$table->timestamp('published_ever_at')->nullable()->after('published_at');
});
}
public function down(): void
{
Schema::table('press_releases', function (Blueprint $table) {
$table->dropColumn('published_ever_at');
});
}
};

View file

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Bildkennzeichnung E.6 (Decision-Update S17): Zwischenzustand „Bild
* vorläufig zurückgehalten" während ein Einwand gegen Nachweis oder
* Kennzeichnung geprüft wird. Gesetzt: Bild verschwindet aus der
* öffentlichen Seite, der Platzhalter greift; die Meldung bleibt online.
*/
return new class extends Migration
{
public function up(): void
{
Schema::table('press_release_images', function (Blueprint $table) {
$table->timestamp('withheld_at')->nullable()->after('customer_confirmation_requested_at');
});
}
public function down(): void
{
Schema::table('press_release_images', function (Blueprint $table) {
$table->dropColumn('withheld_at');
});
}
};

View file

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Korrekturweg (Bildkennzeichnung E.6, Decision-Update S17): Ein Admin
* ändert Bildnachweis oder Portalkennzeichnung mit Pflichtgrund. Alt/neu,
* Grund und Bearbeiter bleiben insert-only erhalten; die ursprüngliche
* Bestätigung und der ursprüngliche Befund werden nicht angefasst. Block F
* spiegelt jede Korrektur zusätzlich als Archiveintrag `label_corrected`.
*/
return new class extends Migration
{
public function up(): void
{
Schema::create('image_declaration_corrections', function (Blueprint $table) {
$table->id();
$table->foreignId('press_release_image_id')->nullable()->constrained()->nullOnDelete();
$table->foreignId('press_release_id')->nullable()->constrained()->nullOnDelete();
$table->foreignId('admin_id')->nullable()->constrained('users')->nullOnDelete();
$table->string('field', 40);
$table->text('old_value')->nullable();
$table->text('new_value')->nullable();
$table->string('old_source', 30)->nullable();
$table->string('reason', 1000);
$table->timestamp('created_at')->nullable();
$table->index(['press_release_image_id', 'created_at'], 'idc_image_created_idx');
});
}
public function down(): void
{
Schema::dropIfExists('image_declaration_corrections');
}
};