WS-2: Firmen-Scope für PMs & Magic-Link-Zugang für Pressekontakte
Firmen-Scope (Fundament): - PM-Zugriff war hart an user_id (Autor) gebunden. Jetzt additiv: Autor ODER Mitglied der zugeordneten Firma (Owner via owner_user_id oder company_user- Pivot). Geändert in PressReleasePolicy (canManage) sowie den Queries der Listen-, Show- und Edit-Komponenten. Helfer User::accessibleCompanyIds()/ canAccessCompany(). Solo-Owner unverändert; Firmenmitglieder sehen/bearbeiten alle PMs ihrer Firma. Magic-Link-Zugang für Pressekontakte (ContactAccessService): - Öffentliches, enumeration-sicheres Formular (/pressekontakt-zugang) mit Honeypot + Rate-Limit. Eine hinterlegte Kontakt-E-Mail führt zu einem lazy angelegten, de-duplizierten customer-Account (aktiv, verifiziert über den Magic-Link-Kanal), der den Firmen seiner Kontakte als Mitglied zugeordnet wird. Versand über den bestehenden Login-Magic-Link (Generator + Consume wiederverwendet) – keine Schema-Änderung, kein paralleles System. - Dezenter Einstiegslink von der Login-Seite (PM-Frontend-Wiring später). Tests: PressReleaseCompanyScopeTest (3), ContactAccessTest (6, inkl. De-Dup, Enumeration-Sicherheit, Honeypot). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
94cb209a9f
commit
980763c362
11 changed files with 493 additions and 7 deletions
|
|
@ -630,8 +630,16 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
{
|
||||
// Pro Livewire-Request memoisiert: mount(), with() und save() greifen
|
||||
// sonst jeweils mit einer eigenen Query auf dieselbe PM zu.
|
||||
$user = auth()->user();
|
||||
|
||||
return $this->cachedPressRelease ??= PressRelease::withoutGlobalScopes()
|
||||
->where('user_id', auth()->id())
|
||||
->where(function ($q) use ($user): void {
|
||||
$q->where('user_id', $user->id);
|
||||
$companyIds = $user->accessibleCompanyIds();
|
||||
if ($companyIds !== []) {
|
||||
$q->orWhereIn('company_id', $companyIds);
|
||||
}
|
||||
})
|
||||
->findOrFail($this->id);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,9 +113,16 @@ new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class
|
|||
$userId = auth()->id();
|
||||
$context = app(CustomerCompanyContext::class);
|
||||
$selectedCompanyId = $context->selectedCompanyId(auth()->user());
|
||||
// Firmen-Scope: eigene PMs (Autor) ODER PMs der zugeordneten Firmen.
|
||||
$accessibleCompanyIds = auth()->user()->accessibleCompanyIds();
|
||||
|
||||
$base = PressRelease::withoutGlobalScopes()
|
||||
->where('user_id', $userId)
|
||||
->where(function ($q) use ($userId, $accessibleCompanyIds): void {
|
||||
$q->where('user_id', $userId);
|
||||
if ($accessibleCompanyIds !== []) {
|
||||
$q->orWhereIn('company_id', $accessibleCompanyIds);
|
||||
}
|
||||
})
|
||||
->when($selectedCompanyId !== null, fn($q) => $q->where('company_id', $selectedCompanyId))
|
||||
->when($selectedCompanyId === null && $this->companyFilter === 'assigned', fn($q) => $q->whereNotNull('company_id'))
|
||||
->when($selectedCompanyId === null && $this->companyFilter === 'unassigned', fn($q) => $q->whereNull('company_id'));
|
||||
|
|
|
|||
|
|
@ -176,8 +176,16 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
|
||||
private function getMyPR(): PressRelease
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
return PressRelease::withoutGlobalScopes()
|
||||
->where('user_id', auth()->id())
|
||||
->where(function ($q) use ($user): void {
|
||||
$q->where('user_id', $user->id);
|
||||
$companyIds = $user->accessibleCompanyIds();
|
||||
if ($companyIds !== []) {
|
||||
$q->orWhereIn('company_id', $companyIds);
|
||||
}
|
||||
})
|
||||
->with([
|
||||
'company:id,name,email,phone,address,portal,logo_path,legacy_portal,is_active,type',
|
||||
'category.translations',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue