feat(timetable): ArtistResolver::fromPortalToken — engagement-scoped subject resolution
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>
This commit is contained in:
25
api/app/Exceptions/Artist/ArtistDeletedException.php
Normal file
25
api/app/Exceptions/Artist/ArtistDeletedException.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Artist;
|
||||
|
||||
use DomainException;
|
||||
|
||||
/**
|
||||
* Raised by ArtistResolver::fromPortalToken when the engagement's
|
||||
* portal_token matches but the master Artist has been soft-deleted.
|
||||
* Per RFC v0.2 D27 the engagement itself remains usable; the portal
|
||||
* flow surfaces a clear 410 Gone rather than crashing on a null
|
||||
* subject downstream.
|
||||
*/
|
||||
final class ArtistDeletedException extends DomainException
|
||||
{
|
||||
public function __construct(public readonly string $engagementId)
|
||||
{
|
||||
parent::__construct(sprintf(
|
||||
'Master Artist for engagement %s has been deleted; portal flow is not available.',
|
||||
$engagementId,
|
||||
));
|
||||
}
|
||||
}
|
||||
21
api/app/Exceptions/Artist/InvalidPortalTokenException.php
Normal file
21
api/app/Exceptions/Artist/InvalidPortalTokenException.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Artist;
|
||||
|
||||
use DomainException;
|
||||
|
||||
/**
|
||||
* Raised by ArtistResolver::fromPortalToken when the supplied portal
|
||||
* token does not match any active artist_engagements row. Maps to a
|
||||
* 404 at the HTTP boundary — distinguishes from ArtistDeletedException
|
||||
* (engagement exists but master Artist is soft-deleted, → 410 Gone).
|
||||
*/
|
||||
final class InvalidPortalTokenException extends DomainException
|
||||
{
|
||||
public static function create(): self
|
||||
{
|
||||
return new self('Portal token does not resolve to an active engagement.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user