1- <?php declare (strict_types=1 );
1+ <?php
2+
3+ declare (strict_types=1 );
24
35namespace App \Controller \API ;
46
2325use Symfony \Component \HttpFoundation \Response ;
2426use Symfony \Component \HttpKernel \Attribute \MapRequestPayload ;
2527use Symfony \Component \HttpKernel \Exception \BadRequestHttpException ;
28+ use Symfony \Component \HttpKernel \Exception \NotFoundHttpException ;
2629use Symfony \Component \Security \Http \Attribute \IsGranted ;
30+ use Symfony \Component \Validator \Exception \ValidationFailedException ;
31+ use Symfony \Component \Validator \Validator \ValidatorInterface ;
2732
2833/**
2934 * @extends AbstractRestController<User, User>
@@ -41,7 +46,8 @@ public function __construct(
4146 DOMJudgeService $ dj ,
4247 ConfigurationService $ config ,
4348 EventLogService $ eventLogService ,
44- protected readonly ImportExportService $ importExportService
49+ protected readonly ImportExportService $ importExportService ,
50+ protected readonly ValidatorInterface $ validator ,
4551 ) {
4652 parent ::__construct ($ entityManager , $ dj , $ config , $ eventLogService );
4753 }
@@ -86,9 +92,10 @@ public function addGroupsAction(Request $request): string
8692 $ message = null ;
8793 $ result = -1 ;
8894 if ((($ tsvFile && ($ result = $ this ->importExportService ->importTsv ('groups ' , $ tsvFile , $ message ))) ||
89- ($ jsonFile && ($ result = $ this ->importExportService ->importJson ('groups ' , $ jsonFile , $ message )))) &&
90- $ result >= 0 ) {
91- return "$ result new group(s) successfully added. " ;
95+ ($ jsonFile && ($ result = $ this ->importExportService ->importJson ('groups ' , $ jsonFile , $ message )))) &&
96+ $ result >= 0
97+ ) {
98+ return "$ result new group(s) successfully added. " ;
9299 } else {
93100 throw new BadRequestHttpException ("Error while adding groups: $ message " );
94101 }
@@ -110,7 +117,8 @@ public function addGroupsAction(Request $request): string
110117 property: 'json ' ,
111118 description: 'The organizations.json files to import. ' ,
112119 type: 'string ' ,
113- format: 'binary ' ),
120+ format: 'binary '
121+ ),
114122 ]
115123 )
116124 )
@@ -121,7 +129,8 @@ public function addOrganizationsAction(Request $request): string
121129 $ message = null ;
122130 /** @var UploadedFile|null $jsonFile */
123131 $ jsonFile = $ request ->files ->get ('json ' );
124- if ($ jsonFile &&
132+ if (
133+ $ jsonFile &&
125134 ($ result = $ this ->importExportService ->importJson ('organizations ' , $ jsonFile , $ message )) &&
126135 $ result >= 0
127136 ) {
@@ -171,8 +180,9 @@ public function addTeamsAction(Request $request): string
171180 }
172181 $ message = null ;
173182 if ((($ tsvFile && ($ result = $ this ->importExportService ->importTsv ('teams ' , $ tsvFile , $ message ))) ||
174- ($ jsonFile && ($ result = $ this ->importExportService ->importJson ('teams ' , $ jsonFile , $ message )))) &&
175- $ result >= 0 ) {
183+ ($ jsonFile && ($ result = $ this ->importExportService ->importJson ('teams ' , $ jsonFile , $ message )))) &&
184+ $ result >= 0
185+ ) {
176186 // TODO: better return all teams here?
177187 return "$ result new team(s) successfully added. " ;
178188 } else {
@@ -235,8 +245,9 @@ public function addAccountsAction(Request $request): string
235245
236246 $ message = null ;
237247 if ((($ tsvFile && ($ result = $ this ->importExportService ->importTsv ('accounts ' , $ tsvFile , $ message ))) ||
238- ($ jsonFile && ($ result = $ this ->importExportService ->importJson ('accounts ' , $ jsonFile , $ message )))) &&
239- $ result >= 0 ) {
248+ ($ jsonFile && ($ result = $ this ->importExportService ->importJson ('accounts ' , $ jsonFile , $ message )))) &&
249+ $ result >= 0
250+ ) {
240251 // TODO: better return all accounts here?
241252 return "$ result new account(s) successfully added. " ;
242253 } else {
@@ -259,11 +270,13 @@ public function addAccountsAction(Request $request): string
259270 )
260271 )]
261272 #[OA \Parameter(ref: '#/components/parameters/idlist ' )]
262- #[OA \Parameter(
263- name: 'team_id ' ,
264- description: 'Only show users for the given team ' ,
265- in: 'query ' ,
266- schema: new OA \Schema (type: 'string ' ))
273+ #[
274+ OA \Parameter(
275+ name: 'team_id ' ,
276+ description: 'Only show users for the given team ' ,
277+ in: 'query ' ,
278+ schema: new OA \Schema (type: 'string ' )
279+ )
267280 ]
268281 public function listAction (Request $ request ): Response
269282 {
@@ -291,13 +304,12 @@ public function singleAction(Request $request, string $id): Response
291304 * Add a new user.
292305 */
293306 #[IsGranted('ROLE_API_WRITER ' )]
294- #[Rest \Post]
307+ #[Rest \Post() ]
295308 #[OA \RequestBody(
296309 required: true ,
297310 content: [
298- new OA \MediaType (
299- mediaType: 'multipart/form-data ' ,
300- schema: new OA \Schema (ref: new Model (type: AddUser::class))
311+ new OA \JsonContent (
312+ ref: new Model (type: AddUser::class)
301313 ),
302314 ]
303315 )]
@@ -307,24 +319,23 @@ public function singleAction(Request $request, string $id): Response
307319 content: new Model (type: User::class)
308320 )]
309321 public function addAction (
310- #[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST )]
322+ #[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST , validationGroups: [ ' add ' ] )]
311323 AddUser $ addUser ,
312324 Request $ request
313325 ): Response {
314- return $ this ->addOrUpdateUser ($ addUser , $ request );
326+ return $ this ->addOrUpdateUser ($ addUser , new User (), $ request );
315327 }
316328
317329 /**
318- * Update an existing User or create one with the given ID
330+ * Update an existing User
319331 */
320332 #[IsGranted('ROLE_API_WRITER ' )]
321- #[Rest \Put ('/{id} ' )]
333+ #[Rest \Patch ('/{id} ' )]
322334 #[OA \RequestBody(
323335 required: true ,
324336 content: [
325- new OA \MediaType (
326- mediaType: 'multipart/form-data ' ,
327- schema: new OA \Schema (ref: new Model (type: UpdateUser::class))
337+ new OA \JsonContent (
338+ ref: new Model (type: UpdateUser::class)
328339 ),
329340 ]
330341 )]
@@ -335,58 +346,55 @@ public function addAction(
335346 )]
336347 public function updateAction (
337348 #[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST )]
338- UpdateUser $ updateUser ,
349+ UpdateUser $ mutateUser ,
350+ string $ id ,
339351 Request $ request
340352 ): Response {
341- return $ this ->addOrUpdateUser ($ updateUser , $ request );
353+ /** @var User|null $user */
354+ $ user = $ this ->em ->getRepository (User::class)->findOneBy (['externalid ' => $ id ]);
355+ if ($ user === null ) {
356+ throw new NotFoundHttpException (sprintf ("User with id %s not found " , $ id ));
357+ }
358+ return $ this ->addOrUpdateUser ($ mutateUser , $ user , $ request );
342359 }
343360
344- protected function addOrUpdateUser (AddUser $ addUser , Request $ request ): Response
361+ protected function addOrUpdateUser (AddUser | UpdateUser $ mutateUser , User $ user , Request $ request ): Response
345362 {
346- if ($ addUser instanceof UpdateUser && ! $ addUser -> id ) {
347- throw new BadRequestHttpException ( ' `id` field is required ' );
363+ if ($ mutateUser -> username !== null ) {
364+ $ user -> setUsername ( $ mutateUser -> username );
348365 }
349-
350- if ($ this ->em ->getRepository (User::class)->findOneBy (['username ' => $ addUser ->username ])) {
351- throw new BadRequestHttpException (sprintf ("User %s already exists " , $ addUser ->username ));
366+ if ($ mutateUser ->name !== null ) {
367+ $ user ->setName ($ mutateUser ->name );
352368 }
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- }
369+ if ($ mutateUser ->email !== null ) {
370+ $ user ->setEmail ($ mutateUser ->email );
360371 }
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 );
372+ if ($ mutateUser ->ip !== null ) {
373+ $ user ->setIpAddress ($ mutateUser ->ip );
371374 }
372-
373- if ($ addUser ->teamId ) {
375+ if ($ mutateUser ->password !== null ) {
376+ $ user ->setPlainPassword ($ mutateUser ->password );
377+ }
378+ if ($ mutateUser ->enabled !== null ) {
379+ $ user ->setEnabled ($ mutateUser ->enabled );
380+ }
381+ if ($ mutateUser ->teamId ) {
374382 /** @var Team|null $team */
375383 $ team = $ this ->em ->createQueryBuilder ()
376384 ->from (Team::class, 't ' )
377385 ->select ('t ' )
378386 ->andWhere ('t.externalid = :team ' )
379- ->setParameter ('team ' , $ addUser ->teamId )
387+ ->setParameter ('team ' , $ mutateUser ->teamId )
380388 ->getQuery ()
381389 ->getOneOrNullResult ();
382390
383391 if ($ team === null ) {
384- throw new BadRequestHttpException (sprintf ("Team %s not found " , $ addUser ->teamId ));
392+ throw new BadRequestHttpException (sprintf ("Team %s not found " , $ mutateUser ->teamId ));
385393 }
386394 $ user ->setTeam ($ team );
387395 }
388396
389- $ roles = $ addUser ->roles ;
397+ $ roles = $ mutateUser ->roles ;
390398 // For the file import we change a CDS user to the roles needed for ICPC CDS.
391399 if ($ user ->getUsername () === 'cds ' ) {
392400 $ roles = ['cds ' ];
@@ -408,18 +416,23 @@ protected function addOrUpdateUser(AddUser $addUser, Request $request): Response
408416 $ user ->addUserRole ($ role );
409417 }
410418
419+ $ validation = $ this ->validator ->validate ($ user );
420+ if (count ($ validation ) > 0 ) {
421+ throw new ValidationFailedException ($ user , $ validation );
422+ }
423+
411424 $ this ->em ->persist ($ user );
412425 $ this ->em ->flush ();
413- $ this ->dj ->auditlog ('user ' , $ user ->getUserid (), 'added ' );
426+ $ this ->dj ->auditlog ('user ' , $ user ->getUserid (), 'updated ' );
414427
415428 return $ this ->renderCreateData ($ request , $ user , 'user ' , $ user ->getUserid ());
416429 }
417430
418431 protected function getQueryBuilder (Request $ request ): QueryBuilder
419432 {
420433 $ queryBuilder = $ this ->em ->createQueryBuilder ()
421- ->from (User::class, 'u ' )
422- ->select ('u ' );
434+ ->from (User::class, 'u ' )
435+ ->select ('u ' );
423436
424437 if ($ request ->query ->has ('team ' )) {
425438 $ queryBuilder
0 commit comments