From cc48011da662646aa1a3ec7d900ce1c4b3d1890d Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Fri, 8 May 2026 22:13:34 +0200 Subject: [PATCH] =?UTF-8?q?feat(timetable):=20ArtistResolver::fromPortalTo?= =?UTF-8?q?ken=20=E2=80=94=20engagement-scoped=20subject=20resolution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves the artist subject + event_id + engagement for the artist_advance portal flow. Per RFC v0.2 D15 + ARCH-FORM-BUILDER §17.3 footnote: master Artist is the subject (preserves form_submissions.subject_type='artist'), engagement provides event_id (per WS-4 denormalisation), and engagement itself rides along so callers can resolve advance_section context without a second query. Token comparison uses SHA-256 hex digest matching Session 1's storage shape (commit eb6d396). Two domain exceptions distinguish 404 (no matching token → InvalidPortalTokenException) from 410 (master artist soft-deleted post-engagement → ArtistDeletedException with engagementId attached). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Artist/ArtistDeletedException.php | 25 ++++++++ .../Artist/InvalidPortalTokenException.php | 21 +++++++ .../FormBuilder/Resolvers/ArtistResolver.php | 59 +++++++++++++++++++ .../Resolvers/ArtistResolverResult.php | 27 +++++++++ 4 files changed, 132 insertions(+) create mode 100644 api/app/Exceptions/Artist/ArtistDeletedException.php create mode 100644 api/app/Exceptions/Artist/InvalidPortalTokenException.php create mode 100644 api/app/FormBuilder/Resolvers/ArtistResolver.php create mode 100644 api/app/FormBuilder/Resolvers/ArtistResolverResult.php diff --git a/api/app/Exceptions/Artist/ArtistDeletedException.php b/api/app/Exceptions/Artist/ArtistDeletedException.php new file mode 100644 index 00000000..49a76057 --- /dev/null +++ b/api/app/Exceptions/Artist/ArtistDeletedException.php @@ -0,0 +1,25 @@ +withoutGlobalScope(OrganisationScope::class) + ->where('portal_token', $digest) + ->first(); + + if ($engagement === null) { + throw InvalidPortalTokenException::create(); + } + + $artist = Artist::query() + ->withoutGlobalScope(OrganisationScope::class) + ->whereKey($engagement->artist_id) + ->first(); + + if (! $artist instanceof Artist) { + throw new ArtistDeletedException((string) $engagement->id); + } + + return new ArtistResolverResult( + subject: $artist, + eventId: (string) $engagement->event_id, + engagement: $engagement, + ); + } +} diff --git a/api/app/FormBuilder/Resolvers/ArtistResolverResult.php b/api/app/FormBuilder/Resolvers/ArtistResolverResult.php new file mode 100644 index 00000000..2acbb0b9 --- /dev/null +++ b/api/app/FormBuilder/Resolvers/ArtistResolverResult.php @@ -0,0 +1,27 @@ +