Backend-WIP: Kontakt-Queue, Page-Cache, Validierungs-Feedback, i18n

Gebuendelt, da ueber gemeinsame lang-/Service-Dateien verschraenkt:
- Kontakt-Nachrichten: contact_messages-Tabelle + Model/Enums/Factory,
  Admin-Queue (livewire/admin/contact-messages), ContactController
  persistiert das Anliegen zusaetzlich zur Brand-Mail
- Page-Data-Cache (Services/Web/*PageData) + web-archive-Indizes auf
  press_releases
- Validierungs-Feedback + Company-Portal-Restriction (Feature-Tests)
- i18n-Auslagerung lang/de|en (auth, passwords, validation, static)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-09 15:38:11 +00:00
parent c6b5ec8479
commit fd8e050cb7
60 changed files with 3963 additions and 327 deletions

View file

@ -6,6 +6,8 @@ use App\Enums\SinglePurchaseStatus;
use App\Enums\SinglePurchaseType;
use App\Exceptions\InsufficientCreditsException;
use App\Exceptions\ProofPdfNotAvailableException;
use App\Models\Company;
use App\Models\Contact;
use App\Models\Plan;
use App\Models\PressRelease;
use App\Models\User;
@ -88,6 +90,61 @@ test('the rendered proof is a non-trivial pdf document', function () {
expect(strlen($pdf))->toBeGreaterThan(800);
});
test('the rendered proof contains the full release, boilerplate, press contact and publisher', function () {
$company = Company::factory()->create([
'name' => 'Musterfirma GmbH',
'address' => 'Beispielweg 1, 10115 Berlin',
'boilerplate' => 'Die Musterfirma ist ein Beispielunternehmen mit Standardabbinder.',
'website' => 'https://musterfirma.example',
]);
$contact = Contact::factory()->create([
'company_id' => $company->id,
'first_name' => 'Max',
'last_name' => 'Mustermann',
'responsibility' => 'Pressesprecher',
'email' => 'max@musterfirma.example',
'phone' => '030 1234567',
]);
$pr = PressRelease::factory()->published()->create([
'company_id' => $company->id,
'title' => 'Grosse Ankuendigung',
'subtitle' => 'Ein aussagekraeftiger Untertitel',
'text' => '<p>Erster Absatz der Meldung.</p><p>Zweiter Absatz mit Schlusswort.</p>',
]);
$pr->contacts()->attach($contact->id);
$pdf = app(ProofPdfRenderer::class)->render($pr);
expect($pdf)
->toContain('Grosse Ankuendigung')
->toContain('Untertitel')
->toContain('Erster Absatz der Meldung.')
->toContain('Schlusswort.')
->toContain('Standardabbinder.')
->toContain('Mustermann')
->toContain('Pressesprecher')
->toContain('max@musterfirma.example')
->toContain('Musterfirma GmbH')
->toContain('10115 Berlin')
->toContain('https://musterfirma.example');
});
test('a long release flows over multiple numbered pages', function () {
$pr = PressRelease::factory()->published()->create([
'title' => 'Lange Meldung',
'text' => implode('', array_map(
static fn (int $i): string => '<p>Absatz '.$i.': '.str_repeat('Dies ist ein ausfuehrlicher Beispielsatz. ', 12).'</p>',
range(1, 12),
)),
]);
$pdf = app(ProofPdfRenderer::class)->render($pr);
expect(substr_count($pdf, '/Type /Page /Parent'))->toBeGreaterThanOrEqual(2);
expect($pdf)->toContain('Seite 1 von');
expect($pdf)->toContain('Fortsetzung');
});
test('the rendered proof documents the real public url of the release', function () {
$pr = PressRelease::factory()->published()->create([
'portal' => Portal::Presseecho,