feat(timetable): 13 form requests for artist domain endpoints
Created under app/Http/Requests/Api/V1/Artist/, mirroring the
existing FormRequest pattern (final class, authorize() returns true,
controller-level Gate::authorize). One request per CRUD shape plus the
two domain-specific endpoints:
artists create / update
genres create / update (with org-scoped unique)
stages create / update (with event-scoped unique)
stages/order ReorderStagesRequest — permutation check
engagements create / update — per RFC §10.3, with
ContractRequiresFee + OptionExpiresInFuture
conditional rules wired
performances create / update — per §10.2; cross-FK
engagement.event_id ↔ event_id chain
enforced via withValidator closure;
update is non-placement only (placement
edits go through /timetable/move)
timetable/move per §10.4; resolves target_event_id from
target_stage_id + target_start_at via
stage_days, then reuses StageActiveOnEvent
+ WithinEventBounds for downstream rules
stages/{stage}/days §10.5 matrix replace; each event_id must
equal stage.event_id (flat) or be sub-event
(festival)
Custom error messages in Dutch where user-facing. Cross-FK rules that
span request inputs (engagement vs event-id chain, day matrix sub-event
membership) live in withValidator after-closures so the rule cache is
stable per request.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use App\Models\ArtistEngagement;
|
||||
use App\Models\Event;
|
||||
use App\Rules\Artist\StageActiveOnEvent;
|
||||
use App\Rules\Artist\WithinEventBounds;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* RFC v0.2 §10.2.
|
||||
*/
|
||||
final class CreatePerformanceRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$event = $this->route('event');
|
||||
$organisationId = $event instanceof Event ? $event->organisation_id : null;
|
||||
$eventIdInput = (string) $this->input('event_id', '');
|
||||
|
||||
return [
|
||||
'engagement_id' => [
|
||||
'required', 'string', 'max:30',
|
||||
Rule::exists('artist_engagements', 'id')->where('organisation_id', $organisationId),
|
||||
],
|
||||
'event_id' => [
|
||||
'required', 'string', 'max:30',
|
||||
Rule::exists('events', 'id')->where('organisation_id', $organisationId),
|
||||
],
|
||||
'stage_id' => [
|
||||
'nullable', 'string', 'max:30',
|
||||
Rule::exists('stages', 'id'),
|
||||
new StageActiveOnEvent($eventIdInput),
|
||||
],
|
||||
'start_at' => ['required', 'date_format:Y-m-d H:i:s', new WithinEventBounds($eventIdInput)],
|
||||
'end_at' => ['required', 'date_format:Y-m-d H:i:s', 'after:start_at', new WithinEventBounds($eventIdInput)],
|
||||
'lane' => ['nullable', 'integer', 'min:0', 'max:9'],
|
||||
'notes' => ['nullable', 'string', 'max:1000'],
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(function (Validator $validator): void {
|
||||
$engagementId = $this->input('engagement_id');
|
||||
$eventId = $this->input('event_id');
|
||||
if (! is_string($engagementId) || ! is_string($eventId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$engagement = ArtistEngagement::query()->find($engagementId);
|
||||
if ($engagement === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event = Event::withoutGlobalScopes()->find($eventId);
|
||||
if ($event === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// event_id must equal engagement.event_id (flat case) OR be a
|
||||
// sub-event of engagement.event_id (festival case).
|
||||
if (
|
||||
$eventId !== $engagement->event_id
|
||||
&& $event->parent_event_id !== $engagement->event_id
|
||||
) {
|
||||
$validator->errors()->add(
|
||||
'event_id',
|
||||
'event_id moet gelijk zijn aan de engagement.event_id of een sub-event daarvan.',
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user