Skip to content

Commit 680536f

Browse files
Use DTO's for POST/PUT request bodies.
1 parent 4acbb89 commit 680536f

28 files changed

+567
-373
lines changed

composer.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@
7171
"mbostock/d3": "^3.5",
7272
"nelmio/api-doc-bundle": "^4.11",
7373
"novus/nvd3": "^1.8",
74+
"phpdocumentor/reflection-docblock": "^5.3",
75+
"phpstan/phpdoc-parser": "^1.25",
7476
"promphp/prometheus_client_php": "^2.6",
7577
"ramsey/uuid": "^4.2",
7678
"select2/select2": "4.*",
@@ -89,9 +91,12 @@
8991
"symfony/intl": "6.4.*",
9092
"symfony/mime": "6.4.*",
9193
"symfony/monolog-bundle": "^3.8.0",
94+
"symfony/property-access": "6.4.*",
95+
"symfony/property-info": "6.4.*",
9296
"symfony/runtime": "6.4.*",
9397
"symfony/security-bundle": "6.4.*",
9498
"symfony/security-csrf": "6.4.*",
99+
"symfony/serializer": "6.4.*",
95100
"symfony/stopwatch": "6.4.*",
96101
"symfony/twig-bundle": "6.4.*",
97102
"symfony/validator": "6.4.*",

composer.lock

Lines changed: 100 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webapp/config/packages/framework.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ framework:
66
http_method_override: true
77
annotations: true
88
handle_all_throwables: true
9+
serializer:
10+
enabled: true
11+
name_converter: serializer.name_converter.camel_case_to_snake_case
912

1013
# Enables session support. Note that the session will ONLY be started if you read or write from it.
1114
# Remove or comment this section to explicitly disable session support.

webapp/config/packages/nelmio_api_doc.yaml

Lines changed: 0 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -124,107 +124,5 @@ nelmio_api_doc:
124124
text/html:
125125
schema:
126126
type: string
127-
schemas:
128-
AddUser:
129-
required:
130-
- username
131-
- name
132-
- password
133-
- roles
134-
type: object
135-
properties:
136-
username:
137-
type: string
138-
name:
139-
type: string
140-
email:
141-
type: string
142-
format: email
143-
ip:
144-
type: string
145-
password:
146-
type: string
147-
format: password
148-
enabled:
149-
type: boolean
150-
nullable: true
151-
team_id:
152-
type: string
153-
roles:
154-
type: array
155-
items:
156-
type: string
157-
ClarificationPost:
158-
type: object
159-
required: [text]
160-
properties:
161-
text:
162-
type: string
163-
description: The body of the clarification to send
164-
problem_id:
165-
type: string
166-
description: The problem the clarification is for
167-
nullable: true
168-
reply_to_id:
169-
type: string
170-
description: The ID of the clarification this clarification is a reply to
171-
nullable: true
172-
from_team_id:
173-
type: string
174-
description: The team the clarification came from. Only used when adding a clarification as admin
175-
nullable: true
176-
to_team_id:
177-
type: string
178-
description: The team the clarification must be sent to. Only used when adding a clarification as admin
179-
nullable: true
180-
time:
181-
type: string
182-
format: date-time
183-
description: The time to use for the clarification. Only used when adding a clarification as admin
184-
id:
185-
type: string
186-
description: The ID to use for the clarification. Only used when adding a clarification as admin and only allowed with PUT
187-
ContestProblemPut:
188-
type: object
189-
required: [label]
190-
properties:
191-
label:
192-
type: string
193-
description: The label of the problem to add to the contest
194-
color:
195-
type: string
196-
description: Human readable color of the problem to add. Will be overwritten by `rgb` if supplied
197-
rgb:
198-
type: string
199-
description: Hexadecimal RGB value of the color of the problem to add. Will be used if both `color` and `rgb` are supplied
200-
points:
201-
type: integer
202-
description: The number of points for the problem to add. Defaults to 1
203-
lazy_eval_results:
204-
type: boolean
205-
description: Whether to use lazy evaluation for this problem. Defaults to the global setting
206-
TeamCategoryPost:
207-
type: object
208-
required: [name]
209-
properties:
210-
hidden:
211-
type: boolean
212-
description: Show this group on the scoreboard?
213-
nullable: true
214-
icpc_id:
215-
type: string
216-
description: The ID in the ICPC CMS for this group
217-
nullable: true
218-
name:
219-
type: string
220-
description: How to name this group on the scoreboard
221-
sortorder:
222-
type: integer
223-
minimum: 0
224-
description: Bundle groups with the same sortorder, create different scoreboards per sortorder
225-
color:
226-
type: string
227-
nullable: true
228-
description: Color to use for teams in this group on the scoreboard
229127
areas:
230128
path_patterns: [ ^/api/v4 ]

webapp/src/Controller/API/AbstractRestController.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,15 @@ protected function renderCreateData(
137137
$params = [
138138
'id' => $id,
139139
];
140+
$postfix = '';
140141
if ($routeType !== 'user') {
141142
$params['cid'] = $request->attributes->get('cid');
143+
if ($params['cid'] === null) {
144+
$postfix = '_1';
145+
}
142146
}
143147
$headers = [
144-
'Location' => $this->generateUrl("v4_app_api_{$routeType}_single", $params, UrlGeneratorInterface::ABSOLUTE_URL),
148+
'Location' => $this->generateUrl("v4_app_api_{$routeType}_single$postfix", $params, UrlGeneratorInterface::ABSOLUTE_URL),
145149
];
146150
return $this->renderData($request, $data, Response::HTTP_CREATED,
147151
$headers);

webapp/src/Controller/API/ClarificationController.php

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Controller\API;
44

5+
use App\DataTransferObject\ClarificationPost;
56
use App\Entity\Clarification;
67
use App\Entity\Contest;
78
use App\Entity\ContestProblem;
@@ -17,6 +18,7 @@
1718
use Symfony\Component\ExpressionLanguage\Expression;
1819
use Symfony\Component\HttpFoundation\Request;
1920
use Symfony\Component\HttpFoundation\Response;
21+
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
2022
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
2123
use Symfony\Component\Security\Http\Attribute\IsGranted;
2224

@@ -92,36 +94,30 @@ public function singleAction(Request $request, string $id): Response
9294
content: [
9395
new OA\MediaType(
9496
mediaType: 'multipart/form-data',
95-
schema: new OA\Schema(ref: '#/components/schemas/ClarificationPost')
96-
),
97-
new OA\MediaType(
98-
mediaType: 'application/json',
99-
schema: new OA\Schema(ref: '#/components/schemas/ClarificationPost'))
97+
schema: new OA\Schema(ref: new Model(type: ClarificationPost::class))
98+
)
10099
]
101100
)]
102101
#[OA\Response(
103102
response: 200,
104103
description: 'When creating a clarification was successful',
105104
content: new Model(type: Clarification::class)
106105
)]
107-
public function addAction(Request $request, ?string $id): Response
108-
{
109-
$required = ['text'];
110-
foreach ($required as $argument) {
111-
if (!$request->request->has($argument)) {
112-
throw new BadRequestHttpException(sprintf("Argument '%s' is mandatory.", $argument));
113-
}
114-
}
115-
106+
public function addAction(
107+
#[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST)]
108+
ClarificationPost $clarificationPost,
109+
Request $request,
110+
?string $id
111+
): Response {
116112
$contestId = $this->getContestId($request);
117113
$contest = $this->em->getRepository(Contest::class)->find($contestId);
118114

119115
$clarification = new Clarification();
120116
$clarification
121117
->setContest($contest)
122-
->setBody($request->request->get('text'));
118+
->setBody($clarificationPost->text);
123119

124-
if ($problemId = $request->request->get('problem_id')) {
120+
if ($problemId = $clarificationPost->problemId) {
125121
// Load the problem
126122
/** @var ContestProblem|null $problem */
127123
$problem = $this->em->createQueryBuilder()
@@ -146,7 +142,7 @@ public function addAction(Request $request, ?string $id): Response
146142
$clarification->setProblem($problem->getProblem());
147143
}
148144

149-
if ($replyToId = $request->request->get('reply_to_id')) {
145+
if ($replyToId = $clarificationPost->replyToId) {
150146
// Load the clarification.
151147
/** @var Clarification|null $replyTo */
152148
$replyTo = $this->em->createQueryBuilder()
@@ -169,7 +165,7 @@ public function addAction(Request $request, ?string $id): Response
169165

170166
// By default, use the team of the user
171167
$fromTeam = $this->isGranted('ROLE_API_WRITER') ? null : $this->dj->getUser()->getTeam();
172-
if ($fromTeamId = $request->request->get('from_team_id')) {
168+
if ($fromTeamId = $clarificationPost->fromTeamId) {
173169
$idField = $this->eventLogService->externalIdFieldForEntity(Team::class) ?? 'teamid';
174170
$method = sprintf('get%s', ucfirst($idField));
175171

@@ -189,7 +185,7 @@ public function addAction(Request $request, ?string $id): Response
189185

190186
// By default, send to jury.
191187
$toTeam = null;
192-
if ($toTeamId = $request->request->get('to_team_id')) {
188+
if ($toTeamId = $clarificationPost->toTeamId) {
193189
$idField = $this->eventLogService->externalIdFieldForEntity(Team::class) ?? 'teamid';
194190

195191
// If the user is an admin or API writer, allow it to specify the team.
@@ -207,7 +203,7 @@ public function addAction(Request $request, ?string $id): Response
207203
}
208204

209205
$time = Utils::now();
210-
if ($timeString = $request->request->get('time')) {
206+
if ($timeString = $clarificationPost->time) {
211207
if ($this->isGranted('ROLE_API_WRITER')) {
212208
try {
213209
$time = Utils::toEpochFloat($timeString);
@@ -221,7 +217,7 @@ public function addAction(Request $request, ?string $id): Response
221217

222218
$clarification->setSubmittime($time);
223219

224-
if ($clarificationId = $request->request->get('id')) {
220+
if ($clarificationId = $clarificationPost->id) {
225221
if ($request->isMethod('POST')) {
226222
throw new BadRequestHttpException('Passing an ID is not supported for POST.');
227223
} elseif ($id !== $clarificationId) {

0 commit comments

Comments
 (0)