2323use Symfony \Component \HttpFoundation \Response ;
2424use Symfony \Component \HttpKernel \Attribute \MapRequestPayload ;
2525use Symfony \Component \HttpKernel \Exception \BadRequestHttpException ;
26+ use Symfony \Component \HttpKernel \Exception \NotFoundHttpException ;
2627use Symfony \Component \Security \Http \Attribute \IsGranted ;
28+ use Symfony \Component \Validator \Exception \ValidationFailedException ;
29+ use Symfony \Component \Validator \Validator \ValidatorInterface ;
2730
2831/**
2932 * @extends AbstractRestController<User, User>
@@ -41,7 +44,8 @@ public function __construct(
4144 DOMJudgeService $ dj ,
4245 ConfigurationService $ config ,
4346 EventLogService $ eventLogService ,
44- protected readonly ImportExportService $ importExportService
47+ protected readonly ImportExportService $ importExportService ,
48+ protected readonly ValidatorInterface $ validator ,
4549 ) {
4650 parent ::__construct ($ entityManager , $ dj , $ config , $ eventLogService );
4751 }
@@ -86,9 +90,9 @@ public function addGroupsAction(Request $request): string
8690 $ message = null ;
8791 $ result = -1 ;
8892 if ((($ tsvFile && ($ result = $ this ->importExportService ->importTsv ('groups ' , $ tsvFile , $ message ))) ||
89- ($ jsonFile && ($ result = $ this ->importExportService ->importJson ('groups ' , $ jsonFile , $ message )))) &&
93+ ($ jsonFile && ($ result = $ this ->importExportService ->importJson ('groups ' , $ jsonFile , $ message )))) &&
9094 $ result >= 0 ) {
91- return "$ result new group(s) successfully added. " ;
95+ return "$ result new group(s) successfully added. " ;
9296 } else {
9397 throw new BadRequestHttpException ("Error while adding groups: $ message " );
9498 }
@@ -171,7 +175,7 @@ public function addTeamsAction(Request $request): string
171175 }
172176 $ message = null ;
173177 if ((($ tsvFile && ($ result = $ this ->importExportService ->importTsv ('teams ' , $ tsvFile , $ message ))) ||
174- ($ jsonFile && ($ result = $ this ->importExportService ->importJson ('teams ' , $ jsonFile , $ message )))) &&
178+ ($ jsonFile && ($ result = $ this ->importExportService ->importJson ('teams ' , $ jsonFile , $ message )))) &&
175179 $ result >= 0 ) {
176180 // TODO: better return all teams here?
177181 return "$ result new team(s) successfully added. " ;
@@ -235,7 +239,7 @@ public function addAccountsAction(Request $request): string
235239
236240 $ message = null ;
237241 if ((($ tsvFile && ($ result = $ this ->importExportService ->importTsv ('accounts ' , $ tsvFile , $ message ))) ||
238- ($ jsonFile && ($ result = $ this ->importExportService ->importJson ('accounts ' , $ jsonFile , $ message )))) &&
242+ ($ jsonFile && ($ result = $ this ->importExportService ->importJson ('accounts ' , $ jsonFile , $ message )))) &&
239243 $ result >= 0 ) {
240244 // TODO: better return all accounts here?
241245 return "$ result new account(s) successfully added. " ;
@@ -291,13 +295,12 @@ public function singleAction(Request $request, string $id): Response
291295 * Add a new user.
292296 */
293297 #[IsGranted('ROLE_API_WRITER ' )]
294- #[Rest \Post]
298+ #[Rest \Post() ]
295299 #[OA \RequestBody(
296300 required: true ,
297301 content: [
298- new OA \MediaType (
299- mediaType: 'multipart/form-data ' ,
300- schema: new OA \Schema (ref: new Model (type: AddUser::class))
302+ new OA \JsonContent (
303+ ref: new Model (type: AddUser::class)
301304 ),
302305 ]
303306 )]
@@ -307,86 +310,86 @@ public function singleAction(Request $request, string $id): Response
307310 content: new Model (type: User::class)
308311 )]
309312 public function addAction (
310- #[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST )]
313+ #[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST , validationGroups: [ ' add ' ] )]
311314 AddUser $ addUser ,
312315 Request $ request
313316 ): Response {
314317 return $ this ->addOrUpdateUser ($ addUser , $ request );
315318 }
316319
317320 /**
318- * Update an existing User or create one with the given ID
321+ * Update an existing User
319322 */
320323 #[IsGranted('ROLE_API_WRITER ' )]
321- #[Rest \Put ('/{id} ' )]
324+ #[Rest \Patch ('/{id} ' )]
322325 #[OA \RequestBody(
323326 required: true ,
324327 content: [
325- new OA \MediaType (
326- mediaType: 'multipart/form-data ' ,
327- schema: new OA \Schema (ref: new Model (type: UpdateUser::class))
328+ new OA \JsonContent (
329+ ref: new Model (type: UpdateUser::class)
328330 ),
329331 ]
330332 )]
331333 #[OA \Response(
332334 response: 201 ,
333- description: 'Returns the added user ' ,
335+ description: 'Returns the updated user ' ,
334336 content: new Model (type: User::class)
335337 )]
336338 public function updateAction (
337339 #[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST )]
338- UpdateUser $ updateUser ,
340+ UpdateUser $ mutateUser ,
341+ string $ id ,
339342 Request $ request
340343 ): Response {
341- return $ this ->addOrUpdateUser ($ updateUser , $ request );
344+ /** @var User|null $user */
345+ $ user = $ this ->em ->getRepository (User::class)->findOneBy (['externalid ' => $ id ]);
346+ if ($ user === null ) {
347+ throw new NotFoundHttpException (sprintf ("User with id %s not found " , $ id ));
348+ }
349+ return $ this ->addOrUpdateUser ($ mutateUser , $ request , $ user );
342350 }
343351
344- protected function addOrUpdateUser (AddUser $ addUser , Request $ request ): Response
352+ protected function addOrUpdateUser (AddUser | UpdateUser $ mutateUser , Request $ request, ? User $ user = null ): Response
345353 {
346- if ($ addUser instanceof UpdateUser && !$ addUser ->id ) {
347- throw new BadRequestHttpException ('`id` field is required ' );
354+ // if the user to update is not set, create a new user
355+ if (!$ user ) {
356+ $ user = new User ();
348357 }
349-
350- if ($ this ->em ->getRepository (User::class)->findOneBy (['username ' => $ addUser ->username ])) {
351- throw new BadRequestHttpException (sprintf ("User %s already exists " , $ addUser ->username ));
358+ if ($ mutateUser ->username !== null ) {
359+ $ user ->setUsername ($ mutateUser ->username );
352360 }
353-
354- $ user = new User ();
355- if ($ addUser instanceof UpdateUser) {
356- $ existingUser = $ this ->em ->getRepository (User::class)->findOneBy (['externalid ' => $ addUser ->id ]);
357- if ($ existingUser ) {
358- $ user = $ existingUser ;
359- }
361+ if ($ mutateUser ->name !== null ) {
362+ $ user ->setName ($ mutateUser ->name );
360363 }
361- $ user
362- ->setUsername ($ addUser ->username )
363- ->setName ($ addUser ->name )
364- ->setEmail ($ addUser ->email )
365- ->setIpAddress ($ addUser ->ip )
366- ->setPlainPassword ($ addUser ->password )
367- ->setEnabled ($ addUser ->enabled ?? true );
368-
369- if ($ addUser instanceof UpdateUser) {
370- $ user ->setExternalid ($ addUser ->id );
364+ if ($ mutateUser ->email !== null ) {
365+ $ user ->setEmail ($ mutateUser ->email );
371366 }
372-
373- if ($ addUser ->teamId ) {
367+ if ($ mutateUser ->ip !== null ) {
368+ $ user ->setIpAddress ($ mutateUser ->ip );
369+ }
370+ if ($ mutateUser ->password !== null ) {
371+ $ user ->setPlainPassword ($ mutateUser ->password );
372+ }
373+ if ($ mutateUser ->enabled !== null ) {
374+ $ user ->setEnabled ($ mutateUser ->enabled );
375+ }
376+ if ($ mutateUser ->teamId ) {
374377 /** @var Team|null $team */
375378 $ team = $ this ->em ->createQueryBuilder ()
376379 ->from (Team::class, 't ' )
377380 ->select ('t ' )
378381 ->andWhere ('t.externalid = :team ' )
379- ->setParameter ('team ' , $ addUser ->teamId )
382+ ->setParameter ('team ' , $ mutateUser ->teamId )
380383 ->getQuery ()
381384 ->getOneOrNullResult ();
382385
383386 if ($ team === null ) {
384- throw new BadRequestHttpException (sprintf ("Team %s not found " , $ addUser ->teamId ));
387+ throw new BadRequestHttpException (sprintf ("Team %s not found " , $ mutateUser ->teamId ));
385388 }
386389 $ user ->setTeam ($ team );
387390 }
388391
389- $ roles = $ addUser ->roles ;
392+ $ roles = $ mutateUser ->roles ;
390393 // For the file import we change a CDS user to the roles needed for ICPC CDS.
391394 if ($ user ->getUsername () === 'cds ' ) {
392395 $ roles = ['cds ' ];
@@ -408,18 +411,23 @@ protected function addOrUpdateUser(AddUser $addUser, Request $request): Response
408411 $ user ->addUserRole ($ role );
409412 }
410413
414+ $ validation = $ this ->validator ->validate ($ user );
415+ if (count ($ validation ) > 0 ) {
416+ throw new ValidationFailedException ($ user , $ validation );
417+ }
418+
411419 $ this ->em ->persist ($ user );
412420 $ this ->em ->flush ();
413- $ this ->dj ->auditlog ('user ' , $ user ->getUserid (), 'added ' );
421+ $ this ->dj ->auditlog ('user ' , $ user ->getUserid (), 'updated ' );
414422
415423 return $ this ->renderCreateData ($ request , $ user , 'user ' , $ user ->getUserid ());
416424 }
417425
418426 protected function getQueryBuilder (Request $ request ): QueryBuilder
419427 {
420428 $ queryBuilder = $ this ->em ->createQueryBuilder ()
421- ->from (User::class, 'u ' )
422- ->select ('u ' );
429+ ->from (User::class, 'u ' )
430+ ->select ('u ' );
423431
424432 if ($ request ->query ->has ('team ' )) {
425433 $ queryBuilder
0 commit comments