Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use DTO's for POST/PUT request bodies. #2321

Merged
merged 5 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ]
8 changes: 7 additions & 1 deletion webapp/src/Controller/API/AbstractRestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,17 @@ protected function renderCreateData(
$params = [
'id' => $id,
];
$postfix = '';
if ($routeType !== 'user') {
$params['cid'] = $request->attributes->get('cid');
// If we request any entity without contest, we need to use the rout postfixed with _1,
// which is the route without the contest in the URL.
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
Loading