Files
crewli/api/app/Policies/PerformancePolicy.php
bert.hausmans 05e44a39ae feat(timetable): add 5 artist-domain policies
ArtistPolicy, ArtistEngagementPolicy, StagePolicy, PerformancePolicy,
GenrePolicy. Role-based authorization mirroring PersonPolicy/ShiftPolicy
pattern: super_admin bypass, org-membership check via wherePivotIn,
event_manager fallback for event-level operations.

Each policy carries a class-level docblock mapping the RFC §9
permission strings (events.view_program, events.manage_program,
organisations.manage_artists, organisations.manage_settings) to the
roles authorised, deferring permission-based authorisation to
AUTH-PERMISSIONS-MIGRATION.

ArtistPolicy.delete additionally guards on no-active-engagements
(D27): blocks soft-delete while any engagement is not Cancelled,
Rejected, or Declined.

PerformancePolicy.move and StagePolicy.reorder reuse canManageProgram
so the move endpoint and stage-reorder share the manage_program
permission semantics.

Auto-discovered by Laravel 11 (policies live at App\Policies\* matching
top-level App\Models\* — no explicit Gate::policy registration needed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 20:45:46 +02:00

111 lines
3.0 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Event;
use App\Models\Performance;
use App\Models\User;
/**
* Authorization model — role-based per existing codebase pattern.
*
* RFC v0.2 §9 permission mapping (deferred to AUTH-PERMISSIONS-MIGRATION
* when fine-grained permissions are operationally required):
* events.view_program — program_manager, production_assistant,
* event_manager, org_admin, super_admin
* events.manage_program — program_manager, event_manager, org_admin,
* super_admin (covers move / park / unpark)
*/
final class PerformancePolicy
{
public function viewAny(User $user, Event $event): bool
{
return $this->canViewProgram($user, $event);
}
public function view(User $user, Performance $performance, Event $event): bool
{
if ($performance->event_id !== $event->id) {
return false;
}
return $this->canViewProgram($user, $event);
}
public function create(User $user, Event $event): bool
{
return $this->canManageProgram($user, $event);
}
public function update(User $user, Performance $performance, Event $event): bool
{
if ($performance->event_id !== $event->id) {
return false;
}
return $this->canManageProgram($user, $event);
}
public function delete(User $user, Performance $performance, Event $event): bool
{
if ($performance->event_id !== $event->id) {
return false;
}
return $this->canManageProgram($user, $event);
}
public function move(User $user, Performance $performance, Event $event): bool
{
if ($performance->event_id !== $event->id) {
return false;
}
return $this->canManageProgram($user, $event);
}
private function canViewProgram(User $user, Event $event): bool
{
if ($user->hasRole('super_admin')) {
return true;
}
$isOrgMember = $event->organisation->users()
->where('user_id', $user->id)
->wherePivotIn('role', ['org_admin', 'program_manager', 'production_assistant'])
->exists();
if ($isOrgMember) {
return true;
}
return $event->users()
->where('user_id', $user->id)
->wherePivot('role', 'event_manager')
->exists();
}
private function canManageProgram(User $user, Event $event): bool
{
if ($user->hasRole('super_admin')) {
return true;
}
$isOrgManager = $event->organisation->users()
->where('user_id', $user->id)
->wherePivotIn('role', ['org_admin', 'program_manager'])
->exists();
if ($isOrgManager) {
return true;
}
return $event->users()
->where('user_id', $user->id)
->wherePivot('role', 'event_manager')
->exists();
}
}