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

GEWIS / sudosos-backend / 22688682576

04 Mar 2026 08:50PM UTC coverage: 89.292% (-0.08%) from 89.37%
22688682576

push

github

web-flow
refactor: change return signatures of service class methods (#774)

1781 of 2163 branches covered (82.34%)

Branch coverage included in aggregate %.

362 of 373 new or added lines in 49 files covered. (97.05%)

2 existing lines in 1 file now uncovered.

9135 of 10062 relevant lines covered (90.79%)

970.05 hits per line

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

85.71
/src/controller/user-controller.ts
1
/**
2
 *  SudoSOS back-end API service.
3
 *  Copyright (C) 2026 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, {
34
  AddRoleRequest,
35
  CreateUserRequest,
36
  PatchUserTypeRequest,
37
  UpdateUserRequest,
38
} from './request/user-request';
39
import { parseRequestPagination, toResponse } from '../helpers/pagination';
2✔
40
import ProductService from '../service/product-service';
2✔
41
import PointOfSaleService from '../service/point-of-sale-service';
2✔
42
import TransactionService, { parseGetTransactionsFilters } from '../service/transaction-service';
2✔
43
import ContainerService from '../service/container-service';
2✔
44
import TransferService, { parseGetTransferFilters } from '../service/transfer-service';
2✔
45
import AuthenticationService from '../service/authentication-service';
2✔
46
import TokenHandler from '../authentication/token-handler';
47
import RBACService from '../service/rbac-service';
2✔
48
import { isFail } from '../helpers/specification-validation';
2✔
49
import verifyUpdatePinRequest from './request/validators/update-pin-request-spec';
2✔
50
import UpdatePinRequest from './request/update-pin-request';
51
import UserService, {
2✔
52
  asUserResponse,
53
  parseGetFinancialMutationsFilters,
54
  parseGetUsersFilters,
55
  UserFilterParameters,
56
} from '../service/user-service';
57
import { asFromAndTillDate, asNumber, asReturnFileType } from '../helpers/validators';
2✔
58
import { verifyCreateUserRequest } from './request/validators/user-request-spec';
2✔
59
import userTokenInOrgan from '../helpers/token-helper';
2✔
60
import { parseUserToResponse } from '../helpers/revision-to-response';
2✔
61
import { AcceptTosRequest } from './request/accept-tos-request';
62
import PinAuthenticator from '../entity/authenticator/pin-authenticator';
2✔
63
import LocalAuthenticator from '../entity/authenticator/local-authenticator';
2✔
64
import UpdateLocalRequest from './request/update-local-request';
65
import verifyUpdateLocalRequest from './request/validators/update-local-request-spec';
2✔
66
import StripeService from '../service/stripe-service';
2✔
67
import verifyUpdateNfcRequest from './request/validators/update-nfc-request-spec';
2✔
68
import UpdateNfcRequest from './request/update-nfc-request';
69
import NfcAuthenticator from '../entity/authenticator/nfc-authenticator';
2✔
70
import KeyAuthenticator from '../entity/authenticator/key-authenticator';
2✔
71
import UpdateKeyResponse from './response/update-key-response';
72
import { randomBytes } from 'crypto';
2✔
73
import DebtorService, { WaiveFinesParams } from '../service/debtor-service';
2✔
74
import ReportService, { BuyerReportService, SalesReportService } from '../service/report-service';
2✔
75
import { ReturnFileType, UserReportParametersType } from 'pdf-generator-client';
2✔
76
import { reportPDFhelper } from '../helpers/express-pdf';
2✔
77
import { PdfError } from '../errors';
2✔
78
import { WaiveFinesRequest } from './request/debtor-request';
79
import Dinero from 'dinero.js';
2✔
80
import Role from '../entity/rbac/role';
2✔
81
import WrappedService from '../service/wrapped-service';
2✔
82
import UserSettingsStore from '../user-settings/user-settings-store';
2✔
83
import { PatchUserSettingsRequest } from './request/user-request';
84

85
export default class UserController extends BaseController {
2✔
86
  private logger: Logger = log4js.getLogger('UserController');
3✔
87

88
  /**
89
   * Reference to the token handler of the application.
90
   */
91
  private tokenHandler: TokenHandler;
92

93
  /**
94
   * Create a new user controller instance.
95
   * @param options - The options passed to the base controller.
96
   * @param tokenHandler
97
   */
98
  public constructor(
99
    options: BaseControllerOptions,
100
    tokenHandler: TokenHandler,
101
  ) {
102
    super(options);
3✔
103
    this.logger.level = process.env.LOG_LEVEL;
3✔
104
    this.tokenHandler = tokenHandler;
3✔
105
  }
106

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

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

388
  /**
389
   * Function to determine which credentials are needed to GET
390
   *    'all' if user is not connected to User
391
   *    'organ' if user is connected to User via organ
392
   *    'own' if user is connected to User
393
   * @param req
394
   * @return whether User is connected to used token
395
   */
396
  static getRelation(req: RequestWithToken): string {
397
    if (userTokenInOrgan(req, asNumber(req.params.id))) return 'organ';
194✔
398
    return req.params.id === req.token.user.id.toString() ? 'own' : 'all';
192✔
399
  }
400

401
  static getAttributes(req: RequestWithToken): string[] {
402
    const attributes: string[] = [];
15✔
403
    const body = req.body as BaseUserRequest;
15✔
404
    for (const key in body) {
15✔
405
      if (body.hasOwnProperty(key)) {
17✔
406
        attributes.push(key);
17✔
407
      }
408
    }
409
    return attributes;
15✔
410
  }
411

412
  /**
413
   * Returns whether the token in the request is allowed to see the email field
414
   * of a User with the given relation (own/organ/all).
415
   */
416
  private async canSeeEmail(req: RequestWithToken, relation: string): Promise<boolean> {
417
    return this.roleManager.can(req.token.roles, 'get', relation, 'User', ['email']);
23✔
418
  }
419

420
  /**
421
   * GET /users
422
   * @summary Get a list of all users
423
   * @operationId getAllUsers
424
   * @tags users - Operations of user controller
425
   * @security JWT
426
   * @param {integer} take.query - How many users the endpoint should return
427
   * @param {integer} skip.query - How many users should be skipped (for pagination)
428
   * @param {string} search.query - Filter based on first name, last name, nickname & email
429
   * @param {boolean} active.query - Filter based if the user is active
430
   * @param {boolean} ofAge.query - Filter based if the user is 18+
431
   * @param {integer} id.query - Filter based on user ID
432
   * @param {string} type.query - enum:MEMBER,ORGAN,VOUCHER,LOCAL_USER,LOCAL_ADMIN,INVOICE,AUTOMATIC_INVOICE - Filter based on user type.
433
   * @return {PaginatedUserResponse} 200 - A list of all users
434
   */
435
  public async getAllUsers(req: RequestWithToken, res: Response): Promise<void> {
436
    this.logger.trace('Get all users by user', req.token.user);
12✔
437

438
    let take;
439
    let skip;
440
    let filters: UserFilterParameters;
441
    try {
12✔
442
      const pagination = parseRequestPagination(req);
12✔
443
      filters = parseGetUsersFilters(req);
12✔
444
      take = pagination.take;
12✔
445
      skip = pagination.skip;
12✔
446
    } catch (e) {
447
      res.status(400).send(e.message);
×
448
      return;
×
449
    }
450

451
    try {
12✔
452
      const [users, count] = await UserService.getUsers(filters, { take, skip });
12✔
453
      const records = users.map((u) => asUserResponse(u, true));
138✔
454
      if (!await this.canSeeEmail(req, 'all')) {
12✔
455
        records.forEach((u) => { u.email = undefined; });
20✔
456
      }
457
      res.status(200).json({
12✔
458
        _pagination: { take, skip, count },
459
        records,
460
      });
461
    } catch (error) {
462
      this.logger.error('Could not get users:', error);
×
463
      res.status(500).json('Internal server error.');
×
464
    }
465
  }
466

467
  /**
468
   * GET /users/usertype/{userType}
469
   * @summary Get all users of user type
470
   * @operationId getAllUsersOfUserType
471
   * @tags users - Operations of user controller
472
   * @param {string} userType.path.required - The userType of the requested users
473
   * @security JWT
474
   * @param {integer} take.query - How many users the endpoint should return
475
   * @param {integer} skip.query - How many users should be skipped (for pagination)
476
   * @return {PaginatedUserResponse} 200 - A list of all users
477
   * @return {string} 404 - Nonexistent usertype
478
   */
479
  public async getAllUsersOfUserType(req: RequestWithToken, res: Response): Promise<void> {
480
    const parameters = req.params;
5✔
481
    this.logger.trace('Get all users of userType', parameters, 'by user', req.token.user);
5✔
482
    const userType = req.params.userType.toUpperCase();
5✔
483

484
    // If it does not exist, return a 404 error
485
    const type = UserType[userType as keyof typeof UserType];
5✔
486
    if (!type || Number(userType)) {
5✔
487
      res.status(404).json('Unknown userType.');
1✔
488
      return;
1✔
489
    }
490

491
    try {
4✔
492
      req.query.type = userType;
4✔
493
      await this.getAllUsers(req, res);
4✔
494
    } catch (error) {
495
      this.logger.error('Could not get users:', error);
×
496
      res.status(500).json('Internal server error.');
×
497
    }
498
  }
499

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

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

527
      const validation = await verifyUpdatePinRequest(updatePinRequest);
2✔
528
      if (isFail(validation)) {
2✔
529
        res.status(400).json(validation.fail.value);
1✔
530
        return;
1✔
531
      }
532

533
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
534
        updatePinRequest.pin.toString(), PinAuthenticator);
535
      res.status(204).json();
1✔
536
    } catch (error) {
537
      this.logger.error('Could not update pin:', error);
×
538
      res.status(500).json('Internal server error.');
×
539
    }
540
  }
541

542
  /**
543
   * PUT /users/{id}/authenticator/nfc
544
   * @summary Put a users NFC code
545
   * @operationId updateUserNfc
546
   * @tags users - Operations of user controller
547
   * @param {integer} id.path.required - The id of the user
548
   * @param {UpdateNfcRequest} request.body.required -
549
   *    The NFC code to update to
550
   * @security JWT
551
   * @return 204 - Update success
552
   * @return {string} 400 - Validation Error
553
   * @return {string} 404 - Nonexistent user id
554
   */
555
  public async updateUserNfc(req: RequestWithToken, res: Response): Promise<void> {
556
    const { params } = req;
10✔
557
    const updateNfcRequest = req.body as UpdateNfcRequest;
10✔
558
    this.logger.trace('Update user NFC', params, 'by user', req.token.user);
10✔
559

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

569
      const validation = await verifyUpdateNfcRequest(updateNfcRequest);
9✔
570
      if (isFail(validation)) {
9✔
571
        res.status(400).json(validation.fail.value);
2✔
572
        return;
2✔
573
      }
574

575
      await new AuthenticationService().setUserAuthenticationNfc(user,
7✔
576
        updateNfcRequest.nfcCode.toString(), NfcAuthenticator);
577
      res.status(204).json();
7✔
578
    } catch (error) {
579
      this.logger.error('Could not update NFC:', error);
×
580
      res.status(500).json('Internal server error.');
×
581
    }
582
  }
583

584
  /**
585
   * DELETE /users/{id}/authenticator/nfc
586
   * @summary Delete a nfc code
587
   * @operationId deleteUserNfc
588
   * @tags users - Operations of user controller
589
   * @param {integer} id.path.required - The id of the user
590
   * @security JWT
591
   * @return 200 - Delete nfc success
592
   * @return {string} 400 - Validation Error
593
   * @return {string} 403 - Nonexistent user nfc
594
   * @return {string} 404 - Nonexistent user id
595
   */
596
  public async deleteUserNfc(req: RequestWithToken, res: Response): Promise<void> {
597
    const parameters = req.params;
9✔
598
    this.logger.trace('Delete user NFC', parameters, 'by user', req.token.user);
9✔
599

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

609
      if (await NfcAuthenticator.count({ where: { userId: parseInt(parameters.id, 10) } }) == 0) {
6✔
610
        res.status(403).json('No saved nfc');
3✔
611
        return;
3✔
612
      }
613

614
      await NfcAuthenticator.delete(parseInt(parameters.id, 10));
3✔
615
      res.status(204).json();
3✔
616
    } catch (error) {
617
      this.logger.error('Could not update NFC:', error);
×
618
      res.status(500).json('Internal server error.');
×
619
    }
620
  }
621

622
  /**
623
   * POST /users/{id}/authenticator/key
624
   * @summary POST an users update to new key code
625
   * @operationId updateUserKey
626
   * @tags users - Operations of user controller
627
   * @param {integer} id.path.required - The id of the user
628
   * @security JWT
629
   * @return {UpdateKeyResponse} 200 - The new key
630
   * @return {string} 400 - Validation Error
631
   * @return {string} 404 - Nonexistent user id
632
   */
633
  public async updateUserKey(req: RequestWithToken, res: Response): Promise<void> {
634
    const { params } = req;
2✔
635
    this.logger.trace('Update user key', params, 'by user', req.token.user);
2✔
636

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

647
      const generatedKey = randomBytes(128).toString('hex');
1✔
648
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
649
        generatedKey, KeyAuthenticator);
650
      const response = { key: generatedKey } as UpdateKeyResponse;
1✔
651
      res.status(200).json(response);
1✔
652
    } catch (error) {
653
      this.logger.error('Could not update key:', error);
×
654
      res.status(500).json('Internal server error.');
×
655
    }
656
  }
657

658
  /**
659
   * Delete /users/{id}/authenticator/key
660
   * @summary Delete a users key code
661
   * @operationId deleteUserKey
662
   * @tags users - Operations of user controller
663
   * @param {integer} id.path.required - The id of the user
664
   * @security JWT
665
   * @return  200 - Deletion succesfull
666
   * @return {string} 400 - Validation Error
667
   * @return {string} 404 - Nonexistent user id
668
   */
669
  public async deleteUserKey(req: RequestWithToken, res: Response): Promise<void> {
670
    const { params } = req;
2✔
671
    this.logger.trace('Delete user key', params, 'by user', req.token.user);
2✔
672

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

682

683
      await KeyAuthenticator.delete(parseInt(params.id, 10));
1✔
684
      res.status(204).json();
1✔
685
    } catch (error) {
686
      this.logger.error('Could not delete key:', error);
×
687
      res.status(500).json('Internal server error.');
×
688
    }
689
  }
690

691
  /**
692
   * PUT /users/{id}/authenticator/local
693
   * @summary Put a user's local password
694
   * @operationId updateUserLocalPassword
695
   * @tags users - Operations of user controller
696
   * @param {integer} id.path.required - The id of the user
697
   * @param {UpdateLocalRequest} request.body.required -
698
   *    The password update
699
   * @security JWT
700
   * @return 204 - Update success
701
   * @return {string} 400 - Validation Error
702
   * @return {string} 404 - Nonexistent user id
703
   */
704
  public async updateUserLocalPassword(req: RequestWithToken, res: Response): Promise<void> {
705
    const parameters = req.params;
3✔
706
    const updateLocalRequest = req.body as UpdateLocalRequest;
3✔
707
    this.logger.trace('Update user local password', parameters, 'by user', req.token.user);
3✔
708

709
    try {
3✔
710
      const id = Number.parseInt(parameters.id, 10);
3✔
711
      // Get the user object if it exists
712
      const user = await User.findOne({ where: { id, deleted: false } });
3✔
713
      // If it does not exist, return a 404 error
714
      if (user == null) {
3✔
715
        res.status(404).json('Unknown user ID.');
1✔
716
        return;
1✔
717
      }
718

719
      const validation = await verifyUpdateLocalRequest(updateLocalRequest);
2✔
720
      if (isFail(validation)) {
2✔
721
        res.status(400).json(validation.fail.value);
1✔
722
        return;
1✔
723
      }
724

725
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
726
        updateLocalRequest.password, LocalAuthenticator);
727
      res.status(204).json();
1✔
728
    } catch (error) {
729
      this.logger.error('Could not update local password:', error);
×
730
      res.status(500).json('Internal server error.');
×
731
    }
732
  }
733

734
  /**
735
   * GET /users/{id}/members
736
   * @summary Get an organs members
737
   * @operationId getOrganMembers
738
   * @tags users - Operations of user controller
739
   * @param {integer} id.path.required - The id of the user
740
   * @param {integer} take.query - How many members the endpoint should return
741
   * @param {integer} skip.query - How many members should be skipped (for pagination)
742
   * @security JWT
743
   * @return {PaginatedUserResponse} 200 - All members of the organ
744
   * @return {string} 404 - Nonexistent user id
745
   * @return {string} 400 - User is not an organ
746
   */
747
  public async getOrganMembers(req: RequestWithToken, res: Response): Promise<void> {
748
    const parameters = req.params;
4✔
749
    this.logger.trace('Get organ members', parameters, 'by user', req.token.user);
4✔
750

751
    let take;
752
    let skip;
753
    try {
4✔
754
      const pagination = parseRequestPagination(req);
4✔
755
      take = pagination.take;
4✔
756
      skip = pagination.skip;
4✔
757
    } catch (e) {
758
      res.status(400).send(e.message);
×
759
      return;
×
760
    }
761

762
    try {
4✔
763
      const organId = asNumber(parameters.id);
4✔
764
      // Get the user object if it exists
765
      const user = await User.findOne({ where: { id: organId } });
4✔
766
      // If it does not exist, return a 404 error
767
      if (user == null) {
4✔
768
        res.status(404).json('Unknown user ID.');
1✔
769
        return;
1✔
770
      }
771

772
      if (user.type !== UserType.ORGAN) {
3✔
773
        res.status(400).json('User is not of type Organ');
1✔
774
        return;
1✔
775
      }
776

777
      const [members, count] = await UserService.getUsers({ organId }, { take, skip });
2✔
778
      const records = members.map((u) => asUserResponse(u, true));
11✔
779
      if (!await this.canSeeEmail(req, UserController.getRelation(req))) {
2!
NEW
780
        records.forEach((u) => { u.email = undefined; });
×
781
      }
782
      res.status(200).json({
2✔
783
        _pagination: { take, skip, count },
784
        records,
785
      });
786
    } catch (error) {
787
      this.logger.error('Could not get organ members:', error);
×
788
      res.status(500).json('Internal server error.');
×
789
    }
790
  }
791

792
  /**
793
   * GET /users/{id}
794
   * @summary Get an individual user
795
   * @operationId getIndividualUser
796
   * @tags users - Operations of user controller
797
   * @param {integer} id.path.required - userID
798
   * @security JWT
799
   * @return {UserResponse} 200 - Individual user
800
   * @return {string} 404 - Nonexistent user id
801
   */
802
  public async getIndividualUser(req: RequestWithToken, res: Response): Promise<void> {
803
    const parameters = req.params;
10✔
804
    this.logger.trace('Get individual user', parameters, 'by user', req.token.user);
10✔
805

806
    try {
10✔
807
      // Get the user object if it exists
808
      const user = await UserService.getSingleUser(asNumber(parameters.id));
10✔
809
      // If it does not exist, return a 404 error
810
      if (user == null) {
10✔
811
        res.status(404).json('Unknown user ID.');
3✔
812
        return;
3✔
813
      }
814

815
      const response = asUserResponse(user, true);
7✔
816
      if (!await this.canSeeEmail(req, UserController.getRelation(req))) {
7✔
817
        response.email = undefined;
1✔
818
      }
819
      res.status(200).json(response);
7✔
820
    } catch (error) {
821
      this.logger.error('Could not get individual user:', error);
×
822
      res.status(500).json('Internal server error.');
×
823
    }
824
  }
825

826
  /**
827
   * POST /users
828
   * @summary Create a new user
829
   * @operationId createUser
830
   * @tags users - Operations of user controller
831
   * @param {CreateUserRequest} request.body.required -
832
   * The user which should be created
833
   * @security JWT
834
   * @return {UserResponse} 200 - New user
835
   * @return {string} 400 - Bad request
836
   */
837
  // eslint-disable-next-line class-methods-use-this
838
  public async createUser(req: RequestWithToken, res: Response): Promise<void> {
839
    const body = req.body as CreateUserRequest;
7✔
840
    this.logger.trace('Create user', body, 'by user', req.token.user);
7✔
841

842
    try {
7✔
843
      const validation = await verifyCreateUserRequest(body);
7✔
844
      if (isFail(validation)) {
7✔
845
        res.status(400).json(validation.fail.value);
3✔
846
        return;
3✔
847
      }
848

849
      const user = await UserService.createUser(body);
4✔
850
      res.status(201).json(asUserResponse(user, true));
4✔
851
    } catch (error) {
852
      this.logger.error('Could not create user:', error);
×
853
      res.status(500).json('Internal server error.');
×
854
    }
855
  }
856

857
  /**
858
   * PATCH /users/{id}
859
   * @summary Update a user
860
   * @operationId updateUser
861
   * @tags users - Operations of user controller
862
   * @param {integer} id.path.required - The id of the user
863
   * @param {UpdateUserRequest} request.body.required - The user which should be updated
864
   * @security JWT
865
   * @return {UserResponse} 200 - New user
866
   * @return {string} 400 - Bad request
867
   */
868
  public async updateUser(req: RequestWithToken, res: Response): Promise<void> {
869
    const body = req.body as UpdateUserRequest;
13✔
870
    const parameters = req.params;
13✔
871
    this.logger.trace('Update user', parameters.id, 'with', body, 'by user', req.token.user);
13✔
872

873
    if (body.firstName !== undefined && body.firstName.length === 0) {
13✔
874
      res.status(400).json('firstName cannot be empty');
1✔
875
      return;
1✔
876
    }
877
    if (body.firstName !== undefined && body.firstName.length > 64) {
12✔
878
      res.status(400).json('firstName too long');
1✔
879
      return;
1✔
880
    }
881
    if (body.lastName !== undefined && body.lastName.length > 64) {
11✔
882
      res.status(400).json('lastName too long');
1✔
883
      return;
1✔
884
    }
885
    if (body.nickname !== undefined && body.nickname.length > 64) {
10✔
886
      res.status(400).json('nickname too long');
1✔
887
      return;
1✔
888
    }
889
    if (body.nickname === '') body.nickname = null;
9✔
890

891
    try {
9✔
892
      const id = parseInt(parameters.id, 10);
9✔
893
      // Get the user object if it exists
894
      let user = await User.findOne({ where: { id, deleted: false } });
9✔
895
      // If it does not exist, return a 404 error
896
      if (user == null) {
9!
897
        res.status(404).json('Unknown user ID.');
×
898
        return;
×
899
      }
900

901
      user = {
9✔
902
        ...body,
903
      } as User;
904
      await User.update(parameters.id, user);
9✔
905
      const updatedUser = await UserService.getSingleUser(asNumber(parameters.id));
9✔
906
      res.status(200).json(asUserResponse(updatedUser, true));
9✔
907
    } catch (error) {
908
      this.logger.error('Could not update user:', error);
×
909
      res.status(500).json('Internal server error.');
×
910
    }
911
  }
912

913
  /**
914
   * DELETE /users/{id}
915
   * @summary Delete a single user
916
   * @operationId deleteUser
917
   * @tags users - Operations of user controller
918
   * @param {integer} id.path.required - The id of the user
919
   * @security JWT
920
   * @return 204 - User successfully deleted
921
   * @return {string} 400 - Cannot delete yourself
922
   */
923
  public async deleteUser(req: RequestWithToken, res: Response): Promise<void> {
924
    const parameters = req.params;
5✔
925
    this.logger.trace('Delete individual user', parameters, 'by user', req.token.user);
5✔
926

927
    if (req.token.user.id === parseInt(parameters.id, 10)) {
5✔
928
      res.status(400).json('Cannot delete yourself');
1✔
929
      return;
1✔
930
    }
931

932
    try {
4✔
933
      const id = parseInt(parameters.id, 10);
4✔
934
      // Get the user object if it exists
935
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
936
      // If it does not exist, return a 404 error
937
      if (user == null) {
4✔
938
        res.status(404).json('Unknown user ID.');
2✔
939
        return;
2✔
940
      }
941

942
      user.deleted = true;
2✔
943
      await user.save();
2✔
944
      res.status(204).json('User deleted');
2✔
945
    } catch (error) {
946
      this.logger.error('Could not create product:', error);
×
947
      res.status(500).json('Internal server error.');
×
948
    }
949
  }
950

951
  /**
952
   * GET /users/nfc/{nfcCode}
953
   * @summary Get a user using the nfc code
954
   * @operationId findUserNfc
955
   * @tags users - Operations of the user controller
956
   * @security JWT
957
   * @param {string} nfcCode.path.required - The nfc code of the user
958
   * @return {UserResponse} 200 - The requested user
959
   * @return {string} 404 - The user with the given nfc code does not exist
960
   */
961
  public async findUserNfc(req: RequestWithToken, res: Response): Promise<void> {
962
    const parameters = req.params;
3✔
963
    this.logger.trace('Find user nfc', parameters, 'by user', req.token.user);
3✔
964

965
    try {
3✔
966
      const nfcCode = String(parameters.nfcCode);
3✔
967
      const nfc = await NfcAuthenticator.findOne({ where: { nfcCode } });
3✔
968

969
      if (nfc === null) {
3✔
970
        res.status(404).json('Unknown nfc code');
1✔
971
        return;
1✔
972
      }
973

974
      const userResponse = parseUserToResponse(nfc.user);
2✔
975
      if (!await this.canSeeEmail(req, 'all')) {
2!
976
        userResponse.email = undefined;
×
977
      }
978
      res.status(200).json(userResponse);
2✔
979
    } catch (error) {
980
      this.logger.error('Could not find user using nfc:', error);
×
981
      res.status(500).json('Internal server error.');
×
982
    }
983
  }
984

985
  /**
986
   * POST /users/acceptTos
987
   * @summary Accept the Terms of Service if you have not accepted it yet
988
   * @operationId acceptTos
989
   * @tags users - Operations of the User controller
990
   * @param {AcceptTosRequest} request.body.required - "Tosrequest body"
991
   * @security JWT
992
   * @return 204 - ToS accepted
993
   * @return {string} 400 - ToS already accepted
994
   */
995
  public async acceptToS(req: RequestWithToken, res: Response): Promise<void> {
996
    this.logger.trace('Accept ToS for user', req.token.user);
3✔
997

998
    const { id } = req.token.user;
3✔
999
    const body = req.body as AcceptTosRequest;
3✔
1000

1001
    try {
3✔
1002
      const user = await UserService.getSingleUser(id);
3✔
1003
      if (user == null) {
3!
1004
        res.status(404).json('User not found.');
×
1005
        return;
×
1006
      }
1007

1008
      const success = await UserService.acceptToS(id, body);
3✔
1009
      if (!success) {
3✔
1010
        res.status(400).json('User already accepted ToS.');
1✔
1011
        return;
1✔
1012
      }
1013

1014
      res.status(204).json();
2✔
1015
      return;
2✔
1016
    } catch (error) {
1017
      this.logger.error('Could not accept ToS for user:', error);
×
1018
      res.status(500).json('Internal server error.');
×
1019
    }
1020
  }
1021

1022
  /**
1023
   * GET /users/{id}/products
1024
   * @summary Get an user's products
1025
   * @operationId getUsersProducts
1026
   * @tags users - Operations of user controller
1027
   * @param {integer} id.path.required - The id of the user
1028
   * @param {integer} take.query - How many products the endpoint should return
1029
   * @param {integer} skip.query - How many products should be skipped (for pagination)
1030
   * @security JWT
1031
   * @return {PaginatedProductResponse} 200 - List of products.
1032
   */
1033
  public async getUsersProducts(req: RequestWithToken, res: Response): Promise<void> {
1034
    const parameters = req.params;
4✔
1035
    this.logger.trace("Get user's products", parameters, 'by user', req.token.user);
4✔
1036

1037
    let take;
1038
    let skip;
1039
    try {
4✔
1040
      const pagination = parseRequestPagination(req);
4✔
1041
      take = pagination.take;
4✔
1042
      skip = pagination.skip;
4✔
1043
    } catch (e) {
1044
      res.status(400).send(e.message);
×
1045
      return;
×
1046
    }
1047

1048
    // Handle request
1049
    try {
4✔
1050
      const id = parseInt(parameters.id, 10);
4✔
1051
      const owner = await User.findOne({ where: { id, deleted: false } });
4✔
1052
      if (owner == null) {
4✔
1053
        res.status(404).json({});
1✔
1054
        return;
1✔
1055
      }
1056

1057
      const [revisions, count] = await ProductService.getProducts({}, { take, skip }, owner);
3✔
1058
      const records = revisions.map((r) => ProductService.revisionToResponse(r));
21✔
1059
      res.json(toResponse(records, count, { take, skip }));
3✔
1060
    } catch (error) {
1061
      this.logger.error('Could not return all products:', error);
×
1062
      res.status(500).json('Internal server error.');
×
1063
    }
1064
  }
1065

1066
  /**
1067
   * GET /users/{id}/containers
1068
   * @summary Returns the user's containers
1069
   * @operationId getUsersContainers
1070
   * @tags users - Operations of user controller
1071
   * @param {integer} id.path.required - The id of the user
1072
   * @security JWT
1073
   * @param {integer} take.query - How many containers the endpoint should return
1074
   * @param {integer} skip.query - How many containers should be skipped (for pagination)
1075
   * @return {PaginatedContainerResponse} 200 - All users updated containers
1076
   * @return {string} 404 - Not found error
1077
   * @return {string} 500 - Internal server error
1078
   */
1079
  public async getUsersContainers(req: RequestWithToken, res: Response): Promise<void> {
1080
    const { id } = req.params;
4✔
1081
    this.logger.trace("Get user's containers", id, 'by user', req.token.user);
4✔
1082

1083
    let take;
1084
    let skip;
1085
    try {
4✔
1086
      const pagination = parseRequestPagination(req);
4✔
1087
      take = pagination.take;
4✔
1088
      skip = pagination.skip;
4✔
1089
    } catch (e) {
1090
      res.status(400).send(e.message);
×
1091
      return;
×
1092
    }
1093

1094
    // handle request
1095
    try {
4✔
1096
      // Get the user object if it exists
1097
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1098
      // If it does not exist, return a 404 error
1099
      if (user == null) {
4✔
1100
        res.status(404).json('Unknown user ID.');
1✔
1101
        return;
1✔
1102
      }
1103

1104
      const [revisions, count] = await ContainerService
3✔
1105
        .getContainers({}, { take, skip }, user);
1106
      const records = revisions.map((r) => ContainerService.revisionToResponse(r));
9✔
1107
      res.json(toResponse(records, count, { take, skip }));
3✔
1108
    } catch (error) {
1109
      this.logger.error('Could not return containers:', error);
×
1110
      res.status(500).json('Internal server error.');
×
1111
    }
1112
  }
1113

1114
  /**
1115
   * GET /users/{id}/pointsofsale
1116
   * @summary Returns the user's Points of Sale
1117
   * @operationId getUsersPointsOfSale
1118
   * @tags users - Operations of user controller
1119
   * @param {integer} id.path.required - The id of the user
1120
   * @param {integer} take.query - How many points of sale the endpoint should return
1121
   * @param {integer} skip.query - How many points of sale should be skipped (for pagination)
1122
   * @security JWT
1123
   * @return {PaginatedPointOfSaleResponse} 200 - All users updated point of sales
1124
   * @return {string} 404 - Not found error
1125
   * @return {string} 500 - Internal server error
1126
   */
1127
  public async getUsersPointsOfSale(req: RequestWithToken, res: Response): Promise<void> {
1128
    const { id } = req.params;
4✔
1129
    this.logger.trace("Get user's points of sale", id, 'by user', req.token.user);
4✔
1130

1131
    let take;
1132
    let skip;
1133
    try {
4✔
1134
      const pagination = parseRequestPagination(req);
4✔
1135
      take = pagination.take;
4✔
1136
      skip = pagination.skip;
4✔
1137
    } catch (e) {
1138
      res.status(400).send(e.message);
×
1139
      return;
×
1140
    }
1141

1142
    // handle request
1143
    try {
4✔
1144
      // Get the user object if it exists
1145
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1146
      // If it does not exist, return a 404 error
1147
      if (user == null) {
4✔
1148
        res.status(404).json('Unknown user ID.');
1✔
1149
        return;
1✔
1150
      }
1151

1152
      const [revisions, count] = await PointOfSaleService
3✔
1153
        .getPointsOfSale({}, { take, skip }, user);
1154
      const records = revisions.map((r) => PointOfSaleService.revisionToResponse(r));
9✔
1155
      res.json(toResponse(records, count, { take, skip }));
3✔
1156
    } catch (error) {
1157
      this.logger.error('Could not return point of sale:', error);
×
1158
      res.status(500).json('Internal server error.');
×
1159
    }
1160
  }
1161

1162
  /**
1163
   * GET /users/{id}/transactions
1164
   * @summary Get transactions from a user.
1165
   * @operationId getUsersTransactions
1166
   * @tags users - Operations of user controller
1167
   * @param {integer} id.path.required - The id of the user that should be involved
1168
   * in all returned transactions
1169
   * @param {integer} fromId.query - From-user for selected transactions
1170
   * @param {integer} createdById.query - User that created selected transaction
1171
   * @param {integer} toId.query - To-user for selected transactions
1172
   * transactions. Requires ContainerId
1173
   * @param {integer} productId.query - Product ID for selected transactions
1174
   * @param {integer} productRevision.query - Product Revision for selected
1175
   * transactions. Requires ProductID
1176
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1177
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1178
   * @param {integer} take.query - How many transactions the endpoint should return
1179
   * @param {integer} skip.query - How many transactions should be skipped (for pagination)
1180
   * @security JWT
1181
   * @return {PaginatedBaseTransactionResponse} 200 - List of transactions.
1182
   */
1183
  public async getUsersTransactions(req: RequestWithToken, res: Response): Promise<void> {
1184
    const { id } = req.params;
4✔
1185
    this.logger.trace("Get user's", id, 'transactions by user', req.token.user);
4✔
1186

1187
    // Parse the filters given in the query parameters. If there are any issues,
1188
    // the parse method will throw an exception. We will then return a 400 error.
1189
    let filters;
1190
    try {
4✔
1191
      filters = parseGetTransactionsFilters(req);
4✔
1192
    } catch (e) {
1193
      res.status(400).json(e.message);
×
1194
      return;
×
1195
    }
1196

1197
    let take;
1198
    let skip;
1199
    try {
4✔
1200
      const pagination = parseRequestPagination(req);
4✔
1201
      take = pagination.take;
4✔
1202
      skip = pagination.skip;
4✔
1203
    } catch (e) {
1204
      res.status(400).send(e.message);
×
1205
      return;
×
1206
    }
1207

1208
    try {
4✔
1209
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
4✔
1210
      if (user == null) {
4✔
1211
        res.status(404).json({});
1✔
1212
        return;
1✔
1213
      }
1214
      const [records, count] = await new TransactionService().getTransactions(filters, { take, skip }, user);
3✔
1215

1216
      res.status(200).json(toResponse(records, count, { take, skip }));
3✔
1217
    } catch (error) {
1218
      this.logger.error('Could not return all transactions:', error);
×
1219
      res.status(500).json('Internal server error.');
×
1220
    }
1221
  }
1222

1223
  /**
1224
   * GET /users/{id}/transactions/sales/report
1225
   * @summary Get sales report for the given user
1226
   * @operationId getUsersSalesReport
1227
   * @tags users - Operations of user controller
1228
   * @param {integer} id.path.required - The id of the user to get the sales report for
1229
   * @security JWT
1230
   * @param {string} fromDate.query.required - Start date for selected sales (inclusive)
1231
   * @param {string} tillDate.query.required - End date for selected sales (exclusive)
1232
   * @return {ReportResponse} 200 - The sales report of the user
1233
   * @return {string} 400 - Validation error
1234
   * @return {string} 404 - User not found error.
1235
   */
1236
  public async getUsersSalesReport(req: RequestWithToken, res: Response): Promise<void> {
1237
    const { id } = req.params;
7✔
1238
    this.logger.trace('Get sales report for user ', id, ' by user', req.token.user);
7✔
1239

1240
    let filters: { fromDate: Date, tillDate: Date };
1241
    try {
7✔
1242
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1243
    } catch (e) {
1244
      res.status(400).json(e.message);
1✔
1245
      return;
1✔
1246
    }
1247

1248
    try {
6✔
1249
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
6✔
1250
      if (user == null) {
6✔
1251
        res.status(404).json('Unknown user ID.');
1✔
1252
        return;
1✔
1253
      }
1254

1255
      const report = await (new SalesReportService()).getReport({ ...filters, forId: user.id });
5✔
1256
      res.status(200).json(ReportService.reportToResponse(report));
5✔
1257
    } catch (error) {
1258
      this.logger.error('Could not get sales report:', error);
×
1259
      res.status(500).json('Internal server error.');
×
1260
    }
1261
  }
1262

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

1284
    let filters: { fromDate: Date, tillDate: Date };
1285
    let description: string;
1286
    let fileType: ReturnFileType;
1287
    try {
7✔
1288
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1289
      description = String(req.query.description);
4✔
1290
      fileType = asReturnFileType(req.query.fileType);
4✔
1291
    } catch (e) {
1292
      res.status(400).json(e.message);
4✔
1293
      return;
4✔
1294
    }
1295

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

1314
  /**
1315
   * GET /users/{id}/transactions/purchases/report/pdf
1316
   * @summary Get purchase report pdf for the given user
1317
   * @operationId getUsersPurchaseReportPdf
1318
   * @tags users - Operations of user controller
1319
   * @param {integer} id.path.required - The id of the user to get the purchase report for
1320
   * @security JWT
1321
   * @param {string} fromDate.query.required - Start date for selected purchases (inclusive)
1322
   * @param {string} tillDate.query.required - End date for selected purchases (exclusive)
1323
   * @param {string} fileType.query - enum:PDF,TEX - The file type of the report
1324
   * @return {string} 404 - User not found error.
1325
   * @returns {string} 200 - The requested report - application/pdf
1326
   * @return {string} 400 - Validation error
1327
   * @return {string} 500 - Internal server error
1328
   * @return {string} 502 - PDF generation failed
1329
   */
1330
  public async getUsersPurchaseReportPdf(req: RequestWithToken, res: Response): Promise<void> {
1331
    const { id } = req.params;
7✔
1332
    this.logger.trace('Get purchase report pdf for user ', id, ' by user', req.token.user);
7✔
1333

1334
    let filters: { fromDate: Date, tillDate: Date };
1335
    let description: string;
1336
    let fileType: ReturnFileType;
1337
    try {
7✔
1338
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1339
      description = String(req.query.description);
4✔
1340
      fileType = asReturnFileType(req.query.fileType);
4✔
1341
    } catch (e) {
1342
      res.status(400).json(e.message);
4✔
1343
      return;
4✔
1344
    }
1345

1346
    try {
3✔
1347
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1348
      if (user == null) {
3✔
1349
        res.status(404).json('Unknown user ID.');
1✔
1350
        return;
1✔
1351
      }
1352
      const service = new BuyerReportService();
2✔
1353
      await (reportPDFhelper(res))(service, filters, description, user.id, UserReportParametersType.Purchases, fileType);
2✔
1354
    } catch (error) {
1355
      this.logger.error('Could not get sales report:', error);
1✔
1356
      if (error instanceof PdfError) {
1✔
1357
        res.status(502).json('PDF Generator service failed.');
1✔
1358
        return;
1✔
1359
      }
1360
      res.status(500).json('Internal server error.');
×
1361
    }
1362
  }
1363

1364
  /**
1365
   * GET /users/{id}/transactions/purchases/report
1366
   * @summary Get purchases report for the given user
1367
   * @operationId getUsersPurchasesReport
1368
   * @tags users - Operations of user controller
1369
   * @param {integer} id.path.required - The id of the user to get the purchases report for
1370
   * @security JWT
1371
   * @param {string} fromDate.query.required - Start date for selected purchases (inclusive)
1372
   * @param {string} tillDate.query.required - End date for selected purchases (exclusive)
1373
   * @return {ReportResponse} 200 - The purchases report of the user
1374
   * @return {string} 400 - Validation error
1375
   * @return {string} 404 - User not found error.
1376
   */
1377
  public async getUsersPurchasesReport(req: RequestWithToken, res: Response): Promise<void> {
1378
    const { id } = req.params;
4✔
1379
    this.logger.trace('Get purchases report for user ', id, ' by user', req.token.user);
4✔
1380

1381
    let filters: { fromDate: Date, tillDate: Date };
1382
    try {
4✔
1383
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
4✔
1384
    } catch (e) {
1385
      res.status(400).json(e.message);
1✔
1386
      return;
1✔
1387
    }
1388

1389
    try {
3✔
1390
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1391
      if (user == null) {
3✔
1392
        res.status(404).json('Unknown user ID.');
1✔
1393
        return;
1✔
1394
      }
1395

1396
      const report = await (new BuyerReportService()).getReport({ ...filters, forId: user.id });
2✔
1397
      res.status(200).json(ReportService.reportToResponse(report));
2✔
1398
    } catch (error) {
1399
      this.logger.error('Could not get sales report:', error);
×
1400
      res.status(500).json('Internal server error.');
×
1401
    }
1402
  }
1403

1404
  /**
1405
   * GET /users/{id}/transfers
1406
   * @summary Get transfers to or from an user.
1407
   * @operationId getUsersTransfers
1408
   * @tags users - Operations of user controller
1409
   * @param {integer} id.path.required - The id of the user that should be involved
1410
   * in all returned transfers
1411
   * @param {integer} take.query - How many transfers the endpoint should return
1412
   * @param {integer} skip.query - How many transfers should be skipped (for pagination)
1413
   * @param {integer} fromId.query - From-user for selected transfers
1414
   * @param {integer} toId.query - To-user for selected transfers
1415
   * @param {integer} id.query - ID of selected transfers
1416
   * @security JWT
1417
   * @return {PaginatedTransferResponse} 200 - List of transfers.
1418
   */
1419
  public async getUsersTransfers(req: RequestWithToken, res: Response): Promise<void> {
1420
    const { id } = req.params;
2✔
1421
    this.logger.trace("Get user's transfers", id, 'by user', req.token.user);
2✔
1422

1423
    // Parse the filters given in the query parameters. If there are any issues,
1424
    // the parse method will throw an exception. We will then return a 400 error.
1425
    let filters;
1426
    try {
2✔
1427
      filters = parseGetTransferFilters(req);
2✔
1428
    } catch (e) {
1429
      res.status(400).json(e.message);
×
1430
      return;
×
1431
    }
1432

1433
    let take;
1434
    let skip;
1435
    try {
2✔
1436
      const pagination = parseRequestPagination(req);
2✔
1437
      take = pagination.take;
2✔
1438
      skip = pagination.skip;
2✔
1439
    } catch (e) {
1440
      res.status(400).send(e.message);
×
1441
      return;
×
1442
    }
1443

1444
    // handle request
1445
    try {
2✔
1446
      // Get the user object if it exists
1447
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
2✔
1448
      // If it does not exist, return a 404 error
1449
      if (user == null) {
2!
1450
        res.status(404).json('Unknown user ID.');
×
1451
        return;
×
1452
      }
1453

1454
      const [transfers, count] = await new TransferService().getTransfers(
2✔
1455
        { ...filters }, { take, skip }, user,
1456
      );
1457
      const records = transfers.map((t) => TransferService.asTransferResponse(t));
18✔
1458
      res.json(toResponse(records, count, { take, skip }));
2✔
1459
    } catch (error) {
1460
      this.logger.error('Could not return user transfers', error);
×
1461
      res.status(500).json('Internal server error.');
×
1462
    }
1463
  }
1464

1465
  /**
1466
   * GET /users/{id}/roles
1467
   * @summary Get all roles assigned to the user.
1468
   * @operationId getUserRoles
1469
   * @tags users - Operations of user controller
1470
   * @param {integer} id.path.required - The id of the user to get the roles from
1471
   * @security JWT
1472
   * @return {Array.<RoleWithPermissionsResponse>} 200 - The roles of the user
1473
   * @return {string} 404 - User not found error.
1474
   */
1475
  public async getUserRoles(req: RequestWithToken, res: Response): Promise<void> {
1476
    const parameters = req.params;
3✔
1477
    this.logger.trace('Get roles of user', parameters, 'by user', req.token.user);
3✔
1478

1479
    try {
3✔
1480
      const id = parseInt(parameters.id, 10);
3✔
1481
      // Get the user object if it exists
1482
      const user = await User.findOne({ where: { id, deleted: false } });
3✔
1483
      // If it does not exist, return a 404 error
1484
      if (user == null) {
3✔
1485
        res.status(404).json('Unknown user ID.');
1✔
1486
        return;
1✔
1487
      }
1488

1489
      const rolesWithPermissions = await this.roleManager.getRoles(user, true);
2✔
1490
      const response = rolesWithPermissions.map((r) => RBACService.asRoleResponse(r));
2✔
1491
      res.status(200).json(response);
2✔
1492
    } catch (error) {
1493
      this.logger.error('Could not get roles of user:', error);
×
1494
      res.status(500).json('Internal server error.');
×
1495
    }
1496
  }
1497

1498
  /**
1499
   * GET /users/{id}/financialmutations
1500
   * @summary Get all financial mutations of a user (from or to).
1501
   * @operationId getUsersFinancialMutations
1502
   * @tags users - Operations of user controller
1503
   * @param {integer} id.path.required - The id of the user to get the mutations from
1504
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1505
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1506
   * @param {integer} take.query - How many transactions the endpoint should return
1507
   * @param {integer} skip.query - How many transactions should be skipped (for pagination)
1508
   * @security JWT
1509
   * @return {PaginatedFinancialMutationResponse} 200 - The financial mutations of the user
1510
   * @return {string} 404 - User not found error.
1511
   */
1512
  public async getUsersFinancialMutations(req: RequestWithToken, res: Response): Promise<void> {
1513
    const parameters = req.params;
4✔
1514
    this.logger.trace('Get financial mutations of user', parameters, 'by user', req.token.user);
4✔
1515

1516
    let filters;
1517
    let take;
1518
    let skip;
1519
    try {
4✔
1520
      filters = parseGetFinancialMutationsFilters(req);
4✔
1521
      const pagination = parseRequestPagination(req);
4✔
1522
      take = pagination.take;
4✔
1523
      skip = pagination.skip;
4✔
1524
    } catch (e) {
1525
      res.status(400).send(e.message);
×
1526
      return;
×
1527
    }
1528

1529
    try {
4✔
1530
      const id = parseInt(parameters.id, 10);
4✔
1531
      // Get the user object if it exists
1532
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
1533
      // If it does not exist, return a 404 error
1534
      if (user == null) {
4!
1535
        res.status(404).json('Unknown user ID.');
×
1536
        return;
×
1537
      }
1538

1539
      const mutations = await UserService.getUserFinancialMutations(user, filters, { take, skip });
4✔
1540
      res.status(200).json(mutations);
4✔
1541
    } catch (error) {
1542
      this.logger.error('Could not get financial mutations of user:', error);
×
1543
      res.status(500).json('Internal server error.');
×
1544
    }
1545
  }
1546

1547
  /**
1548
   * GET /users/{id}/deposits
1549
   * @summary Get all deposits of a user that are still being processed by Stripe
1550
   * @operationId getUsersProcessingDeposits
1551
   * @tags users - Operations of user controller
1552
   * @param {integer} id.path.required - The id of the user to get the deposits from
1553
   * @security JWT
1554
   * @return {Array.<RoleResponse>} 200 - The processing deposits of a user
1555
   * @return {string} 404 - User not found error.
1556
   */
1557
  public async getUsersProcessingDeposits(req: RequestWithToken, res: Response): Promise<void> {
1558
    const parameters = req.params;
2✔
1559
    this.logger.trace('Get users processing deposits from user', parameters.id);
2✔
1560

1561
    try {
2✔
1562
      const id = parseInt(parameters.id, 10);
2✔
1563

1564
      const user = await User.findOne({ where: { id } });
2✔
1565
      if (user == null) {
2✔
1566
        res.status(404).json('Unknown user ID.');
1✔
1567
        return;
1✔
1568
      }
1569

1570
      const deposits = await StripeService.getProcessingStripeDepositsFromUser(id);
1✔
1571
      res.status(200).json(deposits.map((d) => StripeService.asStripeDepositResponse(d)));
1✔
1572
    } catch (error) {
1573
      this.logger.error('Could not get processing deposits of user:', error);
×
1574
      res.status(500).json('Internal server error.');
×
1575
    }
1576
  }
1577

1578
  /**
1579
   * GET /users/{id}/transactions/report
1580
   * @summary Get transaction report for the given user
1581
   * @operationId getUsersTransactionsReport
1582
   * @tags users - Operations of user controller
1583
   * @param {integer} id.path.required - The id of the user to get the transaction report from
1584
   * @security JWT
1585
   * @return {Array.<TransactionReportResponse>} 200 - The transaction report of the user
1586
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1587
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1588
   * @param {integer} fromId.query - From-user for selected transactions
1589
   * @param {integer} toId.query - To-user for selected transactions
1590
   * @param {boolean} exclusiveToId.query - If all sub-transactions should be to the toId user, default true
1591
   * @deprecated - Use /users/{id}/transactions/sales/report or /users/{id}/transactions/purchases/report instead
1592
   * @return {string} 404 - User not found error.
1593
   */
1594
  public async getUsersTransactionsReport(req: RequestWithToken, res: Response): Promise<void> {
1595
    const parameters = req.params;
6✔
1596
    this.logger.trace('Get transaction report for user ', req.params.id, ' by user', req.token.user);
6✔
1597

1598
    let filters;
1599
    try {
6✔
1600
      filters = parseGetTransactionsFilters(req);
6✔
1601
    } catch (e) {
1602
      res.status(400).json(e.message);
1✔
1603
      return;
1✔
1604
    }
1605

1606
    try {
5✔
1607
      if ((filters.toId !== undefined && filters.fromId !== undefined) || (filters.toId === undefined && filters.fromId === undefined)) {
5✔
1608
        res.status(400).json('Need to provide either a toId or a fromId.');
2✔
1609
        return;
2✔
1610
      }
1611

1612
      const id = parseInt(parameters.id, 10);
3✔
1613

1614
      const user = await User.findOne({ where: { id } });
3✔
1615
      if (user == null) {
3✔
1616
        res.status(404).json('Unknown user ID.');
1✔
1617
        return;
1✔
1618
      }
1619

1620
      const report = await (new TransactionService()).getTransactionReportResponse(filters);
2✔
1621
      res.status(200).json(report);
2✔
1622
    } catch (e) {
1623
      res.status(500).send();
×
1624
      this.logger.error(e);
×
1625
    }
1626
  }
1627

1628
  /**
1629
   * POST /users/{id}/fines/waive
1630
   * @summary Waive all given user's fines
1631
   * @tags users - Operations of user controller
1632
   * @param {integer} id.path.required - The id of the user
1633
   * @param {WaiveFinesRequest} request.body
1634
   * Optional body, see https://github.com/GEWIS/sudosos-backend/pull/344
1635
   * @operationId waiveUserFines
1636
   * @security JWT
1637
   * @return 204 - Success
1638
   * @return {string} 400 - User has no fines.
1639
   * @return {string} 404 - User not found error.
1640
   */
1641
  public async waiveUserFines(req: RequestWithToken, res: Response): Promise<void> {
1642
    const { id: rawId } = req.params;
9✔
1643
    const body = req.body as WaiveFinesRequest;
9✔
1644
    this.logger.trace('Waive fines', body, 'of user', rawId, 'by', req.token.user);
9✔
1645

1646
    try {
9✔
1647
      const id = parseInt(rawId, 10);
9✔
1648

1649
      const user = await User.findOne({ where: { id }, relations: { currentFines: { fines: true } } });
9✔
1650
      if (user == null) {
9✔
1651
        res.status(404).json('Unknown user ID.');
1✔
1652
        return;
1✔
1653
      }
1654
      if (user.currentFines == null) {
8✔
1655
        res.status(400).json('User has no fines.');
1✔
1656
        return;
1✔
1657
      }
1658

1659
      const totalAmountOfFines = user.currentFines!.fines.reduce((total, f) => total.add(f.amount), Dinero());
14✔
1660
      // Backwards compatibility with old version, where you could only waive all user's fines
1661
      const amountToWaive = body?.amount ?? totalAmountOfFines.toObject();
7✔
1662
      if (amountToWaive.amount <= 0) {
7✔
1663
        res.status(400).json('Amount to waive cannot be zero or negative.');
2✔
1664
        return;
2✔
1665
      }
1666
      if (amountToWaive.amount > totalAmountOfFines.getAmount()) {
5✔
1667
        res.status(400).json('Amount to waive cannot be more than the total amount of fines.');
1✔
1668
        return;
1✔
1669
      }
1670

1671
      await new DebtorService().waiveFines(id, { amount: amountToWaive } as WaiveFinesParams);
4✔
1672
      res.status(204).send();
4✔
1673
    } catch (e) {
1674
      res.status(500).send();
×
1675
      this.logger.error(e);
×
1676
    }
1677
  }
1678

1679
  /**
1680
   * DELETE /users/{id}/roles/{roleId}
1681
   * @summary Deletes a role from a user
1682
   * @tags users - Operations of user controller
1683
   * @param {integer} id.path.required - The id of the user
1684
   * @param {integer} roleId.path.required - The id of the role
1685
   * @operationId deleteUserRole
1686
   * @security JWT
1687
   * @return 204 - Success
1688
   */
1689
  public async deleteUserRole(req: RequestWithToken, res: Response): Promise<void> {
1690
    const { id: rawUserId, roleId: rawRoleId } = req.params;
4✔
1691

1692
    const userId = parseInt(rawUserId, 10);
4✔
1693
    const roleId = parseInt(rawRoleId, 10);
4✔
1694

1695
    const user = await User.findOne({ where: { id: userId } });
4✔
1696
    if (!user) {
4✔
1697
      res.status(404).json('user not found');
1✔
1698
      return;
1✔
1699
    }
1700
    const role = await Role.findOne({ where: { id: roleId } });
3✔
1701
    if (!role) {
3✔
1702
      res.status(404).json('role not found');
1✔
1703
      return;
1✔
1704
    }
1705

1706
    await UserService.deleteUserRole(user, role);
2✔
1707
    res.status(204).send();
2✔
1708
  }
1709

1710
  /**
1711
   * POST /users/{id}/roles
1712
   * @summary Adds a role to a user
1713
   * @tags users - Operations of user controller
1714
   * @param {integer} id.path.required - The id of the user
1715
   * @param {AddRoleRequest} request.body.required
1716
   * @operationId addUserRole
1717
   * @security JWT
1718
   * @return 204 - Success
1719
   */
1720
  public async addUserRole(req: RequestWithToken, res: Response): Promise<void> {
1721
    const { id: rawUserId } = req.params;
2✔
1722
    const userId = parseInt(rawUserId, 10);
2✔
1723
    const { roleId } = req.body as AddRoleRequest;
2✔
1724

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

1736
    await UserService.addUserRole(user, role);
1✔
1737
    res.status(204).send();
1✔
1738
  }
1739

1740
  /**
1741
   * GET /users/{id}/wrapped
1742
   * @summary Get wrapped for a user
1743
   * @operationId getWrapped
1744
   * @tags users - Operations of user controller
1745
   * @param {integer} id.path.required - The id of the user
1746
   * @security JWT
1747
   * @return {WrappedResponse} 200 - The requested user's wrapped
1748
   * @return {string} 404 - Wrapped for user not found error
1749
   * @return {string} 500 - Internal server error
1750
   */
1751
  private async getUserWrapped(req: RequestWithToken, res: Response): Promise<void> {
1752
    const { id: rawUserId } = req.params;
4✔
1753

1754
    try {
4✔
1755
      const userId = parseInt(rawUserId, 10);
4✔
1756
    
1757
      const wrappedRes = await new WrappedService().getWrappedForUser(userId);
4✔
1758
      if (wrappedRes == null) {
4✔
1759
        res.status(404).json('Wrapped not found for user');
1✔
1760
        return;
1✔
1761
      }
1762

1763
      res.json(WrappedService.asWrappedResponse(wrappedRes));
3✔
1764
    } catch (error) {
1765
      res.status(500).json({ message: 'Internal server error' });
×
1766
    }
1767
  }
1768

1769
  /**
1770
   * POST /users/{id}/wrapped
1771
   * @summary Recompute wrapped for a user
1772
   * @operationId updateWrapped
1773
   * @tags users - Operations of user controller
1774
   * @param {integer} id.path.required - The id of the user
1775
   * @security JWT
1776
   * @return {WrappedResponse} 200 - The requested user's wrapped
1777
   * @return {string} 500 - Internal server error
1778
   */
1779
  private async computedWrapped(req: RequestWithToken, res: Response): Promise<void> {
1780
    const { id: rawUserId } = req.params;
2✔
1781

1782
    try {
2✔
1783
      const userId = parseInt(rawUserId, 10);
2✔
1784

1785
      res.json(await new WrappedService().updateWrapped({ ids: [userId] }));
2✔
1786
    } catch (error) {
1787
      res.status(500).json({ message: 'Internal server error' });
×
1788
    }
1789
  }
1790

1791
  /**
1792
   * GET /users/{id}/settings
1793
   * @summary Get all user settings
1794
   * @operationId getUserSettings
1795
   * @tags users - Operations of user controller
1796
   * @param {integer} id.path.required - The id of the user
1797
   * @security JWT
1798
   * @return {UserSettingsResponse} 200 - The user's settings
1799
   * @return {string} 404 - User not found
1800
   * @return {string} 500 - Internal server error
1801
   */
1802
  private async getUserSettings(req: RequestWithToken, res: Response): Promise<void> {
1803
    const { id: rawUserId } = req.params;
5✔
1804

1805
    try {
5✔
1806
      const userId = parseInt(rawUserId, 10);
5✔
1807
      
1808
      const user = await User.findOne({ where: { id: userId } });
5✔
1809
      if (!user) {
5✔
1810
        res.status(404).json('User not found.');
1✔
1811
        return;
1✔
1812
      }
1813

1814
      const store = new UserSettingsStore();
4✔
1815
      const settings = await store.getAllSettings(userId);
4✔
1816
      const response = UserSettingsStore.toResponse(settings);
4✔
1817
      res.json(response);
4✔
1818
    } catch (error) {
1819
      this.logger.error('Could not get user settings:', error);
×
1820
      res.status(500).json('Internal server error.');
×
1821
    }
1822
  }
1823

1824
  /**
1825
   * PATCH /users/{id}/settings
1826
   * @summary Update user settings
1827
   * @operationId patchUserSettings
1828
   * @tags users - Operations of user controller
1829
   * @param {integer} id.path.required - The id of the user
1830
   * @param {PatchUserSettingsRequest} request.body.required - The settings to update
1831
   * @security JWT
1832
   * @return {UserSettingsResponse} 200 - The updated user settings
1833
   * @return {string} 404 - User not found
1834
   * @return {string} 500 - Internal server error
1835
   */
1836
  private async patchUserSettings(req: RequestWithToken, res: Response): Promise<void> {
1837
    const { id: rawUserId } = req.params;
10✔
1838
    const body = req.body as PatchUserSettingsRequest;
10✔
1839

1840
    try {
10✔
1841
      const userId = parseInt(rawUserId, 10);
10✔
1842
      
1843
      const user = await User.findOne({ where: { id: userId } });
10✔
1844
      if (!user) {
10✔
1845
        res.status(404).json('User not found.');
1✔
1846
        return;
1✔
1847
      }
1848

1849
      const store = new UserSettingsStore();
9✔
1850
      await store.setSettings(userId, body);
9✔
1851

1852
      const settings = await store.getAllSettings(userId);
9✔
1853

1854
      res.json(UserSettingsStore.toResponse(settings));
9✔
1855
    } catch (error) {
1856
      this.logger.error('Could not update user settings:', error);
×
1857
      res.status(500).json('Internal server error.');
×
1858
    }
1859
  }
1860

1861
  /**
1862
   * PATCH /users/{id}/usertype
1863
   * @summary Update user type
1864
   * @operationId patchUserType
1865
   * @tags users - Operations of user controller
1866
   * @param {integer} id.path.required - The id of the user
1867
   * @param {PatchUserTypeRequest} request.body.required - The user type to update to
1868
   * @security JWT
1869
   * @return {UserResponse} 200 - The updated user
1870
   * @return {string} 404 - User not found
1871
   * @return {string} 400 - Bad request
1872
   * @return {string} 409 - Conflict error
1873
   * @return {string} 422 - Unprocessable entity
1874
   * @return {string} 500 - Internal server error
1875
   */
1876
  private async patchUserType(req: RequestWithToken, res: Response): Promise<void> {
1877
    const { id: rawUserId } = req.params;
5✔
1878
    const body = req.body as PatchUserTypeRequest;
5✔
1879

1880
    try {
5✔
1881
      const userId = parseInt(rawUserId, 10);
5✔
1882
      const userOptions = UserService.getOptions({ id: userId });
5✔
1883
      const user = await User.findOne(userOptions);
5✔
1884
      if (!user) {
5✔
1885
        res.status(404).json('User not found.');
1✔
1886
        return;
1✔
1887
      }
1888

1889
      const allowedTypes = [UserType.MEMBER, UserType.LOCAL_USER];
4✔
1890

1891
      if (!allowedTypes.includes(user.type)) {
4✔
1892
        res.status(422).json('It is not possible to change the user type for users of this type.');
1✔
1893
        return;
1✔
1894
      }
1895

1896
      if (!allowedTypes.includes(body.userType)) {
3✔
1897
        res.status(422).json(`User type can only be changed to [${allowedTypes}].`);
1✔
1898
        return;
1✔
1899
      }
1900

1901
      if (body.userType === user.type) {
2✔
1902
        res.status(409).json('User is already of this type.');
1✔
1903
        return;
1✔
1904
      }
1905

1906
      if (!user.email) {
1!
1907
        res.status(400).json('Cannot change user type of user without email.');
×
1908
        return;
×
1909
      }
1910

1911
      if (body.userType === UserType.MEMBER && !user.memberUser) {
1!
1912
        res.status(400).json('Cannot change to MEMBER since no memberId is associated to this user.');
×
1913
        return;
×
1914
      }
1915

1916
      await UserService.updateUserType(user, body.userType);
1✔
1917
      const updatedUser = await UserService.getSingleUser(userId);
1✔
1918
      res.status(200).json(asUserResponse(updatedUser, true));
1✔
1919
    } catch (e) {
1920
      res.status(500).send('Internal server error.');
×
1921
      this.logger.error(e);
×
1922
    }
1923
  }
1924
}
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