Skip to content

fix(api): seperate and fix user add and update endpoint logic #3040

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
100 changes: 81 additions & 19 deletions webapp/src/Controller/API/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,12 +291,12 @@
* Add a new user.
*/
#[IsGranted('ROLE_API_WRITER')]
#[Rest\Post]
#[Rest\Put("")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to also allow PUT here, but dropping POST doesn't make sense to me.

#[OA\RequestBody(
required: true,
content: [
new OA\MediaType(
mediaType: 'multipart/form-data',
mediaType: 'application/json',
schema: new OA\Schema(ref: new Model(type: AddUser::class))
),
]
Expand All @@ -311,19 +311,19 @@
AddUser $addUser,
Request $request
): Response {
return $this->addOrUpdateUser($addUser, $request);
return $this->addUser($addUser, $request);
}

/**
* Update an existing User or create one with the given ID
* Update an existing User
*/
#[IsGranted('ROLE_API_WRITER')]
#[Rest\Put('/{id}')]
#[Rest\Patch('/{id}')]
#[OA\RequestBody(
required: true,
content: [
new OA\MediaType(
mediaType: 'multipart/form-data',
mediaType: 'application/json',
schema: new OA\Schema(ref: new Model(type: UpdateUser::class))
),
]
Expand All @@ -336,28 +336,19 @@
public function updateAction(
#[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST)]
UpdateUser $updateUser,
string $id,
Request $request
): Response {
return $this->addOrUpdateUser($updateUser, $request);
return $this->updateUser($updateUser, $id, $request);
}

protected function addOrUpdateUser(AddUser $addUser, Request $request): Response
protected function addUser(AddUser $addUser, Request $request): Response
{
if ($addUser instanceof UpdateUser && !$addUser->id) {
throw new BadRequestHttpException('`id` field is required');
}

if ($this->em->getRepository(User::class)->findOneBy(['username' => $addUser->username])) {
throw new BadRequestHttpException(sprintf("User %s already exists", $addUser->username));
}

$user = new User();
if ($addUser instanceof UpdateUser) {
$existingUser = $this->em->getRepository(User::class)->findOneBy(['externalid' => $addUser->id]);
if ($existingUser) {
$user = $existingUser;
}
}
$user
->setUsername($addUser->username)
->setName($addUser->name)
Expand All @@ -366,7 +357,8 @@
->setPlainPassword($addUser->password)
->setEnabled($addUser->enabled ?? true);

if ($addUser instanceof UpdateUser) {
// if not set let it autogenerate
if ($addUser->id) {
$user->setExternalid($addUser->id);
}

Expand Down Expand Up @@ -415,6 +407,76 @@
return $this->renderCreateData($request, $user, 'user', $user->getUserid());
}


protected function updateUser(UpdateUser $updateUser, string $id, Request $request): Response
{
$user = $this->em->getRepository(User::class)->findOneBy(['externalid' => $id]);
if (!$user) {
throw new BadRequestHttpException(sprintf("User does not exist", $id));

Check failure on line 415 in webapp/src/Controller/API/UserController.php

View workflow job for this annotation

GitHub Actions / phpstan

Call to sprintf contains 0 placeholders, 1 value given.
}
if ($updateUser->username !== null) {
$user->setUsername($updateUser->username);
}
if ($updateUser->name !== null) {
$user->setName($updateUser->name);
}
if ($updateUser->email !== null) {
$user->setEmail($updateUser->email);
}
if ($updateUser->ip !== null) {
$user->setIpAddress($updateUser->ip);
}
if ($updateUser->password !== null) {
$user->setPlainPassword($updateUser->password);
}
if ($updateUser->enabled !== null) {
$user->setEnabled($updateUser->enabled);
}
if ($updateUser->teamId) {
/** @var Team|null $team */
$team = $this->em->createQueryBuilder()
->from(Team::class, 't')
->select('t')
->andWhere('t.externalid = :team')
->setParameter('team', $updateUser->teamId)
->getQuery()
->getOneOrNullResult();

if ($team === null) {
throw new BadRequestHttpException(sprintf("Team %s not found", $updateUser->teamId));
}
$user->setTeam($team);
}

$roles = $updateUser->roles;
// For the file import we change a CDS user to the roles needed for ICPC CDS.
if ($user->getUsername() === 'cds') {
$roles = ['cds'];
}
if (in_array('cds', $roles)) {
$roles = ['api_source_reader', 'api_writer', 'api_reader', ...array_diff($roles, ['cds'])];
}
foreach ($roles as $djRole) {
if ($djRole === '') {
continue;
}
if ($djRole === 'judge') {
$djRole = 'jury';
}
$role = $this->em->getRepository(Role::class)->findOneBy(['dj_role' => $djRole]);
if ($role === null) {
throw new BadRequestHttpException(sprintf("Role %s not found", $djRole));
}
$user->addUserRole($role);
}

$this->em->persist($user);
$this->em->flush();
$this->dj->auditlog('user', $user->getUserid(), 'updated');

return $this->renderCreateData($request, $user, 'user', $user->getUserid());
}

protected function getQueryBuilder(Request $request): QueryBuilder
{
$queryBuilder = $this->em->createQueryBuilder()
Expand Down
7 changes: 3 additions & 4 deletions webapp/src/DataTransferObject/AddUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
#[OA\Schema(required: ['username', 'name', 'roles'])]
class AddUser
{
/**
* @param array<string> $roles
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHPstan does not agree with removing this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah sorry, I forgot to run PHPstan

*/
public function __construct(

Check failure on line 11 in webapp/src/DataTransferObject/AddUser.php

View workflow job for this annotation

GitHub Actions / phpstan

Method App\DataTransferObject\AddUser::__construct() has parameter $roles with no value type specified in iterable type array.
#[OA\Property(nullable: true)]
public readonly ?string $id,
public readonly string $username,
public readonly string $name,
#[OA\Property(format: 'email', nullable: true)]
Expand All @@ -25,6 +24,6 @@
#[OA\Property(nullable: true)]
public readonly ?string $teamId,
#[Serializer\Type('array<string>')]
public readonly array $roles,
public readonly array $roles = [],
) {}
}
36 changes: 22 additions & 14 deletions webapp/src/DataTransferObject/UpdateUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,30 @@

namespace App\DataTransferObject;

use JMS\Serializer\Annotation as Serializer;
use OpenApi\Attributes as OA;

#[OA\Schema(required: ['id', 'username', 'name', 'roles'])]
class UpdateUser extends AddUser
#[OA\Schema(required: ['roles'])]
class UpdateUser
{
public function __construct(

Check failure on line 11 in webapp/src/DataTransferObject/UpdateUser.php

View workflow job for this annotation

GitHub Actions / phpstan

Method App\DataTransferObject\UpdateUser::__construct() has parameter $roles with no value type specified in iterable type array.
public readonly string $id,
string $username,
string $name,
?string $email,
?string $ip,
?string $password,
?bool $enabled,
?string $teamId,
array $roles
) {
parent::__construct($username, $name, $email, $ip, $password, $enabled, $teamId, $roles);
}
public readonly ?string $id = null,
#[OA\Property(nullable: true)]
public readonly ?string $username = null,
#[OA\Property(nullable: true)]
public readonly ?string $name = null,
#[OA\Property(format: 'email', nullable: true)]
public readonly ?string $email = null,
#[OA\Property(nullable: true)]
public readonly ?string $ip = null,
#[OA\Property(format: 'password', nullable: true)]
public readonly ?string $password = null,
#[OA\Property(nullable: true)]
public readonly ?bool $enabled = null,
#[OA\Property(nullable: true)]
public readonly ?string $teamId = null,
#[OA\Property(nullable: true)]
#[Serializer\Type('array<string>')]
public readonly array $roles = []
) {}
}
Loading