Skip to content

Commit

Permalink
Use DTO's for POST/PUT request bodies.
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicky Gerritsen committed Feb 9, 2024
1 parent af00d7e commit ff295c5
Show file tree
Hide file tree
Showing 28 changed files with 569 additions and 373 deletions.
5 changes: 5 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
"mbostock/d3": "^3.5",
"nelmio/api-doc-bundle": "^4.11",
"novus/nvd3": "^1.8",
"phpdocumentor/reflection-docblock": "^5.3",
"phpstan/phpdoc-parser": "^1.25",
"promphp/prometheus_client_php": "^2.6",
"ramsey/uuid": "^4.2",
"select2/select2": "4.*",
Expand All @@ -89,9 +91,12 @@
"symfony/intl": "6.4.*",
"symfony/mime": "6.4.*",
"symfony/monolog-bundle": "^3.8.0",
"symfony/property-access": "6.4.*",
"symfony/property-info": "6.4.*",
"symfony/runtime": "6.4.*",
"symfony/security-bundle": "6.4.*",
"symfony/security-csrf": "6.4.*",
"symfony/serializer": "6.4.*",
"symfony/stopwatch": "6.4.*",
"symfony/twig-bundle": "6.4.*",
"symfony/validator": "6.4.*",
Expand Down
102 changes: 100 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions webapp/config/packages/framework.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ framework:
http_method_override: true
annotations: true
handle_all_throwables: true
serializer:
enabled: true
name_converter: serializer.name_converter.camel_case_to_snake_case

# Enables session support. Note that the session will ONLY be started if you read or write from it.
# Remove or comment this section to explicitly disable session support.
Expand Down
102 changes: 0 additions & 102 deletions webapp/config/packages/nelmio_api_doc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -124,107 +124,5 @@ nelmio_api_doc:
text/html:
schema:
type: string
schemas:
AddUser:
required:
- username
- name
- password
- roles
type: object
properties:
username:
type: string
name:
type: string
email:
type: string
format: email
ip:
type: string
password:
type: string
format: password
enabled:
type: boolean
nullable: true
team_id:
type: string
roles:
type: array
items:
type: string
ClarificationPost:
type: object
required: [text]
properties:
text:
type: string
description: The body of the clarification to send
problem_id:
type: string
description: The problem the clarification is for
nullable: true
reply_to_id:
type: string
description: The ID of the clarification this clarification is a reply to
nullable: true
from_team_id:
type: string
description: The team the clarification came from. Only used when adding a clarification as admin
nullable: true
to_team_id:
type: string
description: The team the clarification must be sent to. Only used when adding a clarification as admin
nullable: true
time:
type: string
format: date-time
description: The time to use for the clarification. Only used when adding a clarification as admin
id:
type: string
description: The ID to use for the clarification. Only used when adding a clarification as admin and only allowed with PUT
ContestProblemPut:
type: object
required: [label]
properties:
label:
type: string
description: The label of the problem to add to the contest
color:
type: string
description: Human readable color of the problem to add. Will be overwritten by `rgb` if supplied
rgb:
type: string
description: Hexadecimal RGB value of the color of the problem to add. Will be used if both `color` and `rgb` are supplied
points:
type: integer
description: The number of points for the problem to add. Defaults to 1
lazy_eval_results:
type: boolean
description: Whether to use lazy evaluation for this problem. Defaults to the global setting
TeamCategoryPost:
type: object
required: [name]
properties:
hidden:
type: boolean
description: Show this group on the scoreboard?
nullable: true
icpc_id:
type: string
description: The ID in the ICPC CMS for this group
nullable: true
name:
type: string
description: How to name this group on the scoreboard
sortorder:
type: integer
minimum: 0
description: Bundle groups with the same sortorder, create different scoreboards per sortorder
color:
type: string
nullable: true
description: Color to use for teams in this group on the scoreboard
areas:
path_patterns: [ ^/api/v4 ]
6 changes: 5 additions & 1 deletion webapp/src/Controller/API/AbstractRestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,15 @@ protected function renderCreateData(
$params = [
'id' => $id,
];
$postfix = '';
if ($routeType !== 'user') {
$params['cid'] = $request->attributes->get('cid');
if ($params['cid'] === null) {
$postfix = '_1';
}
}
$headers = [
'Location' => $this->generateUrl("v4_app_api_{$routeType}_single", $params, UrlGeneratorInterface::ABSOLUTE_URL),
'Location' => $this->generateUrl("v4_app_api_{$routeType}_single$postfix", $params, UrlGeneratorInterface::ABSOLUTE_URL),
];
return $this->renderData($request, $data, Response::HTTP_CREATED,
$headers);
Expand Down
38 changes: 17 additions & 21 deletions webapp/src/Controller/API/ClarificationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Controller\API;

use App\DataTransferObject\ClarificationPost;
use App\Entity\Clarification;
use App\Entity\Contest;
use App\Entity\ContestProblem;
Expand All @@ -17,6 +18,7 @@
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Http\Attribute\IsGranted;

Expand Down Expand Up @@ -92,36 +94,30 @@ public function singleAction(Request $request, string $id): Response
content: [
new OA\MediaType(
mediaType: 'multipart/form-data',
schema: new OA\Schema(ref: '#/components/schemas/ClarificationPost')
),
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(ref: '#/components/schemas/ClarificationPost'))
schema: new OA\Schema(ref: new Model(type: ClarificationPost::class))
)
]
)]
#[OA\Response(
response: 200,
description: 'When creating a clarification was successful',
content: new Model(type: Clarification::class)
)]
public function addAction(Request $request, ?string $id): Response
{
$required = ['text'];
foreach ($required as $argument) {
if (!$request->request->has($argument)) {
throw new BadRequestHttpException(sprintf("Argument '%s' is mandatory.", $argument));
}
}

public function addAction(
#[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST)]
ClarificationPost $clarificationPost,
Request $request,
?string $id
): Response {
$contestId = $this->getContestId($request);
$contest = $this->em->getRepository(Contest::class)->find($contestId);

$clarification = new Clarification();
$clarification
->setContest($contest)
->setBody($request->request->get('text'));
->setBody($clarificationPost->text);

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

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

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

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

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

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

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

$clarification->setSubmittime($time);

if ($clarificationId = $request->request->get('id')) {
if ($clarificationId = $clarificationPost->id) {
if ($request->isMethod('POST')) {
throw new BadRequestHttpException('Passing an ID is not supported for POST.');
} elseif ($id !== $clarificationId) {
Expand Down
Loading

0 comments on commit ff295c5

Please sign in to comment.