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

GEWIS / sudosos-backend / 21366511389

26 Jan 2026 05:03PM UTC coverage: 89.574% (-0.03%) from 89.601%
21366511389

Pull #622

github

web-flow
Merge 9d1dfbf35 into b311a076f
Pull Request #622: Add general documentation to the docs

1706 of 2056 branches covered (82.98%)

Branch coverage included in aggregate %.

8810 of 9684 relevant lines covered (90.97%)

1000.87 hits per line

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

85.81
/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(
7✔
114
            req.token.roles, 'get', 'all', 'User', ['*'],
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', ['*'],
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', ['*'],
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(
9✔
203
            req.token.roles, 'get', UserController.getRelation(req), 'User', ['*'],
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', ['*'],
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';
182✔
397
    return req.params.id === req.token.user.id.toString() ? 'own' : 'all';
181✔
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
   * GET /users
413
   * @summary Get a list of all users
414
   * @operationId getAllUsers
415
   * @tags users - Operations of user controller
416
   * @security JWT
417
   * @param {integer} take.query - How many users the endpoint should return
418
   * @param {integer} skip.query - How many users should be skipped (for pagination)
419
   * @param {string} search.query - Filter based on first name
420
   * @param {boolean} active.query - Filter based if the user is active
421
   * @param {boolean} ofAge.query - Filter based if the user is 18+
422
   * @param {integer} id.query - Filter based on user ID
423
   * @param {string} type.query - enum:MEMBER,ORGAN,VOUCHER,LOCAL_USER,LOCAL_ADMIN,INVOICE,AUTOMATIC_INVOICE - Filter based on user type.
424
   * @return {PaginatedUserResponse} 200 - A list of all users
425
   */
426
  public async getAllUsers(req: RequestWithToken, res: Response): Promise<void> {
427
    this.logger.trace('Get all users by user', req.token.user);
10✔
428

429
    let take;
430
    let skip;
431
    let filters: UserFilterParameters;
432
    try {
10✔
433
      const pagination = parseRequestPagination(req);
10✔
434
      filters = parseGetUsersFilters(req);
10✔
435
      take = pagination.take;
10✔
436
      skip = pagination.skip;
10✔
437
    } catch (e) {
438
      res.status(400).send(e.message);
×
439
      return;
×
440
    }
441

442
    try {
10✔
443
      const users = await UserService.getUsers(filters, { take, skip });
10✔
444
      res.status(200).json(users);
10✔
445
    } catch (error) {
446
      this.logger.error('Could not get users:', error);
×
447
      res.status(500).json('Internal server error.');
×
448
    }
449
  }
450

451
  /**
452
   * GET /users/usertype/{userType}
453
   * @summary Get all users of user type
454
   * @operationId getAllUsersOfUserType
455
   * @tags users - Operations of user controller
456
   * @param {string} userType.path.required - The userType of the requested users
457
   * @security JWT
458
   * @param {integer} take.query - How many users the endpoint should return
459
   * @param {integer} skip.query - How many users should be skipped (for pagination)
460
   * @return {PaginatedUserResponse} 200 - A list of all users
461
   * @return {string} 404 - Nonexistent usertype
462
   */
463
  public async getAllUsersOfUserType(req: RequestWithToken, res: Response): Promise<void> {
464
    const parameters = req.params;
5✔
465
    this.logger.trace('Get all users of userType', parameters, 'by user', req.token.user);
5✔
466
    const userType = req.params.userType.toUpperCase();
5✔
467

468
    // If it does not exist, return a 404 error
469
    const type = UserType[userType as keyof typeof UserType];
5✔
470
    if (!type || Number(userType)) {
5✔
471
      res.status(404).json('Unknown userType.');
1✔
472
      return;
1✔
473
    }
474

475
    try {
4✔
476
      req.query.type = userType;
4✔
477
      await this.getAllUsers(req, res);
4✔
478
    } catch (error) {
479
      this.logger.error('Could not get users:', error);
×
480
      res.status(500).json('Internal server error.');
×
481
    }
482
  }
483

484
  /**
485
   * PUT /users/{id}/authenticator/pin
486
   * @summary Put an users pin code
487
   * @operationId updateUserPin
488
   * @tags users - Operations of user controller
489
   * @param {integer} id.path.required - The id of the user
490
   * @param {UpdatePinRequest} request.body.required -
491
   *    The PIN code to update to
492
   * @security JWT
493
   * @return 204 - Update success
494
   * @return {string} 400 - Validation Error
495
   * @return {string} 404 - Nonexistent user id
496
   */
497
  public async updateUserPin(req: RequestWithToken, res: Response): Promise<void> {
498
    const { params } = req;
3✔
499
    const updatePinRequest = req.body as UpdatePinRequest;
3✔
500
    this.logger.trace('Update user pin', params, 'by user', req.token.user);
3✔
501

502
    try {
3✔
503
      // Get the user object if it exists
504
      const user = await User.findOne({ where: { id: parseInt(params.id, 10), deleted: false } });
3✔
505
      // If it does not exist, return a 404 error
506
      if (user == null) {
3✔
507
        res.status(404).json('Unknown user ID.');
1✔
508
        return;
1✔
509
      }
510

511
      const validation = await verifyUpdatePinRequest(updatePinRequest);
2✔
512
      if (isFail(validation)) {
2✔
513
        res.status(400).json(validation.fail.value);
1✔
514
        return;
1✔
515
      }
516

517
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
518
        updatePinRequest.pin.toString(), PinAuthenticator);
519
      res.status(204).json();
1✔
520
    } catch (error) {
521
      this.logger.error('Could not update pin:', error);
×
522
      res.status(500).json('Internal server error.');
×
523
    }
524
  }
525

526
  /**
527
   * PUT /users/{id}/authenticator/nfc
528
   * @summary Put a users NFC code
529
   * @operationId updateUserNfc
530
   * @tags users - Operations of user controller
531
   * @param {integer} id.path.required - The id of the user
532
   * @param {UpdateNfcRequest} request.body.required -
533
   *    The NFC code to update to
534
   * @security JWT
535
   * @return 204 - Update success
536
   * @return {string} 400 - Validation Error
537
   * @return {string} 404 - Nonexistent user id
538
   */
539
  public async updateUserNfc(req: RequestWithToken, res: Response): Promise<void> {
540
    const { params } = req;
10✔
541
    const updateNfcRequest = req.body as UpdateNfcRequest;
10✔
542
    this.logger.trace('Update user NFC', params, 'by user', req.token.user);
10✔
543

544
    try {
10✔
545
      // Get the user object if it exists
546
      const user = await User.findOne({ where: { id: parseInt(params.id, 10), deleted: false } });
10✔
547
      // If it does not exist, return a 404 error
548
      if (user == null) {
10✔
549
        res.status(404).json('Unknown user ID.');
1✔
550
        return;
1✔
551
      }
552

553
      const validation = await verifyUpdateNfcRequest(updateNfcRequest);
9✔
554
      if (isFail(validation)) {
9✔
555
        res.status(400).json(validation.fail.value);
2✔
556
        return;
2✔
557
      }
558

559
      await new AuthenticationService().setUserAuthenticationNfc(user,
7✔
560
        updateNfcRequest.nfcCode.toString(), NfcAuthenticator);
561
      res.status(204).json();
7✔
562
    } catch (error) {
563
      this.logger.error('Could not update NFC:', error);
×
564
      res.status(500).json('Internal server error.');
×
565
    }
566
  }
567

568
  /**
569
   * DELETE /users/{id}/authenticator/nfc
570
   * @summary Delete a nfc code
571
   * @operationId deleteUserNfc
572
   * @tags users - Operations of user controller
573
   * @param {integer} id.path.required - The id of the user
574
   * @security JWT
575
   * @return 200 - Delete nfc success
576
   * @return {string} 400 - Validation Error
577
   * @return {string} 403 - Nonexistent user nfc
578
   * @return {string} 404 - Nonexistent user id
579
   */
580
  public async deleteUserNfc(req: RequestWithToken, res: Response): Promise<void> {
581
    const parameters = req.params;
9✔
582
    this.logger.trace('Delete user NFC', parameters, 'by user', req.token.user);
9✔
583

584
    try {
9✔
585
      // Get the user object if it exists
586
      const user = await User.findOne({ where: { id: parseInt(parameters.id, 10), deleted: false } });
9✔
587
      // If it does not exist, return a 404 error
588
      if (user == null) {
9✔
589
        res.status(404).json('Unknown user ID.');
3✔
590
        return;
3✔
591
      }
592

593
      if (await NfcAuthenticator.count({ where: { userId: parseInt(parameters.id, 10) } }) == 0) {
6✔
594
        res.status(403).json('No saved nfc');
3✔
595
        return;
3✔
596
      }
597

598
      await NfcAuthenticator.delete(parseInt(parameters.id, 10));
3✔
599
      res.status(204).json();
3✔
600
    } catch (error) {
601
      this.logger.error('Could not update NFC:', error);
×
602
      res.status(500).json('Internal server error.');
×
603
    }
604
  }
605

606
  /**
607
   * POST /users/{id}/authenticator/key
608
   * @summary POST an users update to new key code
609
   * @operationId updateUserKey
610
   * @tags users - Operations of user controller
611
   * @param {integer} id.path.required - The id of the user
612
   * @security JWT
613
   * @return {UpdateKeyResponse} 200 - The new key
614
   * @return {string} 400 - Validation Error
615
   * @return {string} 404 - Nonexistent user id
616
   */
617
  public async updateUserKey(req: RequestWithToken, res: Response): Promise<void> {
618
    const { params } = req;
2✔
619
    this.logger.trace('Update user key', params, 'by user', req.token.user);
2✔
620

621
    try {
2✔
622
      const userId = parseInt(params.id, 10);
2✔
623
      // Get the user object if it exists
624
      const user = await User.findOne({ where: { id: userId, deleted: false } });
2✔
625
      // If it does not exist, return a 404 error
626
      if (user == null) {
2✔
627
        res.status(404).json('Unknown user ID.');
1✔
628
        return;
1✔
629
      }
630

631
      const generatedKey = randomBytes(128).toString('hex');
1✔
632
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
633
        generatedKey, KeyAuthenticator);
634
      const response = { key: generatedKey } as UpdateKeyResponse;
1✔
635
      res.status(200).json(response);
1✔
636
    } catch (error) {
637
      this.logger.error('Could not update key:', error);
×
638
      res.status(500).json('Internal server error.');
×
639
    }
640
  }
641

642
  /**
643
   * Delete /users/{id}/authenticator/key
644
   * @summary Delete a users key code
645
   * @operationId deleteUserKey
646
   * @tags users - Operations of user controller
647
   * @param {integer} id.path.required - The id of the user
648
   * @security JWT
649
   * @return  200 - Deletion succesfull
650
   * @return {string} 400 - Validation Error
651
   * @return {string} 404 - Nonexistent user id
652
   */
653
  public async deleteUserKey(req: RequestWithToken, res: Response): Promise<void> {
654
    const { params } = req;
2✔
655
    this.logger.trace('Delete user key', params, 'by user', req.token.user);
2✔
656

657
    try {
2✔
658
      // Get the user object if it exists
659
      const user = await User.findOne({ where: { id: parseInt(params.id, 10), deleted: false } });
2✔
660
      // If it does not exist, return a 404 error
661
      if (user == null) {
2✔
662
        res.status(404).json('Unknown user ID.');
1✔
663
        return;
1✔
664
      }
665

666

667
      await KeyAuthenticator.delete(parseInt(params.id, 10));
1✔
668
      res.status(204).json();
1✔
669
    } catch (error) {
670
      this.logger.error('Could not delete key:', error);
×
671
      res.status(500).json('Internal server error.');
×
672
    }
673
  }
674

675
  /**
676
   * PUT /users/{id}/authenticator/local
677
   * @summary Put a user's local password
678
   * @operationId updateUserLocalPassword
679
   * @tags users - Operations of user controller
680
   * @param {integer} id.path.required - The id of the user
681
   * @param {UpdateLocalRequest} request.body.required -
682
   *    The password update
683
   * @security JWT
684
   * @return 204 - Update success
685
   * @return {string} 400 - Validation Error
686
   * @return {string} 404 - Nonexistent user id
687
   */
688
  public async updateUserLocalPassword(req: RequestWithToken, res: Response): Promise<void> {
689
    const parameters = req.params;
3✔
690
    const updateLocalRequest = req.body as UpdateLocalRequest;
3✔
691
    this.logger.trace('Update user local password', parameters, 'by user', req.token.user);
3✔
692

693
    try {
3✔
694
      const id = Number.parseInt(parameters.id, 10);
3✔
695
      // Get the user object if it exists
696
      const user = await User.findOne({ where: { id, deleted: false } });
3✔
697
      // If it does not exist, return a 404 error
698
      if (user == null) {
3✔
699
        res.status(404).json('Unknown user ID.');
1✔
700
        return;
1✔
701
      }
702

703
      const validation = await verifyUpdateLocalRequest(updateLocalRequest);
2✔
704
      if (isFail(validation)) {
2✔
705
        res.status(400).json(validation.fail.value);
1✔
706
        return;
1✔
707
      }
708

709
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
710
        updateLocalRequest.password, LocalAuthenticator);
711
      res.status(204).json();
1✔
712
    } catch (error) {
713
      this.logger.error('Could not update local password:', error);
×
714
      res.status(500).json('Internal server error.');
×
715
    }
716
  }
717

718
  /**
719
   * GET /users/{id}/members
720
   * @summary Get an organs members
721
   * @operationId getOrganMembers
722
   * @tags users - Operations of user controller
723
   * @param {integer} id.path.required - The id of the user
724
   * @param {integer} take.query - How many members the endpoint should return
725
   * @param {integer} skip.query - How many members should be skipped (for pagination)
726
   * @security JWT
727
   * @return {PaginatedUserResponse} 200 - All members of the organ
728
   * @return {string} 404 - Nonexistent user id
729
   * @return {string} 400 - User is not an organ
730
   */
731
  public async getOrganMembers(req: RequestWithToken, res: Response): Promise<void> {
732
    const parameters = req.params;
4✔
733
    this.logger.trace('Get organ members', parameters, 'by user', req.token.user);
4✔
734

735
    let take;
736
    let skip;
737
    try {
4✔
738
      const pagination = parseRequestPagination(req);
4✔
739
      take = pagination.take;
4✔
740
      skip = pagination.skip;
4✔
741
    } catch (e) {
742
      res.status(400).send(e.message);
×
743
      return;
×
744
    }
745

746
    try {
4✔
747
      const organId = asNumber(parameters.id);
4✔
748
      // Get the user object if it exists
749
      const user = await User.findOne({ where: { id: organId } });
4✔
750
      // If it does not exist, return a 404 error
751
      if (user == null) {
4✔
752
        res.status(404).json('Unknown user ID.');
1✔
753
        return;
1✔
754
      }
755

756
      if (user.type !== UserType.ORGAN) {
3✔
757
        res.status(400).json('User is not of type Organ');
1✔
758
        return;
1✔
759
      }
760

761
      const members = await UserService.getUsers({ organId }, { take, skip });
2✔
762
      res.status(200).json(members);
2✔
763
    } catch (error) {
764
      this.logger.error('Could not get organ members:', error);
×
765
      res.status(500).json('Internal server error.');
×
766
    }
767
  }
768

769
  /**
770
   * GET /users/{id}
771
   * @summary Get an individual user
772
   * @operationId getIndividualUser
773
   * @tags users - Operations of user controller
774
   * @param {integer} id.path.required - userID
775
   * @security JWT
776
   * @return {UserResponse} 200 - Individual user
777
   * @return {string} 404 - Nonexistent user id
778
   */
779
  public async getIndividualUser(req: RequestWithToken, res: Response): Promise<void> {
780
    const parameters = req.params;
7✔
781
    this.logger.trace('Get individual user', parameters, 'by user', req.token.user);
7✔
782

783
    try {
7✔
784
      // Get the user object if it exists
785
      const user = await UserService.getSingleUser(asNumber(parameters.id));
7✔
786
      // If it does not exist, return a 404 error
787
      if (user == null) {
7✔
788
        res.status(404).json('Unknown user ID.');
3✔
789
        return;
3✔
790
      }
791

792
      res.status(200).json(user);
4✔
793
    } catch (error) {
794
      this.logger.error('Could not get individual user:', error);
×
795
      res.status(500).json('Internal server error.');
×
796
    }
797
  }
798

799
  /**
800
   * POST /users
801
   * @summary Create a new user
802
   * @operationId createUser
803
   * @tags users - Operations of user controller
804
   * @param {CreateUserRequest} request.body.required -
805
   * The user which should be created
806
   * @security JWT
807
   * @return {UserResponse} 200 - New user
808
   * @return {string} 400 - Bad request
809
   */
810
  // eslint-disable-next-line class-methods-use-this
811
  public async createUser(req: RequestWithToken, res: Response): Promise<void> {
812
    const body = req.body as CreateUserRequest;
7✔
813
    this.logger.trace('Create user', body, 'by user', req.token.user);
7✔
814

815
    try {
7✔
816
      const validation = await verifyCreateUserRequest(body);
7✔
817
      if (isFail(validation)) {
7✔
818
        res.status(400).json(validation.fail.value);
3✔
819
        return;
3✔
820
      }
821

822
      const user = await UserService.createUser(body);
4✔
823
      res.status(201).json(user);
4✔
824
    } catch (error) {
825
      this.logger.error('Could not create user:', error);
×
826
      res.status(500).json('Internal server error.');
×
827
    }
828
  }
829

830
  /**
831
   * PATCH /users/{id}
832
   * @summary Update a user
833
   * @operationId updateUser
834
   * @tags users - Operations of user controller
835
   * @param {integer} id.path.required - The id of the user
836
   * @param {UpdateUserRequest} request.body.required - The user which should be updated
837
   * @security JWT
838
   * @return {UserResponse} 200 - New user
839
   * @return {string} 400 - Bad request
840
   */
841
  public async updateUser(req: RequestWithToken, res: Response): Promise<void> {
842
    const body = req.body as UpdateUserRequest;
13✔
843
    const parameters = req.params;
13✔
844
    this.logger.trace('Update user', parameters.id, 'with', body, 'by user', req.token.user);
13✔
845

846
    if (body.firstName !== undefined && body.firstName.length === 0) {
13✔
847
      res.status(400).json('firstName cannot be empty');
1✔
848
      return;
1✔
849
    }
850
    if (body.firstName !== undefined && body.firstName.length > 64) {
12✔
851
      res.status(400).json('firstName too long');
1✔
852
      return;
1✔
853
    }
854
    if (body.lastName !== undefined && body.lastName.length > 64) {
11✔
855
      res.status(400).json('lastName too long');
1✔
856
      return;
1✔
857
    }
858
    if (body.nickname !== undefined && body.nickname.length > 64) {
10✔
859
      res.status(400).json('nickname too long');
1✔
860
      return;
1✔
861
    }
862
    if (body.nickname === '') body.nickname = null;
9✔
863

864
    try {
9✔
865
      const id = parseInt(parameters.id, 10);
9✔
866
      // Get the user object if it exists
867
      let user = await User.findOne({ where: { id, deleted: false } });
9✔
868
      // If it does not exist, return a 404 error
869
      if (user == null) {
9!
870
        res.status(404).json('Unknown user ID.');
×
871
        return;
×
872
      }
873

874
      user = {
9✔
875
        ...body,
876
      } as User;
877
      await User.update(parameters.id, user);
9✔
878
      res.status(200).json(
9✔
879
        await UserService.getSingleUser(asNumber(parameters.id)),
880
      );
881
    } catch (error) {
882
      this.logger.error('Could not update user:', error);
×
883
      res.status(500).json('Internal server error.');
×
884
    }
885
  }
886

887
  /**
888
   * DELETE /users/{id}
889
   * @summary Delete a single user
890
   * @operationId deleteUser
891
   * @tags users - Operations of user controller
892
   * @param {integer} id.path.required - The id of the user
893
   * @security JWT
894
   * @return 204 - User successfully deleted
895
   * @return {string} 400 - Cannot delete yourself
896
   */
897
  public async deleteUser(req: RequestWithToken, res: Response): Promise<void> {
898
    const parameters = req.params;
5✔
899
    this.logger.trace('Delete individual user', parameters, 'by user', req.token.user);
5✔
900

901
    if (req.token.user.id === parseInt(parameters.id, 10)) {
5✔
902
      res.status(400).json('Cannot delete yourself');
1✔
903
      return;
1✔
904
    }
905

906
    try {
4✔
907
      const id = parseInt(parameters.id, 10);
4✔
908
      // Get the user object if it exists
909
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
910
      // If it does not exist, return a 404 error
911
      if (user == null) {
4✔
912
        res.status(404).json('Unknown user ID.');
2✔
913
        return;
2✔
914
      }
915

916
      user.deleted = true;
2✔
917
      await user.save();
2✔
918
      res.status(204).json('User deleted');
2✔
919
    } catch (error) {
920
      this.logger.error('Could not create product:', error);
×
921
      res.status(500).json('Internal server error.');
×
922
    }
923
  }
924

925
  /**
926
   * GET /users/nfc/{nfcCode}
927
   * @summary Get a user using the nfc code
928
   * @operationId findUserNfc
929
   * @tags users - Operations of the user controller
930
   * @security JWT
931
   * @param {string} nfcCode.path.required - The nfc code of the user
932
   * @return {UserResponse} 200 - The requested user
933
   * @return {string} 404 - The user with the given nfc code does not exist
934
   */
935
  public async findUserNfc(req: RequestWithToken, res: Response): Promise<void> {
936
    const parameters = req.params;
3✔
937
    this.logger.trace('Find user nfc', parameters, 'by user', req.token.user);
3✔
938

939
    try {
3✔
940
      const nfcCode = String(parameters.nfcCode);
3✔
941
      const nfc = await NfcAuthenticator.findOne({ where: { nfcCode } });
3✔
942

943
      if (nfc === null) {
3✔
944
        res.status(404).json('Unknown nfc code');
1✔
945
        return;
1✔
946
      }
947

948
      res.status(200).json(parseUserToResponse(nfc.user));
2✔
949
    } catch (error) {
950
      this.logger.error('Could not find user using nfc:', error);
×
951
      res.status(500).json('Internal server error.');
×
952
    }
953
  }
954

955
  /**
956
   * POST /users/acceptTos
957
   * @summary Accept the Terms of Service if you have not accepted it yet
958
   * @operationId acceptTos
959
   * @tags users - Operations of the User controller
960
   * @param {AcceptTosRequest} request.body.required - "Tosrequest body"
961
   * @security JWT
962
   * @return 204 - ToS accepted
963
   * @return {string} 400 - ToS already accepted
964
   */
965
  public async acceptToS(req: RequestWithToken, res: Response): Promise<void> {
966
    this.logger.trace('Accept ToS for user', req.token.user);
3✔
967

968
    const { id } = req.token.user;
3✔
969
    const body = req.body as AcceptTosRequest;
3✔
970

971
    try {
3✔
972
      const user = await UserService.getSingleUser(id);
3✔
973
      if (user == null) {
3!
974
        res.status(404).json('User not found.');
×
975
        return;
×
976
      }
977

978
      const success = await UserService.acceptToS(id, body);
3✔
979
      if (!success) {
3✔
980
        res.status(400).json('User already accepted ToS.');
1✔
981
        return;
1✔
982
      }
983

984
      res.status(204).json();
2✔
985
      return;
2✔
986
    } catch (error) {
987
      this.logger.error('Could not accept ToS for user:', error);
×
988
      res.status(500).json('Internal server error.');
×
989
    }
990
  }
991

992
  /**
993
   * GET /users/{id}/products
994
   * @summary Get an user's products
995
   * @operationId getUsersProducts
996
   * @tags users - Operations of user controller
997
   * @param {integer} id.path.required - The id of the user
998
   * @param {integer} take.query - How many products the endpoint should return
999
   * @param {integer} skip.query - How many products should be skipped (for pagination)
1000
   * @security JWT
1001
   * @return {PaginatedProductResponse} 200 - List of products.
1002
   */
1003
  public async getUsersProducts(req: RequestWithToken, res: Response): Promise<void> {
1004
    const parameters = req.params;
4✔
1005
    this.logger.trace("Get user's products", parameters, 'by user', req.token.user);
4✔
1006

1007
    let take;
1008
    let skip;
1009
    try {
4✔
1010
      const pagination = parseRequestPagination(req);
4✔
1011
      take = pagination.take;
4✔
1012
      skip = pagination.skip;
4✔
1013
    } catch (e) {
1014
      res.status(400).send(e.message);
×
1015
      return;
×
1016
    }
1017

1018
    // Handle request
1019
    try {
4✔
1020
      const id = parseInt(parameters.id, 10);
4✔
1021
      const owner = await User.findOne({ where: { id, deleted: false } });
4✔
1022
      if (owner == null) {
4✔
1023
        res.status(404).json({});
1✔
1024
        return;
1✔
1025
      }
1026

1027
      const products = await ProductService.getProducts({}, { take, skip }, owner);
3✔
1028
      res.json(products);
3✔
1029
    } catch (error) {
1030
      this.logger.error('Could not return all products:', error);
×
1031
      res.status(500).json('Internal server error.');
×
1032
    }
1033
  }
1034

1035
  /**
1036
   * GET /users/{id}/containers
1037
   * @summary Returns the user's containers
1038
   * @operationId getUsersContainers
1039
   * @tags users - Operations of user controller
1040
   * @param {integer} id.path.required - The id of the user
1041
   * @security JWT
1042
   * @param {integer} take.query - How many containers the endpoint should return
1043
   * @param {integer} skip.query - How many containers should be skipped (for pagination)
1044
   * @return {PaginatedContainerResponse} 200 - All users updated containers
1045
   * @return {string} 404 - Not found error
1046
   * @return {string} 500 - Internal server error
1047
   */
1048
  public async getUsersContainers(req: RequestWithToken, res: Response): Promise<void> {
1049
    const { id } = req.params;
4✔
1050
    this.logger.trace("Get user's containers", id, 'by user', req.token.user);
4✔
1051

1052
    let take;
1053
    let skip;
1054
    try {
4✔
1055
      const pagination = parseRequestPagination(req);
4✔
1056
      take = pagination.take;
4✔
1057
      skip = pagination.skip;
4✔
1058
    } catch (e) {
1059
      res.status(400).send(e.message);
×
1060
      return;
×
1061
    }
1062

1063
    // handle request
1064
    try {
4✔
1065
      // Get the user object if it exists
1066
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1067
      // If it does not exist, return a 404 error
1068
      if (user == null) {
4✔
1069
        res.status(404).json('Unknown user ID.');
1✔
1070
        return;
1✔
1071
      }
1072

1073
      const containers = (await ContainerService
3✔
1074
        .getContainers({}, { take, skip }, user));
1075
      res.json(containers);
3✔
1076
    } catch (error) {
1077
      this.logger.error('Could not return containers:', error);
×
1078
      res.status(500).json('Internal server error.');
×
1079
    }
1080
  }
1081

1082
  /**
1083
   * GET /users/{id}/pointsofsale
1084
   * @summary Returns the user's Points of Sale
1085
   * @operationId getUsersPointsOfSale
1086
   * @tags users - Operations of user controller
1087
   * @param {integer} id.path.required - The id of the user
1088
   * @param {integer} take.query - How many points of sale the endpoint should return
1089
   * @param {integer} skip.query - How many points of sale should be skipped (for pagination)
1090
   * @security JWT
1091
   * @return {PaginatedPointOfSaleResponse} 200 - All users updated point of sales
1092
   * @return {string} 404 - Not found error
1093
   * @return {string} 500 - Internal server error
1094
   */
1095
  public async getUsersPointsOfSale(req: RequestWithToken, res: Response): Promise<void> {
1096
    const { id } = req.params;
4✔
1097
    this.logger.trace("Get user's points of sale", id, 'by user', req.token.user);
4✔
1098

1099
    let take;
1100
    let skip;
1101
    try {
4✔
1102
      const pagination = parseRequestPagination(req);
4✔
1103
      take = pagination.take;
4✔
1104
      skip = pagination.skip;
4✔
1105
    } catch (e) {
1106
      res.status(400).send(e.message);
×
1107
      return;
×
1108
    }
1109

1110
    // handle request
1111
    try {
4✔
1112
      // Get the user object if it exists
1113
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1114
      // If it does not exist, return a 404 error
1115
      if (user == null) {
4✔
1116
        res.status(404).json('Unknown user ID.');
1✔
1117
        return;
1✔
1118
      }
1119

1120
      const pointsOfSale = (await PointOfSaleService
3✔
1121
        .getPointsOfSale({}, { take, skip }, user));
1122
      res.json(pointsOfSale);
3✔
1123
    } catch (error) {
1124
      this.logger.error('Could not return point of sale:', error);
×
1125
      res.status(500).json('Internal server error.');
×
1126
    }
1127
  }
1128

1129
  /**
1130
   * GET /users/{id}/transactions
1131
   * @summary Get transactions from a user.
1132
   * @operationId getUsersTransactions
1133
   * @tags users - Operations of user controller
1134
   * @param {integer} id.path.required - The id of the user that should be involved
1135
   * in all returned transactions
1136
   * @param {integer} fromId.query - From-user for selected transactions
1137
   * @param {integer} createdById.query - User that created selected transaction
1138
   * @param {integer} toId.query - To-user for selected transactions
1139
   * transactions. Requires ContainerId
1140
   * @param {integer} productId.query - Product ID for selected transactions
1141
   * @param {integer} productRevision.query - Product Revision for selected
1142
   * transactions. Requires ProductID
1143
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1144
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1145
   * @param {integer} take.query - How many transactions the endpoint should return
1146
   * @param {integer} skip.query - How many transactions should be skipped (for pagination)
1147
   * @security JWT
1148
   * @return {PaginatedBaseTransactionResponse} 200 - List of transactions.
1149
   */
1150
  public async getUsersTransactions(req: RequestWithToken, res: Response): Promise<void> {
1151
    const { id } = req.params;
4✔
1152
    this.logger.trace("Get user's", id, 'transactions by user', req.token.user);
4✔
1153

1154
    // Parse the filters given in the query parameters. If there are any issues,
1155
    // the parse method will throw an exception. We will then return a 400 error.
1156
    let filters;
1157
    try {
4✔
1158
      filters = parseGetTransactionsFilters(req);
4✔
1159
    } catch (e) {
1160
      res.status(400).json(e.message);
×
1161
      return;
×
1162
    }
1163

1164
    let take;
1165
    let skip;
1166
    try {
4✔
1167
      const pagination = parseRequestPagination(req);
4✔
1168
      take = pagination.take;
4✔
1169
      skip = pagination.skip;
4✔
1170
    } catch (e) {
1171
      res.status(400).send(e.message);
×
1172
      return;
×
1173
    }
1174

1175
    try {
4✔
1176
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
4✔
1177
      if (user == null) {
4✔
1178
        res.status(404).json({});
1✔
1179
        return;
1✔
1180
      }
1181
      const transactions = await new TransactionService().getTransactions(filters, { take, skip }, user);
3✔
1182

1183
      res.status(200).json(transactions);
3✔
1184
    } catch (error) {
1185
      this.logger.error('Could not return all transactions:', error);
×
1186
      res.status(500).json('Internal server error.');
×
1187
    }
1188
  }
1189

1190
  /**
1191
   * GET /users/{id}/transactions/sales/report
1192
   * @summary Get sales report for the given user
1193
   * @operationId getUsersSalesReport
1194
   * @tags users - Operations of user controller
1195
   * @param {integer} id.path.required - The id of the user to get the sales report for
1196
   * @security JWT
1197
   * @param {string} fromDate.query.required - Start date for selected sales (inclusive)
1198
   * @param {string} tillDate.query.required - End date for selected sales (exclusive)
1199
   * @return {ReportResponse} 200 - The sales report of the user
1200
   * @return {string} 400 - Validation error
1201
   * @return {string} 404 - User not found error.
1202
   */
1203
  public async getUsersSalesReport(req: RequestWithToken, res: Response): Promise<void> {
1204
    const { id } = req.params;
7✔
1205
    this.logger.trace('Get sales report for user ', id, ' by user', req.token.user);
7✔
1206

1207
    let filters: { fromDate: Date, tillDate: Date };
1208
    try {
7✔
1209
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1210
    } catch (e) {
1211
      res.status(400).json(e.message);
1✔
1212
      return;
1✔
1213
    }
1214

1215
    try {
6✔
1216
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
6✔
1217
      if (user == null) {
6✔
1218
        res.status(404).json('Unknown user ID.');
1✔
1219
        return;
1✔
1220
      }
1221

1222
      const report = await (new SalesReportService()).getReport({ ...filters, forId: user.id });
5✔
1223
      res.status(200).json(ReportService.reportToResponse(report));
5✔
1224
    } catch (error) {
1225
      this.logger.error('Could not get sales report:', error);
×
1226
      res.status(500).json('Internal server error.');
×
1227
    }
1228
  }
1229

1230
  /**
1231
   * GET /users/{id}/transactions/sales/report/pdf
1232
   * @summary Get sales report for the given user
1233
   * @operationId getUsersSalesReportPdf
1234
   * @tags users - Operations of user controller
1235
   * @param {integer} id.path.required - The id of the user to get the sales report for
1236
   * @security JWT
1237
   * @param {string} fromDate.query.required - Start date for selected sales (inclusive)
1238
   * @param {string} tillDate.query.required - End date for selected sales (exclusive)
1239
   * @param {string} description.query - Description of the report
1240
   * @param {string} fileType.query - enum:PDF,TEX - The file type of the report
1241
   * @return {string} 404 - User not found error.
1242
   * @returns {string} 200 - The requested report - application/pdf
1243
   * @return {string} 400 - Validation error
1244
   * @return {string} 500 - Internal server error
1245
   * @return {string} 502 - PDF generation failed
1246
   */
1247
  public async getUsersSalesReportPdf(req: RequestWithToken, res: Response): Promise<void> {
1248
    const { id } = req.params;
7✔
1249
    this.logger.trace('Get sales report pdf for user ', id, ' by user', req.token.user);
7✔
1250

1251
    let filters: { fromDate: Date, tillDate: Date };
1252
    let description: string;
1253
    let fileType: ReturnFileType;
1254
    try {
7✔
1255
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1256
      description = String(req.query.description);
4✔
1257
      fileType = asReturnFileType(req.query.fileType);
4✔
1258
    } catch (e) {
1259
      res.status(400).json(e.message);
4✔
1260
      return;
4✔
1261
    }
1262

1263
    try {
3✔
1264
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1265
      if (user == null) {
3✔
1266
        res.status(404).json('Unknown user ID.');
1✔
1267
        return;
1✔
1268
      }
1269
      const service = new SalesReportService();
2✔
1270
      await reportPDFhelper(res)(service, filters, description, user.id, UserReportParametersType.Sales, fileType);
2✔
1271
    } catch (error) {
1272
      this.logger.error('Could not get sales report:', error);
1✔
1273
      if (error instanceof PdfError) {
1✔
1274
        res.status(502).json('PDF Generator service failed.');
1✔
1275
        return;
1✔
1276
      }
1277
      res.status(500).json('Internal server error.');
×
1278
    }
1279
  }
1280

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

1301
    let filters: { fromDate: Date, tillDate: Date };
1302
    let description: string;
1303
    let fileType: ReturnFileType;
1304
    try {
7✔
1305
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1306
      description = String(req.query.description);
4✔
1307
      fileType = asReturnFileType(req.query.fileType);
4✔
1308
    } catch (e) {
1309
      res.status(400).json(e.message);
4✔
1310
      return;
4✔
1311
    }
1312

1313
    try {
3✔
1314
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1315
      if (user == null) {
3✔
1316
        res.status(404).json('Unknown user ID.');
1✔
1317
        return;
1✔
1318
      }
1319
      const service = new BuyerReportService();
2✔
1320
      await (reportPDFhelper(res))(service, filters, description, user.id, UserReportParametersType.Purchases, fileType);
2✔
1321
    } catch (error) {
1322
      this.logger.error('Could not get sales report:', error);
1✔
1323
      if (error instanceof PdfError) {
1✔
1324
        res.status(502).json('PDF Generator service failed.');
1✔
1325
        return;
1✔
1326
      }
1327
      res.status(500).json('Internal server error.');
×
1328
    }
1329
  }
1330

1331
  /**
1332
   * GET /users/{id}/transactions/purchases/report
1333
   * @summary Get purchases report for the given user
1334
   * @operationId getUsersPurchasesReport
1335
   * @tags users - Operations of user controller
1336
   * @param {integer} id.path.required - The id of the user to get the purchases report for
1337
   * @security JWT
1338
   * @param {string} fromDate.query.required - Start date for selected purchases (inclusive)
1339
   * @param {string} tillDate.query.required - End date for selected purchases (exclusive)
1340
   * @return {ReportResponse} 200 - The purchases report of the user
1341
   * @return {string} 400 - Validation error
1342
   * @return {string} 404 - User not found error.
1343
   */
1344
  public async getUsersPurchasesReport(req: RequestWithToken, res: Response): Promise<void> {
1345
    const { id } = req.params;
4✔
1346
    this.logger.trace('Get purchases report for user ', id, ' by user', req.token.user);
4✔
1347

1348
    let filters: { fromDate: Date, tillDate: Date };
1349
    try {
4✔
1350
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
4✔
1351
    } catch (e) {
1352
      res.status(400).json(e.message);
1✔
1353
      return;
1✔
1354
    }
1355

1356
    try {
3✔
1357
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1358
      if (user == null) {
3✔
1359
        res.status(404).json('Unknown user ID.');
1✔
1360
        return;
1✔
1361
      }
1362

1363
      const report = await (new BuyerReportService()).getReport({ ...filters, forId: user.id });
2✔
1364
      res.status(200).json(ReportService.reportToResponse(report));
2✔
1365
    } catch (error) {
1366
      this.logger.error('Could not get sales report:', error);
×
1367
      res.status(500).json('Internal server error.');
×
1368
    }
1369
  }
1370

1371
  /**
1372
   * GET /users/{id}/transfers
1373
   * @summary Get transfers to or from an user.
1374
   * @operationId getUsersTransfers
1375
   * @tags users - Operations of user controller
1376
   * @param {integer} id.path.required - The id of the user that should be involved
1377
   * in all returned transfers
1378
   * @param {integer} take.query - How many transfers the endpoint should return
1379
   * @param {integer} skip.query - How many transfers should be skipped (for pagination)
1380
   * @param {integer} fromId.query - From-user for selected transfers
1381
   * @param {integer} toId.query - To-user for selected transfers
1382
   * @param {integer} id.query - ID of selected transfers
1383
   * @security JWT
1384
   * @return {PaginatedTransferResponse} 200 - List of transfers.
1385
   */
1386
  public async getUsersTransfers(req: RequestWithToken, res: Response): Promise<void> {
1387
    const { id } = req.params;
2✔
1388
    this.logger.trace("Get user's transfers", id, 'by user', req.token.user);
2✔
1389

1390
    // Parse the filters given in the query parameters. If there are any issues,
1391
    // the parse method will throw an exception. We will then return a 400 error.
1392
    let filters;
1393
    try {
2✔
1394
      filters = parseGetTransferFilters(req);
2✔
1395
    } catch (e) {
1396
      res.status(400).json(e.message);
×
1397
      return;
×
1398
    }
1399

1400
    let take;
1401
    let skip;
1402
    try {
2✔
1403
      const pagination = parseRequestPagination(req);
2✔
1404
      take = pagination.take;
2✔
1405
      skip = pagination.skip;
2✔
1406
    } catch (e) {
1407
      res.status(400).send(e.message);
×
1408
      return;
×
1409
    }
1410

1411
    // handle request
1412
    try {
2✔
1413
      // Get the user object if it exists
1414
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
2✔
1415
      // If it does not exist, return a 404 error
1416
      if (user == null) {
2!
1417
        res.status(404).json('Unknown user ID.');
×
1418
        return;
×
1419
      }
1420

1421
      const transfers = (await new TransferService().getTransfers(
2✔
1422
        { ...filters }, { take, skip }, user,
1423
      ));
1424
      res.json(transfers);
2✔
1425
    } catch (error) {
1426
      this.logger.error('Could not return user transfers', error);
×
1427
      res.status(500).json('Internal server error.');
×
1428
    }
1429
  }
1430

1431
  /**
1432
   * GET /users/{id}/roles
1433
   * @summary Get all roles assigned to the user.
1434
   * @operationId getUserRoles
1435
   * @tags users - Operations of user controller
1436
   * @param {integer} id.path.required - The id of the user to get the roles from
1437
   * @security JWT
1438
   * @return {Array.<RoleWithPermissionsResponse>} 200 - The roles of the user
1439
   * @return {string} 404 - User not found error.
1440
   */
1441
  public async getUserRoles(req: RequestWithToken, res: Response): Promise<void> {
1442
    const parameters = req.params;
3✔
1443
    this.logger.trace('Get roles of user', parameters, 'by user', req.token.user);
3✔
1444

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

1455
      const rolesWithPermissions = await this.roleManager.getRoles(user, true);
2✔
1456
      const response = rolesWithPermissions.map((r) => RBACService.asRoleResponse(r));
2✔
1457
      res.status(200).json(response);
2✔
1458
    } catch (error) {
1459
      this.logger.error('Could not get roles of user:', error);
×
1460
      res.status(500).json('Internal server error.');
×
1461
    }
1462
  }
1463

1464
  /**
1465
   * GET /users/{id}/financialmutations
1466
   * @summary Get all financial mutations of a user (from or to).
1467
   * @operationId getUsersFinancialMutations
1468
   * @tags users - Operations of user controller
1469
   * @param {integer} id.path.required - The id of the user to get the mutations from
1470
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1471
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1472
   * @param {integer} take.query - How many transactions the endpoint should return
1473
   * @param {integer} skip.query - How many transactions should be skipped (for pagination)
1474
   * @security JWT
1475
   * @return {PaginatedFinancialMutationResponse} 200 - The financial mutations of the user
1476
   * @return {string} 404 - User not found error.
1477
   */
1478
  public async getUsersFinancialMutations(req: RequestWithToken, res: Response): Promise<void> {
1479
    const parameters = req.params;
4✔
1480
    this.logger.trace('Get financial mutations of user', parameters, 'by user', req.token.user);
4✔
1481

1482
    let filters;
1483
    let take;
1484
    let skip;
1485
    try {
4✔
1486
      filters = parseGetFinancialMutationsFilters(req);
4✔
1487
      const pagination = parseRequestPagination(req);
4✔
1488
      take = pagination.take;
4✔
1489
      skip = pagination.skip;
4✔
1490
    } catch (e) {
1491
      res.status(400).send(e.message);
×
1492
      return;
×
1493
    }
1494

1495
    try {
4✔
1496
      const id = parseInt(parameters.id, 10);
4✔
1497
      // Get the user object if it exists
1498
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
1499
      // If it does not exist, return a 404 error
1500
      if (user == null) {
4!
1501
        res.status(404).json('Unknown user ID.');
×
1502
        return;
×
1503
      }
1504

1505
      const mutations = await UserService.getUserFinancialMutations(user, filters, { take, skip });
4✔
1506
      res.status(200).json(mutations);
4✔
1507
    } catch (error) {
1508
      this.logger.error('Could not get financial mutations of user:', error);
×
1509
      res.status(500).json('Internal server error.');
×
1510
    }
1511
  }
1512

1513
  /**
1514
   * GET /users/{id}/deposits
1515
   * @summary Get all deposits of a user that are still being processed by Stripe
1516
   * @operationId getUsersProcessingDeposits
1517
   * @tags users - Operations of user controller
1518
   * @param {integer} id.path.required - The id of the user to get the deposits from
1519
   * @security JWT
1520
   * @return {Array.<RoleResponse>} 200 - The processing deposits of a user
1521
   * @return {string} 404 - User not found error.
1522
   */
1523
  public async getUsersProcessingDeposits(req: RequestWithToken, res: Response): Promise<void> {
1524
    const parameters = req.params;
2✔
1525
    this.logger.trace('Get users processing deposits from user', parameters.id);
2✔
1526

1527
    try {
2✔
1528
      const id = parseInt(parameters.id, 10);
2✔
1529

1530
      const user = await User.findOne({ where: { id } });
2✔
1531
      if (user == null) {
2✔
1532
        res.status(404).json('Unknown user ID.');
1✔
1533
        return;
1✔
1534
      }
1535

1536
      const deposits = await StripeService.getProcessingStripeDepositsFromUser(id);
1✔
1537
      res.status(200).json(deposits);
1✔
1538
    } catch (error) {
1539
      this.logger.error('Could not get processing deposits of user:', error);
×
1540
      res.status(500).json('Internal server error.');
×
1541
    }
1542
  }
1543

1544
  /**
1545
   * GET /users/{id}/transactions/report
1546
   * @summary Get transaction report for the given user
1547
   * @operationId getUsersTransactionsReport
1548
   * @tags users - Operations of user controller
1549
   * @param {integer} id.path.required - The id of the user to get the transaction report from
1550
   * @security JWT
1551
   * @return {Array.<TransactionReportResponse>} 200 - The transaction report of the user
1552
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1553
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1554
   * @param {integer} fromId.query - From-user for selected transactions
1555
   * @param {integer} toId.query - To-user for selected transactions
1556
   * @param {boolean} exclusiveToId.query - If all sub-transactions should be to the toId user, default true
1557
   * @deprecated - Use /users/{id}/transactions/sales/report or /users/{id}/transactions/purchases/report instead
1558
   * @return {string} 404 - User not found error.
1559
   */
1560
  public async getUsersTransactionsReport(req: RequestWithToken, res: Response): Promise<void> {
1561
    const parameters = req.params;
6✔
1562
    this.logger.trace('Get transaction report for user ', req.params.id, ' by user', req.token.user);
6✔
1563

1564
    let filters;
1565
    try {
6✔
1566
      filters = parseGetTransactionsFilters(req);
6✔
1567
    } catch (e) {
1568
      res.status(400).json(e.message);
1✔
1569
      return;
1✔
1570
    }
1571

1572
    try {
5✔
1573
      if ((filters.toId !== undefined && filters.fromId !== undefined) || (filters.toId === undefined && filters.fromId === undefined)) {
5✔
1574
        res.status(400).json('Need to provide either a toId or a fromId.');
2✔
1575
        return;
2✔
1576
      }
1577

1578
      const id = parseInt(parameters.id, 10);
3✔
1579

1580
      const user = await User.findOne({ where: { id } });
3✔
1581
      if (user == null) {
3✔
1582
        res.status(404).json('Unknown user ID.');
1✔
1583
        return;
1✔
1584
      }
1585

1586
      const report = await (new TransactionService()).getTransactionReportResponse(filters);
2✔
1587
      res.status(200).json(report);
2✔
1588
    } catch (e) {
1589
      res.status(500).send();
×
1590
      this.logger.error(e);
×
1591
    }
1592
  }
1593

1594
  /**
1595
   * POST /users/{id}/fines/waive
1596
   * @summary Waive all given user's fines
1597
   * @tags users - Operations of user controller
1598
   * @param {integer} id.path.required - The id of the user
1599
   * @param {WaiveFinesRequest} request.body
1600
   * Optional body, see https://github.com/GEWIS/sudosos-backend/pull/344
1601
   * @operationId waiveUserFines
1602
   * @security JWT
1603
   * @return 204 - Success
1604
   * @return {string} 400 - User has no fines.
1605
   * @return {string} 404 - User not found error.
1606
   */
1607
  public async waiveUserFines(req: RequestWithToken, res: Response): Promise<void> {
1608
    const { id: rawId } = req.params;
9✔
1609
    const body = req.body as WaiveFinesRequest;
9✔
1610
    this.logger.trace('Waive fines', body, 'of user', rawId, 'by', req.token.user);
9✔
1611

1612
    try {
9✔
1613
      const id = parseInt(rawId, 10);
9✔
1614

1615
      const user = await User.findOne({ where: { id }, relations: { currentFines: { fines: true } } });
9✔
1616
      if (user == null) {
9✔
1617
        res.status(404).json('Unknown user ID.');
1✔
1618
        return;
1✔
1619
      }
1620
      if (user.currentFines == null) {
8✔
1621
        res.status(400).json('User has no fines.');
1✔
1622
        return;
1✔
1623
      }
1624

1625
      const totalAmountOfFines = user.currentFines!.fines.reduce((total, f) => total.add(f.amount), Dinero());
14✔
1626
      // Backwards compatibility with old version, where you could only waive all user's fines
1627
      const amountToWaive = body?.amount ?? totalAmountOfFines.toObject();
7✔
1628
      if (amountToWaive.amount <= 0) {
7✔
1629
        res.status(400).json('Amount to waive cannot be zero or negative.');
2✔
1630
        return;
2✔
1631
      }
1632
      if (amountToWaive.amount > totalAmountOfFines.getAmount()) {
5✔
1633
        res.status(400).json('Amount to waive cannot be more than the total amount of fines.');
1✔
1634
        return;
1✔
1635
      }
1636

1637
      await new DebtorService().waiveFines(id, { amount: amountToWaive } as WaiveFinesParams);
4✔
1638
      res.status(204).send();
4✔
1639
    } catch (e) {
1640
      res.status(500).send();
×
1641
      this.logger.error(e);
×
1642
    }
1643
  }
1644

1645
  /**
1646
   * DELETE /users/{id}/roles/{roleId}
1647
   * @summary Deletes a role from a user
1648
   * @tags users - Operations of user controller
1649
   * @param {integer} id.path.required - The id of the user
1650
   * @param {integer} roleId.path.required - The id of the role
1651
   * @operationId deleteUserRole
1652
   * @security JWT
1653
   * @return 204 - Success
1654
   */
1655
  public async deleteUserRole(req: RequestWithToken, res: Response): Promise<void> {
1656
    const { id: rawUserId, roleId: rawRoleId } = req.params;
4✔
1657

1658
    const userId = parseInt(rawUserId, 10);
4✔
1659
    const roleId = parseInt(rawRoleId, 10);
4✔
1660

1661
    const user = await User.findOne({ where: { id: userId } });
4✔
1662
    if (!user) {
4✔
1663
      res.status(404).json('user not found');
1✔
1664
      return;
1✔
1665
    }
1666
    const role = await Role.findOne({ where: { id: roleId } });
3✔
1667
    if (!role) {
3✔
1668
      res.status(404).json('role not found');
1✔
1669
      return;
1✔
1670
    }
1671

1672
    await UserService.deleteUserRole(user, role);
2✔
1673
    res.status(204).send();
2✔
1674
  }
1675

1676
  /**
1677
   * POST /users/{id}/roles
1678
   * @summary Adds a role to a user
1679
   * @tags users - Operations of user controller
1680
   * @param {integer} id.path.required - The id of the user
1681
   * @param {AddRoleRequest} request.body.required
1682
   * @operationId addUserRole
1683
   * @security JWT
1684
   * @return 204 - Success
1685
   */
1686
  public async addUserRole(req: RequestWithToken, res: Response): Promise<void> {
1687
    const { id: rawUserId } = req.params;
2✔
1688
    const userId = parseInt(rawUserId, 10);
2✔
1689
    const { roleId } = req.body as AddRoleRequest;
2✔
1690

1691
    const user = await User.findOne({ where: { id: userId } });
2✔
1692
    if (!user) {
2!
1693
      res.status(404).json('user not found');
×
1694
      return;
×
1695
    }
1696
    const role = await Role.findOne({ where: { id: roleId } });
2✔
1697
    if (!role) {
2✔
1698
      res.status(404).json('role not found');
1✔
1699
      return;
1✔
1700
    }
1701

1702
    await UserService.addUserRole(user, role);
1✔
1703
    res.status(204).send();
1✔
1704
  }
1705

1706
  /**
1707
   * GET /users/{id}/wrapped
1708
   * @summary Get wrapped for a user
1709
   * @operationId getWrapped
1710
   * @tags users - Operations of user controller
1711
   * @param {integer} id.path.required - The id of the user
1712
   * @security JWT
1713
   * @return {WrappedResponse} 200 - The requested user's wrapped
1714
   * @return {string} 404 - Wrapped for user not found error
1715
   * @return {string} 500 - Internal server error
1716
   */
1717
  private async getUserWrapped(req: RequestWithToken, res: Response): Promise<void> {
1718
    const { id: rawUserId } = req.params;
4✔
1719

1720
    try {
4✔
1721
      const userId = parseInt(rawUserId, 10);
4✔
1722
    
1723
      const wrappedRes = await new WrappedService().getWrappedForUser(userId);
4✔
1724
      if (wrappedRes == null) {
4✔
1725
        res.status(404).json('Wrapped not found for user');
1✔
1726
        return;
1✔
1727
      }
1728

1729
      res.json(WrappedService.asWrappedResponse(wrappedRes));
3✔
1730
    } catch (error) {
1731
      res.status(500).json({ message: 'Internal server error' });
×
1732
    }
1733
  }
1734

1735
  /**
1736
   * POST /users/{id}/wrapped
1737
   * @summary Recompute wrapped for a user
1738
   * @operationId updateWrapped
1739
   * @tags users - Operations of user controller
1740
   * @param {integer} id.path.required - The id of the user
1741
   * @security JWT
1742
   * @return {WrappedResponse} 200 - The requested user's wrapped
1743
   * @return {string} 500 - Internal server error
1744
   */
1745
  private async computedWrapped(req: RequestWithToken, res: Response): Promise<void> {
1746
    const { id: rawUserId } = req.params;
2✔
1747

1748
    try {
2✔
1749
      const userId = parseInt(rawUserId, 10);
2✔
1750

1751
      res.json(await new WrappedService().updateWrapped({ ids: [userId] }));
2✔
1752
    } catch (error) {
1753
      res.status(500).json({ message: 'Internal server error' });
×
1754
    }
1755
  }
1756

1757
  /**
1758
   * GET /users/{id}/settings
1759
   * @summary Get all user settings
1760
   * @operationId getUserSettings
1761
   * @tags users - Operations of user controller
1762
   * @param {integer} id.path.required - The id of the user
1763
   * @security JWT
1764
   * @return {UserSettingsResponse} 200 - The user's settings
1765
   * @return {string} 404 - User not found
1766
   * @return {string} 500 - Internal server error
1767
   */
1768
  private async getUserSettings(req: RequestWithToken, res: Response): Promise<void> {
1769
    const { id: rawUserId } = req.params;
5✔
1770

1771
    try {
5✔
1772
      const userId = parseInt(rawUserId, 10);
5✔
1773
      
1774
      const user = await User.findOne({ where: { id: userId } });
5✔
1775
      if (!user) {
5✔
1776
        res.status(404).json('User not found.');
1✔
1777
        return;
1✔
1778
      }
1779

1780
      const store = new UserSettingsStore();
4✔
1781
      const settings = await store.getAllSettings(userId);
4✔
1782
      const response = UserSettingsStore.toResponse(settings);
4✔
1783
      res.json(response);
4✔
1784
    } catch (error) {
1785
      this.logger.error('Could not get user settings:', error);
×
1786
      res.status(500).json('Internal server error.');
×
1787
    }
1788
  }
1789

1790
  /**
1791
   * PATCH /users/{id}/settings
1792
   * @summary Update user settings
1793
   * @operationId patchUserSettings
1794
   * @tags users - Operations of user controller
1795
   * @param {integer} id.path.required - The id of the user
1796
   * @param {PatchUserSettingsRequest} request.body.required - The settings to update
1797
   * @security JWT
1798
   * @return {UserSettingsResponse} 200 - The updated user settings
1799
   * @return {string} 404 - User not found
1800
   * @return {string} 500 - Internal server error
1801
   */
1802
  private async patchUserSettings(req: RequestWithToken, res: Response): Promise<void> {
1803
    const { id: rawUserId } = req.params;
10✔
1804
    const body = req.body as PatchUserSettingsRequest;
10✔
1805

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

1815
      const store = new UserSettingsStore();
9✔
1816
      await store.setSettings(userId, body);
9✔
1817

1818
      const settings = await store.getAllSettings(userId);
9✔
1819

1820
      res.json(UserSettingsStore.toResponse(settings));
9✔
1821
    } catch (error) {
1822
      this.logger.error('Could not update user settings:', error);
×
1823
      res.status(500).json('Internal server error.');
×
1824
    }
1825
  }
1826

1827
  /**
1828
   * PATCH /users/{id}/usertype
1829
   * @summary Update user type
1830
   * @operationId patchUserType
1831
   * @tags users - Operations of user controller
1832
   * @param {integer} id.path.required - The id of the user
1833
   * @param {PatchUserTypeRequest} request.body.required - The user type to update to
1834
   * @security JWT
1835
   * @return {UserResponse} 200 - The updated user
1836
   * @return {string} 404 - User not found
1837
   * @return {string} 400 - Bad request
1838
   * @return {string} 409 - Conflict error
1839
   * @return {string} 422 - Unprocessable entity
1840
   * @return {string} 500 - Internal server error
1841
   */
1842
  private async patchUserType(req: RequestWithToken, res: Response): Promise<void> {
1843
    const { id: rawUserId } = req.params;
5✔
1844
    const body = req.body as PatchUserTypeRequest;
5✔
1845

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

1855
      const allowedTypes = [UserType.MEMBER, UserType.LOCAL_USER];
4✔
1856

1857
      if (!allowedTypes.includes(user.type)) {
4✔
1858
        res.status(422).json('It is not possible to change the user type for users of this type.');
1✔
1859
        return;
1✔
1860
      }
1861

1862
      if (!allowedTypes.includes(body.userType)) {
3✔
1863
        res.status(422).json(`User type can only be changed to [${allowedTypes}].`);
1✔
1864
        return;
1✔
1865
      }
1866

1867
      if (body.userType === user.type) {
2✔
1868
        res.status(409).json('User is already of this type.');
1✔
1869
        return;
1✔
1870
      }
1871

1872
      if (!user.email) {
1!
1873
        res.status(400).json('Cannot change user type of user without email.');
×
1874
        return;
×
1875
      }
1876

1877
      if (body.userType === UserType.MEMBER && !user.memberUser) {
1!
1878
        res.status(400).json('Cannot change to MEMBER since no memberId is associated to this user.');
×
1879
        return;
×
1880
      }
1881

1882
      await UserService.updateUserType(user, body.userType);
1✔
1883
      res.status(200).json(
1✔
1884
        await UserService.getSingleUser(userId),
1885
      );
1886
    } catch (e) {
1887
      res.status(500).send('Internal server error.');
×
1888
      this.logger.error(e);
×
1889
    }
1890
  }
1891
}
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