feat(timetable): EngagementPortalController + /p/artist/{token}/* routes
Three backend endpoints under public throttle:30,1:
GET /p/artist/{token} — engagement summary + sections
GET /p/artist/{token}/sections/{section} — form schema + draft values
POST /p/artist/{token}/sections/{section} — section submit
Token resolution via ArtistResolver::fromPortalToken (Step 2). The
master Artist becomes the FormSubmission subject; engagement.event_id
populates form_submissions.event_id per WS-4 denormalisation. Token
mismatches map to 404 (InvalidPortalTokenException), soft-deleted
master artists to 410 Gone (ArtistDeletedException).
Section submit reuses the existing FormBindingApplicator pipeline
(RFC-WS-6 v1.3.1) by dispatching FormSubmissionSectionSubmitted —
no parallel apply path. Drafts are idempotent on
'artist_advance:{engagement_id}', so repeated POSTs find the same
submission. AdvanceSection (engagement-scoped) ↔ FormSchemaSection
bridge: case-sensitive name match against the org's artist_advance
schema; the default seeder names them in lockstep.
Frontend in Session 5 — backend complete here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1\Portal;
|
||||
|
||||
use App\Models\AdvanceSection;
|
||||
use App\Models\Artist;
|
||||
use App\Models\ArtistEngagement;
|
||||
use App\Models\Event;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* Public payload returned by GET /p/artist/{token}.
|
||||
*
|
||||
* Carries just enough for the portal to render the section
|
||||
* navigation: artist + event identification, plus the engagement's
|
||||
* AdvanceSection rows with their submission_status and ordering.
|
||||
*
|
||||
* @property ArtistEngagement $resource
|
||||
*/
|
||||
final class EngagementPortalResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$engagement = $this->resource;
|
||||
$artist = $engagement->getRelation('artist');
|
||||
$event = $engagement->getRelation('event');
|
||||
|
||||
/** @var \Illuminate\Database\Eloquent\Collection<int, AdvanceSection> $sections */
|
||||
$sections = $engagement->getRelation('advanceSections');
|
||||
|
||||
return [
|
||||
'engagement_id' => (string) $engagement->id,
|
||||
'artist' => $artist instanceof Artist ? [
|
||||
'id' => (string) $artist->id,
|
||||
'name' => (string) $artist->name,
|
||||
] : null,
|
||||
'event' => $event instanceof Event ? [
|
||||
'id' => (string) $event->id,
|
||||
'name' => (string) $event->name,
|
||||
] : null,
|
||||
'advancing_completed_count' => (int) $engagement->advancing_completed_count,
|
||||
'advancing_total_count' => (int) $engagement->advancing_total_count,
|
||||
'sections' => $sections
|
||||
->sortBy('sort_order')
|
||||
->values()
|
||||
->map(static fn (AdvanceSection $section): array => [
|
||||
'id' => (string) $section->id,
|
||||
'name' => (string) $section->name,
|
||||
'type' => $section->getRawOriginal('type'),
|
||||
'sort_order' => (int) $section->sort_order,
|
||||
'is_open' => (bool) $section->is_open,
|
||||
'submission_status' => $section->getRawOriginal('submission_status'),
|
||||
'last_submitted_at' => $section->getRawOriginal('last_submitted_at'),
|
||||
])
|
||||
->all(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user