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

GEWIS / sudosos-backend / 23108646033

15 Mar 2026 10:36AM UTC coverage: 89.306% (+0.1%) from 89.198%
23108646033

push

github

web-flow
refactor(seed): move seed entry point to cli/ to eliminate src/→test/ import (#787)

1790 of 2178 branches covered (82.19%)

Branch coverage included in aggregate %.

9167 of 10091 relevant lines covered (90.84%)

968.46 hits per line

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

85.79
/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 { maxPagination, 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
      '/recently-charged': {
154
        GET: {
155
          policy: async (req) => this.roleManager.can(
6✔
156
            req.token.roles, 'get', 'all', 'User', ['id', 'firstName', 'lastName'],
157
          ),
158
          handler: this.getRecentlyChargedUsers.bind(this),
159
        },
160
      },
161
      '/:id(\\d+)/authenticator/pin': {
162
        PUT: {
163
          body: { modelName: 'UpdatePinRequest' },
164
          policy: async (req) => this.roleManager.can(
4✔
165
            req.token.roles, 'update', UserController.getRelation(req), 'Authenticator', ['pin'],
166
          ),
167
          handler: this.updateUserPin.bind(this),
168
        },
169
      },
170
      '/:id(\\d+)/authenticator/nfc': {
171
        PUT: {
172
          body: { modelName: 'UpdateNfcRequest' },
173
          policy: async (req) => this.roleManager.can(
11✔
174
            req.token.roles, 'update', UserController.getRelation(req), 'Authenticator', ['nfcCode'],
175
          ),
176
          handler: this.updateUserNfc.bind(this),
177
        },
178
        DELETE: {
179
          policy: async (req) => this.roleManager.can(
9✔
180
            req.token.roles, 'delete', UserController.getRelation(req), 'Authenticator', [],
181
          ),
182
          handler: this.deleteUserNfc.bind(this),
183
        },
184
      },
185

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

396
  /**
397
   * Function to determine which credentials are needed to GET
398
   *    'all' if user is not connected to User
399
   *    'organ' if user is connected to User via organ
400
   *    'own' if user is connected to User
401
   * @param req
402
   * @return whether User is connected to used token
403
   */
404
  static getRelation(req: RequestWithToken): string {
405
    if (userTokenInOrgan(req, asNumber(req.params.id))) return 'organ';
194✔
406
    return req.params.id === req.token.user.id.toString() ? 'own' : 'all';
192✔
407
  }
408

409
  static getAttributes(req: RequestWithToken): string[] {
410
    const attributes: string[] = [];
15✔
411
    const body = req.body as BaseUserRequest;
15✔
412
    for (const key in body) {
15✔
413
      if (body.hasOwnProperty(key)) {
17✔
414
        attributes.push(key);
17✔
415
      }
416
    }
417
    return attributes;
15✔
418
  }
419

420
  /**
421
   * Returns whether the token in the request is allowed to see the email field
422
   * of a User with the given relation (own/organ/all).
423
   */
424
  private async canSeeEmail(req: RequestWithToken, relation: string): Promise<boolean> {
425
    return this.roleManager.can(req.token.roles, 'get', relation, 'User', ['email']);
27✔
426
  }
427

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

446
    let take;
447
    let skip;
448
    let filters: UserFilterParameters;
449
    try {
12✔
450
      const pagination = parseRequestPagination(req);
12✔
451
      filters = parseGetUsersFilters(req);
12✔
452
      take = pagination.take;
12✔
453
      skip = pagination.skip;
12✔
454
    } catch (e) {
455
      res.status(400).send(e.message);
×
456
      return;
×
457
    }
458

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

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

492
    // If it does not exist, return a 404 error
493
    const type = UserType[userType as keyof typeof UserType];
5✔
494
    if (!type || Number(userType)) {
5✔
495
      res.status(404).json('Unknown userType.');
1✔
496
      return;
1✔
497
    }
498

499
    try {
4✔
500
      req.query.type = userType;
4✔
501
      await this.getAllUsers(req, res);
4✔
502
    } catch (error) {
503
      this.logger.error('Could not get users:', error);
×
504
      res.status(500).json('Internal server error.');
×
505
    }
506
  }
507

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

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

535
      const validation = await verifyUpdatePinRequest(updatePinRequest);
2✔
536
      if (isFail(validation)) {
2✔
537
        res.status(400).json(validation.fail.value);
1✔
538
        return;
1✔
539
      }
540

541
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
542
        updatePinRequest.pin.toString(), PinAuthenticator);
543
      res.status(204).json();
1✔
544
    } catch (error) {
545
      this.logger.error('Could not update pin:', error);
×
546
      res.status(500).json('Internal server error.');
×
547
    }
548
  }
549

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

568
    try {
10✔
569
      // Get the user object if it exists
570
      const user = await User.findOne({ where: { id: parseInt(params.id, 10), deleted: false } });
10✔
571
      // If it does not exist, return a 404 error
572
      if (user == null) {
10✔
573
        res.status(404).json('Unknown user ID.');
1✔
574
        return;
1✔
575
      }
576

577
      const validation = await verifyUpdateNfcRequest(updateNfcRequest);
9✔
578
      if (isFail(validation)) {
9✔
579
        res.status(400).json(validation.fail.value);
2✔
580
        return;
2✔
581
      }
582

583
      await new AuthenticationService().setUserAuthenticationNfc(user,
7✔
584
        updateNfcRequest.nfcCode.toString(), NfcAuthenticator);
585
      res.status(204).json();
7✔
586
    } catch (error) {
587
      this.logger.error('Could not update NFC:', error);
×
588
      res.status(500).json('Internal server error.');
×
589
    }
590
  }
591

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

608
    try {
9✔
609
      // Get the user object if it exists
610
      const user = await User.findOne({ where: { id: parseInt(parameters.id, 10), deleted: false } });
9✔
611
      // If it does not exist, return a 404 error
612
      if (user == null) {
9✔
613
        res.status(404).json('Unknown user ID.');
3✔
614
        return;
3✔
615
      }
616

617
      if (await NfcAuthenticator.count({ where: { userId: parseInt(parameters.id, 10) } }) == 0) {
6✔
618
        res.status(403).json('No saved nfc');
3✔
619
        return;
3✔
620
      }
621

622
      await NfcAuthenticator.delete(parseInt(parameters.id, 10));
3✔
623
      res.status(204).json();
3✔
624
    } catch (error) {
625
      this.logger.error('Could not update NFC:', error);
×
626
      res.status(500).json('Internal server error.');
×
627
    }
628
  }
629

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

645
    try {
2✔
646
      const userId = parseInt(params.id, 10);
2✔
647
      // Get the user object if it exists
648
      const user = await User.findOne({ where: { id: userId, deleted: false } });
2✔
649
      // If it does not exist, return a 404 error
650
      if (user == null) {
2✔
651
        res.status(404).json('Unknown user ID.');
1✔
652
        return;
1✔
653
      }
654

655
      const generatedKey = randomBytes(128).toString('hex');
1✔
656
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
657
        generatedKey, KeyAuthenticator);
658
      const response = { key: generatedKey } as UpdateKeyResponse;
1✔
659
      res.status(200).json(response);
1✔
660
    } catch (error) {
661
      this.logger.error('Could not update key:', error);
×
662
      res.status(500).json('Internal server error.');
×
663
    }
664
  }
665

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

681
    try {
2✔
682
      // Get the user object if it exists
683
      const user = await User.findOne({ where: { id: parseInt(params.id, 10), deleted: false } });
2✔
684
      // If it does not exist, return a 404 error
685
      if (user == null) {
2✔
686
        res.status(404).json('Unknown user ID.');
1✔
687
        return;
1✔
688
      }
689

690

691
      await KeyAuthenticator.delete(parseInt(params.id, 10));
1✔
692
      res.status(204).json();
1✔
693
    } catch (error) {
694
      this.logger.error('Could not delete key:', error);
×
695
      res.status(500).json('Internal server error.');
×
696
    }
697
  }
698

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

717
    try {
3✔
718
      const id = Number.parseInt(parameters.id, 10);
3✔
719
      // Get the user object if it exists
720
      const user = await User.findOne({ where: { id, deleted: false } });
3✔
721
      // If it does not exist, return a 404 error
722
      if (user == null) {
3✔
723
        res.status(404).json('Unknown user ID.');
1✔
724
        return;
1✔
725
      }
726

727
      const validation = await verifyUpdateLocalRequest(updateLocalRequest);
2✔
728
      if (isFail(validation)) {
2✔
729
        res.status(400).json(validation.fail.value);
1✔
730
        return;
1✔
731
      }
732

733
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
734
        updateLocalRequest.password, LocalAuthenticator);
735
      res.status(204).json();
1✔
736
    } catch (error) {
737
      this.logger.error('Could not update local password:', error);
×
738
      res.status(500).json('Internal server error.');
×
739
    }
740
  }
741

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

759
    let take;
760
    let skip;
761
    try {
4✔
762
      const pagination = parseRequestPagination(req);
4✔
763
      take = pagination.take;
4✔
764
      skip = pagination.skip;
4✔
765
    } catch (e) {
766
      res.status(400).send(e.message);
×
767
      return;
×
768
    }
769

770
    try {
4✔
771
      const organId = asNumber(parameters.id);
4✔
772
      // Get the user object if it exists
773
      const user = await User.findOne({ where: { id: organId } });
4✔
774
      // If it does not exist, return a 404 error
775
      if (user == null) {
4✔
776
        res.status(404).json('Unknown user ID.');
1✔
777
        return;
1✔
778
      }
779

780
      if (user.type !== UserType.ORGAN) {
3✔
781
        res.status(400).json('User is not of type Organ');
1✔
782
        return;
1✔
783
      }
784

785
      const [members, count] = await UserService.getUsers({ organId }, { take, skip });
2✔
786
      const records = members.map((u) => asUserResponse(u, true));
11✔
787
      if (!await this.canSeeEmail(req, UserController.getRelation(req))) {
2!
788
        records.forEach((u) => { u.email = undefined; });
×
789
      }
790
      res.status(200).json({
2✔
791
        _pagination: { take, skip, count },
792
        records,
793
      });
794
    } catch (error) {
795
      this.logger.error('Could not get organ members:', error);
×
796
      res.status(500).json('Internal server error.');
×
797
    }
798
  }
799

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

814
    try {
10✔
815
      // Get the user object if it exists
816
      const user = await UserService.getSingleUser(asNumber(parameters.id));
10✔
817
      // If it does not exist, return a 404 error
818
      if (user == null) {
10✔
819
        res.status(404).json('Unknown user ID.');
3✔
820
        return;
3✔
821
      }
822

823
      const response = asUserResponse(user, true);
7✔
824
      if (!await this.canSeeEmail(req, UserController.getRelation(req))) {
7✔
825
        response.email = undefined;
1✔
826
      }
827
      res.status(200).json(response);
7✔
828
    } catch (error) {
829
      this.logger.error('Could not get individual user:', error);
×
830
      res.status(500).json('Internal server error.');
×
831
    }
832
  }
833

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

850
    try {
7✔
851
      const validation = await verifyCreateUserRequest(body);
7✔
852
      if (isFail(validation)) {
7✔
853
        res.status(400).json(validation.fail.value);
3✔
854
        return;
3✔
855
      }
856

857
      const user = await UserService.createUser(body);
4✔
858
      res.status(201).json(asUserResponse(user, true));
4✔
859
    } catch (error) {
860
      this.logger.error('Could not create user:', error);
×
861
      res.status(500).json('Internal server error.');
×
862
    }
863
  }
864

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

881
    if (body.firstName !== undefined && body.firstName.length === 0) {
13✔
882
      res.status(400).json('firstName cannot be empty');
1✔
883
      return;
1✔
884
    }
885
    if (body.firstName !== undefined && body.firstName.length > 64) {
12✔
886
      res.status(400).json('firstName too long');
1✔
887
      return;
1✔
888
    }
889
    if (body.lastName !== undefined && body.lastName.length > 64) {
11✔
890
      res.status(400).json('lastName too long');
1✔
891
      return;
1✔
892
    }
893
    if (body.nickname !== undefined && body.nickname.length > 64) {
10✔
894
      res.status(400).json('nickname too long');
1✔
895
      return;
1✔
896
    }
897
    if (body.nickname === '') body.nickname = null;
9✔
898

899
    try {
9✔
900
      const id = parseInt(parameters.id, 10);
9✔
901
      // Get the user object if it exists
902
      let user = await User.findOne({ where: { id, deleted: false } });
9✔
903
      // If it does not exist, return a 404 error
904
      if (user == null) {
9!
905
        res.status(404).json('Unknown user ID.');
×
906
        return;
×
907
      }
908

909
      user = {
9✔
910
        ...body,
911
      } as User;
912
      await User.update(parameters.id, user);
9✔
913
      const updatedUser = await UserService.getSingleUser(asNumber(parameters.id));
9✔
914
      res.status(200).json(asUserResponse(updatedUser, true));
9✔
915
    } catch (error) {
916
      this.logger.error('Could not update user:', error);
×
917
      res.status(500).json('Internal server error.');
×
918
    }
919
  }
920

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

935
    if (req.token.user.id === parseInt(parameters.id, 10)) {
5✔
936
      res.status(400).json('Cannot delete yourself');
1✔
937
      return;
1✔
938
    }
939

940
    try {
4✔
941
      const id = parseInt(parameters.id, 10);
4✔
942
      // Get the user object if it exists
943
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
944
      // If it does not exist, return a 404 error
945
      if (user == null) {
4✔
946
        res.status(404).json('Unknown user ID.');
2✔
947
        return;
2✔
948
      }
949

950
      user.deleted = true;
2✔
951
      await user.save();
2✔
952
      res.status(204).json('User deleted');
2✔
953
    } catch (error) {
954
      this.logger.error('Could not create product:', error);
×
955
      res.status(500).json('Internal server error.');
×
956
    }
957
  }
958

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

973
    try {
3✔
974
      const nfcCode = String(parameters.nfcCode);
3✔
975
      const nfc = await NfcAuthenticator.findOne({ where: { nfcCode } });
3✔
976

977
      if (nfc === null) {
3✔
978
        res.status(404).json('Unknown nfc code');
1✔
979
        return;
1✔
980
      }
981

982
      const userResponse = parseUserToResponse(nfc.user);
2✔
983
      if (!await this.canSeeEmail(req, 'all')) {
2!
984
        userResponse.email = undefined;
×
985
      }
986
      res.status(200).json(userResponse);
2✔
987
    } catch (error) {
988
      this.logger.error('Could not find user using nfc:', error);
×
989
      res.status(500).json('Internal server error.');
×
990
    }
991
  }
992

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

1006
    const { id } = req.token.user;
3✔
1007
    const body = req.body as AcceptTosRequest;
3✔
1008

1009
    try {
3✔
1010
      const user = await UserService.getSingleUser(id);
3✔
1011
      if (user == null) {
3!
1012
        res.status(404).json('User not found.');
×
1013
        return;
×
1014
      }
1015

1016
      const success = await UserService.acceptToS(id, body);
3✔
1017
      if (!success) {
3✔
1018
        res.status(400).json('User already accepted ToS.');
1✔
1019
        return;
1✔
1020
      }
1021

1022
      res.status(204).json();
2✔
1023
      return;
2✔
1024
    } catch (error) {
1025
      this.logger.error('Could not accept ToS for user:', error);
×
1026
      res.status(500).json('Internal server error.');
×
1027
    }
1028
  }
1029

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

1045
    let take;
1046
    let skip;
1047
    try {
4✔
1048
      const pagination = parseRequestPagination(req);
4✔
1049
      take = pagination.take;
4✔
1050
      skip = pagination.skip;
4✔
1051
    } catch (e) {
1052
      res.status(400).send(e.message);
×
1053
      return;
×
1054
    }
1055

1056
    // Handle request
1057
    try {
4✔
1058
      const id = parseInt(parameters.id, 10);
4✔
1059
      const owner = await User.findOne({ where: { id, deleted: false } });
4✔
1060
      if (owner == null) {
4✔
1061
        res.status(404).json({});
1✔
1062
        return;
1✔
1063
      }
1064

1065
      const [revisions, count] = await ProductService.getProducts({}, { take, skip }, owner);
3✔
1066
      const records = revisions.map((r) => ProductService.revisionToResponse(r));
21✔
1067
      res.json(toResponse(records, count, { take, skip }));
3✔
1068
    } catch (error) {
1069
      this.logger.error('Could not return all products:', error);
×
1070
      res.status(500).json('Internal server error.');
×
1071
    }
1072
  }
1073

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

1091
    let take;
1092
    let skip;
1093
    try {
4✔
1094
      const pagination = parseRequestPagination(req);
4✔
1095
      take = pagination.take;
4✔
1096
      skip = pagination.skip;
4✔
1097
    } catch (e) {
1098
      res.status(400).send(e.message);
×
1099
      return;
×
1100
    }
1101

1102
    // handle request
1103
    try {
4✔
1104
      // Get the user object if it exists
1105
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1106
      // If it does not exist, return a 404 error
1107
      if (user == null) {
4✔
1108
        res.status(404).json('Unknown user ID.');
1✔
1109
        return;
1✔
1110
      }
1111

1112
      const [revisions, count] = await ContainerService
3✔
1113
        .getContainers({}, { take, skip }, user);
1114
      const records = revisions.map((r) => ContainerService.revisionToResponse(r));
9✔
1115
      res.json(toResponse(records, count, { take, skip }));
3✔
1116
    } catch (error) {
1117
      this.logger.error('Could not return containers:', error);
×
1118
      res.status(500).json('Internal server error.');
×
1119
    }
1120
  }
1121

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

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

1150
    // handle request
1151
    try {
4✔
1152
      // Get the user object if it exists
1153
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1154
      // If it does not exist, return a 404 error
1155
      if (user == null) {
4✔
1156
        res.status(404).json('Unknown user ID.');
1✔
1157
        return;
1✔
1158
      }
1159

1160
      const [revisions, count] = await PointOfSaleService
3✔
1161
        .getPointsOfSale({}, { take, skip }, user);
1162
      const records = revisions.map((r) => PointOfSaleService.revisionToResponse(r));
9✔
1163
      res.json(toResponse(records, count, { take, skip }));
3✔
1164
    } catch (error) {
1165
      this.logger.error('Could not return point of sale:', error);
×
1166
      res.status(500).json('Internal server error.');
×
1167
    }
1168
  }
1169

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

1195
    // Parse the filters given in the query parameters. If there are any issues,
1196
    // the parse method will throw an exception. We will then return a 400 error.
1197
    let filters;
1198
    try {
4✔
1199
      filters = parseGetTransactionsFilters(req);
4✔
1200
    } catch (e) {
1201
      res.status(400).json(e.message);
×
1202
      return;
×
1203
    }
1204

1205
    let take;
1206
    let skip;
1207
    try {
4✔
1208
      const pagination = parseRequestPagination(req);
4✔
1209
      take = pagination.take;
4✔
1210
      skip = pagination.skip;
4✔
1211
    } catch (e) {
1212
      res.status(400).send(e.message);
×
1213
      return;
×
1214
    }
1215

1216
    try {
4✔
1217
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
4✔
1218
      if (user == null) {
4✔
1219
        res.status(404).json({});
1✔
1220
        return;
1✔
1221
      }
1222
      const [records, count] = await new TransactionService().getTransactions(filters, { take, skip }, user);
3✔
1223

1224
      res.status(200).json(toResponse(records, count, { take, skip }));
3✔
1225
    } catch (error) {
1226
      this.logger.error('Could not return all transactions:', error);
×
1227
      res.status(500).json('Internal server error.');
×
1228
    }
1229
  }
1230

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

1248
    let filters: { fromDate: Date, tillDate: Date };
1249
    try {
7✔
1250
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1251
    } catch (e) {
1252
      res.status(400).json(e.message);
1✔
1253
      return;
1✔
1254
    }
1255

1256
    try {
6✔
1257
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
6✔
1258
      if (user == null) {
6✔
1259
        res.status(404).json('Unknown user ID.');
1✔
1260
        return;
1✔
1261
      }
1262

1263
      const report = await (new SalesReportService()).getReport({ ...filters, forId: user.id });
5✔
1264
      res.status(200).json(ReportService.reportToResponse(report));
5✔
1265
    } catch (error) {
1266
      this.logger.error('Could not get sales report:', error);
×
1267
      res.status(500).json('Internal server error.');
×
1268
    }
1269
  }
1270

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

1292
    let filters: { fromDate: Date, tillDate: Date };
1293
    let description: string;
1294
    let fileType: ReturnFileType;
1295
    try {
7✔
1296
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1297
      description = String(req.query.description);
4✔
1298
      fileType = asReturnFileType(req.query.fileType);
4✔
1299
    } catch (e) {
1300
      res.status(400).json(e.message);
4✔
1301
      return;
4✔
1302
    }
1303

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

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

1342
    let filters: { fromDate: Date, tillDate: Date };
1343
    let description: string;
1344
    let fileType: ReturnFileType;
1345
    try {
7✔
1346
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1347
      description = String(req.query.description);
4✔
1348
      fileType = asReturnFileType(req.query.fileType);
4✔
1349
    } catch (e) {
1350
      res.status(400).json(e.message);
4✔
1351
      return;
4✔
1352
    }
1353

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

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

1389
    let filters: { fromDate: Date, tillDate: Date };
1390
    try {
4✔
1391
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
4✔
1392
    } catch (e) {
1393
      res.status(400).json(e.message);
1✔
1394
      return;
1✔
1395
    }
1396

1397
    try {
3✔
1398
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1399
      if (user == null) {
3✔
1400
        res.status(404).json('Unknown user ID.');
1✔
1401
        return;
1✔
1402
      }
1403

1404
      const report = await (new BuyerReportService()).getReport({ ...filters, forId: user.id });
2✔
1405
      res.status(200).json(ReportService.reportToResponse(report));
2✔
1406
    } catch (error) {
1407
      this.logger.error('Could not get sales report:', error);
×
1408
      res.status(500).json('Internal server error.');
×
1409
    }
1410
  }
1411

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

1431
    // Parse the filters given in the query parameters. If there are any issues,
1432
    // the parse method will throw an exception. We will then return a 400 error.
1433
    let filters;
1434
    try {
2✔
1435
      filters = parseGetTransferFilters(req);
2✔
1436
    } catch (e) {
1437
      res.status(400).json(e.message);
×
1438
      return;
×
1439
    }
1440

1441
    let take;
1442
    let skip;
1443
    try {
2✔
1444
      const pagination = parseRequestPagination(req);
2✔
1445
      take = pagination.take;
2✔
1446
      skip = pagination.skip;
2✔
1447
    } catch (e) {
1448
      res.status(400).send(e.message);
×
1449
      return;
×
1450
    }
1451

1452
    // handle request
1453
    try {
2✔
1454
      // Get the user object if it exists
1455
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
2✔
1456
      // If it does not exist, return a 404 error
1457
      if (user == null) {
2!
1458
        res.status(404).json('Unknown user ID.');
×
1459
        return;
×
1460
      }
1461

1462
      const [transfers, count] = await new TransferService().getTransfers(
2✔
1463
        { ...filters }, { take, skip }, user,
1464
      );
1465
      const records = transfers.map((t) => TransferService.asTransferResponse(t));
18✔
1466
      res.json(toResponse(records, count, { take, skip }));
2✔
1467
    } catch (error) {
1468
      this.logger.error('Could not return user transfers', error);
×
1469
      res.status(500).json('Internal server error.');
×
1470
    }
1471
  }
1472

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

1487
    try {
3✔
1488
      const id = parseInt(parameters.id, 10);
3✔
1489
      // Get the user object if it exists
1490
      const user = await User.findOne({ where: { id, deleted: false } });
3✔
1491
      // If it does not exist, return a 404 error
1492
      if (user == null) {
3✔
1493
        res.status(404).json('Unknown user ID.');
1✔
1494
        return;
1✔
1495
      }
1496

1497
      const rolesWithPermissions = await this.roleManager.getRoles(user, true);
2✔
1498
      const response = rolesWithPermissions.map((r) => RBACService.asRoleResponse(r));
2✔
1499
      res.status(200).json(response);
2✔
1500
    } catch (error) {
1501
      this.logger.error('Could not get roles of user:', error);
×
1502
      res.status(500).json('Internal server error.');
×
1503
    }
1504
  }
1505

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

1524
    let filters;
1525
    let take;
1526
    let skip;
1527
    try {
4✔
1528
      filters = parseGetFinancialMutationsFilters(req);
4✔
1529
      const pagination = parseRequestPagination(req);
4✔
1530
      take = pagination.take;
4✔
1531
      skip = pagination.skip;
4✔
1532
    } catch (e) {
1533
      res.status(400).send(e.message);
×
1534
      return;
×
1535
    }
1536

1537
    try {
4✔
1538
      const id = parseInt(parameters.id, 10);
4✔
1539
      // Get the user object if it exists
1540
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
1541
      // If it does not exist, return a 404 error
1542
      if (user == null) {
4!
1543
        res.status(404).json('Unknown user ID.');
×
1544
        return;
×
1545
      }
1546

1547
      const mutations = await UserService.getUserFinancialMutations(user, filters, { take, skip });
4✔
1548
      res.status(200).json(mutations);
4✔
1549
    } catch (error) {
1550
      this.logger.error('Could not get financial mutations of user:', error);
×
1551
      res.status(500).json('Internal server error.');
×
1552
    }
1553
  }
1554

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

1569
    try {
2✔
1570
      const id = parseInt(parameters.id, 10);
2✔
1571

1572
      const user = await User.findOne({ where: { id } });
2✔
1573
      if (user == null) {
2✔
1574
        res.status(404).json('Unknown user ID.');
1✔
1575
        return;
1✔
1576
      }
1577

1578
      const deposits = await StripeService.getProcessingStripeDepositsFromUser(id);
1✔
1579
      res.status(200).json(deposits.map((d) => StripeService.asStripeDepositResponse(d)));
1✔
1580
    } catch (error) {
1581
      this.logger.error('Could not get processing deposits of user:', error);
×
1582
      res.status(500).json('Internal server error.');
×
1583
    }
1584
  }
1585

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

1606
    let filters;
1607
    try {
6✔
1608
      filters = parseGetTransactionsFilters(req);
6✔
1609
    } catch (e) {
1610
      res.status(400).json(e.message);
1✔
1611
      return;
1✔
1612
    }
1613

1614
    try {
5✔
1615
      if ((filters.toId !== undefined && filters.fromId !== undefined) || (filters.toId === undefined && filters.fromId === undefined)) {
5✔
1616
        res.status(400).json('Need to provide either a toId or a fromId.');
2✔
1617
        return;
2✔
1618
      }
1619

1620
      const id = parseInt(parameters.id, 10);
3✔
1621

1622
      const user = await User.findOne({ where: { id } });
3✔
1623
      if (user == null) {
3✔
1624
        res.status(404).json('Unknown user ID.');
1✔
1625
        return;
1✔
1626
      }
1627

1628
      const report = await (new TransactionService()).getTransactionReportResponse(filters);
2✔
1629
      res.status(200).json(report);
2✔
1630
    } catch (e) {
1631
      res.status(500).send();
×
1632
      this.logger.error(e);
×
1633
    }
1634
  }
1635

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

1654
    try {
9✔
1655
      const id = parseInt(rawId, 10);
9✔
1656

1657
      const user = await User.findOne({ where: { id }, relations: { currentFines: { fines: true } } });
9✔
1658
      if (user == null) {
9✔
1659
        res.status(404).json('Unknown user ID.');
1✔
1660
        return;
1✔
1661
      }
1662
      if (user.currentFines == null) {
8✔
1663
        res.status(400).json('User has no fines.');
1✔
1664
        return;
1✔
1665
      }
1666

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

1679
      await new DebtorService().waiveFines(id, { amount: amountToWaive } as WaiveFinesParams);
4✔
1680
      res.status(204).send();
4✔
1681
    } catch (e) {
1682
      res.status(500).send();
×
1683
      this.logger.error(e);
×
1684
    }
1685
  }
1686

1687
  /**
1688
   * DELETE /users/{id}/roles/{roleId}
1689
   * @summary Deletes a role from a user
1690
   * @tags users - Operations of user controller
1691
   * @param {integer} id.path.required - The id of the user
1692
   * @param {integer} roleId.path.required - The id of the role
1693
   * @operationId deleteUserRole
1694
   * @security JWT
1695
   * @return 204 - Success
1696
   */
1697
  public async deleteUserRole(req: RequestWithToken, res: Response): Promise<void> {
1698
    const { id: rawUserId, roleId: rawRoleId } = req.params;
4✔
1699

1700
    const userId = parseInt(rawUserId, 10);
4✔
1701
    const roleId = parseInt(rawRoleId, 10);
4✔
1702

1703
    const user = await User.findOne({ where: { id: userId } });
4✔
1704
    if (!user) {
4✔
1705
      res.status(404).json('user not found');
1✔
1706
      return;
1✔
1707
    }
1708
    const role = await Role.findOne({ where: { id: roleId } });
3✔
1709
    if (!role) {
3✔
1710
      res.status(404).json('role not found');
1✔
1711
      return;
1✔
1712
    }
1713

1714
    await UserService.deleteUserRole(user, role);
2✔
1715
    res.status(204).send();
2✔
1716
  }
1717

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

1733
    const user = await User.findOne({ where: { id: userId } });
2✔
1734
    if (!user) {
2!
1735
      res.status(404).json('user not found');
×
1736
      return;
×
1737
    }
1738
    const role = await Role.findOne({ where: { id: roleId } });
2✔
1739
    if (!role) {
2✔
1740
      res.status(404).json('role not found');
1✔
1741
      return;
1✔
1742
    }
1743

1744
    await UserService.addUserRole(user, role);
1✔
1745
    res.status(204).send();
1✔
1746
  }
1747

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

1762
    try {
4✔
1763
      const userId = parseInt(rawUserId, 10);
4✔
1764
    
1765
      const wrappedRes = await new WrappedService().getWrappedForUser(userId);
4✔
1766
      if (wrappedRes == null) {
4✔
1767
        res.status(404).json('Wrapped not found for user');
1✔
1768
        return;
1✔
1769
      }
1770

1771
      res.json(WrappedService.asWrappedResponse(wrappedRes));
3✔
1772
    } catch (error) {
1773
      res.status(500).json({ message: 'Internal server error' });
×
1774
    }
1775
  }
1776

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

1790
    try {
2✔
1791
      const userId = parseInt(rawUserId, 10);
2✔
1792

1793
      res.json(await new WrappedService().updateWrapped({ ids: [userId] }));
2✔
1794
    } catch (error) {
1795
      res.status(500).json({ message: 'Internal server error' });
×
1796
    }
1797
  }
1798

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

1813
    try {
5✔
1814
      const userId = parseInt(rawUserId, 10);
5✔
1815
      
1816
      const user = await User.findOne({ where: { id: userId } });
5✔
1817
      if (!user) {
5✔
1818
        res.status(404).json('User not found.');
1✔
1819
        return;
1✔
1820
      }
1821

1822
      const store = new UserSettingsStore();
4✔
1823
      const settings = await store.getAllSettings(userId);
4✔
1824
      const response = UserSettingsStore.toResponse(settings);
4✔
1825
      res.json(response);
4✔
1826
    } catch (error) {
1827
      this.logger.error('Could not get user settings:', error);
×
1828
      res.status(500).json('Internal server error.');
×
1829
    }
1830
  }
1831

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

1848
    try {
10✔
1849
      const userId = parseInt(rawUserId, 10);
10✔
1850
      
1851
      const user = await User.findOne({ where: { id: userId } });
10✔
1852
      if (!user) {
10✔
1853
        res.status(404).json('User not found.');
1✔
1854
        return;
1✔
1855
      }
1856

1857
      const store = new UserSettingsStore();
9✔
1858
      await store.setSettings(userId, body);
9✔
1859

1860
      const settings = await store.getAllSettings(userId);
9✔
1861

1862
      res.json(UserSettingsStore.toResponse(settings));
9✔
1863
    } catch (error) {
1864
      this.logger.error('Could not update user settings:', error);
×
1865
      res.status(500).json('Internal server error.');
×
1866
    }
1867
  }
1868

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

1888
    try {
5✔
1889
      const userId = parseInt(rawUserId, 10);
5✔
1890
      const userOptions = UserService.getOptions({ id: userId });
5✔
1891
      const user = await User.findOne(userOptions);
5✔
1892
      if (!user) {
5✔
1893
        res.status(404).json('User not found.');
1✔
1894
        return;
1✔
1895
      }
1896

1897
      const allowedTypes = [UserType.MEMBER, UserType.LOCAL_USER];
4✔
1898

1899
      if (!allowedTypes.includes(user.type)) {
4✔
1900
        res.status(422).json('It is not possible to change the user type for users of this type.');
1✔
1901
        return;
1✔
1902
      }
1903

1904
      if (!allowedTypes.includes(body.userType)) {
3✔
1905
        res.status(422).json(`User type can only be changed to [${allowedTypes}].`);
1✔
1906
        return;
1✔
1907
      }
1908

1909
      if (body.userType === user.type) {
2✔
1910
        res.status(409).json('User is already of this type.');
1✔
1911
        return;
1✔
1912
      }
1913

1914
      if (!user.email) {
1!
1915
        res.status(400).json('Cannot change user type of user without email.');
×
1916
        return;
×
1917
      }
1918

1919
      if (body.userType === UserType.MEMBER && !user.memberUser) {
1!
1920
        res.status(400).json('Cannot change to MEMBER since no memberId is associated to this user.');
×
1921
        return;
×
1922
      }
1923

1924
      await UserService.updateUserType(user, body.userType);
1✔
1925
      const updatedUser = await UserService.getSingleUser(userId);
1✔
1926
      res.status(200).json(asUserResponse(updatedUser, true));
1✔
1927
    } catch (e) {
1928
      res.status(500).send('Internal server error.');
×
1929
      this.logger.error(e);
×
1930
    }
1931
  }
1932

1933
  /**
1934
   * GET /users/recently-charged
1935
   * @summary Get users recently charged by the caller via an authenticated point of sale.
1936
   * Returns distinct buyers ordered by most recent transaction first, intended for
1937
   * quick suggestions in the authenticated POS flow.
1938
   * @operationId getRecentlyChargedUsers
1939
   * @tags users - Operations of user controller
1940
   * @param {integer} take.query - Maximum number of users to return (default 50)
1941
   * @security JWT
1942
   * @return {Array.<UserResponse>} 200 - List of recently charged users.
1943
   */
1944
  public async getRecentlyChargedUsers(req: RequestWithToken, res: Response): Promise<void> {
1945
    this.logger.trace('Get recently charged users by user', req.token.user);
5✔
1946

1947
    let take: number;
1948
    try {
5✔
1949
      take = req.query.take !== undefined ? asNumber(req.query.take) : 50;
5✔
1950
      take = Math.min(Math.max(1, Math.trunc(take)), maxPagination());
4✔
1951
    } catch (e) {
1952
      res.status(400).send(e.message);
1✔
1953
      return;
1✔
1954
    }
1955

1956
    try {
4✔
1957
      const users = await new TransactionService().getRecentlyChargedUsers(req.token.user.id, take);
4✔
1958
      const records = users.map((u) => asUserResponse(u));
4✔
1959
      if (!await this.canSeeEmail(req, 'all')) {
4✔
1960
        records.forEach((u) => { u.email = undefined; });
1✔
1961
      }
1962
      res.status(200).json(records);
4✔
1963
    } catch (e) {
1964
      res.status(500).send('Internal server error.');
×
1965
      this.logger.error(e);
×
1966
    }
1967
  }
1968
}
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