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) <noreply@anthropic.com>
60 lines
1.9 KiB
PHP
60 lines
1.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\FormBuilder\Resolvers;
|
|
|
|
use App\Exceptions\Artist\ArtistDeletedException;
|
|
use App\Exceptions\Artist\InvalidPortalTokenException;
|
|
use App\Models\Artist;
|
|
use App\Models\ArtistEngagement;
|
|
use App\Models\Scopes\OrganisationScope;
|
|
|
|
/**
|
|
* Engagement-scoped subject resolution for the artist_advance portal
|
|
* flow. Per ARCH-FORM-BUILDER §17.3 footnote and RFC-TIMETABLE v0.2
|
|
* D15: the master Artist is the FormSubmission subject (subject_type
|
|
* = 'artist'), but the engagement provides the event_id (denormalised
|
|
* onto form_submissions per WS-4) and any advance_section context.
|
|
*
|
|
* The portal token itself is stored on artist_engagements.portal_token
|
|
* as a SHA-256 hex digest (Session 1 commit eb6d396). Callers pass
|
|
* the plaintext token; we hash and look up.
|
|
*
|
|
* This resolver is the single shared helper for portal-token →
|
|
* engagement resolution. PortalTokenMiddleware delegates to it; the
|
|
* EngagementPortalController calls it directly to produce the value
|
|
* object the FormSubmissionService needs.
|
|
*/
|
|
final class ArtistResolver
|
|
{
|
|
public function fromPortalToken(string $portalToken): ArtistResolverResult
|
|
{
|
|
$digest = hash('sha256', $portalToken);
|
|
|
|
$engagement = ArtistEngagement::query()
|
|
->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,
|
|
);
|
|
}
|
|
}
|