Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
41 / 41 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
| RolePlaySessionDeletionService | |
100.00% |
41 / 41 |
|
100.00% |
2 / 2 |
8 | |
100.00% |
1 / 1 |
| delete | |
100.00% |
37 / 37 |
|
100.00% |
1 / 1 |
6 | |||
| getThreshold | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace App\Http\Services\RolePlay; |
| 4 | |
| 5 | use App\Http\Models\Auth\User; |
| 6 | use App\Http\Models\Parameter; |
| 7 | use App\Http\Models\RolePlayConversations; |
| 8 | use App\Http\Models\RolePlayProjects; |
| 9 | use App\Http\Models\UserRolePlayProgression; |
| 10 | use Illuminate\Auth\Access\AuthorizationException; |
| 11 | use Illuminate\Support\Facades\Log; |
| 12 | |
| 13 | /** |
| 14 | * Deletes a single short roleplay session (soft-delete) and recomputes |
| 15 | * all downstream aggregates: |
| 16 | * |
| 17 | * - The owning user's UserRolePlayProgression for the session's |
| 18 | * call_type (drops the entry, replays EMA). |
| 19 | * - The persona's average_score. |
| 20 | * |
| 21 | * Deletion is gated by the CMC-configured threshold parameter |
| 22 | * `role_play_min_session_duration_seconds` (default 5). Sessions at or |
| 23 | * above the threshold cannot be deleted through this endpoint. |
| 24 | * |
| 25 | * Daily usage metrics and UserInfo counters are left intact — the |
| 26 | * session is soft-deleted and UsageTrait::getUsage() queries those rows |
| 27 | * with `withTrashed()` so daily numbers remain stable. |
| 28 | */ |
| 29 | class RolePlaySessionDeletionService |
| 30 | { |
| 31 | use RolePlayDeletionAuthTrait; |
| 32 | |
| 33 | public const PARAMETER_NAME = 'role_play_min_session_duration_seconds'; |
| 34 | |
| 35 | public const DEFAULT_THRESHOLD_SECONDS = 5; |
| 36 | |
| 37 | /** |
| 38 | * @return array{progression: array<int, UserRolePlayProgression>, persona_average_score: float|null} |
| 39 | * |
| 40 | * @throws AuthorizationException |
| 41 | */ |
| 42 | public function delete(User $actor, RolePlayConversations $session): array |
| 43 | { |
| 44 | $ownerId = (string) $session->user_id; |
| 45 | $owner = User::find($ownerId); |
| 46 | $ownerCompanyId = $owner?->company_id ? (string) $owner->company_id : null; |
| 47 | |
| 48 | $this->assertCanActOn($actor, $ownerId, $ownerCompanyId); |
| 49 | |
| 50 | $threshold = $this->getThreshold(); |
| 51 | $duration = (int) ($session->duration ?? 0); |
| 52 | if ($duration >= $threshold) { |
| 53 | throw new AuthorizationException(sprintf( |
| 54 | 'Only sessions shorter than %d seconds can be deleted.', |
| 55 | $threshold, |
| 56 | )); |
| 57 | } |
| 58 | |
| 59 | $sessionId = (string) $session->id; |
| 60 | $projectId = (string) $session->project_id; |
| 61 | |
| 62 | $session->delete(); |
| 63 | |
| 64 | $progressions = UserRolePlayProgression::where('user_id', $ownerId)->get(); |
| 65 | foreach ($progressions as $progression) { |
| 66 | $progression->dropEntriesAndRecompute([$sessionId], []); |
| 67 | } |
| 68 | |
| 69 | $personaAverage = null; |
| 70 | $persona = RolePlayProjects::find($projectId); |
| 71 | if ($persona) { |
| 72 | $average = RolePlayConversations::where('project_id', $projectId) |
| 73 | ->where('status', 'done') |
| 74 | ->avg('score'); |
| 75 | $personaAverage = $average !== null ? (float) $average : 0.0; |
| 76 | $persona->average_score = $personaAverage; |
| 77 | $persona->save(); |
| 78 | } |
| 79 | |
| 80 | Log::info('Roleplay session deleted', [ |
| 81 | 'session_id' => $sessionId, |
| 82 | 'persona_id' => $projectId, |
| 83 | 'owner_id' => $ownerId, |
| 84 | 'actor_id' => (string) $actor->id, |
| 85 | 'duration' => $duration, |
| 86 | ]); |
| 87 | |
| 88 | return [ |
| 89 | 'progression' => $progressions->values()->all(), |
| 90 | 'persona_average_score' => $personaAverage, |
| 91 | ]; |
| 92 | } |
| 93 | |
| 94 | public function getThreshold(): int |
| 95 | { |
| 96 | $value = Parameter::where('name', self::PARAMETER_NAME)->first()?->value; |
| 97 | |
| 98 | if ($value === null) { |
| 99 | return self::DEFAULT_THRESHOLD_SECONDS; |
| 100 | } |
| 101 | |
| 102 | return (int) $value; |
| 103 | } |
| 104 | } |