Lines 100.00% 90 / 90
Functions and Methods 100.00% 3 / 3
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
AgentStat 100.00% 90 / 90 100.00% 3 / 3 6 100.00% 1 / 1
 __construct 100.00% 44 / 44 100.00% 1 / 1 1
 getFactionCode 100.00% 4 / 4 100.00% 1 / 1 4
 getStats 100.00% 42 / 42 100.00% 1 / 1 1
1<?php
2
3declare(strict_types=1);
4
5namespace App\Entity;
6
7use ApiPlatform\Metadata\ApiResource;
8use ApiPlatform\Metadata\Get;
9use ApiPlatform\Metadata\GetCollection;
10use ApiPlatform\Metadata\Post;
11use App\Dto\AgentStatInput;
12use App\Exception\AgentNameException;
13use App\Exception\StatsAlreadyAddedException;
14use App\Repository\AgentStatRepository;
15use App\Security\Voter\AgentStatVoter;
16use App\State\AgentStatProcessor;
17use Doctrine\DBAL\Types\Types;
18use Doctrine\ORM\Mapping\Column;
19use Doctrine\ORM\Mapping\Entity;
20use Doctrine\ORM\Mapping\GeneratedValue;
21use Doctrine\ORM\Mapping\Id;
22use Doctrine\ORM\Mapping\JoinColumn;
23use Doctrine\ORM\Mapping\ManyToOne;
24use Doctrine\ORM\Mapping\UniqueConstraint;
25use Symfony\Component\Serializer\Attribute\Context;
26use Symfony\Component\Serializer\Attribute\Ignore;
27use Symfony\Component\Serializer\Attribute\SerializedName;
28use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
29
30/**
31 * Ingress all-time agent stats, with the columns of 4e-website's agent_stat table.
32 * 4e-website links a row to its Agent; here it belongs to the User who imported it.
33 *
34 * API: the user's own stats, newest first (OwnerExtension), and POST of an AgentStatInput.
35 */
36#[Entity(repositoryClass: AgentStatRepository::class)]
37#[UniqueConstraint(name: 'agent_stat_owner_datetime', columns: ['owner_id', 'datetime'])]
38#[ApiResource(
39    description: "Ingress all-time agent stats, with the column names of 4e-website's agent_stat table. Lists only your own, all of them, newest first.",
40    operations: [
41        // All of them at once: the app needs the whole history, and a page per request made it wait
42        new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"),
43        new Get(security: "is_granted('".AgentStatVoter::VIEW."', object)"),
44        new Post(
45            exceptionToStatus: [StatsAlreadyAddedException::class => 409, AgentNameException::class => 422],
46            collectDenormalizationErrors: true,
47            security: "is_granted('ROLE_USER')",
48            input: AgentStatInput::class,
49            processor: AgentStatProcessor::class,
50        ),
51    ],
52    order: ['datetime' => 'DESC'],
53)]
54class AgentStat
55{
56    #[Column, Id, GeneratedValue]
57    public ?int $id = null {
58        get {
59            return $this->id;
60        }
61    }
62
63    /** The same format as AgentStatInput::$datetime. */
64    #[Column(type: Types::DATETIME_IMMUTABLE)]
65    #[Context([DateTimeNormalizer::FORMAT_KEY => AgentStatInput::DATETIME_FORMAT])]
66    public private(set) \DateTimeImmutable $datetime;
67
68    #[Column(type: Types::DATETIME_IMMUTABLE)]
69    public private(set) \DateTimeImmutable $createdAt;
70
71    #[Column(length: 150)]
72    public private(set) string $nickname;
73
74    #[Column(length: 50)]
75    public private(set) string $faction;
76
77    #[Column]
78    public private(set) int $ap;
79
80    #[Column(nullable: true)]
81    public private(set) ?int $level;
82
83    #[Column(nullable: true)]
84    public private(set) ?int $explorer;
85
86    #[Column(nullable: true)]
87    public private(set) ?int $recon;
88
89    #[Column(nullable: true)]
90    public private(set) ?int $seer;
91
92    #[Column(nullable: true)]
93    public private(set) ?int $trekker;
94
95    #[Column(nullable: true)]
96    public private(set) ?int $builder;
97
98    #[Column(nullable: true)]
99    public private(set) ?int $connector;
100
101    #[Column(nullable: true)]
102    #[SerializedName('mind_controller')]
103    public private(set) ?int $mindController;
104
105    #[Column(nullable: true)]
106    public private(set) ?int $illuminator;
107
108    #[Column(nullable: true)]
109    public private(set) ?int $recharger;
110
111    #[Column(nullable: true)]
112    public private(set) ?int $liberator;
113
114    #[Column(nullable: true)]
115    public private(set) ?int $pioneer;
116
117    #[Column(nullable: true)]
118    public private(set) ?int $engineer;
119
120    #[Column(nullable: true)]
121    public private(set) ?int $purifier;
122
123    #[Column(nullable: true)]
124    public private(set) ?int $specops;
125
126    #[Column(nullable: true)]
127    public private(set) ?int $hacker;
128
129    #[Column(nullable: true)]
130    public private(set) ?int $translator;
131
132    #[Column(nullable: true)]
133    public private(set) ?int $sojourner;
134
135    #[Column(nullable: true)]
136    public private(set) ?int $recruiter;
137
138    #[Column(nullable: true)]
139    public private(set) ?int $missionday;
140
141    #[Column(nullable: true)]
142    #[SerializedName('nl1331_meetups')]
143    public private(set) ?int $nl1331Meetups;
144
145    #[Column(nullable: true)]
146    public private(set) ?int $ifs;
147
148    #[Column(nullable: true)]
149    #[SerializedName('current_challenge')]
150    public private(set) ?int $currentChallenge;
151
152    #[Column(nullable: true)]
153    public private(set) ?int $scout;
154
155    #[Column(nullable: true)]
156    #[SerializedName('scout_controller')]
157    public private(set) ?int $scoutController;
158
159    #[Column(nullable: true)]
160    #[SerializedName('longest_link')]
161    public private(set) ?int $longestLink;
162
163    #[Column(nullable: true)]
164    #[SerializedName('largest_field')]
165    public private(set) ?int $largestField;
166
167    #[Column(nullable: true)]
168    public private(set) ?int $recursions;
169
170    #[Column(nullable: true)]
171    #[SerializedName('drone_flight_distance')]
172    public private(set) ?int $droneFlightDistance;
173
174    #[Column(nullable: true)]
175    public private(set) ?int $maverick;
176
177    #[Column(nullable: true)]
178    #[SerializedName('drone_portals_visited')]
179    public private(set) ?int $dronePortalsVisited;
180
181    #[Column(nullable: true)]
182    #[SerializedName('drone_forced_recalls')]
183    public private(set) ?int $droneForcedRecalls;
184
185    #[Column(nullable: true)]
186    #[SerializedName('kinetic_capsules_completed')]
187    public private(set) ?int $kineticCapsulesCompleted;
188
189    #[Column(nullable: true)]
190    #[SerializedName('months_subscribed')]
191    public private(set) ?int $monthsSubscribed;
192
193    #[Column(nullable: true)]
194    public private(set) ?int $epoch;
195
196    #[Column(nullable: true)]
197    #[SerializedName('portals_discovered')]
198    public private(set) ?int $portalsDiscovered;
199
200    #[Column(nullable: true)]
201    #[SerializedName('drones_returned')]
202    public private(set) ?int $dronesReturned;
203
204    #[Column(nullable: true)]
205    #[SerializedName('second_sunday')]
206    public private(set) ?int $secondSunday;
207
208    #[Column(nullable: true)]
209    public private(set) ?int $reclaimer;
210
211    /**
212     * @param AgentStatInput $input validated input
213     */
214    public function __construct(#[ManyToOne, JoinColumn(nullable: false, onDelete: 'CASCADE'), Ignore]
215        public private(set) User $owner, AgentStatInput $input)
216    {
217        $this->datetime = $input->getDateTime();
218        $this->createdAt = new \DateTimeImmutable();
219        $this->nickname = \trim($input->nickname);
220        $this->faction = \trim($input->faction);
221        $this->ap = $input->ap ?? 0;
222        $this->level = $input->level;
223        $this->explorer = $input->explorer;
224        $this->recon = $input->recon;
225        $this->seer = $input->seer;
226        $this->trekker = $input->trekker;
227        $this->builder = $input->builder;
228        $this->connector = $input->connector;
229        $this->mindController = $input->mindController;
230        $this->illuminator = $input->illuminator;
231        $this->recharger = $input->recharger;
232        $this->liberator = $input->liberator;
233        $this->pioneer = $input->pioneer;
234        $this->engineer = $input->engineer;
235        $this->purifier = $input->purifier;
236        $this->specops = $input->specops;
237        $this->hacker = $input->hacker;
238        $this->translator = $input->translator;
239        $this->sojourner = $input->sojourner;
240        $this->recruiter = $input->recruiter;
241        $this->missionday = $input->missionday;
242        $this->nl1331Meetups = $input->nl1331Meetups;
243        $this->ifs = $input->ifs;
244        $this->currentChallenge = $input->currentChallenge;
245        $this->scout = $input->scout;
246        $this->scoutController = $input->scoutController;
247        $this->longestLink = $input->longestLink;
248        $this->largestField = $input->largestField;
249        $this->recursions = $input->recursions;
250        $this->droneFlightDistance = $input->droneFlightDistance;
251        $this->maverick = $input->maverick;
252        $this->dronePortalsVisited = $input->dronePortalsVisited;
253        $this->droneForcedRecalls = $input->droneForcedRecalls;
254        $this->kineticCapsulesCompleted = $input->kineticCapsulesCompleted;
255        $this->monthsSubscribed = $input->monthsSubscribed;
256        $this->epoch = $input->epoch;
257        $this->portalsDiscovered = $input->portalsDiscovered;
258        $this->dronesReturned = $input->dronesReturned;
259        $this->secondSunday = $input->secondSunday;
260        $this->reclaimer = $input->reclaimer;
261    }
262
263    /**
264     * ENL or RES (as 4e-website's faction names and logos), null for another faction.
265     */
266    #[Ignore]
267    public function getFactionCode(): ?string
268    {
269        return match (\strtolower(\trim($this->faction))) {
270            'enlightened' => 'ENL',
271            'resistance' => 'RES',
272            default => null,
273        };
274    }
275
276    /**
277     * The stats by agent_stat column name, for display.
278     *
279     * @return array<string, int|null>
280     */
281    #[Ignore]
282    public function getStats(): array
283    {
284        return [
285            'level' => $this->level,
286            'ap' => $this->ap,
287            'explorer' => $this->explorer,
288            'recon' => $this->recon,
289            'seer' => $this->seer,
290            'trekker' => $this->trekker,
291            'builder' => $this->builder,
292            'connector' => $this->connector,
293            'mind_controller' => $this->mindController,
294            'illuminator' => $this->illuminator,
295            'recharger' => $this->recharger,
296            'liberator' => $this->liberator,
297            'pioneer' => $this->pioneer,
298            'engineer' => $this->engineer,
299            'purifier' => $this->purifier,
300            'specops' => $this->specops,
301            'hacker' => $this->hacker,
302            'translator' => $this->translator,
303            'sojourner' => $this->sojourner,
304            'recruiter' => $this->recruiter,
305            'missionday' => $this->missionday,
306            'nl1331_meetups' => $this->nl1331Meetups,
307            'ifs' => $this->ifs,
308            'current_challenge' => $this->currentChallenge,
309            'scout' => $this->scout,
310            'scout_controller' => $this->scoutController,
311            'longest_link' => $this->longestLink,
312            'largest_field' => $this->largestField,
313            'recursions' => $this->recursions,
314            'drone_flight_distance' => $this->droneFlightDistance,
315            'maverick' => $this->maverick,
316            'drone_portals_visited' => $this->dronePortalsVisited,
317            'drone_forced_recalls' => $this->droneForcedRecalls,
318            'kinetic_capsules_completed' => $this->kineticCapsulesCompleted,
319            'months_subscribed' => $this->monthsSubscribed,
320            'epoch' => $this->epoch,
321            'portals_discovered' => $this->portalsDiscovered,
322            'drones_returned' => $this->dronesReturned,
323            'second_sunday' => $this->secondSunday,
324            'reclaimer' => $this->reclaimer,
325        ];
326    }
327}