User Panel: Phase-8-Abschluss, Titelbild/Lizenzen/Zeitzonen und KI-Pruef-Pipeline
Phase 8 (Rest) + Umbauten vom 10./11.06.: - Ein Titelbild pro PM (Cover 1280x580), SVG-Platzhalter-Set + Picker, PressReleaseCoverImage-Resolver - Lizenz-/Rechteformular nach "Lizenztyp Bildupload" (7 Lizenztypen, Personen-/Sachrechte-Status, bedingte Pflichtfelder, Risikohinweise) - Veroeffentlichungs-Box vereinfacht (Embargo aus der Form-UI entfernt), geplante Termine in Europe/Berlin (Speicherung UTC, DISPLAY_TIMEZONE) - Quota-Stub (users.press_release_quota) + monatlicher Reset-Command - Einreichungs-Modal einheitlich in Show/Create/Edit; Ghost-Buttons auf filled; PM-Editor-Layout responsive entkoppelt (.pr-editor-layout) KI-Pruef-Pipeline (Phasen 1-5 des Entwicklungsplans): - API-Haertung: status nicht mehr per API setzbar, eigene Submit-Route durch denselben Funnel (Blacklist, Quota, Status-Log) - Klassifikation Rot/Gelb/Gruen asynchron (Queue classification, OpenAI-Treiber + deterministischer Fallback), ki_audits-Audit-Log - Routing: Rot -> rejected + Mail, Gelb -> Review-Queue, Gruen -> Auto-Publish; Scheduler publiziert nur gruene faellige PMs - Content-Score 0-100 -> Stufe (Standard/Geprueft/Hochwertig) inkl. Editor-Panel und Badges; Re-Klassifikation/-Score bei Aenderung - Admin: KI-Badge + Filter, On-Demand-Pruefung mit Anbieter-Override Suite: 442 passed, 4 skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0efabaf446
commit
a000238ca8
141 changed files with 5922 additions and 1001 deletions
|
|
@ -3,6 +3,9 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Enums\Portal;
|
||||
use App\Enums\PressReleaseClassification;
|
||||
use App\Enums\PressReleaseContentTier;
|
||||
use App\Enums\PressReleasePlaceholder;
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Models\Concerns\HasUniqueSlug;
|
||||
use App\Scopes\PortalScope;
|
||||
|
|
@ -14,6 +17,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class PressRelease extends Model
|
||||
|
|
@ -21,6 +25,13 @@ class PressRelease extends Model
|
|||
/** @use HasFactory<PressReleaseFactory> */
|
||||
use HasFactory, HasUniqueSlug, SoftDeletes;
|
||||
|
||||
/**
|
||||
* Anzeige-Zeitzone für vom Nutzer erfasste Termine (scheduled_at,
|
||||
* embargo_at). In der Datenbank wird weiterhin UTC gespeichert
|
||||
* (config app.timezone).
|
||||
*/
|
||||
public const DISPLAY_TIMEZONE = 'Europe/Berlin';
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
|
|
@ -37,6 +48,13 @@ class PressRelease extends Model
|
|||
protected static function booted(): void
|
||||
{
|
||||
static::addGlobalScope(new PortalScope);
|
||||
|
||||
static::creating(function (self $pressRelease): void {
|
||||
if (blank($pressRelease->placeholder_variant)) {
|
||||
$seed = $pressRelease->uuid ?? $pressRelease->title ?? (string) now()->timestamp;
|
||||
$pressRelease->placeholder_variant = PressReleasePlaceholder::fromSeed($seed)->value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected $fillable = [
|
||||
|
|
@ -51,9 +69,15 @@ class PressRelease extends Model
|
|||
'slug',
|
||||
'text',
|
||||
'boilerplate_override',
|
||||
'placeholder_variant',
|
||||
'backlink_url',
|
||||
'keywords',
|
||||
'status',
|
||||
'classification',
|
||||
'classified_at',
|
||||
'content_score',
|
||||
'content_tier',
|
||||
'scored_at',
|
||||
'hits',
|
||||
'teaser_begin',
|
||||
'teaser_end',
|
||||
|
|
@ -69,7 +93,13 @@ class PressRelease extends Model
|
|||
{
|
||||
return [
|
||||
'portal' => Portal::class,
|
||||
'placeholder_variant' => PressReleasePlaceholder::class,
|
||||
'status' => PressReleaseStatus::class,
|
||||
'classification' => PressReleaseClassification::class,
|
||||
'classified_at' => 'datetime',
|
||||
'content_score' => 'integer',
|
||||
'content_tier' => PressReleaseContentTier::class,
|
||||
'scored_at' => 'datetime',
|
||||
'hits' => 'integer',
|
||||
'teaser_begin' => 'integer',
|
||||
'teaser_end' => 'integer',
|
||||
|
|
@ -81,6 +111,22 @@ class PressRelease extends Model
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Geplanter Veröffentlichungstermin in der Anzeige-Zeitzone (Europe/Berlin).
|
||||
*/
|
||||
public function scheduledAtLocal(): ?Carbon
|
||||
{
|
||||
return $this->scheduled_at?->copy()->setTimezone(self::DISPLAY_TIMEZONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sperrfrist (Embargo) in der Anzeige-Zeitzone (Europe/Berlin).
|
||||
*/
|
||||
public function embargoAtLocal(): ?Carbon
|
||||
{
|
||||
return $this->embargo_at?->copy()->setTimezone(self::DISPLAY_TIMEZONE);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
|
|
@ -116,6 +162,11 @@ class PressRelease extends Model
|
|||
return $this->hasMany(PressReleaseStatusLog::class)->orderByDesc('created_at');
|
||||
}
|
||||
|
||||
public function kiAudits(): HasMany
|
||||
{
|
||||
return $this->hasMany(KiAudit::class)->orderByDesc('created_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display-ready text. Returns sanitized HTML for Phase-7+ PMs and
|
||||
* <p>/<br>-wrapped legacy plain text for older imports.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue