23
23
use Symfony \Component \HttpFoundation \Response ;
24
24
use Symfony \Component \HttpKernel \Attribute \MapRequestPayload ;
25
25
use Symfony \Component \HttpKernel \Exception \BadRequestHttpException ;
26
+ use Symfony \Component \HttpKernel \Exception \NotFoundHttpException ;
26
27
use Symfony \Component \Security \Http \Attribute \IsGranted ;
28
+ use Symfony \Component \Validator \Exception \ValidationFailedException ;
29
+ use Symfony \Component \Validator \Validator \ValidatorInterface ;
27
30
28
31
/**
29
32
* @extends AbstractRestController<User, User>
@@ -41,7 +44,8 @@ public function __construct(
41
44
DOMJudgeService $ dj ,
42
45
ConfigurationService $ config ,
43
46
EventLogService $ eventLogService ,
44
- protected readonly ImportExportService $ importExportService
47
+ protected readonly ImportExportService $ importExportService ,
48
+ protected readonly ValidatorInterface $ validator ,
45
49
) {
46
50
parent ::__construct ($ entityManager , $ dj , $ config , $ eventLogService );
47
51
}
@@ -86,9 +90,9 @@ public function addGroupsAction(Request $request): string
86
90
$ message = null ;
87
91
$ result = -1 ;
88
92
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 )))) &&
90
94
$ result >= 0 ) {
91
- return "$ result new group(s) successfully added. " ;
95
+ return "$ result new group(s) successfully added. " ;
92
96
} else {
93
97
throw new BadRequestHttpException ("Error while adding groups: $ message " );
94
98
}
@@ -171,7 +175,7 @@ public function addTeamsAction(Request $request): string
171
175
}
172
176
$ message = null ;
173
177
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 )))) &&
175
179
$ result >= 0 ) {
176
180
// TODO: better return all teams here?
177
181
return "$ result new team(s) successfully added. " ;
@@ -235,7 +239,7 @@ public function addAccountsAction(Request $request): string
235
239
236
240
$ message = null ;
237
241
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 )))) &&
239
243
$ result >= 0 ) {
240
244
// TODO: better return all accounts here?
241
245
return "$ result new account(s) successfully added. " ;
@@ -291,13 +295,12 @@ public function singleAction(Request $request, string $id): Response
291
295
* Add a new user.
292
296
*/
293
297
#[IsGranted('ROLE_API_WRITER ' )]
294
- #[Rest \Post]
298
+ #[Rest \Post() ]
295
299
#[OA \RequestBody(
296
300
required: true ,
297
301
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)
301
304
),
302
305
]
303
306
)]
@@ -307,86 +310,86 @@ public function singleAction(Request $request, string $id): Response
307
310
content: new Model (type: User::class)
308
311
)]
309
312
public function addAction (
310
- #[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST )]
313
+ #[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST , validationGroups: [ ' add ' ] )]
311
314
AddUser $ addUser ,
312
315
Request $ request
313
316
): Response {
314
317
return $ this ->addOrUpdateUser ($ addUser , $ request );
315
318
}
316
319
317
320
/**
318
- * Update an existing User or create one with the given ID
321
+ * Update an existing User
319
322
*/
320
323
#[IsGranted('ROLE_API_WRITER ' )]
321
- #[Rest \Put ('/{id} ' )]
324
+ #[Rest \Patch ('/{id} ' )]
322
325
#[OA \RequestBody(
323
326
required: true ,
324
327
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)
328
330
),
329
331
]
330
332
)]
331
333
#[OA \Response(
332
334
response: 201 ,
333
- description: 'Returns the added user ' ,
335
+ description: 'Returns the updated user ' ,
334
336
content: new Model (type: User::class)
335
337
)]
336
338
public function updateAction (
337
339
#[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST )]
338
- UpdateUser $ updateUser ,
340
+ UpdateUser $ mutateUser ,
341
+ string $ id ,
339
342
Request $ request
340
343
): 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 );
342
350
}
343
351
344
- protected function addOrUpdateUser (AddUser $ addUser , Request $ request ): Response
352
+ protected function addOrUpdateUser (AddUser | UpdateUser $ mutateUser , Request $ request, ? User $ user = null ): Response
345
353
{
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 ();
348
357
}
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 );
352
360
}
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 );
360
363
}
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 );
371
366
}
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 ) {
374
377
/** @var Team|null $team */
375
378
$ team = $ this ->em ->createQueryBuilder ()
376
379
->from (Team::class, 't ' )
377
380
->select ('t ' )
378
381
->andWhere ('t.externalid = :team ' )
379
- ->setParameter ('team ' , $ addUser ->teamId )
382
+ ->setParameter ('team ' , $ mutateUser ->teamId )
380
383
->getQuery ()
381
384
->getOneOrNullResult ();
382
385
383
386
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 ));
385
388
}
386
389
$ user ->setTeam ($ team );
387
390
}
388
391
389
- $ roles = $ addUser ->roles ;
392
+ $ roles = $ mutateUser ->roles ;
390
393
// For the file import we change a CDS user to the roles needed for ICPC CDS.
391
394
if ($ user ->getUsername () === 'cds ' ) {
392
395
$ roles = ['cds ' ];
@@ -408,18 +411,23 @@ protected function addOrUpdateUser(AddUser $addUser, Request $request): Response
408
411
$ user ->addUserRole ($ role );
409
412
}
410
413
414
+ $ validation = $ this ->validator ->validate ($ user );
415
+ if (count ($ validation ) > 0 ) {
416
+ throw new ValidationFailedException ($ user , $ validation );
417
+ }
418
+
411
419
$ this ->em ->persist ($ user );
412
420
$ this ->em ->flush ();
413
- $ this ->dj ->auditlog ('user ' , $ user ->getUserid (), 'added ' );
421
+ $ this ->dj ->auditlog ('user ' , $ user ->getUserid (), 'updated ' );
414
422
415
423
return $ this ->renderCreateData ($ request , $ user , 'user ' , $ user ->getUserid ());
416
424
}
417
425
418
426
protected function getQueryBuilder (Request $ request ): QueryBuilder
419
427
{
420
428
$ queryBuilder = $ this ->em ->createQueryBuilder ()
421
- ->from (User::class, 'u ' )
422
- ->select ('u ' );
429
+ ->from (User::class, 'u ' )
430
+ ->select ('u ' );
423
431
424
432
if ($ request ->query ->has ('team ' )) {
425
433
$ queryBuilder
0 commit comments