Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
UserSessionHistoryResource
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
3 / 3
8
100.00% covered (success)
100.00%
1 / 1
 toArray
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
2
 resolvePersona
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
3
 normalizeJsonField
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace App\Http\Resources\v2;
4
5use Illuminate\Http\Resources\Json\JsonResource;
6
7/**
8 * Unified per-user session history row.
9 *
10 * Hydrates `persona` from whichever of `project` (user-owned) or
11 * `companyProject` (corporate, direct-call) is loaded on the underlying
12 * conversation, so the FE has a single shape to render regardless of
13 * source. Both relations should be eager-loaded by the caller.
14 *
15 * @property string $_id
16 * @property string|null $project_id
17 * @property string|null $company_project_id
18 * @property float|null $score
19 * @property string $status
20 * @property int|null $duration
21 * @property mixed $icp
22 * @property mixed $agent
23 * @property \Illuminate\Support\Carbon|null $created_at
24 * @property-read \App\Http\Models\RolePlayProjects|null $project
25 * @property-read \App\Http\Models\CompanyRolePlayProject|null $companyProject
26 */
27class UserSessionHistoryResource extends JsonResource
28{
29    /**
30     * @param \Illuminate\Http\Request $request
31     * @return array<string, mixed>
32     */
33    public function toArray($request): array
34    {
35        $persona = $this->resolvePersona();
36
37        return [
38            'id' => (string) $this->_id,
39            'project_id' => $this->project_id,
40            'company_project_id' => $this->company_project_id,
41            'score' => $this->score,
42            'status' => $this->status,
43            'duration' => $this->duration,
44            'icp' => $this->normalizeJsonField($this->icp),
45            'agent' => $this->normalizeJsonField($this->agent),
46            'created_at' => $this->created_at?->toIso8601String(),
47            'persona' => $persona,
48            // Keep the legacy `project` shape for back-compat with any FE
49            // that hasn't migrated to `persona` yet. Always carries the
50            // user-owned project when present; null for direct-call rows.
51            'project' => $this->project
52                ? [
53                    'id' => (string) $this->project->_id,
54                    'name' => $this->project->name,
55                    'call_type' => $this->project->type,
56                ]
57                : null,
58        ];
59    }
60
61    /**
62     * Build the unified persona descriptor from whichever relation hydrated.
63     * Returns null if neither relation is loaded (defensive — a healthy row
64     * always has one).
65     *
66     * @return array{id: string, name: string, type: string|null, source: string}|null
67     */
68    private function resolvePersona(): ?array
69    {
70        if ($this->project) {
71            return [
72                'id' => (string) $this->project->_id,
73                'name' => (string) $this->project->name,
74                'type' => $this->project->type,
75                'source' => 'user',
76            ];
77        }
78
79        if ($this->companyProject) {
80            return [
81                'id' => (string) $this->companyProject->_id,
82                'name' => (string) $this->companyProject->name,
83                'type' => $this->companyProject->type,
84                'source' => 'company',
85            ];
86        }
87
88        return null;
89    }
90
91    /**
92     * Conversations historically stored `icp` / `agent` as either a JSON
93     * string or an array depending on the write path. Coerce both shapes
94     * so the FE only ever receives an array.
95     */
96    private function normalizeJsonField(mixed $value): mixed
97    {
98        if (is_string($value)) {
99            $decoded = json_decode($value, true);
100            return $decoded === null ? $value : $decoded;
101        }
102        return $value;
103    }
104}