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

GEWIS / sudosos-backend / 22626178559

03 Mar 2026 01:55PM UTC coverage: 89.455% (-0.001%) from 89.456%
22626178559

push

github

web-flow
test: fix user controller test case (#772)

1762 of 2134 branches covered (82.57%)

Branch coverage included in aggregate %.

9062 of 9966 relevant lines covered (90.93%)

980.33 hits per line

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

85.54
/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 } 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
  parseGetFinancialMutationsFilters,
53
  parseGetUsersFilters,
54
  UserFilterParameters,
55
} from '../service/user-service';
56
import { asFromAndTillDate, asNumber, asReturnFileType } from '../helpers/validators';
2✔
57
import { verifyCreateUserRequest } from './request/validators/user-request-spec';
2✔
58
import userTokenInOrgan from '../helpers/token-helper';
2✔
59
import { parseUserToResponse } from '../helpers/revision-to-response';
2✔
60
import { AcceptTosRequest } from './request/accept-tos-request';
61
import PinAuthenticator from '../entity/authenticator/pin-authenticator';
2✔
62
import LocalAuthenticator from '../entity/authenticator/local-authenticator';
2✔
63
import UpdateLocalRequest from './request/update-local-request';
64
import verifyUpdateLocalRequest from './request/validators/update-local-request-spec';
2✔
65
import StripeService from '../service/stripe-service';
2✔
66
import verifyUpdateNfcRequest from './request/validators/update-nfc-request-spec';
2✔
67
import UpdateNfcRequest from './request/update-nfc-request';
68
import NfcAuthenticator from '../entity/authenticator/nfc-authenticator';
2✔
69
import KeyAuthenticator from '../entity/authenticator/key-authenticator';
2✔
70
import UpdateKeyResponse from './response/update-key-response';
71
import { randomBytes } from 'crypto';
2✔
72
import DebtorService, { WaiveFinesParams } from '../service/debtor-service';
2✔
73
import ReportService, { BuyerReportService, SalesReportService } from '../service/report-service';
2✔
74
import { ReturnFileType, UserReportParametersType } from 'pdf-generator-client';
2✔
75
import { reportPDFhelper } from '../helpers/express-pdf';
2✔
76
import { PdfError } from '../errors';
2✔
77
import { WaiveFinesRequest } from './request/debtor-request';
78
import Dinero from 'dinero.js';
2✔
79
import Role from '../entity/rbac/role';
2✔
80
import WrappedService from '../service/wrapped-service';
2✔
81
import UserSettingsStore from '../user-settings/user-settings-store';
2✔
82
import { PatchUserSettingsRequest } from './request/user-request';
83

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

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

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

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

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

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

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

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

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

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

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

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

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

486
    try {
4✔
487
      req.query.type = userType;
4✔
488
      await this.getAllUsers(req, res);
4✔
489
    } catch (error) {
490
      this.logger.error('Could not get users:', error);
×
491
      res.status(500).json('Internal server error.');
×
492
    }
493
  }
494

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

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

522
      const validation = await verifyUpdatePinRequest(updatePinRequest);
2✔
523
      if (isFail(validation)) {
2✔
524
        res.status(400).json(validation.fail.value);
1✔
525
        return;
1✔
526
      }
527

528
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
529
        updatePinRequest.pin.toString(), PinAuthenticator);
530
      res.status(204).json();
1✔
531
    } catch (error) {
532
      this.logger.error('Could not update pin:', error);
×
533
      res.status(500).json('Internal server error.');
×
534
    }
535
  }
536

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

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

564
      const validation = await verifyUpdateNfcRequest(updateNfcRequest);
9✔
565
      if (isFail(validation)) {
9✔
566
        res.status(400).json(validation.fail.value);
2✔
567
        return;
2✔
568
      }
569

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

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

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

604
      if (await NfcAuthenticator.count({ where: { userId: parseInt(parameters.id, 10) } }) == 0) {
6✔
605
        res.status(403).json('No saved nfc');
3✔
606
        return;
3✔
607
      }
608

609
      await NfcAuthenticator.delete(parseInt(parameters.id, 10));
3✔
610
      res.status(204).json();
3✔
611
    } catch (error) {
612
      this.logger.error('Could not update NFC:', error);
×
613
      res.status(500).json('Internal server error.');
×
614
    }
615
  }
616

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

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

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

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

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

677

678
      await KeyAuthenticator.delete(parseInt(params.id, 10));
1✔
679
      res.status(204).json();
1✔
680
    } catch (error) {
681
      this.logger.error('Could not delete key:', error);
×
682
      res.status(500).json('Internal server error.');
×
683
    }
684
  }
685

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

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

714
      const validation = await verifyUpdateLocalRequest(updateLocalRequest);
2✔
715
      if (isFail(validation)) {
2✔
716
        res.status(400).json(validation.fail.value);
1✔
717
        return;
1✔
718
      }
719

720
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
721
        updateLocalRequest.password, LocalAuthenticator);
722
      res.status(204).json();
1✔
723
    } catch (error) {
724
      this.logger.error('Could not update local password:', error);
×
725
      res.status(500).json('Internal server error.');
×
726
    }
727
  }
728

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

746
    let take;
747
    let skip;
748
    try {
4✔
749
      const pagination = parseRequestPagination(req);
4✔
750
      take = pagination.take;
4✔
751
      skip = pagination.skip;
4✔
752
    } catch (e) {
753
      res.status(400).send(e.message);
×
754
      return;
×
755
    }
756

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

767
      if (user.type !== UserType.ORGAN) {
3✔
768
        res.status(400).json('User is not of type Organ');
1✔
769
        return;
1✔
770
      }
771

772
      const members = await UserService.getUsers({ organId }, { take, skip });
2✔
773
      if (!await this.canSeeEmail(req, UserController.getRelation(req))) {
2!
774
        members.records.forEach((u) => { u.email = undefined; });
×
775
      }
776
      res.status(200).json(members);
2✔
777
    } catch (error) {
778
      this.logger.error('Could not get organ members:', error);
×
779
      res.status(500).json('Internal server error.');
×
780
    }
781
  }
782

783
  /**
784
   * GET /users/{id}
785
   * @summary Get an individual user
786
   * @operationId getIndividualUser
787
   * @tags users - Operations of user controller
788
   * @param {integer} id.path.required - userID
789
   * @security JWT
790
   * @return {UserResponse} 200 - Individual user
791
   * @return {string} 404 - Nonexistent user id
792
   */
793
  public async getIndividualUser(req: RequestWithToken, res: Response): Promise<void> {
794
    const parameters = req.params;
10✔
795
    this.logger.trace('Get individual user', parameters, 'by user', req.token.user);
10✔
796

797
    try {
10✔
798
      // Get the user object if it exists
799
      const user = await UserService.getSingleUser(asNumber(parameters.id));
10✔
800
      // If it does not exist, return a 404 error
801
      if (user == null) {
10✔
802
        res.status(404).json('Unknown user ID.');
3✔
803
        return;
3✔
804
      }
805

806
      if (!await this.canSeeEmail(req, UserController.getRelation(req))) {
7✔
807
        user.email = undefined;
1✔
808
      }
809
      res.status(200).json(user);
7✔
810
    } catch (error) {
811
      this.logger.error('Could not get individual user:', error);
×
812
      res.status(500).json('Internal server error.');
×
813
    }
814
  }
815

816
  /**
817
   * POST /users
818
   * @summary Create a new user
819
   * @operationId createUser
820
   * @tags users - Operations of user controller
821
   * @param {CreateUserRequest} request.body.required -
822
   * The user which should be created
823
   * @security JWT
824
   * @return {UserResponse} 200 - New user
825
   * @return {string} 400 - Bad request
826
   */
827
  // eslint-disable-next-line class-methods-use-this
828
  public async createUser(req: RequestWithToken, res: Response): Promise<void> {
829
    const body = req.body as CreateUserRequest;
7✔
830
    this.logger.trace('Create user', body, 'by user', req.token.user);
7✔
831

832
    try {
7✔
833
      const validation = await verifyCreateUserRequest(body);
7✔
834
      if (isFail(validation)) {
7✔
835
        res.status(400).json(validation.fail.value);
3✔
836
        return;
3✔
837
      }
838

839
      const user = await UserService.createUser(body);
4✔
840
      res.status(201).json(user);
4✔
841
    } catch (error) {
842
      this.logger.error('Could not create user:', error);
×
843
      res.status(500).json('Internal server error.');
×
844
    }
845
  }
846

847
  /**
848
   * PATCH /users/{id}
849
   * @summary Update a user
850
   * @operationId updateUser
851
   * @tags users - Operations of user controller
852
   * @param {integer} id.path.required - The id of the user
853
   * @param {UpdateUserRequest} request.body.required - The user which should be updated
854
   * @security JWT
855
   * @return {UserResponse} 200 - New user
856
   * @return {string} 400 - Bad request
857
   */
858
  public async updateUser(req: RequestWithToken, res: Response): Promise<void> {
859
    const body = req.body as UpdateUserRequest;
13✔
860
    const parameters = req.params;
13✔
861
    this.logger.trace('Update user', parameters.id, 'with', body, 'by user', req.token.user);
13✔
862

863
    if (body.firstName !== undefined && body.firstName.length === 0) {
13✔
864
      res.status(400).json('firstName cannot be empty');
1✔
865
      return;
1✔
866
    }
867
    if (body.firstName !== undefined && body.firstName.length > 64) {
12✔
868
      res.status(400).json('firstName too long');
1✔
869
      return;
1✔
870
    }
871
    if (body.lastName !== undefined && body.lastName.length > 64) {
11✔
872
      res.status(400).json('lastName too long');
1✔
873
      return;
1✔
874
    }
875
    if (body.nickname !== undefined && body.nickname.length > 64) {
10✔
876
      res.status(400).json('nickname too long');
1✔
877
      return;
1✔
878
    }
879
    if (body.nickname === '') body.nickname = null;
9✔
880

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

891
      user = {
9✔
892
        ...body,
893
      } as User;
894
      await User.update(parameters.id, user);
9✔
895
      res.status(200).json(
9✔
896
        await UserService.getSingleUser(asNumber(parameters.id)),
897
      );
898
    } catch (error) {
899
      this.logger.error('Could not update user:', error);
×
900
      res.status(500).json('Internal server error.');
×
901
    }
902
  }
903

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

918
    if (req.token.user.id === parseInt(parameters.id, 10)) {
5✔
919
      res.status(400).json('Cannot delete yourself');
1✔
920
      return;
1✔
921
    }
922

923
    try {
4✔
924
      const id = parseInt(parameters.id, 10);
4✔
925
      // Get the user object if it exists
926
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
927
      // If it does not exist, return a 404 error
928
      if (user == null) {
4✔
929
        res.status(404).json('Unknown user ID.');
2✔
930
        return;
2✔
931
      }
932

933
      user.deleted = true;
2✔
934
      await user.save();
2✔
935
      res.status(204).json('User deleted');
2✔
936
    } catch (error) {
937
      this.logger.error('Could not create product:', error);
×
938
      res.status(500).json('Internal server error.');
×
939
    }
940
  }
941

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

956
    try {
3✔
957
      const nfcCode = String(parameters.nfcCode);
3✔
958
      const nfc = await NfcAuthenticator.findOne({ where: { nfcCode } });
3✔
959

960
      if (nfc === null) {
3✔
961
        res.status(404).json('Unknown nfc code');
1✔
962
        return;
1✔
963
      }
964

965
      const userResponse = parseUserToResponse(nfc.user);
2✔
966
      if (!await this.canSeeEmail(req, 'all')) {
2!
967
        userResponse.email = undefined;
×
968
      }
969
      res.status(200).json(userResponse);
2✔
970
    } catch (error) {
971
      this.logger.error('Could not find user using nfc:', error);
×
972
      res.status(500).json('Internal server error.');
×
973
    }
974
  }
975

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

989
    const { id } = req.token.user;
3✔
990
    const body = req.body as AcceptTosRequest;
3✔
991

992
    try {
3✔
993
      const user = await UserService.getSingleUser(id);
3✔
994
      if (user == null) {
3!
995
        res.status(404).json('User not found.');
×
996
        return;
×
997
      }
998

999
      const success = await UserService.acceptToS(id, body);
3✔
1000
      if (!success) {
3✔
1001
        res.status(400).json('User already accepted ToS.');
1✔
1002
        return;
1✔
1003
      }
1004

1005
      res.status(204).json();
2✔
1006
      return;
2✔
1007
    } catch (error) {
1008
      this.logger.error('Could not accept ToS for user:', error);
×
1009
      res.status(500).json('Internal server error.');
×
1010
    }
1011
  }
1012

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

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

1039
    // Handle request
1040
    try {
4✔
1041
      const id = parseInt(parameters.id, 10);
4✔
1042
      const owner = await User.findOne({ where: { id, deleted: false } });
4✔
1043
      if (owner == null) {
4✔
1044
        res.status(404).json({});
1✔
1045
        return;
1✔
1046
      }
1047

1048
      const products = await ProductService.getProducts({}, { take, skip }, owner);
3✔
1049
      res.json(products);
3✔
1050
    } catch (error) {
1051
      this.logger.error('Could not return all products:', error);
×
1052
      res.status(500).json('Internal server error.');
×
1053
    }
1054
  }
1055

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

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

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

1094
      const containers = (await ContainerService
3✔
1095
        .getContainers({}, { take, skip }, user));
1096
      res.json(containers);
3✔
1097
    } catch (error) {
1098
      this.logger.error('Could not return containers:', error);
×
1099
      res.status(500).json('Internal server error.');
×
1100
    }
1101
  }
1102

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

1120
    let take;
1121
    let skip;
1122
    try {
4✔
1123
      const pagination = parseRequestPagination(req);
4✔
1124
      take = pagination.take;
4✔
1125
      skip = pagination.skip;
4✔
1126
    } catch (e) {
1127
      res.status(400).send(e.message);
×
1128
      return;
×
1129
    }
1130

1131
    // handle request
1132
    try {
4✔
1133
      // Get the user object if it exists
1134
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1135
      // If it does not exist, return a 404 error
1136
      if (user == null) {
4✔
1137
        res.status(404).json('Unknown user ID.');
1✔
1138
        return;
1✔
1139
      }
1140

1141
      const pointsOfSale = (await PointOfSaleService
3✔
1142
        .getPointsOfSale({}, { take, skip }, user));
1143
      res.json(pointsOfSale);
3✔
1144
    } catch (error) {
1145
      this.logger.error('Could not return point of sale:', error);
×
1146
      res.status(500).json('Internal server error.');
×
1147
    }
1148
  }
1149

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

1175
    // Parse the filters given in the query parameters. If there are any issues,
1176
    // the parse method will throw an exception. We will then return a 400 error.
1177
    let filters;
1178
    try {
4✔
1179
      filters = parseGetTransactionsFilters(req);
4✔
1180
    } catch (e) {
1181
      res.status(400).json(e.message);
×
1182
      return;
×
1183
    }
1184

1185
    let take;
1186
    let skip;
1187
    try {
4✔
1188
      const pagination = parseRequestPagination(req);
4✔
1189
      take = pagination.take;
4✔
1190
      skip = pagination.skip;
4✔
1191
    } catch (e) {
1192
      res.status(400).send(e.message);
×
1193
      return;
×
1194
    }
1195

1196
    try {
4✔
1197
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
4✔
1198
      if (user == null) {
4✔
1199
        res.status(404).json({});
1✔
1200
        return;
1✔
1201
      }
1202
      const transactions = await new TransactionService().getTransactions(filters, { take, skip }, user);
3✔
1203

1204
      res.status(200).json(transactions);
3✔
1205
    } catch (error) {
1206
      this.logger.error('Could not return all transactions:', error);
×
1207
      res.status(500).json('Internal server error.');
×
1208
    }
1209
  }
1210

1211
  /**
1212
   * GET /users/{id}/transactions/sales/report
1213
   * @summary Get sales report for the given user
1214
   * @operationId getUsersSalesReport
1215
   * @tags users - Operations of user controller
1216
   * @param {integer} id.path.required - The id of the user to get the sales report for
1217
   * @security JWT
1218
   * @param {string} fromDate.query.required - Start date for selected sales (inclusive)
1219
   * @param {string} tillDate.query.required - End date for selected sales (exclusive)
1220
   * @return {ReportResponse} 200 - The sales report of the user
1221
   * @return {string} 400 - Validation error
1222
   * @return {string} 404 - User not found error.
1223
   */
1224
  public async getUsersSalesReport(req: RequestWithToken, res: Response): Promise<void> {
1225
    const { id } = req.params;
7✔
1226
    this.logger.trace('Get sales report for user ', id, ' by user', req.token.user);
7✔
1227

1228
    let filters: { fromDate: Date, tillDate: Date };
1229
    try {
7✔
1230
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1231
    } catch (e) {
1232
      res.status(400).json(e.message);
1✔
1233
      return;
1✔
1234
    }
1235

1236
    try {
6✔
1237
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
6✔
1238
      if (user == null) {
6✔
1239
        res.status(404).json('Unknown user ID.');
1✔
1240
        return;
1✔
1241
      }
1242

1243
      const report = await (new SalesReportService()).getReport({ ...filters, forId: user.id });
5✔
1244
      res.status(200).json(ReportService.reportToResponse(report));
5✔
1245
    } catch (error) {
1246
      this.logger.error('Could not get sales report:', error);
×
1247
      res.status(500).json('Internal server error.');
×
1248
    }
1249
  }
1250

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

1272
    let filters: { fromDate: Date, tillDate: Date };
1273
    let description: string;
1274
    let fileType: ReturnFileType;
1275
    try {
7✔
1276
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1277
      description = String(req.query.description);
4✔
1278
      fileType = asReturnFileType(req.query.fileType);
4✔
1279
    } catch (e) {
1280
      res.status(400).json(e.message);
4✔
1281
      return;
4✔
1282
    }
1283

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

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

1322
    let filters: { fromDate: Date, tillDate: Date };
1323
    let description: string;
1324
    let fileType: ReturnFileType;
1325
    try {
7✔
1326
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1327
      description = String(req.query.description);
4✔
1328
      fileType = asReturnFileType(req.query.fileType);
4✔
1329
    } catch (e) {
1330
      res.status(400).json(e.message);
4✔
1331
      return;
4✔
1332
    }
1333

1334
    try {
3✔
1335
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1336
      if (user == null) {
3✔
1337
        res.status(404).json('Unknown user ID.');
1✔
1338
        return;
1✔
1339
      }
1340
      const service = new BuyerReportService();
2✔
1341
      await (reportPDFhelper(res))(service, filters, description, user.id, UserReportParametersType.Purchases, fileType);
2✔
1342
    } catch (error) {
1343
      this.logger.error('Could not get sales report:', error);
1✔
1344
      if (error instanceof PdfError) {
1✔
1345
        res.status(502).json('PDF Generator service failed.');
1✔
1346
        return;
1✔
1347
      }
1348
      res.status(500).json('Internal server error.');
×
1349
    }
1350
  }
1351

1352
  /**
1353
   * GET /users/{id}/transactions/purchases/report
1354
   * @summary Get purchases report for the given user
1355
   * @operationId getUsersPurchasesReport
1356
   * @tags users - Operations of user controller
1357
   * @param {integer} id.path.required - The id of the user to get the purchases report for
1358
   * @security JWT
1359
   * @param {string} fromDate.query.required - Start date for selected purchases (inclusive)
1360
   * @param {string} tillDate.query.required - End date for selected purchases (exclusive)
1361
   * @return {ReportResponse} 200 - The purchases report of the user
1362
   * @return {string} 400 - Validation error
1363
   * @return {string} 404 - User not found error.
1364
   */
1365
  public async getUsersPurchasesReport(req: RequestWithToken, res: Response): Promise<void> {
1366
    const { id } = req.params;
4✔
1367
    this.logger.trace('Get purchases report for user ', id, ' by user', req.token.user);
4✔
1368

1369
    let filters: { fromDate: Date, tillDate: Date };
1370
    try {
4✔
1371
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
4✔
1372
    } catch (e) {
1373
      res.status(400).json(e.message);
1✔
1374
      return;
1✔
1375
    }
1376

1377
    try {
3✔
1378
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1379
      if (user == null) {
3✔
1380
        res.status(404).json('Unknown user ID.');
1✔
1381
        return;
1✔
1382
      }
1383

1384
      const report = await (new BuyerReportService()).getReport({ ...filters, forId: user.id });
2✔
1385
      res.status(200).json(ReportService.reportToResponse(report));
2✔
1386
    } catch (error) {
1387
      this.logger.error('Could not get sales report:', error);
×
1388
      res.status(500).json('Internal server error.');
×
1389
    }
1390
  }
1391

1392
  /**
1393
   * GET /users/{id}/transfers
1394
   * @summary Get transfers to or from an user.
1395
   * @operationId getUsersTransfers
1396
   * @tags users - Operations of user controller
1397
   * @param {integer} id.path.required - The id of the user that should be involved
1398
   * in all returned transfers
1399
   * @param {integer} take.query - How many transfers the endpoint should return
1400
   * @param {integer} skip.query - How many transfers should be skipped (for pagination)
1401
   * @param {integer} fromId.query - From-user for selected transfers
1402
   * @param {integer} toId.query - To-user for selected transfers
1403
   * @param {integer} id.query - ID of selected transfers
1404
   * @security JWT
1405
   * @return {PaginatedTransferResponse} 200 - List of transfers.
1406
   */
1407
  public async getUsersTransfers(req: RequestWithToken, res: Response): Promise<void> {
1408
    const { id } = req.params;
2✔
1409
    this.logger.trace("Get user's transfers", id, 'by user', req.token.user);
2✔
1410

1411
    // Parse the filters given in the query parameters. If there are any issues,
1412
    // the parse method will throw an exception. We will then return a 400 error.
1413
    let filters;
1414
    try {
2✔
1415
      filters = parseGetTransferFilters(req);
2✔
1416
    } catch (e) {
1417
      res.status(400).json(e.message);
×
1418
      return;
×
1419
    }
1420

1421
    let take;
1422
    let skip;
1423
    try {
2✔
1424
      const pagination = parseRequestPagination(req);
2✔
1425
      take = pagination.take;
2✔
1426
      skip = pagination.skip;
2✔
1427
    } catch (e) {
1428
      res.status(400).send(e.message);
×
1429
      return;
×
1430
    }
1431

1432
    // handle request
1433
    try {
2✔
1434
      // Get the user object if it exists
1435
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
2✔
1436
      // If it does not exist, return a 404 error
1437
      if (user == null) {
2!
1438
        res.status(404).json('Unknown user ID.');
×
1439
        return;
×
1440
      }
1441

1442
      const transfers = (await new TransferService().getTransfers(
2✔
1443
        { ...filters }, { take, skip }, user,
1444
      ));
1445
      res.json(transfers);
2✔
1446
    } catch (error) {
1447
      this.logger.error('Could not return user transfers', error);
×
1448
      res.status(500).json('Internal server error.');
×
1449
    }
1450
  }
1451

1452
  /**
1453
   * GET /users/{id}/roles
1454
   * @summary Get all roles assigned to the user.
1455
   * @operationId getUserRoles
1456
   * @tags users - Operations of user controller
1457
   * @param {integer} id.path.required - The id of the user to get the roles from
1458
   * @security JWT
1459
   * @return {Array.<RoleWithPermissionsResponse>} 200 - The roles of the user
1460
   * @return {string} 404 - User not found error.
1461
   */
1462
  public async getUserRoles(req: RequestWithToken, res: Response): Promise<void> {
1463
    const parameters = req.params;
3✔
1464
    this.logger.trace('Get roles of user', parameters, 'by user', req.token.user);
3✔
1465

1466
    try {
3✔
1467
      const id = parseInt(parameters.id, 10);
3✔
1468
      // Get the user object if it exists
1469
      const user = await User.findOne({ where: { id, deleted: false } });
3✔
1470
      // If it does not exist, return a 404 error
1471
      if (user == null) {
3✔
1472
        res.status(404).json('Unknown user ID.');
1✔
1473
        return;
1✔
1474
      }
1475

1476
      const rolesWithPermissions = await this.roleManager.getRoles(user, true);
2✔
1477
      const response = rolesWithPermissions.map((r) => RBACService.asRoleResponse(r));
2✔
1478
      res.status(200).json(response);
2✔
1479
    } catch (error) {
1480
      this.logger.error('Could not get roles of user:', error);
×
1481
      res.status(500).json('Internal server error.');
×
1482
    }
1483
  }
1484

1485
  /**
1486
   * GET /users/{id}/financialmutations
1487
   * @summary Get all financial mutations of a user (from or to).
1488
   * @operationId getUsersFinancialMutations
1489
   * @tags users - Operations of user controller
1490
   * @param {integer} id.path.required - The id of the user to get the mutations from
1491
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1492
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1493
   * @param {integer} take.query - How many transactions the endpoint should return
1494
   * @param {integer} skip.query - How many transactions should be skipped (for pagination)
1495
   * @security JWT
1496
   * @return {PaginatedFinancialMutationResponse} 200 - The financial mutations of the user
1497
   * @return {string} 404 - User not found error.
1498
   */
1499
  public async getUsersFinancialMutations(req: RequestWithToken, res: Response): Promise<void> {
1500
    const parameters = req.params;
4✔
1501
    this.logger.trace('Get financial mutations of user', parameters, 'by user', req.token.user);
4✔
1502

1503
    let filters;
1504
    let take;
1505
    let skip;
1506
    try {
4✔
1507
      filters = parseGetFinancialMutationsFilters(req);
4✔
1508
      const pagination = parseRequestPagination(req);
4✔
1509
      take = pagination.take;
4✔
1510
      skip = pagination.skip;
4✔
1511
    } catch (e) {
1512
      res.status(400).send(e.message);
×
1513
      return;
×
1514
    }
1515

1516
    try {
4✔
1517
      const id = parseInt(parameters.id, 10);
4✔
1518
      // Get the user object if it exists
1519
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
1520
      // If it does not exist, return a 404 error
1521
      if (user == null) {
4!
1522
        res.status(404).json('Unknown user ID.');
×
1523
        return;
×
1524
      }
1525

1526
      const mutations = await UserService.getUserFinancialMutations(user, filters, { take, skip });
4✔
1527
      res.status(200).json(mutations);
4✔
1528
    } catch (error) {
1529
      this.logger.error('Could not get financial mutations of user:', error);
×
1530
      res.status(500).json('Internal server error.');
×
1531
    }
1532
  }
1533

1534
  /**
1535
   * GET /users/{id}/deposits
1536
   * @summary Get all deposits of a user that are still being processed by Stripe
1537
   * @operationId getUsersProcessingDeposits
1538
   * @tags users - Operations of user controller
1539
   * @param {integer} id.path.required - The id of the user to get the deposits from
1540
   * @security JWT
1541
   * @return {Array.<RoleResponse>} 200 - The processing deposits of a user
1542
   * @return {string} 404 - User not found error.
1543
   */
1544
  public async getUsersProcessingDeposits(req: RequestWithToken, res: Response): Promise<void> {
1545
    const parameters = req.params;
2✔
1546
    this.logger.trace('Get users processing deposits from user', parameters.id);
2✔
1547

1548
    try {
2✔
1549
      const id = parseInt(parameters.id, 10);
2✔
1550

1551
      const user = await User.findOne({ where: { id } });
2✔
1552
      if (user == null) {
2✔
1553
        res.status(404).json('Unknown user ID.');
1✔
1554
        return;
1✔
1555
      }
1556

1557
      const deposits = await StripeService.getProcessingStripeDepositsFromUser(id);
1✔
1558
      res.status(200).json(deposits);
1✔
1559
    } catch (error) {
1560
      this.logger.error('Could not get processing deposits of user:', error);
×
1561
      res.status(500).json('Internal server error.');
×
1562
    }
1563
  }
1564

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

1585
    let filters;
1586
    try {
6✔
1587
      filters = parseGetTransactionsFilters(req);
6✔
1588
    } catch (e) {
1589
      res.status(400).json(e.message);
1✔
1590
      return;
1✔
1591
    }
1592

1593
    try {
5✔
1594
      if ((filters.toId !== undefined && filters.fromId !== undefined) || (filters.toId === undefined && filters.fromId === undefined)) {
5✔
1595
        res.status(400).json('Need to provide either a toId or a fromId.');
2✔
1596
        return;
2✔
1597
      }
1598

1599
      const id = parseInt(parameters.id, 10);
3✔
1600

1601
      const user = await User.findOne({ where: { id } });
3✔
1602
      if (user == null) {
3✔
1603
        res.status(404).json('Unknown user ID.');
1✔
1604
        return;
1✔
1605
      }
1606

1607
      const report = await (new TransactionService()).getTransactionReportResponse(filters);
2✔
1608
      res.status(200).json(report);
2✔
1609
    } catch (e) {
1610
      res.status(500).send();
×
1611
      this.logger.error(e);
×
1612
    }
1613
  }
1614

1615
  /**
1616
   * POST /users/{id}/fines/waive
1617
   * @summary Waive all given user's fines
1618
   * @tags users - Operations of user controller
1619
   * @param {integer} id.path.required - The id of the user
1620
   * @param {WaiveFinesRequest} request.body
1621
   * Optional body, see https://github.com/GEWIS/sudosos-backend/pull/344
1622
   * @operationId waiveUserFines
1623
   * @security JWT
1624
   * @return 204 - Success
1625
   * @return {string} 400 - User has no fines.
1626
   * @return {string} 404 - User not found error.
1627
   */
1628
  public async waiveUserFines(req: RequestWithToken, res: Response): Promise<void> {
1629
    const { id: rawId } = req.params;
9✔
1630
    const body = req.body as WaiveFinesRequest;
9✔
1631
    this.logger.trace('Waive fines', body, 'of user', rawId, 'by', req.token.user);
9✔
1632

1633
    try {
9✔
1634
      const id = parseInt(rawId, 10);
9✔
1635

1636
      const user = await User.findOne({ where: { id }, relations: { currentFines: { fines: true } } });
9✔
1637
      if (user == null) {
9✔
1638
        res.status(404).json('Unknown user ID.');
1✔
1639
        return;
1✔
1640
      }
1641
      if (user.currentFines == null) {
8✔
1642
        res.status(400).json('User has no fines.');
1✔
1643
        return;
1✔
1644
      }
1645

1646
      const totalAmountOfFines = user.currentFines!.fines.reduce((total, f) => total.add(f.amount), Dinero());
14✔
1647
      // Backwards compatibility with old version, where you could only waive all user's fines
1648
      const amountToWaive = body?.amount ?? totalAmountOfFines.toObject();
7✔
1649
      if (amountToWaive.amount <= 0) {
7✔
1650
        res.status(400).json('Amount to waive cannot be zero or negative.');
2✔
1651
        return;
2✔
1652
      }
1653
      if (amountToWaive.amount > totalAmountOfFines.getAmount()) {
5✔
1654
        res.status(400).json('Amount to waive cannot be more than the total amount of fines.');
1✔
1655
        return;
1✔
1656
      }
1657

1658
      await new DebtorService().waiveFines(id, { amount: amountToWaive } as WaiveFinesParams);
4✔
1659
      res.status(204).send();
4✔
1660
    } catch (e) {
1661
      res.status(500).send();
×
1662
      this.logger.error(e);
×
1663
    }
1664
  }
1665

1666
  /**
1667
   * DELETE /users/{id}/roles/{roleId}
1668
   * @summary Deletes a role from a user
1669
   * @tags users - Operations of user controller
1670
   * @param {integer} id.path.required - The id of the user
1671
   * @param {integer} roleId.path.required - The id of the role
1672
   * @operationId deleteUserRole
1673
   * @security JWT
1674
   * @return 204 - Success
1675
   */
1676
  public async deleteUserRole(req: RequestWithToken, res: Response): Promise<void> {
1677
    const { id: rawUserId, roleId: rawRoleId } = req.params;
4✔
1678

1679
    const userId = parseInt(rawUserId, 10);
4✔
1680
    const roleId = parseInt(rawRoleId, 10);
4✔
1681

1682
    const user = await User.findOne({ where: { id: userId } });
4✔
1683
    if (!user) {
4✔
1684
      res.status(404).json('user not found');
1✔
1685
      return;
1✔
1686
    }
1687
    const role = await Role.findOne({ where: { id: roleId } });
3✔
1688
    if (!role) {
3✔
1689
      res.status(404).json('role not found');
1✔
1690
      return;
1✔
1691
    }
1692

1693
    await UserService.deleteUserRole(user, role);
2✔
1694
    res.status(204).send();
2✔
1695
  }
1696

1697
  /**
1698
   * POST /users/{id}/roles
1699
   * @summary Adds a role to a user
1700
   * @tags users - Operations of user controller
1701
   * @param {integer} id.path.required - The id of the user
1702
   * @param {AddRoleRequest} request.body.required
1703
   * @operationId addUserRole
1704
   * @security JWT
1705
   * @return 204 - Success
1706
   */
1707
  public async addUserRole(req: RequestWithToken, res: Response): Promise<void> {
1708
    const { id: rawUserId } = req.params;
2✔
1709
    const userId = parseInt(rawUserId, 10);
2✔
1710
    const { roleId } = req.body as AddRoleRequest;
2✔
1711

1712
    const user = await User.findOne({ where: { id: userId } });
2✔
1713
    if (!user) {
2!
1714
      res.status(404).json('user not found');
×
1715
      return;
×
1716
    }
1717
    const role = await Role.findOne({ where: { id: roleId } });
2✔
1718
    if (!role) {
2✔
1719
      res.status(404).json('role not found');
1✔
1720
      return;
1✔
1721
    }
1722

1723
    await UserService.addUserRole(user, role);
1✔
1724
    res.status(204).send();
1✔
1725
  }
1726

1727
  /**
1728
   * GET /users/{id}/wrapped
1729
   * @summary Get wrapped for a user
1730
   * @operationId getWrapped
1731
   * @tags users - Operations of user controller
1732
   * @param {integer} id.path.required - The id of the user
1733
   * @security JWT
1734
   * @return {WrappedResponse} 200 - The requested user's wrapped
1735
   * @return {string} 404 - Wrapped for user not found error
1736
   * @return {string} 500 - Internal server error
1737
   */
1738
  private async getUserWrapped(req: RequestWithToken, res: Response): Promise<void> {
1739
    const { id: rawUserId } = req.params;
4✔
1740

1741
    try {
4✔
1742
      const userId = parseInt(rawUserId, 10);
4✔
1743
    
1744
      const wrappedRes = await new WrappedService().getWrappedForUser(userId);
4✔
1745
      if (wrappedRes == null) {
4✔
1746
        res.status(404).json('Wrapped not found for user');
1✔
1747
        return;
1✔
1748
      }
1749

1750
      res.json(WrappedService.asWrappedResponse(wrappedRes));
3✔
1751
    } catch (error) {
1752
      res.status(500).json({ message: 'Internal server error' });
×
1753
    }
1754
  }
1755

1756
  /**
1757
   * POST /users/{id}/wrapped
1758
   * @summary Recompute wrapped for a user
1759
   * @operationId updateWrapped
1760
   * @tags users - Operations of user controller
1761
   * @param {integer} id.path.required - The id of the user
1762
   * @security JWT
1763
   * @return {WrappedResponse} 200 - The requested user's wrapped
1764
   * @return {string} 500 - Internal server error
1765
   */
1766
  private async computedWrapped(req: RequestWithToken, res: Response): Promise<void> {
1767
    const { id: rawUserId } = req.params;
2✔
1768

1769
    try {
2✔
1770
      const userId = parseInt(rawUserId, 10);
2✔
1771

1772
      res.json(await new WrappedService().updateWrapped({ ids: [userId] }));
2✔
1773
    } catch (error) {
1774
      res.status(500).json({ message: 'Internal server error' });
×
1775
    }
1776
  }
1777

1778
  /**
1779
   * GET /users/{id}/settings
1780
   * @summary Get all user settings
1781
   * @operationId getUserSettings
1782
   * @tags users - Operations of user controller
1783
   * @param {integer} id.path.required - The id of the user
1784
   * @security JWT
1785
   * @return {UserSettingsResponse} 200 - The user's settings
1786
   * @return {string} 404 - User not found
1787
   * @return {string} 500 - Internal server error
1788
   */
1789
  private async getUserSettings(req: RequestWithToken, res: Response): Promise<void> {
1790
    const { id: rawUserId } = req.params;
5✔
1791

1792
    try {
5✔
1793
      const userId = parseInt(rawUserId, 10);
5✔
1794
      
1795
      const user = await User.findOne({ where: { id: userId } });
5✔
1796
      if (!user) {
5✔
1797
        res.status(404).json('User not found.');
1✔
1798
        return;
1✔
1799
      }
1800

1801
      const store = new UserSettingsStore();
4✔
1802
      const settings = await store.getAllSettings(userId);
4✔
1803
      const response = UserSettingsStore.toResponse(settings);
4✔
1804
      res.json(response);
4✔
1805
    } catch (error) {
1806
      this.logger.error('Could not get user settings:', error);
×
1807
      res.status(500).json('Internal server error.');
×
1808
    }
1809
  }
1810

1811
  /**
1812
   * PATCH /users/{id}/settings
1813
   * @summary Update user settings
1814
   * @operationId patchUserSettings
1815
   * @tags users - Operations of user controller
1816
   * @param {integer} id.path.required - The id of the user
1817
   * @param {PatchUserSettingsRequest} request.body.required - The settings to update
1818
   * @security JWT
1819
   * @return {UserSettingsResponse} 200 - The updated user settings
1820
   * @return {string} 404 - User not found
1821
   * @return {string} 500 - Internal server error
1822
   */
1823
  private async patchUserSettings(req: RequestWithToken, res: Response): Promise<void> {
1824
    const { id: rawUserId } = req.params;
10✔
1825
    const body = req.body as PatchUserSettingsRequest;
10✔
1826

1827
    try {
10✔
1828
      const userId = parseInt(rawUserId, 10);
10✔
1829
      
1830
      const user = await User.findOne({ where: { id: userId } });
10✔
1831
      if (!user) {
10✔
1832
        res.status(404).json('User not found.');
1✔
1833
        return;
1✔
1834
      }
1835

1836
      const store = new UserSettingsStore();
9✔
1837
      await store.setSettings(userId, body);
9✔
1838

1839
      const settings = await store.getAllSettings(userId);
9✔
1840

1841
      res.json(UserSettingsStore.toResponse(settings));
9✔
1842
    } catch (error) {
1843
      this.logger.error('Could not update user settings:', error);
×
1844
      res.status(500).json('Internal server error.');
×
1845
    }
1846
  }
1847

1848
  /**
1849
   * PATCH /users/{id}/usertype
1850
   * @summary Update user type
1851
   * @operationId patchUserType
1852
   * @tags users - Operations of user controller
1853
   * @param {integer} id.path.required - The id of the user
1854
   * @param {PatchUserTypeRequest} request.body.required - The user type to update to
1855
   * @security JWT
1856
   * @return {UserResponse} 200 - The updated user
1857
   * @return {string} 404 - User not found
1858
   * @return {string} 400 - Bad request
1859
   * @return {string} 409 - Conflict error
1860
   * @return {string} 422 - Unprocessable entity
1861
   * @return {string} 500 - Internal server error
1862
   */
1863
  private async patchUserType(req: RequestWithToken, res: Response): Promise<void> {
1864
    const { id: rawUserId } = req.params;
5✔
1865
    const body = req.body as PatchUserTypeRequest;
5✔
1866

1867
    try {
5✔
1868
      const userId = parseInt(rawUserId, 10);
5✔
1869
      const userOptions = UserService.getOptions({ id: userId });
5✔
1870
      const user = await User.findOne(userOptions);
5✔
1871
      if (!user) {
5✔
1872
        res.status(404).json('User not found.');
1✔
1873
        return;
1✔
1874
      }
1875

1876
      const allowedTypes = [UserType.MEMBER, UserType.LOCAL_USER];
4✔
1877

1878
      if (!allowedTypes.includes(user.type)) {
4✔
1879
        res.status(422).json('It is not possible to change the user type for users of this type.');
1✔
1880
        return;
1✔
1881
      }
1882

1883
      if (!allowedTypes.includes(body.userType)) {
3✔
1884
        res.status(422).json(`User type can only be changed to [${allowedTypes}].`);
1✔
1885
        return;
1✔
1886
      }
1887

1888
      if (body.userType === user.type) {
2✔
1889
        res.status(409).json('User is already of this type.');
1✔
1890
        return;
1✔
1891
      }
1892

1893
      if (!user.email) {
1!
1894
        res.status(400).json('Cannot change user type of user without email.');
×
1895
        return;
×
1896
      }
1897

1898
      if (body.userType === UserType.MEMBER && !user.memberUser) {
1!
1899
        res.status(400).json('Cannot change to MEMBER since no memberId is associated to this user.');
×
1900
        return;
×
1901
      }
1902

1903
      await UserService.updateUserType(user, body.userType);
1✔
1904
      res.status(200).json(
1✔
1905
        await UserService.getSingleUser(userId),
1906
      );
1907
    } catch (e) {
1908
      res.status(500).send('Internal server error.');
×
1909
      this.logger.error(e);
×
1910
    }
1911
  }
1912
}
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