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>
This commit is contained in:
101
api/app/Policies/ArtistEngagementPolicy.php
Normal file
101
api/app/Policies/ArtistEngagementPolicy.php
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\ArtistEngagement;
|
||||||
|
use App\Models\Event;
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
final class ArtistEngagementPolicy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user, Event $event): bool
|
||||||
|
{
|
||||||
|
return $this->canViewProgram($user, $event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, ArtistEngagement $engagement, Event $event): bool
|
||||||
|
{
|
||||||
|
if ($engagement->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, ArtistEngagement $engagement, Event $event): bool
|
||||||
|
{
|
||||||
|
if ($engagement->event_id !== $event->id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->canManageProgram($user, $event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, ArtistEngagement $engagement, Event $event): bool
|
||||||
|
{
|
||||||
|
if ($engagement->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();
|
||||||
|
}
|
||||||
|
}
|
||||||
82
api/app/Policies/ArtistPolicy.php
Normal file
82
api/app/Policies/ArtistPolicy.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Enums\Artist\ArtistEngagementStatus;
|
||||||
|
use App\Models\Artist;
|
||||||
|
use App\Models\Organisation;
|
||||||
|
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):
|
||||||
|
* organisations.manage_artists — program_manager, org_admin, super_admin
|
||||||
|
* (read-only access — any authenticated organisation member)
|
||||||
|
*/
|
||||||
|
final class ArtistPolicy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user, Organisation $organisation): bool
|
||||||
|
{
|
||||||
|
return $user->hasRole('super_admin')
|
||||||
|
|| $organisation->users()->where('user_id', $user->id)->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, Artist $artist): bool
|
||||||
|
{
|
||||||
|
return $user->hasRole('super_admin')
|
||||||
|
|| $artist->organisation->users()->where('user_id', $user->id)->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user, Organisation $organisation): bool
|
||||||
|
{
|
||||||
|
return $this->canManageArtists($user, $organisation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, Artist $artist): bool
|
||||||
|
{
|
||||||
|
return $this->canManageArtists($user, $artist->organisation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Soft-delete is blocked while the artist still has any non-terminal
|
||||||
|
* engagement (D27 — soft-delete preserves historical engagements but
|
||||||
|
* the master must not vanish underneath an active booking).
|
||||||
|
*/
|
||||||
|
public function delete(User $user, Artist $artist): bool
|
||||||
|
{
|
||||||
|
if (! $this->canManageArtists($user, $artist->organisation)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasActive = $artist->engagements()
|
||||||
|
->whereNotIn('booking_status', [
|
||||||
|
ArtistEngagementStatus::Cancelled->value,
|
||||||
|
ArtistEngagementStatus::Rejected->value,
|
||||||
|
ArtistEngagementStatus::Declined->value,
|
||||||
|
])
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
return ! $hasActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restore(User $user, Artist $artist): bool
|
||||||
|
{
|
||||||
|
return $this->canManageArtists($user, $artist->organisation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function canManageArtists(User $user, Organisation $organisation): bool
|
||||||
|
{
|
||||||
|
if ($user->hasRole('super_admin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $organisation->users()
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->wherePivotIn('role', ['org_admin', 'program_manager'])
|
||||||
|
->exists();
|
||||||
|
}
|
||||||
|
}
|
||||||
59
api/app/Policies/GenrePolicy.php
Normal file
59
api/app/Policies/GenrePolicy.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\Genre;
|
||||||
|
use App\Models\Organisation;
|
||||||
|
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):
|
||||||
|
* organisations.manage_settings — org_admin, super_admin
|
||||||
|
* (read-only access — any authenticated organisation member)
|
||||||
|
*/
|
||||||
|
final class GenrePolicy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user, Organisation $organisation): bool
|
||||||
|
{
|
||||||
|
return $user->hasRole('super_admin')
|
||||||
|
|| $organisation->users()->where('user_id', $user->id)->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, Genre $genre): bool
|
||||||
|
{
|
||||||
|
return $user->hasRole('super_admin')
|
||||||
|
|| $genre->organisation->users()->where('user_id', $user->id)->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user, Organisation $organisation): bool
|
||||||
|
{
|
||||||
|
return $this->canManageSettings($user, $organisation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, Genre $genre): bool
|
||||||
|
{
|
||||||
|
return $this->canManageSettings($user, $genre->organisation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, Genre $genre): bool
|
||||||
|
{
|
||||||
|
return $this->canManageSettings($user, $genre->organisation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function canManageSettings(User $user, Organisation $organisation): bool
|
||||||
|
{
|
||||||
|
if ($user->hasRole('super_admin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $organisation->users()
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->wherePivot('role', 'org_admin')
|
||||||
|
->exists();
|
||||||
|
}
|
||||||
|
}
|
||||||
110
api/app/Policies/PerformancePolicy.php
Normal file
110
api/app/Policies/PerformancePolicy.php
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<?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();
|
||||||
|
}
|
||||||
|
}
|
||||||
106
api/app/Policies/StagePolicy.php
Normal file
106
api/app/Policies/StagePolicy.php
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\Event;
|
||||||
|
use App\Models\Stage;
|
||||||
|
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 reorder + day matrix)
|
||||||
|
*/
|
||||||
|
final class StagePolicy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user, Event $event): bool
|
||||||
|
{
|
||||||
|
return $this->canViewProgram($user, $event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, Stage $stage, Event $event): bool
|
||||||
|
{
|
||||||
|
if ($stage->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, Stage $stage, Event $event): bool
|
||||||
|
{
|
||||||
|
if ($stage->event_id !== $event->id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->canManageProgram($user, $event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, Stage $stage, Event $event): bool
|
||||||
|
{
|
||||||
|
if ($stage->event_id !== $event->id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->canManageProgram($user, $event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reorder(User $user, Event $event): bool
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user