• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GEWIS / sudosos-backend / 18131239662

30 Sep 2025 01:17PM UTC coverage: 85.253% (+0.03%) from 85.219%
18131239662

Pull #582

github

web-flow
Merge 982960653 into 6dbe21f03
Pull Request #582: feature: add endpoints to add and remove roles from a user

1311 of 1598 branches covered (82.04%)

Branch coverage included in aggregate %.

37 of 39 new or added lines in 2 files covered. (94.87%)

7060 of 8221 relevant lines covered (85.88%)

1068.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.76
/src/controller/user-controller.ts
1
/**
2
 *  SudoSOS back-end API service.
3
 *  Copyright (C) 2024  Study association GEWIS
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU Affero General Public License as published
7
 *  by the Free Software Foundation, either version 3 of the License, or
8
 *  (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU Affero General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU Affero General Public License
16
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
 *
18
 *  @license
19
 */
20

21
/**
22
 * This is the module page of user-controller.
23
 *
24
 * @module users
25
 */
26

27
import { Response } from 'express';
28
import log4js, { Logger } from 'log4js';
2✔
29
import BaseController, { BaseControllerOptions } from './base-controller';
2✔
30
import Policy from './policy';
31
import { RequestWithToken } from '../middleware/token-middleware';
32
import User, { UserType } from '../entity/user/user';
2✔
33
import BaseUserRequest, { AddRoleRequest, CreateUserRequest, UpdateUserRequest } from './request/user-request';
34
import { parseRequestPagination } from '../helpers/pagination';
2✔
35
import ProductService from '../service/product-service';
2✔
36
import PointOfSaleService from '../service/point-of-sale-service';
2✔
37
import TransactionService, { parseGetTransactionsFilters } from '../service/transaction-service';
2✔
38
import ContainerService from '../service/container-service';
2✔
39
import TransferService, { parseGetTransferFilters } from '../service/transfer-service';
2✔
40
import MemberAuthenticator from '../entity/authenticator/member-authenticator';
2✔
41
import AuthenticationService, { AuthenticationContext } from '../service/authentication-service';
2✔
42
import TokenHandler from '../authentication/token-handler';
43
import RBACService from '../service/rbac-service';
2✔
44
import { isFail } from '../helpers/specification-validation';
2✔
45
import verifyUpdatePinRequest from './request/validators/update-pin-request-spec';
2✔
46
import UpdatePinRequest from './request/update-pin-request';
47
import UserService, {
2✔
48
  parseGetFinancialMutationsFilters,
49
  parseGetUsersFilters,
50
  UserFilterParameters,
51
} from '../service/user-service';
52
import { asFromAndTillDate, asNumber, asReturnFileType } from '../helpers/validators';
2✔
53
import { verifyCreateUserRequest } from './request/validators/user-request-spec';
2✔
54
import userTokenInOrgan from '../helpers/token-helper';
2✔
55
import { parseUserToResponse } from '../helpers/revision-to-response';
2✔
56
import { AcceptTosRequest } from './request/accept-tos-request';
57
import PinAuthenticator from '../entity/authenticator/pin-authenticator';
2✔
58
import LocalAuthenticator from '../entity/authenticator/local-authenticator';
2✔
59
import UpdateLocalRequest from './request/update-local-request';
60
import verifyUpdateLocalRequest from './request/validators/update-local-request-spec';
2✔
61
import StripeService from '../service/stripe-service';
2✔
62
import verifyUpdateNfcRequest from './request/validators/update-nfc-request-spec';
2✔
63
import UpdateNfcRequest from './request/update-nfc-request';
64
import NfcAuthenticator from '../entity/authenticator/nfc-authenticator';
2✔
65
import KeyAuthenticator from '../entity/authenticator/key-authenticator';
2✔
66
import UpdateKeyResponse from './response/update-key-response';
67
import { randomBytes } from 'crypto';
2✔
68
import DebtorService, { WaiveFinesParams } from '../service/debtor-service';
2✔
69
import ReportService, { BuyerReportService, SalesReportService } from '../service/report-service';
2✔
70
import { ReturnFileType, UserReportParametersType } from 'pdf-generator-client';
2✔
71
import { reportPDFhelper } from '../helpers/express-pdf';
2✔
72
import { PdfError } from '../errors';
2✔
73
import { WaiveFinesRequest } from './request/debtor-request';
74
import Dinero from 'dinero.js';
2✔
75
import Role from '../entity/rbac/role';
2✔
76

77
export default class UserController extends BaseController {
2✔
78
  private logger: Logger = log4js.getLogger('UserController');
3✔
79

80
  /**
81
   * Reference to the token handler of the application.
82
   */
83
  private tokenHandler: TokenHandler;
84

85
  /**
86
   * Create a new user controller instance.
87
   * @param options - The options passed to the base controller.
88
   * @param tokenHandler
89
   */
90
  public constructor(
91
    options: BaseControllerOptions,
92
    tokenHandler: TokenHandler,
93
  ) {
94
    super(options);
3✔
95
    this.logger.level = process.env.LOG_LEVEL;
3✔
96
    this.tokenHandler = tokenHandler;
3✔
97
  }
98

99
  /**
100
   * @inheritDoc
101
   */
102
  public getPolicy(): Policy {
103
    return {
3✔
104
      '/': {
105
        GET: {
106
          policy: async (req) => this.roleManager.can(
8✔
107
            req.token.roles, 'get', 'all', 'User', ['*'],
108
          ),
109
          handler: this.getAllUsers.bind(this),
110
        },
111
        POST: {
112
          body: { modelName: 'CreateUserRequest' },
113
          policy: async (req) => this.roleManager.can(
8✔
114
            req.token.roles, 'create', 'all', 'User', ['*'],
115
          ),
116
          handler: this.createUser.bind(this),
117
        },
118
      },
119
      '/usertype/:userType': {
120
        GET: {
121
          policy: async (req) => this.roleManager.can(
6✔
122
            req.token.roles, 'get', 'all', 'User', ['*'],
123
          ),
124
          handler: this.getAllUsersOfUserType.bind(this),
125
        },
126
      },
127
      '/acceptTos': {
128
        POST: {
129
          policy: async (req) => this.roleManager.can(
3✔
130
            req.token.roles, 'acceptToS', 'own', 'User', ['*'],
131
          ),
132
          handler: this.acceptToS.bind(this),
133
          body: { modelName: 'AcceptTosRequest' },
134
          restrictions: { acceptedTOS: false },
135
        },
136
      },
137
      '/nfc/:nfcCode': {
138
        GET: {
139
          policy: async (req) => this.roleManager.can(
4✔
140
            req.token.roles, 'get', 'all', 'User', ['*'],
141
          ),
142
          handler: this.findUserNfc.bind(this),
143
        },
144
      },
145
      '/:id(\\d+)/authenticator/pin': {
146
        PUT: {
147
          body: { modelName: 'UpdatePinRequest' },
148
          policy: async (req) => this.roleManager.can(
4✔
149
            req.token.roles, 'update', UserController.getRelation(req), 'Authenticator', ['pin'],
150
          ),
151
          handler: this.updateUserPin.bind(this),
152
        },
153
      },
154
      '/:id(\\d+)/authenticator/nfc': {
155
        PUT: {
156
          body: { modelName: 'UpdateNfcRequest' },
157
          policy: async (req) => this.roleManager.can(
11✔
158
            req.token.roles, 'update', UserController.getRelation(req), 'Authenticator', ['nfcCode'],
159
          ),
160
          handler: this.updateUserNfc.bind(this),
161
        },
162
        DELETE: {
163
          policy: async (req) => this.roleManager.can(
9✔
164
            req.token.roles, 'delete', UserController.getRelation(req), 'Authenticator', [],
165
          ),
166
          handler: this.deleteUserNfc.bind(this),
167
        },
168
      },
169

170
      '/:id(\\d+)/authenticator/key': {
171
        POST: {
172
          policy: async (req) => this.roleManager.can(
3✔
173
            req.token.roles, 'update', UserController.getRelation(req), 'Authenticator', ['key'],
174
          ),
175
          handler: this.updateUserKey.bind(this),
176
        },
177
        DELETE: {
178
          policy: async (req) => this.roleManager.can(
3✔
179
            req.token.roles, 'update', UserController.getRelation(req), 'Authenticator', ['key'],
180
          ),
181
          handler: this.deleteUserKey.bind(this),
182
        },
183
      },
184
      '/:id(\\d+)/authenticator/local': {
185
        PUT: {
186
          body: { modelName: 'UpdateLocalRequest' },
187
          policy: async (req) => this.roleManager.can(
4✔
188
            req.token.roles, 'update', UserController.getRelation(req), 'Authenticator', ['password'],
189
          ),
190
          handler: this.updateUserLocalPassword.bind(this),
191
        },
192
      },
193
      '/:id(\\d+)': {
194
        GET: {
195
          policy: async (req) => this.roleManager.can(
9✔
196
            req.token.roles, 'get', UserController.getRelation(req), 'User', ['*'],
197
          ),
198
          handler: this.getIndividualUser.bind(this),
199
        },
200
        DELETE: {
201
          policy: async (req) => this.roleManager.can(
6✔
202
            req.token.roles, 'delete', UserController.getRelation(req), 'User', ['*'],
203
          ),
204
          handler: this.deleteUser.bind(this),
205
        },
206
        PATCH: {
207
          body: { modelName: 'UpdateUserRequest' },
208
          policy: async (req) => this.roleManager.can(
14✔
209
            req.token.roles, 'update', UserController.getRelation(req), 'User', UserController.getAttributes(req),
210
          ),
211
          handler: this.updateUser.bind(this),
212
        },
213
      },
214
      '/:id(\\d+)/members': {
215
        GET: {
216
          policy: async (req) => this.roleManager.can(
5✔
217
            req.token.roles, 'get', UserController.getRelation(req), 'User', ['*'],
218
          ),
219
          handler: this.getOrganMembers.bind(this),
220
        },
221
      },
222
      '/:id(\\d+)/authenticate': {
223
        POST: {
224
          policy: async () => true,
2✔
225
          handler: this.authenticateAsUser.bind(this),
226
        },
227
        GET: {
228
          policy: async (req) => this.roleManager.can(
3✔
229
            req.token.roles, 'get', UserController.getRelation(req), 'Authenticator', ['*'],
230
          ),
231
          handler: this.getUserAuthenticatable.bind(this),
232
        },
233
      },
234
      '/:id(\\d+)/products': {
235
        GET: {
236
          policy: async (req) => this.roleManager.can(
6✔
237
            req.token.roles, 'get', UserController.getRelation(req), 'Product', ['*'],
238
          ),
239
          handler: this.getUsersProducts.bind(this),
240
        },
241
      },
242
      '/:id(\\d+)/roles': {
243
        GET: {
244
          policy: async (req) => this.roleManager.can(
4✔
245
            req.token.roles, 'get', UserController.getRelation(req), 'Roles', ['*'],
246
          ),
247
          handler: this.getUserRoles.bind(this),
248
        },
249
        POST: {
250
          policy: async (req) => this.roleManager.can(
3✔
251
            req.token.roles, 'create', UserController.getRelation(req), 'Roles', ['*'],
252
          ),
253
          handler: this.addUserRole.bind(this),
254
        },
255
      },
256
      '/:id(\\d+)/roles/:roleId(\\d+)': {
257
        DELETE: {
258
          policy: async (req) => this.roleManager.can(
5✔
259
            req.token.roles, 'delete', UserController.getRelation(req), 'Roles', ['*'],
260
          ),
261
          handler: this.deleteUserRole.bind(this),
262
        },
263
      },
264
      '/:id(\\d+)/containers': {
265
        GET: {
266
          policy: async (req) => this.roleManager.can(
6✔
267
            req.token.roles, 'get', UserController.getRelation(req), 'Container', ['*'],
268
          ),
269
          handler: this.getUsersContainers.bind(this),
270
        },
271
      },
272
      '/:id(\\d+)/pointsofsale': {
273
        GET: {
274
          policy: async (req) => this.roleManager.can(
6✔
275
            req.token.roles, 'get', UserController.getRelation(req), 'PointOfSale', ['*'],
276
          ),
277
          handler: this.getUsersPointsOfSale.bind(this),
278
        },
279
      },
280
      '/:id(\\d+)/transactions': {
281
        GET: {
282
          policy: async (req) => this.roleManager.can(
5✔
283
            req.token.roles, 'get', UserController.getRelation(req), 'Transaction', ['*'],
284
          ),
285
          handler: this.getUsersTransactions.bind(this),
286
        },
287
      },
288
      '/:id(\\d+)/transactions/sales/report': {
289
        GET: {
290
          policy: async (req) => this.roleManager.can(
4✔
291
            req.token.roles, 'get', UserController.getRelation(req), 'Transaction', ['*'],
292
          ),
293
          handler: this.getUsersSalesReport.bind(this),
294
        },
295
      },
296
      '/:id(\\d+)/transactions/sales/report/pdf': {
297
        GET: {
298
          policy: async (req) => this.roleManager.can(
8✔
299
            req.token.roles, 'get', UserController.getRelation(req), 'Transaction', ['*'],
300
          ),
301
          handler: this.getUsersSalesReportPdf.bind(this),
302
        },
303
      },
304
      '/:id(\\d+)/transactions/purchases/report': {
305
        GET: {
306
          policy: async (req) => this.roleManager.can(
4✔
307
            req.token.roles, 'get', UserController.getRelation(req), 'Transaction', ['*'],
308
          ),
309
          handler: this.getUsersPurchasesReport.bind(this),
310
        },
311
      },
312
      '/:id(\\d+)/transactions/purchases/report/pdf': {
313
        GET: {
314
          policy: async (req) => this.roleManager.can(
8✔
315
            req.token.roles, 'get', UserController.getRelation(req), 'Transaction', ['*'],
316
          ),
317
          handler: this.getUsersPurchaseReportPdf.bind(this),
318
        },
319
      },
320
      '/:id(\\d+)/transactions/report': {
321
        GET: {
322
          policy: async (req) => this.roleManager.can(
6✔
323
            req.token.roles, 'get', UserController.getRelation(req), 'Transaction', ['*'],
324
          ),
325
          handler: this.getUsersTransactionsReport.bind(this),
326
        },
327
      },
328
      '/:id(\\d+)/transfers': {
329
        GET: {
330
          policy: async (req) => this.roleManager.can(
2✔
331
            req.token.roles, 'get', UserController.getRelation(req), 'Transfer', ['*'],
332
          ),
333
          handler: this.getUsersTransfers.bind(this),
334
        },
335
      },
336
      '/:id(\\d+)/financialmutations': {
337
        GET: {
338
          policy: async (req) => this.roleManager.can(
4✔
339
            req.token.roles, 'get', UserController.getRelation(req), 'Transfer', ['*'],
340
          ) && this.roleManager.can(
341
            req.token.roles, 'get', UserController.getRelation(req), 'Transaction', ['*'],
342
          ),
343
          handler: this.getUsersFinancialMutations.bind(this),
344
        },
345
      },
346
      '/:id(\\d+)/deposits': {
347
        GET: {
348
          policy: async (req) => this.roleManager.can(
2✔
349
            req.token.roles, 'get', UserController.getRelation(req), 'Transfer', ['*'],
350
          ),
351
          handler: this.getUsersProcessingDeposits.bind(this),
352
        },
353
      },
354
      '/:id(\\d+)/fines/waive': {
355
        POST: {
356
          policy: async (req) => this.roleManager.can(req.token.roles, 'delete', 'all', 'Fine', ['*']),
10✔
357
          handler: this.waiveUserFines.bind(this),
358
          body: { modelName: 'WaiveFinesRequest', allowBlankTarget: true },
359
        },
360
      },
361
    };
362
  }
363

364
  /**
365
   * Function to determine which credentials are needed to GET
366
   *    'all' if user is not connected to User
367
   *    'organ' if user is connected to User via organ
368
   *    'own' if user is connected to User
369
   * @param req
370
   * @return whether User is connected to used token
371
   */
372
  static getRelation(req: RequestWithToken): string {
373
    if (userTokenInOrgan(req, asNumber(req.params.id))) return 'organ';
151✔
374
    return req.params.id === req.token.user.id.toString() ? 'own' : 'all';
150✔
375
  }
376

377
  static getAttributes(req: RequestWithToken): string[] {
378
    const attributes: string[] = [];
15✔
379
    const body = req.body as BaseUserRequest;
15✔
380
    for (const key in body) {
15✔
381
      if (body.hasOwnProperty(key)) {
17✔
382
        attributes.push(key);
17✔
383
      }
384
    }
385
    return attributes;
15✔
386
  }
387

388
  /**
389
   * GET /users
390
   * @summary Get a list of all users
391
   * @operationId getAllUsers
392
   * @tags users - Operations of user controller
393
   * @security JWT
394
   * @param {integer} take.query - How many users the endpoint should return
395
   * @param {integer} skip.query - How many users should be skipped (for pagination)
396
   * @param {string} search.query - Filter based on first name
397
   * @param {boolean} active.query - Filter based if the user is active
398
   * @param {boolean} ofAge.query - Filter based if the user is 18+
399
   * @param {integer} id.query - Filter based on user ID
400
   * @param {string} type.query - enum:MEMBER,ORGAN,VOUCHER,LOCAL_USER,LOCAL_ADMIN,INVOICE,AUTOMATIC_INVOICE - Filter based on user type.
401
   * @return {PaginatedUserResponse} 200 - A list of all users
402
   */
403
  public async getAllUsers(req: RequestWithToken, res: Response): Promise<void> {
404
    this.logger.trace('Get all users by user', req.token.user);
11✔
405

406
    let take;
407
    let skip;
408
    let filters: UserFilterParameters;
409
    try {
11✔
410
      const pagination = parseRequestPagination(req);
11✔
411
      filters = parseGetUsersFilters(req);
11✔
412
      take = pagination.take;
11✔
413
      skip = pagination.skip;
11✔
414
    } catch (e) {
415
      res.status(400).send(e.message);
×
416
      return;
×
417
    }
418

419
    try {
11✔
420
      const users = await UserService.getUsers(filters, { take, skip });
11✔
421
      res.status(200).json(users);
11✔
422
    } catch (error) {
423
      this.logger.error('Could not get users:', error);
×
424
      res.status(500).json('Internal server error.');
×
425
    }
426
  }
427

428
  /**
429
   * GET /users/usertype/{userType}
430
   * @summary Get all users of user type
431
   * @operationId getAllUsersOfUserType
432
   * @tags users - Operations of user controller
433
   * @param {string} userType.path.required - The userType of the requested users
434
   * @security JWT
435
   * @param {integer} take.query - How many users the endpoint should return
436
   * @param {integer} skip.query - How many users should be skipped (for pagination)
437
   * @return {PaginatedUserResponse} 200 - A list of all users
438
   * @return {string} 404 - Nonexistent usertype
439
   */
440
  public async getAllUsersOfUserType(req: RequestWithToken, res: Response): Promise<void> {
441
    const parameters = req.params;
5✔
442
    this.logger.trace('Get all users of userType', parameters, 'by user', req.token.user);
5✔
443
    const userType = req.params.userType.toUpperCase();
5✔
444

445
    // If it does not exist, return a 404 error
446
    const type = UserType[userType as keyof typeof UserType];
5✔
447
    if (!type || Number(userType)) {
5✔
448
      res.status(404).json('Unknown userType.');
1✔
449
      return;
1✔
450
    }
451

452
    try {
4✔
453
      req.query.type = userType;
4✔
454
      await this.getAllUsers(req, res);
4✔
455
    } catch (error) {
456
      this.logger.error('Could not get users:', error);
×
457
      res.status(500).json('Internal server error.');
×
458
    }
459
  }
460

461
  /**
462
   * PUT /users/{id}/authenticator/pin
463
   * @summary Put an users pin code
464
   * @operationId updateUserPin
465
   * @tags users - Operations of user controller
466
   * @param {integer} id.path.required - The id of the user
467
   * @param {UpdatePinRequest} request.body.required -
468
   *    The PIN code to update to
469
   * @security JWT
470
   * @return 204 - Update success
471
   * @return {string} 400 - Validation Error
472
   * @return {string} 404 - Nonexistent user id
473
   */
474
  public async updateUserPin(req: RequestWithToken, res: Response): Promise<void> {
475
    const { params } = req;
3✔
476
    const updatePinRequest = req.body as UpdatePinRequest;
3✔
477
    this.logger.trace('Update user pin', params, 'by user', req.token.user);
3✔
478

479
    try {
3✔
480
      // Get the user object if it exists
481
      const user = await User.findOne({ where: { id: parseInt(params.id, 10), deleted: false } });
3✔
482
      // If it does not exist, return a 404 error
483
      if (user == null) {
3✔
484
        res.status(404).json('Unknown user ID.');
1✔
485
        return;
1✔
486
      }
487

488
      const validation = await verifyUpdatePinRequest(updatePinRequest);
2✔
489
      if (isFail(validation)) {
2✔
490
        res.status(400).json(validation.fail.value);
1✔
491
        return;
1✔
492
      }
493

494
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
495
        updatePinRequest.pin.toString(), PinAuthenticator);
496
      res.status(204).json();
1✔
497
    } catch (error) {
498
      this.logger.error('Could not update pin:', error);
×
499
      res.status(500).json('Internal server error.');
×
500
    }
501
  }
502

503
  /**
504
   * PUT /users/{id}/authenticator/nfc
505
   * @summary Put a users NFC code
506
   * @operationId updateUserNfc
507
   * @tags users - Operations of user controller
508
   * @param {integer} id.path.required - The id of the user
509
   * @param {UpdateNfcRequest} request.body.required -
510
   *    The NFC code to update to
511
   * @security JWT
512
   * @return 204 - Update success
513
   * @return {string} 400 - Validation Error
514
   * @return {string} 404 - Nonexistent user id
515
   */
516
  public async updateUserNfc(req: RequestWithToken, res: Response): Promise<void> {
517
    const { params } = req;
10✔
518
    const updateNfcRequest = req.body as UpdateNfcRequest;
10✔
519
    this.logger.trace('Update user NFC', params, 'by user', req.token.user);
10✔
520

521
    try {
10✔
522
      // Get the user object if it exists
523
      const user = await User.findOne({ where: { id: parseInt(params.id, 10), deleted: false } });
10✔
524
      // If it does not exist, return a 404 error
525
      if (user == null) {
10✔
526
        res.status(404).json('Unknown user ID.');
1✔
527
        return;
1✔
528
      }
529

530
      const validation = await verifyUpdateNfcRequest(updateNfcRequest);
9✔
531
      if (isFail(validation)) {
9✔
532
        res.status(400).json(validation.fail.value);
2✔
533
        return;
2✔
534
      }
535

536
      await new AuthenticationService().setUserAuthenticationNfc(user,
7✔
537
        updateNfcRequest.nfcCode.toString(), NfcAuthenticator);
538
      res.status(204).json();
7✔
539
    } catch (error) {
540
      this.logger.error('Could not update NFC:', error);
×
541
      res.status(500).json('Internal server error.');
×
542
    }
543
  }
544

545
  /**
546
   * DELETE /users/{id}/authenticator/nfc
547
   * @summary Delete a nfc code
548
   * @operationId deleteUserNfc
549
   * @tags users - Operations of user controller
550
   * @param {integer} id.path.required - The id of the user
551
   * @security JWT
552
   * @return 200 - Delete nfc success
553
   * @return {string} 400 - Validation Error
554
   * @return {string} 403 - Nonexistent user nfc
555
   * @return {string} 404 - Nonexistent user id
556
   */
557
  public async deleteUserNfc(req: RequestWithToken, res: Response): Promise<void> {
558
    const parameters = req.params;
9✔
559
    this.logger.trace('Delete user NFC', parameters, 'by user', req.token.user);
9✔
560

561
    try {
9✔
562
      // Get the user object if it exists
563
      const user = await User.findOne({ where: { id: parseInt(parameters.id, 10), deleted: false } });
9✔
564
      // If it does not exist, return a 404 error
565
      if (user == null) {
9✔
566
        res.status(404).json('Unknown user ID.');
3✔
567
        return;
3✔
568
      }
569

570
      if (await NfcAuthenticator.count({ where: { userId: parseInt(parameters.id, 10) } }) == 0) {
6✔
571
        res.status(403).json('No saved nfc');
3✔
572
        return;
3✔
573
      }
574

575
      await NfcAuthenticator.delete(parseInt(parameters.id, 10));
3✔
576
      res.status(204).json();
3✔
577
    } catch (error) {
578
      this.logger.error('Could not update NFC:', error);
×
579
      res.status(500).json('Internal server error.');
×
580
    }
581
  }
582

583
  /**
584
   * POST /users/{id}/authenticator/key
585
   * @summary POST an users update to new key code
586
   * @operationId updateUserKey
587
   * @tags users - Operations of user controller
588
   * @param {integer} id.path.required - The id of the user
589
   * @security JWT
590
   * @return {UpdateKeyResponse} 200 - The new key
591
   * @return {string} 400 - Validation Error
592
   * @return {string} 404 - Nonexistent user id
593
   */
594
  public async updateUserKey(req: RequestWithToken, res: Response): Promise<void> {
595
    const { params } = req;
2✔
596
    this.logger.trace('Update user key', params, 'by user', req.token.user);
2✔
597

598
    try {
2✔
599
      const userId = parseInt(params.id, 10);
2✔
600
      // Get the user object if it exists
601
      const user = await User.findOne({ where: { id: userId, deleted: false } });
2✔
602
      // If it does not exist, return a 404 error
603
      if (user == null) {
2✔
604
        res.status(404).json('Unknown user ID.');
1✔
605
        return;
1✔
606
      }
607

608
      const generatedKey = randomBytes(128).toString('hex');
1✔
609
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
610
        generatedKey, KeyAuthenticator);
611
      const response = { key: generatedKey } as UpdateKeyResponse;
1✔
612
      res.status(200).json(response);
1✔
613
    } catch (error) {
614
      this.logger.error('Could not update key:', error);
×
615
      res.status(500).json('Internal server error.');
×
616
    }
617
  }
618

619
  /**
620
   * Delete /users/{id}/authenticator/key
621
   * @summary Delete a users key code
622
   * @operationId deleteUserKey
623
   * @tags users - Operations of user controller
624
   * @param {integer} id.path.required - The id of the user
625
   * @security JWT
626
   * @return  200 - Deletion succesfull
627
   * @return {string} 400 - Validation Error
628
   * @return {string} 404 - Nonexistent user id
629
   */
630
  public async deleteUserKey(req: RequestWithToken, res: Response): Promise<void> {
631
    const { params } = req;
2✔
632
    this.logger.trace('Delete user key', params, 'by user', req.token.user);
2✔
633

634
    try {
2✔
635
      // Get the user object if it exists
636
      const user = await User.findOne({ where: { id: parseInt(params.id, 10), deleted: false } });
2✔
637
      // If it does not exist, return a 404 error
638
      if (user == null) {
2✔
639
        res.status(404).json('Unknown user ID.');
1✔
640
        return;
1✔
641
      }
642

643

644
      await KeyAuthenticator.delete(parseInt(params.id, 10));
1✔
645
      res.status(204).json();
1✔
646
    } catch (error) {
647
      this.logger.error('Could not delete key:', error);
×
648
      res.status(500).json('Internal server error.');
×
649
    }
650
  }
651

652
  /**
653
   * PUT /users/{id}/authenticator/local
654
   * @summary Put a user's local password
655
   * @operationId updateUserLocalPassword
656
   * @tags users - Operations of user controller
657
   * @param {integer} id.path.required - The id of the user
658
   * @param {UpdateLocalRequest} request.body.required -
659
   *    The password update
660
   * @security JWT
661
   * @return 204 - Update success
662
   * @return {string} 400 - Validation Error
663
   * @return {string} 404 - Nonexistent user id
664
   */
665
  public async updateUserLocalPassword(req: RequestWithToken, res: Response): Promise<void> {
666
    const parameters = req.params;
3✔
667
    const updateLocalRequest = req.body as UpdateLocalRequest;
3✔
668
    this.logger.trace('Update user local password', parameters, 'by user', req.token.user);
3✔
669

670
    try {
3✔
671
      const id = Number.parseInt(parameters.id, 10);
3✔
672
      // Get the user object if it exists
673
      const user = await User.findOne({ where: { id, deleted: false } });
3✔
674
      // If it does not exist, return a 404 error
675
      if (user == null) {
3✔
676
        res.status(404).json('Unknown user ID.');
1✔
677
        return;
1✔
678
      }
679

680
      const validation = await verifyUpdateLocalRequest(updateLocalRequest);
2✔
681
      if (isFail(validation)) {
2✔
682
        res.status(400).json(validation.fail.value);
1✔
683
        return;
1✔
684
      }
685

686
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
687
        updateLocalRequest.password, LocalAuthenticator);
688
      res.status(204).json();
1✔
689
    } catch (error) {
690
      this.logger.error('Could not update local password:', error);
×
691
      res.status(500).json('Internal server error.');
×
692
    }
693
  }
694

695
  /**
696
   * GET /users/{id}/members
697
   * @summary Get an organs members
698
   * @operationId getOrganMembers
699
   * @tags users - Operations of user controller
700
   * @param {integer} id.path.required - The id of the user
701
   * @param {integer} take.query - How many members the endpoint should return
702
   * @param {integer} skip.query - How many members should be skipped (for pagination)
703
   * @security JWT
704
   * @return {PaginatedUserResponse} 200 - All members of the organ
705
   * @return {string} 404 - Nonexistent user id
706
   * @return {string} 400 - User is not an organ
707
   */
708
  public async getOrganMembers(req: RequestWithToken, res: Response): Promise<void> {
709
    const parameters = req.params;
4✔
710
    this.logger.trace('Get organ members', parameters, 'by user', req.token.user);
4✔
711

712
    let take;
713
    let skip;
714
    try {
4✔
715
      const pagination = parseRequestPagination(req);
4✔
716
      take = pagination.take;
4✔
717
      skip = pagination.skip;
4✔
718
    } catch (e) {
719
      res.status(400).send(e.message);
×
720
      return;
×
721
    }
722

723
    try {
4✔
724
      const organId = asNumber(parameters.id);
4✔
725
      // Get the user object if it exists
726
      const user = await User.findOne({ where: { id: organId } });
4✔
727
      // If it does not exist, return a 404 error
728
      if (user == null) {
4✔
729
        res.status(404).json('Unknown user ID.');
1✔
730
        return;
1✔
731
      }
732

733
      if (user.type !== UserType.ORGAN) {
3✔
734
        res.status(400).json('User is not of type Organ');
1✔
735
        return;
1✔
736
      }
737

738
      const members = await UserService.getUsers({ organId }, { take, skip });
2✔
739
      res.status(200).json(members);
2✔
740
    } catch (error) {
741
      this.logger.error('Could not get organ members:', error);
×
742
      res.status(500).json('Internal server error.');
×
743
    }
744
  }
745

746
  /**
747
   * GET /users/{id}
748
   * @summary Get an individual user
749
   * @operationId getIndividualUser
750
   * @tags users - Operations of user controller
751
   * @param {integer} id.path.required - userID
752
   * @security JWT
753
   * @return {UserResponse} 200 - Individual user
754
   * @return {string} 404 - Nonexistent user id
755
   */
756
  public async getIndividualUser(req: RequestWithToken, res: Response): Promise<void> {
757
    const parameters = req.params;
7✔
758
    this.logger.trace('Get individual user', parameters, 'by user', req.token.user);
7✔
759

760
    try {
7✔
761
      // Get the user object if it exists
762
      const user = await UserService.getSingleUser(asNumber(parameters.id));
7✔
763
      // If it does not exist, return a 404 error
764
      if (user == null) {
7✔
765
        res.status(404).json('Unknown user ID.');
3✔
766
        return;
3✔
767
      }
768

769
      res.status(200).json(user);
4✔
770
    } catch (error) {
771
      this.logger.error('Could not get individual user:', error);
×
772
      res.status(500).json('Internal server error.');
×
773
    }
774
  }
775

776
  /**
777
   * POST /users
778
   * @summary Create a new user
779
   * @operationId createUser
780
   * @tags users - Operations of user controller
781
   * @param {CreateUserRequest} request.body.required -
782
   * The user which should be created
783
   * @security JWT
784
   * @return {UserResponse} 200 - New user
785
   * @return {string} 400 - Bad request
786
   */
787
  // eslint-disable-next-line class-methods-use-this
788
  public async createUser(req: RequestWithToken, res: Response): Promise<void> {
789
    const body = req.body as CreateUserRequest;
7✔
790
    this.logger.trace('Create user', body, 'by user', req.token.user);
7✔
791

792
    try {
7✔
793
      const validation = await verifyCreateUserRequest(body);
7✔
794
      if (isFail(validation)) {
7✔
795
        res.status(400).json(validation.fail.value);
3✔
796
        return;
3✔
797
      }
798

799
      const user = await UserService.createUser(body);
4✔
800
      res.status(201).json(user);
4✔
801
    } catch (error) {
802
      this.logger.error('Could not create user:', error);
×
803
      res.status(500).json('Internal server error.');
×
804
    }
805
  }
806

807
  /**
808
   * PATCH /users/{id}
809
   * @summary Update a user
810
   * @operationId updateUser
811
   * @tags users - Operations of user controller
812
   * @param {integer} id.path.required - The id of the user
813
   * @param {UpdateUserRequest} request.body.required - The user which should be updated
814
   * @security JWT
815
   * @return {UserResponse} 200 - New user
816
   * @return {string} 400 - Bad request
817
   */
818
  public async updateUser(req: RequestWithToken, res: Response): Promise<void> {
819
    const body = req.body as UpdateUserRequest;
13✔
820
    const parameters = req.params;
13✔
821
    this.logger.trace('Update user', parameters.id, 'with', body, 'by user', req.token.user);
13✔
822

823
    if (body.firstName !== undefined && body.firstName.length === 0) {
13✔
824
      res.status(400).json('firstName cannot be empty');
1✔
825
      return;
1✔
826
    }
827
    if (body.firstName !== undefined && body.firstName.length > 64) {
12✔
828
      res.status(400).json('firstName too long');
1✔
829
      return;
1✔
830
    }
831
    if (body.lastName !== undefined && body.lastName.length > 64) {
11✔
832
      res.status(400).json('lastName too long');
1✔
833
      return;
1✔
834
    }
835
    if (body.nickname !== undefined && body.nickname.length > 64) {
10✔
836
      res.status(400).json('nickname too long');
1✔
837
      return;
1✔
838
    }
839
    if (body.nickname === '') body.nickname = null;
9✔
840

841
    try {
9✔
842
      const id = parseInt(parameters.id, 10);
9✔
843
      // Get the user object if it exists
844
      let user = await User.findOne({ where: { id, deleted: false } });
9✔
845
      // If it does not exist, return a 404 error
846
      if (user == null) {
9!
847
        res.status(404).json('Unknown user ID.');
×
848
        return;
×
849
      }
850

851
      user = {
9✔
852
        ...body,
853
      } as User;
854
      await User.update(parameters.id, user);
9✔
855
      res.status(200).json(
9✔
856
        await UserService.getSingleUser(asNumber(parameters.id)),
857
      );
858
    } catch (error) {
859
      this.logger.error('Could not update user:', error);
×
860
      res.status(500).json('Internal server error.');
×
861
    }
862
  }
863

864
  /**
865
   * DELETE /users/{id}
866
   * @summary Delete a single user
867
   * @operationId deleteUser
868
   * @tags users - Operations of user controller
869
   * @param {integer} id.path.required - The id of the user
870
   * @security JWT
871
   * @return 204 - User successfully deleted
872
   * @return {string} 400 - Cannot delete yourself
873
   */
874
  public async deleteUser(req: RequestWithToken, res: Response): Promise<void> {
875
    const parameters = req.params;
5✔
876
    this.logger.trace('Delete individual user', parameters, 'by user', req.token.user);
5✔
877

878
    if (req.token.user.id === parseInt(parameters.id, 10)) {
5✔
879
      res.status(400).json('Cannot delete yourself');
1✔
880
      return;
1✔
881
    }
882

883
    try {
4✔
884
      const id = parseInt(parameters.id, 10);
4✔
885
      // Get the user object if it exists
886
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
887
      // If it does not exist, return a 404 error
888
      if (user == null) {
4✔
889
        res.status(404).json('Unknown user ID.');
2✔
890
        return;
2✔
891
      }
892

893
      user.deleted = true;
2✔
894
      await user.save();
2✔
895
      res.status(204).json('User deleted');
2✔
896
    } catch (error) {
897
      this.logger.error('Could not create product:', error);
×
898
      res.status(500).json('Internal server error.');
×
899
    }
900
  }
901

902
  /**
903
   * GET /users/nfc/{nfcCode}
904
   * @summary Get a user using the nfc code
905
   * @operationId findUserNfc
906
   * @tags users - Operations of the user controller
907
   * @security JWT
908
   * @param {string} nfcCode.path.required - The nfc code of the user
909
   * @return {UserResponse} 200 - The requested user
910
   * @return {string} 404 - The user with the given nfc code does not exist
911
   */
912
  public async findUserNfc(req: RequestWithToken, res: Response): Promise<void> {
913
    const parameters = req.params;
3✔
914
    this.logger.trace('Find user nfc', parameters, 'by user', req.token.user);
3✔
915

916
    try {
3✔
917
      const nfcCode = String(parameters.nfcCode);
3✔
918
      const nfc = await NfcAuthenticator.findOne({ where: { nfcCode } });
3✔
919

920
      if (nfc === null) {
3✔
921
        res.status(404).json('Unknown nfc code');
1✔
922
        return;
1✔
923
      }
924

925
      res.status(200).json(parseUserToResponse(nfc.user));
2✔
926
    } catch (error) {
927
      this.logger.error('Could not find user using nfc:', error);
×
928
      res.status(500).json('Internal server error.');
×
929
    }
930
  }
931

932
  /**
933
   * POST /users/acceptTos
934
   * @summary Accept the Terms of Service if you have not accepted it yet
935
   * @operationId acceptTos
936
   * @tags users - Operations of the User controller
937
   * @param {AcceptTosRequest} request.body.required - "Tosrequest body"
938
   * @security JWT
939
   * @return 204 - ToS accepted
940
   * @return {string} 400 - ToS already accepted
941
   */
942
  public async acceptToS(req: RequestWithToken, res: Response): Promise<void> {
943
    this.logger.trace('Accept ToS for user', req.token.user);
3✔
944

945
    const { id } = req.token.user;
3✔
946
    const body = req.body as AcceptTosRequest;
3✔
947

948
    try {
3✔
949
      const user = await UserService.getSingleUser(id);
3✔
950
      if (user == null) {
3!
951
        res.status(404).json('User not found.');
×
952
        return;
×
953
      }
954

955
      const success = await UserService.acceptToS(id, body);
3✔
956
      if (!success) {
3✔
957
        res.status(400).json('User already accepted ToS.');
1✔
958
        return;
1✔
959
      }
960

961
      res.status(204).json();
2✔
962
      return;
2✔
963
    } catch (error) {
964
      this.logger.error('Could not accept ToS for user:', error);
×
965
      res.status(500).json('Internal server error.');
×
966
    }
967
  }
968

969
  /**
970
   * GET /users/{id}/products
971
   * @summary Get an user's products
972
   * @operationId getUsersProducts
973
   * @tags users - Operations of user controller
974
   * @param {integer} id.path.required - The id of the user
975
   * @param {integer} take.query - How many products the endpoint should return
976
   * @param {integer} skip.query - How many products should be skipped (for pagination)
977
   * @security JWT
978
   * @return {PaginatedProductResponse} 200 - List of products.
979
   */
980
  public async getUsersProducts(req: RequestWithToken, res: Response): Promise<void> {
981
    const parameters = req.params;
4✔
982
    this.logger.trace("Get user's products", parameters, 'by user', req.token.user);
4✔
983

984
    let take;
985
    let skip;
986
    try {
4✔
987
      const pagination = parseRequestPagination(req);
4✔
988
      take = pagination.take;
4✔
989
      skip = pagination.skip;
4✔
990
    } catch (e) {
991
      res.status(400).send(e.message);
×
992
      return;
×
993
    }
994

995
    // Handle request
996
    try {
4✔
997
      const id = parseInt(parameters.id, 10);
4✔
998
      const owner = await User.findOne({ where: { id, deleted: false } });
4✔
999
      if (owner == null) {
4✔
1000
        res.status(404).json({});
1✔
1001
        return;
1✔
1002
      }
1003

1004
      const products = await ProductService.getProducts({}, { take, skip }, owner);
3✔
1005
      res.json(products);
3✔
1006
    } catch (error) {
1007
      this.logger.error('Could not return all products:', error);
×
1008
      res.status(500).json('Internal server error.');
×
1009
    }
1010
  }
1011

1012
  /**
1013
   * GET /users/{id}/containers
1014
   * @summary Returns the user's containers
1015
   * @operationId getUsersContainers
1016
   * @tags users - Operations of user controller
1017
   * @param {integer} id.path.required - The id of the user
1018
   * @security JWT
1019
   * @param {integer} take.query - How many containers the endpoint should return
1020
   * @param {integer} skip.query - How many containers should be skipped (for pagination)
1021
   * @return {PaginatedContainerResponse} 200 - All users updated containers
1022
   * @return {string} 404 - Not found error
1023
   * @return {string} 500 - Internal server error
1024
   */
1025
  public async getUsersContainers(req: RequestWithToken, res: Response): Promise<void> {
1026
    const { id } = req.params;
4✔
1027
    this.logger.trace("Get user's containers", id, 'by user', req.token.user);
4✔
1028

1029
    let take;
1030
    let skip;
1031
    try {
4✔
1032
      const pagination = parseRequestPagination(req);
4✔
1033
      take = pagination.take;
4✔
1034
      skip = pagination.skip;
4✔
1035
    } catch (e) {
1036
      res.status(400).send(e.message);
×
1037
      return;
×
1038
    }
1039

1040
    // handle request
1041
    try {
4✔
1042
      // Get the user object if it exists
1043
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1044
      // If it does not exist, return a 404 error
1045
      if (user == null) {
4✔
1046
        res.status(404).json('Unknown user ID.');
1✔
1047
        return;
1✔
1048
      }
1049

1050
      const containers = (await ContainerService
3✔
1051
        .getContainers({}, { take, skip }, user));
1052
      res.json(containers);
3✔
1053
    } catch (error) {
1054
      this.logger.error('Could not return containers:', error);
×
1055
      res.status(500).json('Internal server error.');
×
1056
    }
1057
  }
1058

1059
  /**
1060
   * GET /users/{id}/pointsofsale
1061
   * @summary Returns the user's Points of Sale
1062
   * @operationId getUsersPointsOfSale
1063
   * @tags users - Operations of user controller
1064
   * @param {integer} id.path.required - The id of the user
1065
   * @param {integer} take.query - How many points of sale the endpoint should return
1066
   * @param {integer} skip.query - How many points of sale should be skipped (for pagination)
1067
   * @security JWT
1068
   * @return {PaginatedPointOfSaleResponse} 200 - All users updated point of sales
1069
   * @return {string} 404 - Not found error
1070
   * @return {string} 500 - Internal server error
1071
   */
1072
  public async getUsersPointsOfSale(req: RequestWithToken, res: Response): Promise<void> {
1073
    const { id } = req.params;
4✔
1074
    this.logger.trace("Get user's points of sale", id, 'by user', req.token.user);
4✔
1075

1076
    let take;
1077
    let skip;
1078
    try {
4✔
1079
      const pagination = parseRequestPagination(req);
4✔
1080
      take = pagination.take;
4✔
1081
      skip = pagination.skip;
4✔
1082
    } catch (e) {
1083
      res.status(400).send(e.message);
×
1084
      return;
×
1085
    }
1086

1087
    // handle request
1088
    try {
4✔
1089
      // Get the user object if it exists
1090
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1091
      // If it does not exist, return a 404 error
1092
      if (user == null) {
4✔
1093
        res.status(404).json('Unknown user ID.');
1✔
1094
        return;
1✔
1095
      }
1096

1097
      const pointsOfSale = (await PointOfSaleService
3✔
1098
        .getPointsOfSale({}, { take, skip }, user));
1099
      res.json(pointsOfSale);
3✔
1100
    } catch (error) {
1101
      this.logger.error('Could not return point of sale:', error);
×
1102
      res.status(500).json('Internal server error.');
×
1103
    }
1104
  }
1105

1106
  /**
1107
   * GET /users/{id}/transactions
1108
   * @summary Get transactions from a user.
1109
   * @operationId getUsersTransactions
1110
   * @tags users - Operations of user controller
1111
   * @param {integer} id.path.required - The id of the user that should be involved
1112
   * in all returned transactions
1113
   * @param {integer} fromId.query - From-user for selected transactions
1114
   * @param {integer} createdById.query - User that created selected transaction
1115
   * @param {integer} toId.query - To-user for selected transactions
1116
   * transactions. Requires ContainerId
1117
   * @param {integer} productId.query - Product ID for selected transactions
1118
   * @param {integer} productRevision.query - Product Revision for selected
1119
   * transactions. Requires ProductID
1120
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1121
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1122
   * @param {integer} take.query - How many transactions the endpoint should return
1123
   * @param {integer} skip.query - How many transactions should be skipped (for pagination)
1124
   * @security JWT
1125
   * @return {PaginatedBaseTransactionResponse} 200 - List of transactions.
1126
   */
1127
  public async getUsersTransactions(req: RequestWithToken, res: Response): Promise<void> {
1128
    const { id } = req.params;
4✔
1129
    this.logger.trace("Get user's", id, 'transactions by user', req.token.user);
4✔
1130

1131
    // Parse the filters given in the query parameters. If there are any issues,
1132
    // the parse method will throw an exception. We will then return a 400 error.
1133
    let filters;
1134
    try {
4✔
1135
      filters = parseGetTransactionsFilters(req);
4✔
1136
    } catch (e) {
1137
      res.status(400).json(e.message);
×
1138
      return;
×
1139
    }
1140

1141
    let take;
1142
    let skip;
1143
    try {
4✔
1144
      const pagination = parseRequestPagination(req);
4✔
1145
      take = pagination.take;
4✔
1146
      skip = pagination.skip;
4✔
1147
    } catch (e) {
1148
      res.status(400).send(e.message);
×
1149
      return;
×
1150
    }
1151

1152
    try {
4✔
1153
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
4✔
1154
      if (user == null) {
4✔
1155
        res.status(404).json({});
1✔
1156
        return;
1✔
1157
      }
1158
      const transactions = await new TransactionService().getTransactions(filters, { take, skip }, user);
3✔
1159

1160
      res.status(200).json(transactions);
3✔
1161
    } catch (error) {
1162
      this.logger.error('Could not return all transactions:', error);
×
1163
      res.status(500).json('Internal server error.');
×
1164
    }
1165
  }
1166

1167
  /**
1168
   * GET /users/{id}/transactions/sales/report
1169
   * @summary Get sales report for the given user
1170
   * @operationId getUsersSalesReport
1171
   * @tags users - Operations of user controller
1172
   * @param {integer} id.path.required - The id of the user to get the sales report for
1173
   * @security JWT
1174
   * @param {string} fromDate.query.required - Start date for selected sales (inclusive)
1175
   * @param {string} tillDate.query.required - End date for selected sales (exclusive)
1176
   * @return {ReportResponse} 200 - The sales report of the user
1177
   * @return {string} 400 - Validation error
1178
   * @return {string} 404 - User not found error.
1179
   */
1180
  public async getUsersSalesReport(req: RequestWithToken, res: Response): Promise<void> {
1181
    const { id } = req.params;
4✔
1182
    this.logger.trace('Get sales report for user ', id, ' by user', req.token.user);
4✔
1183

1184
    let filters: { fromDate: Date, tillDate: Date };
1185
    try {
4✔
1186
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
4✔
1187
    } catch (e) {
1188
      res.status(400).json(e.message);
1✔
1189
      return;
1✔
1190
    }
1191

1192
    try {
3✔
1193
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1194
      if (user == null) {
3✔
1195
        res.status(404).json('Unknown user ID.');
1✔
1196
        return;
1✔
1197
      }
1198

1199
      const report = await (new SalesReportService()).getReport({ ...filters, forId: user.id });
2✔
1200
      res.status(200).json(ReportService.reportToResponse(report));
2✔
1201
    } catch (error) {
1202
      this.logger.error('Could not get sales report:', error);
×
1203
      res.status(500).json('Internal server error.');
×
1204
    }
1205
  }
1206

1207
  /**
1208
   * GET /users/{id}/transactions/sales/report/pdf
1209
   * @summary Get sales report for the given user
1210
   * @operationId getUsersSalesReportPdf
1211
   * @tags users - Operations of user controller
1212
   * @param {integer} id.path.required - The id of the user to get the sales report for
1213
   * @security JWT
1214
   * @param {string} fromDate.query.required - Start date for selected sales (inclusive)
1215
   * @param {string} tillDate.query.required - End date for selected sales (exclusive)
1216
   * @param {string} description.query - Description of the report
1217
   * @param {string} fileType.query - enum:PDF,TEX - The file type of the report
1218
   * @return {string} 404 - User not found error.
1219
   * @returns {string} 200 - The requested report - application/pdf
1220
   * @return {string} 400 - Validation error
1221
   * @return {string} 500 - Internal server error
1222
   * @return {string} 502 - PDF generation failed
1223
   */
1224
  public async getUsersSalesReportPdf(req: RequestWithToken, res: Response): Promise<void> {
1225
    const { id } = req.params;
7✔
1226
    this.logger.trace('Get sales report pdf for user ', id, ' by user', req.token.user);
7✔
1227

1228
    let filters: { fromDate: Date, tillDate: Date };
1229
    let description: string;
1230
    let fileType: ReturnFileType;
1231
    try {
7✔
1232
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1233
      description = String(req.query.description);
4✔
1234
      fileType = asReturnFileType(req.query.fileType);
4✔
1235
    } catch (e) {
1236
      res.status(400).json(e.message);
4✔
1237
      return;
4✔
1238
    }
1239

1240
    try {
3✔
1241
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1242
      if (user == null) {
3✔
1243
        res.status(404).json('Unknown user ID.');
1✔
1244
        return;
1✔
1245
      }
1246
      const service = new SalesReportService();
2✔
1247
      await reportPDFhelper(res)(service, filters, description, user.id, UserReportParametersType.Sales, fileType);
2✔
1248
    } catch (error) {
1249
      this.logger.error('Could not get sales report:', error);
1✔
1250
      if (error instanceof PdfError) {
1✔
1251
        res.status(502).json('PDF Generator service failed.');
1✔
1252
        return;
1✔
1253
      }
1254
      res.status(500).json('Internal server error.');
×
1255
    }
1256
  }
1257

1258
  /**
1259
   * GET /users/{id}/transactions/purchases/report/pdf
1260
   * @summary Get purchase report pdf for the given user
1261
   * @operationId getUsersPurchaseReportPdf
1262
   * @tags users - Operations of user controller
1263
   * @param {integer} id.path.required - The id of the user to get the purchase report for
1264
   * @security JWT
1265
   * @param {string} fromDate.query.required - Start date for selected purchases (inclusive)
1266
   * @param {string} tillDate.query.required - End date for selected purchases (exclusive)
1267
   * @param {string} fileType.query - enum:PDF,TEX - The file type of the report
1268
   * @return {string} 404 - User not found error.
1269
   * @returns {string} 200 - The requested report - application/pdf
1270
   * @return {string} 400 - Validation error
1271
   * @return {string} 500 - Internal server error
1272
   * @return {string} 502 - PDF generation failed
1273
   */
1274
  public async getUsersPurchaseReportPdf(req: RequestWithToken, res: Response): Promise<void> {
1275
    const { id } = req.params;
7✔
1276
    this.logger.trace('Get purchase report pdf for user ', id, ' by user', req.token.user);
7✔
1277

1278
    let filters: { fromDate: Date, tillDate: Date };
1279
    let description: string;
1280
    let fileType: ReturnFileType;
1281
    try {
7✔
1282
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1283
      description = String(req.query.description);
4✔
1284
      fileType = asReturnFileType(req.query.fileType);
4✔
1285
    } catch (e) {
1286
      res.status(400).json(e.message);
4✔
1287
      return;
4✔
1288
    }
1289

1290
    try {
3✔
1291
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1292
      if (user == null) {
3✔
1293
        res.status(404).json('Unknown user ID.');
1✔
1294
        return;
1✔
1295
      }
1296
      const service = new BuyerReportService();
2✔
1297
      await (reportPDFhelper(res))(service, filters, description, user.id, UserReportParametersType.Purchases, fileType);
2✔
1298
    } catch (error) {
1299
      this.logger.error('Could not get sales report:', error);
1✔
1300
      if (error instanceof PdfError) {
1✔
1301
        res.status(502).json('PDF Generator service failed.');
1✔
1302
        return;
1✔
1303
      }
1304
      res.status(500).json('Internal server error.');
×
1305
    }
1306
  }
1307

1308
  /**
1309
   * GET /users/{id}/transactions/purchases/report
1310
   * @summary Get purchases report for the given user
1311
   * @operationId getUsersPurchasesReport
1312
   * @tags users - Operations of user controller
1313
   * @param {integer} id.path.required - The id of the user to get the purchases report for
1314
   * @security JWT
1315
   * @param {string} fromDate.query.required - Start date for selected purchases (inclusive)
1316
   * @param {string} tillDate.query.required - End date for selected purchases (exclusive)
1317
   * @return {ReportResponse} 200 - The purchases report of the user
1318
   * @return {string} 400 - Validation error
1319
   * @return {string} 404 - User not found error.
1320
   */
1321
  public async getUsersPurchasesReport(req: RequestWithToken, res: Response): Promise<void> {
1322
    const { id } = req.params;
4✔
1323
    this.logger.trace('Get purchases report for user ', id, ' by user', req.token.user);
4✔
1324

1325
    let filters: { fromDate: Date, tillDate: Date };
1326
    try {
4✔
1327
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
4✔
1328
    } catch (e) {
1329
      res.status(400).json(e.message);
1✔
1330
      return;
1✔
1331
    }
1332

1333
    try {
3✔
1334
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1335
      if (user == null) {
3✔
1336
        res.status(404).json('Unknown user ID.');
1✔
1337
        return;
1✔
1338
      }
1339

1340
      const report = await (new BuyerReportService()).getReport({ ...filters, forId: user.id });
2✔
1341
      res.status(200).json(ReportService.reportToResponse(report));
2✔
1342
    } catch (error) {
1343
      this.logger.error('Could not get sales report:', error);
×
1344
      res.status(500).json('Internal server error.');
×
1345
    }
1346
  }
1347

1348
  /**
1349
   * GET /users/{id}/transfers
1350
   * @summary Get transfers to or from an user.
1351
   * @operationId getUsersTransfers
1352
   * @tags users - Operations of user controller
1353
   * @param {integer} id.path.required - The id of the user that should be involved
1354
   * in all returned transfers
1355
   * @param {integer} take.query - How many transfers the endpoint should return
1356
   * @param {integer} skip.query - How many transfers should be skipped (for pagination)
1357
   * @param {integer} fromId.query - From-user for selected transfers
1358
   * @param {integer} toId.query - To-user for selected transfers
1359
   * @param {integer} id.query - ID of selected transfers
1360
   * @security JWT
1361
   * @return {PaginatedTransferResponse} 200 - List of transfers.
1362
   */
1363
  public async getUsersTransfers(req: RequestWithToken, res: Response): Promise<void> {
1364
    const { id } = req.params;
2✔
1365
    this.logger.trace("Get user's transfers", id, 'by user', req.token.user);
2✔
1366

1367
    // Parse the filters given in the query parameters. If there are any issues,
1368
    // the parse method will throw an exception. We will then return a 400 error.
1369
    let filters;
1370
    try {
2✔
1371
      filters = parseGetTransferFilters(req);
2✔
1372
    } catch (e) {
1373
      res.status(400).json(e.message);
×
1374
      return;
×
1375
    }
1376

1377
    let take;
1378
    let skip;
1379
    try {
2✔
1380
      const pagination = parseRequestPagination(req);
2✔
1381
      take = pagination.take;
2✔
1382
      skip = pagination.skip;
2✔
1383
    } catch (e) {
1384
      res.status(400).send(e.message);
×
1385
      return;
×
1386
    }
1387

1388
    // handle request
1389
    try {
2✔
1390
      // Get the user object if it exists
1391
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
2✔
1392
      // If it does not exist, return a 404 error
1393
      if (user == null) {
2!
1394
        res.status(404).json('Unknown user ID.');
×
1395
        return;
×
1396
      }
1397

1398
      const transfers = (await new TransferService().getTransfers(
2✔
1399
        { ...filters }, { take, skip }, user,
1400
      ));
1401
      res.json(transfers);
2✔
1402
    } catch (error) {
1403
      this.logger.error('Could not return user transfers', error);
×
1404
      res.status(500).json('Internal server error.');
×
1405
    }
1406
  }
1407

1408
  /**
1409
   * POST /users/{id}/authenticate
1410
   * @summary Authenticate as another user
1411
   * @operationId authenticateAs
1412
   * @tags users - Operations of user controller
1413
   * @param {integer} id.path.required - The id of the user that should be authenticated as
1414
   * @security JWT
1415
   * @return {AuthenticationResponse} 200 - The created json web token.
1416
   * @return {string} 400 - Validation error.
1417
   * @return {string} 404 - User not found error.
1418
   * @return {string} 403 - Authentication error.
1419
   */
1420
  public async authenticateAsUser(req: RequestWithToken, res: Response): Promise<void> {
1421
    const parameters = req.params;
2✔
1422
    this.logger.trace('Authenticate as user', parameters, 'by user', req.token.user);
2✔
1423

1424
    try {
2✔
1425
      const id = parseInt(parameters.id, 10);
2✔
1426
      // Get the user object if it exists
1427
      const authenticateAs = await User.findOne({ where: { id, deleted: false } });
2✔
1428
      // If it does not exist, return a 404 error
1429
      if (authenticateAs == null) {
2!
1430
        res.status(404).json('Unknown user ID.');
×
1431
        return;
×
1432
      }
1433

1434
      // Check if user can authenticate as requested user.
1435
      const authenticator = await MemberAuthenticator
2✔
1436
        .findOne({
1437
          where:
1438
            { user: { id: req.token.user.id }, authenticateAs: { id: authenticateAs.id } },
1439
        });
1440

1441
      if (authenticator == null) {
2✔
1442
        res.status(403).json('Authentication error');
1✔
1443
        return;
1✔
1444
      }
1445

1446
      const context: AuthenticationContext = {
1✔
1447
        roleManager: this.roleManager,
1448
        tokenHandler: this.tokenHandler,
1449
      };
1450

1451
      const token = await new AuthenticationService().getSaltedToken(authenticateAs, context, false);
1✔
1452
      res.status(200).json(token);
1✔
1453
    } catch (error) {
1454
      this.logger.error('Could not authenticate as user:', error);
×
1455
      res.status(500).json('Internal server error.');
×
1456
    }
1457
  }
1458

1459
  /**
1460
   * GET /users/{id}/authenticate
1461
   * @summary Get all users that the user can authenticate as
1462
   * @operationId getUserAuthenticatable
1463
   * @tags users - Operations of user controller
1464
   * @param {integer} id.path.required - The id of the user to get authentications of
1465
   * @security JWT
1466
   * @return {string} 404 - User not found error.
1467
   * @return {Array.<UserResponse>} 200 - A list of all users the given ID can authenticate
1468
   */
1469
  public async getUserAuthenticatable(req: RequestWithToken, res: Response): Promise<void> {
1470
    const parameters = req.params;
2✔
1471
    this.logger.trace('Get authenticatable users of user', parameters, 'by user', req.token.user);
2✔
1472

1473
    try {
2✔
1474
      const id = parseInt(parameters.id, 10);
2✔
1475
      // Get the user object if it exists
1476
      const user = await User.findOne({ where: { id, deleted: false } });
2✔
1477
      // If it does not exist, return a 404 error
1478
      if (user == null) {
2✔
1479
        res.status(404).json('Unknown user ID.');
1✔
1480
        return;
1✔
1481
      }
1482

1483
      // Extract from member authenticator table.
1484
      const authenticators = await MemberAuthenticator.find({ where: { user: { id: user.id } }, relations: ['authenticateAs'] });
1✔
1485
      const users = authenticators.map((auth) => parseUserToResponse(auth.authenticateAs));
1✔
1486
      res.status(200).json(users);
1✔
1487
    } catch (error) {
1488
      this.logger.error('Could not get authenticatable of user:', error);
×
1489
      res.status(500).json('Internal server error.');
×
1490
    }
1491
  }
1492

1493
  /**
1494
   * GET /users/{id}/roles
1495
   * @summary Get all roles assigned to the user.
1496
   * @operationId getUserRoles
1497
   * @tags users - Operations of user controller
1498
   * @param {integer} id.path.required - The id of the user to get the roles from
1499
   * @security JWT
1500
   * @return {Array.<RoleWithPermissionsResponse>} 200 - The roles of the user
1501
   * @return {string} 404 - User not found error.
1502
   */
1503
  public async getUserRoles(req: RequestWithToken, res: Response): Promise<void> {
1504
    const parameters = req.params;
3✔
1505
    this.logger.trace('Get roles of user', parameters, 'by user', req.token.user);
3✔
1506

1507
    try {
3✔
1508
      const id = parseInt(parameters.id, 10);
3✔
1509
      // Get the user object if it exists
1510
      const user = await User.findOne({ where: { id, deleted: false } });
3✔
1511
      // If it does not exist, return a 404 error
1512
      if (user == null) {
3✔
1513
        res.status(404).json('Unknown user ID.');
1✔
1514
        return;
1✔
1515
      }
1516

1517
      const rolesWithPermissions = await this.roleManager.getRoles(user, true);
2✔
1518
      const response = rolesWithPermissions.map((r) => RBACService.asRoleResponse(r));
2✔
1519
      res.status(200).json(response);
2✔
1520
    } catch (error) {
1521
      this.logger.error('Could not get roles of user:', error);
×
1522
      res.status(500).json('Internal server error.');
×
1523
    }
1524
  }
1525

1526
  /**
1527
   * GET /users/{id}/financialmutations
1528
   * @summary Get all financial mutations of a user (from or to).
1529
   * @operationId getUsersFinancialMutations
1530
   * @tags users - Operations of user controller
1531
   * @param {integer} id.path.required - The id of the user to get the mutations from
1532
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1533
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1534
   * @param {integer} take.query - How many transactions the endpoint should return
1535
   * @param {integer} skip.query - How many transactions should be skipped (for pagination)
1536
   * @security JWT
1537
   * @return {PaginatedFinancialMutationResponse} 200 - The financial mutations of the user
1538
   * @return {string} 404 - User not found error.
1539
   */
1540
  public async getUsersFinancialMutations(req: RequestWithToken, res: Response): Promise<void> {
1541
    const parameters = req.params;
4✔
1542
    this.logger.trace('Get financial mutations of user', parameters, 'by user', req.token.user);
4✔
1543

1544
    let filters;
1545
    let take;
1546
    let skip;
1547
    try {
4✔
1548
      filters = parseGetFinancialMutationsFilters(req);
4✔
1549
      const pagination = parseRequestPagination(req);
4✔
1550
      take = pagination.take;
4✔
1551
      skip = pagination.skip;
4✔
1552
    } catch (e) {
1553
      res.status(400).send(e.message);
×
1554
      return;
×
1555
    }
1556

1557
    try {
4✔
1558
      const id = parseInt(parameters.id, 10);
4✔
1559
      // Get the user object if it exists
1560
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
1561
      // If it does not exist, return a 404 error
1562
      if (user == null) {
4!
1563
        res.status(404).json('Unknown user ID.');
×
1564
        return;
×
1565
      }
1566

1567
      const mutations = await UserService.getUserFinancialMutations(user, filters, { take, skip });
4✔
1568
      res.status(200).json(mutations);
4✔
1569
    } catch (error) {
1570
      this.logger.error('Could not get financial mutations of user:', error);
×
1571
      res.status(500).json('Internal server error.');
×
1572
    }
1573
  }
1574

1575
  /**
1576
   * GET /users/{id}/deposits
1577
   * @summary Get all deposits of a user that are still being processed by Stripe
1578
   * @operationId getUsersProcessingDeposits
1579
   * @tags users - Operations of user controller
1580
   * @param {integer} id.path.required - The id of the user to get the deposits from
1581
   * @security JWT
1582
   * @return {Array.<RoleResponse>} 200 - The processing deposits of a user
1583
   * @return {string} 404 - User not found error.
1584
   */
1585
  public async getUsersProcessingDeposits(req: RequestWithToken, res: Response): Promise<void> {
1586
    const parameters = req.params;
2✔
1587
    this.logger.trace('Get users processing deposits from user', parameters.id);
2✔
1588

1589
    try {
2✔
1590
      const id = parseInt(parameters.id, 10);
2✔
1591

1592
      const user = await User.findOne({ where: { id } });
2✔
1593
      if (user == null) {
2✔
1594
        res.status(404).json('Unknown user ID.');
1✔
1595
        return;
1✔
1596
      }
1597

1598
      const deposits = await StripeService.getProcessingStripeDepositsFromUser(id);
1✔
1599
      res.status(200).json(deposits);
1✔
1600
    } catch (error) {
1601
      this.logger.error('Could not get processing deposits of user:', error);
×
1602
      res.status(500).json('Internal server error.');
×
1603
    }
1604
  }
1605

1606
  /**
1607
   * GET /users/{id}/transactions/report
1608
   * @summary Get transaction report for the given user
1609
   * @operationId getUsersTransactionsReport
1610
   * @tags users - Operations of user controller
1611
   * @param {integer} id.path.required - The id of the user to get the transaction report from
1612
   * @security JWT
1613
   * @return {Array.<TransactionReportResponse>} 200 - The transaction report of the user
1614
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1615
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1616
   * @param {integer} fromId.query - From-user for selected transactions
1617
   * @param {integer} toId.query - To-user for selected transactions
1618
   * @param {boolean} exclusiveToId.query - If all sub-transactions should be to the toId user, default true
1619
   * @deprecated - Use /users/{id}/transactions/sales/report or /users/{id}/transactions/purchases/report instead
1620
   * @return {string} 404 - User not found error.
1621
   */
1622
  public async getUsersTransactionsReport(req: RequestWithToken, res: Response): Promise<void> {
1623
    const parameters = req.params;
6✔
1624
    this.logger.trace('Get transaction report for user ', req.params.id, ' by user', req.token.user);
6✔
1625

1626
    let filters;
1627
    try {
6✔
1628
      filters = parseGetTransactionsFilters(req);
6✔
1629
    } catch (e) {
1630
      res.status(400).json(e.message);
1✔
1631
      return;
1✔
1632
    }
1633

1634
    try {
5✔
1635
      if ((filters.toId !== undefined && filters.fromId !== undefined) || (filters.toId === undefined && filters.fromId === undefined)) {
5✔
1636
        res.status(400).json('Need to provide either a toId or a fromId.');
2✔
1637
        return;
2✔
1638
      }
1639

1640
      const id = parseInt(parameters.id, 10);
3✔
1641

1642
      const user = await User.findOne({ where: { id } });
3✔
1643
      if (user == null) {
3✔
1644
        res.status(404).json('Unknown user ID.');
1✔
1645
        return;
1✔
1646
      }
1647

1648
      const report = await (new TransactionService()).getTransactionReportResponse(filters);
2✔
1649
      res.status(200).json(report);
2✔
1650
    } catch (e) {
1651
      res.status(500).send();
×
1652
      this.logger.error(e);
×
1653
    }
1654
  }
1655

1656
  /**
1657
   * POST /users/{id}/fines/waive
1658
   * @summary Waive all given user's fines
1659
   * @tags users - Operations of user controller
1660
   * @param {integer} id.path.required - The id of the user
1661
   * @param {WaiveFinesRequest} request.body
1662
   * Optional body, see https://github.com/GEWIS/sudosos-backend/pull/344
1663
   * @operationId waiveUserFines
1664
   * @security JWT
1665
   * @return 204 - Success
1666
   * @return {string} 400 - User has no fines.
1667
   * @return {string} 404 - User not found error.
1668
   */
1669
  public async waiveUserFines(req: RequestWithToken, res: Response): Promise<void> {
1670
    const { id: rawId } = req.params;
9✔
1671
    const body = req.body as WaiveFinesRequest;
9✔
1672
    this.logger.trace('Waive fines', body, 'of user', rawId, 'by', req.token.user);
9✔
1673

1674
    try {
9✔
1675
      const id = parseInt(rawId, 10);
9✔
1676

1677
      const user = await User.findOne({ where: { id }, relations: { currentFines: { fines: true } } });
9✔
1678
      if (user == null) {
9✔
1679
        res.status(404).json('Unknown user ID.');
1✔
1680
        return;
1✔
1681
      }
1682
      if (user.currentFines == null) {
8✔
1683
        res.status(400).json('User has no fines.');
1✔
1684
        return;
1✔
1685
      }
1686

1687
      const totalAmountOfFines = user.currentFines!.fines.reduce((total, f) => total.add(f.amount), Dinero());
14✔
1688
      // Backwards compatibility with old version, where you could only waive all user's fines
1689
      const amountToWaive = body?.amount ?? totalAmountOfFines.toObject();
7✔
1690
      if (amountToWaive.amount <= 0) {
7✔
1691
        res.status(400).json('Amount to waive cannot be zero or negative.');
2✔
1692
        return;
2✔
1693
      }
1694
      if (amountToWaive.amount > totalAmountOfFines.getAmount()) {
5✔
1695
        res.status(400).json('Amount to waive cannot be more than the total amount of fines.');
1✔
1696
        return;
1✔
1697
      }
1698

1699
      await new DebtorService().waiveFines(id, { amount: amountToWaive } as WaiveFinesParams);
4✔
1700
      res.status(204).send();
4✔
1701
    } catch (e) {
1702
      res.status(500).send();
×
1703
      this.logger.error(e);
×
1704
    }
1705
  }
1706

1707
  /**
1708
   * DELETE /users/{id}/roles/{roleId}
1709
   * @summary Deletes a role from a user
1710
   * @tags users - Operations of user controller
1711
   * @param {integer} id.path.required - The id of the user
1712
   * @param {integer} roleId.path.required - The id of the role
1713
   * @operationId deleteUserRole
1714
   * @security JWT
1715
   * @return 204 - Success
1716
   */
1717
  public async deleteUserRole(req: RequestWithToken, res: Response): Promise<void> {
1718
    const { id: rawUserId, roleId: rawRoleId } = req.params;
4✔
1719

1720
    const userId = parseInt(rawUserId, 10);
4✔
1721
    const roleId = parseInt(rawRoleId, 10);
4✔
1722

1723
    const user = await User.findOne({ where: { id: userId } });
4✔
1724
    if (!user) {
4✔
1725
      res.status(404).json('user not found');
1✔
1726
      return;
1✔
1727
    }
1728
    const role = await Role.findOne({ where: { id: roleId } });
3✔
1729
    if (!role) {
3✔
1730
      res.status(404).json('role not found');
1✔
1731
      return;
1✔
1732
    }
1733

1734
    await UserService.deleteUserRole(user, role);
2✔
1735
    res.status(204).send();
2✔
1736
  }
1737

1738
  /**
1739
   * POST /users/{id}/roles
1740
   * @summary Adds a role to a user
1741
   * @tags users - Operations of user controller
1742
   * @param {integer} id.path.required - The id of the user
1743
   * @param {AddRoleRequest} request.body.required
1744
   * @operationId addUserRole
1745
   * @security JWT
1746
   * @return 204 - Success
1747
   */
1748
  public async addUserRole(req: RequestWithToken, res: Response): Promise<void> {
1749
    const { id: rawUserId } = req.params;
2✔
1750
    const userId = parseInt(rawUserId, 10);
2✔
1751
    const { roleId } = req.body as AddRoleRequest;
2✔
1752

1753
    const user = await User.findOne({ where: { id: userId } });
2✔
1754
    if (!user) {
2!
NEW
1755
      res.status(404).json('user not found');
×
NEW
1756
      return;
×
1757
    }
1758
    const role = await Role.findOne({ where: { id: roleId } });
2✔
1759
    if (!role) {
2✔
1760
      res.status(404).json('role not found');
1✔
1761
      return;
1✔
1762
    }
1763

1764
    await UserService.addUserRole(user, role);
1✔
1765
    res.status(204).send();
1✔
1766
  }
1767
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc