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

GEWIS / sudosos-backend / 21110430966

18 Jan 2026 10:44AM UTC coverage: 89.59% (-0.003%) from 89.593%
21110430966

push

github

web-flow
feat: add user settings store (#699)

1671 of 2017 branches covered (82.85%)

Branch coverage included in aggregate %.

71 of 79 new or added lines in 5 files covered. (89.87%)

26 existing lines in 2 files now uncovered.

8682 of 9539 relevant lines covered (91.02%)

1013.72 hits per line

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

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

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

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

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

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

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

375
  /**
376
   * Function to determine which credentials are needed to GET
377
   *    'all' if user is not connected to User
378
   *    'organ' if user is connected to User via organ
379
   *    'own' if user is connected to User
380
   * @param req
381
   * @return whether User is connected to used token
382
   */
383
  static getRelation(req: RequestWithToken): string {
384
    if (userTokenInOrgan(req, asNumber(req.params.id))) return 'organ';
176✔
385
    return req.params.id === req.token.user.id.toString() ? 'own' : 'all';
175✔
386
  }
387

388
  static getAttributes(req: RequestWithToken): string[] {
389
    const attributes: string[] = [];
15✔
390
    const body = req.body as BaseUserRequest;
15✔
391
    for (const key in body) {
15✔
392
      if (body.hasOwnProperty(key)) {
17✔
393
        attributes.push(key);
17✔
394
      }
395
    }
396
    return attributes;
15✔
397
  }
398

399
  /**
400
   * GET /users
401
   * @summary Get a list of all users
402
   * @operationId getAllUsers
403
   * @tags users - Operations of user controller
404
   * @security JWT
405
   * @param {integer} take.query - How many users the endpoint should return
406
   * @param {integer} skip.query - How many users should be skipped (for pagination)
407
   * @param {string} search.query - Filter based on first name
408
   * @param {boolean} active.query - Filter based if the user is active
409
   * @param {boolean} ofAge.query - Filter based if the user is 18+
410
   * @param {integer} id.query - Filter based on user ID
411
   * @param {string} type.query - enum:MEMBER,ORGAN,VOUCHER,LOCAL_USER,LOCAL_ADMIN,INVOICE,AUTOMATIC_INVOICE - Filter based on user type.
412
   * @return {PaginatedUserResponse} 200 - A list of all users
413
   */
414
  public async getAllUsers(req: RequestWithToken, res: Response): Promise<void> {
415
    this.logger.trace('Get all users by user', req.token.user);
10✔
416

417
    let take;
418
    let skip;
419
    let filters: UserFilterParameters;
420
    try {
10✔
421
      const pagination = parseRequestPagination(req);
10✔
422
      filters = parseGetUsersFilters(req);
10✔
423
      take = pagination.take;
10✔
424
      skip = pagination.skip;
10✔
425
    } catch (e) {
426
      res.status(400).send(e.message);
×
427
      return;
×
428
    }
429

430
    try {
10✔
431
      const users = await UserService.getUsers(filters, { take, skip });
10✔
432
      res.status(200).json(users);
10✔
433
    } catch (error) {
434
      this.logger.error('Could not get users:', error);
×
435
      res.status(500).json('Internal server error.');
×
436
    }
437
  }
438

439
  /**
440
   * GET /users/usertype/{userType}
441
   * @summary Get all users of user type
442
   * @operationId getAllUsersOfUserType
443
   * @tags users - Operations of user controller
444
   * @param {string} userType.path.required - The userType of the requested users
445
   * @security JWT
446
   * @param {integer} take.query - How many users the endpoint should return
447
   * @param {integer} skip.query - How many users should be skipped (for pagination)
448
   * @return {PaginatedUserResponse} 200 - A list of all users
449
   * @return {string} 404 - Nonexistent usertype
450
   */
451
  public async getAllUsersOfUserType(req: RequestWithToken, res: Response): Promise<void> {
452
    const parameters = req.params;
5✔
453
    this.logger.trace('Get all users of userType', parameters, 'by user', req.token.user);
5✔
454
    const userType = req.params.userType.toUpperCase();
5✔
455

456
    // If it does not exist, return a 404 error
457
    const type = UserType[userType as keyof typeof UserType];
5✔
458
    if (!type || Number(userType)) {
5✔
459
      res.status(404).json('Unknown userType.');
1✔
460
      return;
1✔
461
    }
462

463
    try {
4✔
464
      req.query.type = userType;
4✔
465
      await this.getAllUsers(req, res);
4✔
466
    } catch (error) {
467
      this.logger.error('Could not get users:', error);
×
468
      res.status(500).json('Internal server error.');
×
469
    }
470
  }
471

472
  /**
473
   * PUT /users/{id}/authenticator/pin
474
   * @summary Put an users pin code
475
   * @operationId updateUserPin
476
   * @tags users - Operations of user controller
477
   * @param {integer} id.path.required - The id of the user
478
   * @param {UpdatePinRequest} request.body.required -
479
   *    The PIN code to update to
480
   * @security JWT
481
   * @return 204 - Update success
482
   * @return {string} 400 - Validation Error
483
   * @return {string} 404 - Nonexistent user id
484
   */
485
  public async updateUserPin(req: RequestWithToken, res: Response): Promise<void> {
486
    const { params } = req;
3✔
487
    const updatePinRequest = req.body as UpdatePinRequest;
3✔
488
    this.logger.trace('Update user pin', params, 'by user', req.token.user);
3✔
489

490
    try {
3✔
491
      // Get the user object if it exists
492
      const user = await User.findOne({ where: { id: parseInt(params.id, 10), deleted: false } });
3✔
493
      // If it does not exist, return a 404 error
494
      if (user == null) {
3✔
495
        res.status(404).json('Unknown user ID.');
1✔
496
        return;
1✔
497
      }
498

499
      const validation = await verifyUpdatePinRequest(updatePinRequest);
2✔
500
      if (isFail(validation)) {
2✔
501
        res.status(400).json(validation.fail.value);
1✔
502
        return;
1✔
503
      }
504

505
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
506
        updatePinRequest.pin.toString(), PinAuthenticator);
507
      res.status(204).json();
1✔
508
    } catch (error) {
509
      this.logger.error('Could not update pin:', error);
×
510
      res.status(500).json('Internal server error.');
×
511
    }
512
  }
513

514
  /**
515
   * PUT /users/{id}/authenticator/nfc
516
   * @summary Put a users NFC code
517
   * @operationId updateUserNfc
518
   * @tags users - Operations of user controller
519
   * @param {integer} id.path.required - The id of the user
520
   * @param {UpdateNfcRequest} request.body.required -
521
   *    The NFC code to update to
522
   * @security JWT
523
   * @return 204 - Update success
524
   * @return {string} 400 - Validation Error
525
   * @return {string} 404 - Nonexistent user id
526
   */
527
  public async updateUserNfc(req: RequestWithToken, res: Response): Promise<void> {
528
    const { params } = req;
10✔
529
    const updateNfcRequest = req.body as UpdateNfcRequest;
10✔
530
    this.logger.trace('Update user NFC', params, 'by user', req.token.user);
10✔
531

532
    try {
10✔
533
      // Get the user object if it exists
534
      const user = await User.findOne({ where: { id: parseInt(params.id, 10), deleted: false } });
10✔
535
      // If it does not exist, return a 404 error
536
      if (user == null) {
10✔
537
        res.status(404).json('Unknown user ID.');
1✔
538
        return;
1✔
539
      }
540

541
      const validation = await verifyUpdateNfcRequest(updateNfcRequest);
9✔
542
      if (isFail(validation)) {
9✔
543
        res.status(400).json(validation.fail.value);
2✔
544
        return;
2✔
545
      }
546

547
      await new AuthenticationService().setUserAuthenticationNfc(user,
7✔
548
        updateNfcRequest.nfcCode.toString(), NfcAuthenticator);
549
      res.status(204).json();
7✔
550
    } catch (error) {
551
      this.logger.error('Could not update NFC:', error);
×
552
      res.status(500).json('Internal server error.');
×
553
    }
554
  }
555

556
  /**
557
   * DELETE /users/{id}/authenticator/nfc
558
   * @summary Delete a nfc code
559
   * @operationId deleteUserNfc
560
   * @tags users - Operations of user controller
561
   * @param {integer} id.path.required - The id of the user
562
   * @security JWT
563
   * @return 200 - Delete nfc success
564
   * @return {string} 400 - Validation Error
565
   * @return {string} 403 - Nonexistent user nfc
566
   * @return {string} 404 - Nonexistent user id
567
   */
568
  public async deleteUserNfc(req: RequestWithToken, res: Response): Promise<void> {
569
    const parameters = req.params;
9✔
570
    this.logger.trace('Delete user NFC', parameters, 'by user', req.token.user);
9✔
571

572
    try {
9✔
573
      // Get the user object if it exists
574
      const user = await User.findOne({ where: { id: parseInt(parameters.id, 10), deleted: false } });
9✔
575
      // If it does not exist, return a 404 error
576
      if (user == null) {
9✔
577
        res.status(404).json('Unknown user ID.');
3✔
578
        return;
3✔
579
      }
580

581
      if (await NfcAuthenticator.count({ where: { userId: parseInt(parameters.id, 10) } }) == 0) {
6✔
582
        res.status(403).json('No saved nfc');
3✔
583
        return;
3✔
584
      }
585

586
      await NfcAuthenticator.delete(parseInt(parameters.id, 10));
3✔
587
      res.status(204).json();
3✔
588
    } catch (error) {
589
      this.logger.error('Could not update NFC:', error);
×
590
      res.status(500).json('Internal server error.');
×
591
    }
592
  }
593

594
  /**
595
   * POST /users/{id}/authenticator/key
596
   * @summary POST an users update to new key code
597
   * @operationId updateUserKey
598
   * @tags users - Operations of user controller
599
   * @param {integer} id.path.required - The id of the user
600
   * @security JWT
601
   * @return {UpdateKeyResponse} 200 - The new key
602
   * @return {string} 400 - Validation Error
603
   * @return {string} 404 - Nonexistent user id
604
   */
605
  public async updateUserKey(req: RequestWithToken, res: Response): Promise<void> {
606
    const { params } = req;
2✔
607
    this.logger.trace('Update user key', params, 'by user', req.token.user);
2✔
608

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

619
      const generatedKey = randomBytes(128).toString('hex');
1✔
620
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
621
        generatedKey, KeyAuthenticator);
622
      const response = { key: generatedKey } as UpdateKeyResponse;
1✔
623
      res.status(200).json(response);
1✔
624
    } catch (error) {
625
      this.logger.error('Could not update key:', error);
×
626
      res.status(500).json('Internal server error.');
×
627
    }
628
  }
629

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

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

654

655
      await KeyAuthenticator.delete(parseInt(params.id, 10));
1✔
656
      res.status(204).json();
1✔
657
    } catch (error) {
658
      this.logger.error('Could not delete key:', error);
×
659
      res.status(500).json('Internal server error.');
×
660
    }
661
  }
662

663
  /**
664
   * PUT /users/{id}/authenticator/local
665
   * @summary Put a user's local password
666
   * @operationId updateUserLocalPassword
667
   * @tags users - Operations of user controller
668
   * @param {integer} id.path.required - The id of the user
669
   * @param {UpdateLocalRequest} request.body.required -
670
   *    The password update
671
   * @security JWT
672
   * @return 204 - Update success
673
   * @return {string} 400 - Validation Error
674
   * @return {string} 404 - Nonexistent user id
675
   */
676
  public async updateUserLocalPassword(req: RequestWithToken, res: Response): Promise<void> {
677
    const parameters = req.params;
3✔
678
    const updateLocalRequest = req.body as UpdateLocalRequest;
3✔
679
    this.logger.trace('Update user local password', parameters, 'by user', req.token.user);
3✔
680

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

691
      const validation = await verifyUpdateLocalRequest(updateLocalRequest);
2✔
692
      if (isFail(validation)) {
2✔
693
        res.status(400).json(validation.fail.value);
1✔
694
        return;
1✔
695
      }
696

697
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
698
        updateLocalRequest.password, LocalAuthenticator);
699
      res.status(204).json();
1✔
700
    } catch (error) {
701
      this.logger.error('Could not update local password:', error);
×
702
      res.status(500).json('Internal server error.');
×
703
    }
704
  }
705

706
  /**
707
   * GET /users/{id}/members
708
   * @summary Get an organs members
709
   * @operationId getOrganMembers
710
   * @tags users - Operations of user controller
711
   * @param {integer} id.path.required - The id of the user
712
   * @param {integer} take.query - How many members the endpoint should return
713
   * @param {integer} skip.query - How many members should be skipped (for pagination)
714
   * @security JWT
715
   * @return {PaginatedUserResponse} 200 - All members of the organ
716
   * @return {string} 404 - Nonexistent user id
717
   * @return {string} 400 - User is not an organ
718
   */
719
  public async getOrganMembers(req: RequestWithToken, res: Response): Promise<void> {
720
    const parameters = req.params;
4✔
721
    this.logger.trace('Get organ members', parameters, 'by user', req.token.user);
4✔
722

723
    let take;
724
    let skip;
725
    try {
4✔
726
      const pagination = parseRequestPagination(req);
4✔
727
      take = pagination.take;
4✔
728
      skip = pagination.skip;
4✔
729
    } catch (e) {
730
      res.status(400).send(e.message);
×
731
      return;
×
732
    }
733

734
    try {
4✔
735
      const organId = asNumber(parameters.id);
4✔
736
      // Get the user object if it exists
737
      const user = await User.findOne({ where: { id: organId } });
4✔
738
      // If it does not exist, return a 404 error
739
      if (user == null) {
4✔
740
        res.status(404).json('Unknown user ID.');
1✔
741
        return;
1✔
742
      }
743

744
      if (user.type !== UserType.ORGAN) {
3✔
745
        res.status(400).json('User is not of type Organ');
1✔
746
        return;
1✔
747
      }
748

749
      const members = await UserService.getUsers({ organId }, { take, skip });
2✔
750
      res.status(200).json(members);
2✔
751
    } catch (error) {
752
      this.logger.error('Could not get organ members:', error);
×
753
      res.status(500).json('Internal server error.');
×
754
    }
755
  }
756

757
  /**
758
   * GET /users/{id}
759
   * @summary Get an individual user
760
   * @operationId getIndividualUser
761
   * @tags users - Operations of user controller
762
   * @param {integer} id.path.required - userID
763
   * @security JWT
764
   * @return {UserResponse} 200 - Individual user
765
   * @return {string} 404 - Nonexistent user id
766
   */
767
  public async getIndividualUser(req: RequestWithToken, res: Response): Promise<void> {
768
    const parameters = req.params;
7✔
769
    this.logger.trace('Get individual user', parameters, 'by user', req.token.user);
7✔
770

771
    try {
7✔
772
      // Get the user object if it exists
773
      const user = await UserService.getSingleUser(asNumber(parameters.id));
7✔
774
      // If it does not exist, return a 404 error
775
      if (user == null) {
7✔
776
        res.status(404).json('Unknown user ID.');
3✔
777
        return;
3✔
778
      }
779

780
      res.status(200).json(user);
4✔
781
    } catch (error) {
782
      this.logger.error('Could not get individual user:', error);
×
783
      res.status(500).json('Internal server error.');
×
784
    }
785
  }
786

787
  /**
788
   * POST /users
789
   * @summary Create a new user
790
   * @operationId createUser
791
   * @tags users - Operations of user controller
792
   * @param {CreateUserRequest} request.body.required -
793
   * The user which should be created
794
   * @security JWT
795
   * @return {UserResponse} 200 - New user
796
   * @return {string} 400 - Bad request
797
   */
798
  // eslint-disable-next-line class-methods-use-this
799
  public async createUser(req: RequestWithToken, res: Response): Promise<void> {
800
    const body = req.body as CreateUserRequest;
7✔
801
    this.logger.trace('Create user', body, 'by user', req.token.user);
7✔
802

803
    try {
7✔
804
      const validation = await verifyCreateUserRequest(body);
7✔
805
      if (isFail(validation)) {
7✔
806
        res.status(400).json(validation.fail.value);
3✔
807
        return;
3✔
808
      }
809

810
      const user = await UserService.createUser(body);
4✔
811
      res.status(201).json(user);
4✔
812
    } catch (error) {
813
      this.logger.error('Could not create user:', error);
×
814
      res.status(500).json('Internal server error.');
×
815
    }
816
  }
817

818
  /**
819
   * PATCH /users/{id}
820
   * @summary Update a user
821
   * @operationId updateUser
822
   * @tags users - Operations of user controller
823
   * @param {integer} id.path.required - The id of the user
824
   * @param {UpdateUserRequest} request.body.required - The user which should be updated
825
   * @security JWT
826
   * @return {UserResponse} 200 - New user
827
   * @return {string} 400 - Bad request
828
   */
829
  public async updateUser(req: RequestWithToken, res: Response): Promise<void> {
830
    const body = req.body as UpdateUserRequest;
13✔
831
    const parameters = req.params;
13✔
832
    this.logger.trace('Update user', parameters.id, 'with', body, 'by user', req.token.user);
13✔
833

834
    if (body.firstName !== undefined && body.firstName.length === 0) {
13✔
835
      res.status(400).json('firstName cannot be empty');
1✔
836
      return;
1✔
837
    }
838
    if (body.firstName !== undefined && body.firstName.length > 64) {
12✔
839
      res.status(400).json('firstName too long');
1✔
840
      return;
1✔
841
    }
842
    if (body.lastName !== undefined && body.lastName.length > 64) {
11✔
843
      res.status(400).json('lastName too long');
1✔
844
      return;
1✔
845
    }
846
    if (body.nickname !== undefined && body.nickname.length > 64) {
10✔
847
      res.status(400).json('nickname too long');
1✔
848
      return;
1✔
849
    }
850
    if (body.nickname === '') body.nickname = null;
9✔
851

852
    try {
9✔
853
      const id = parseInt(parameters.id, 10);
9✔
854
      // Get the user object if it exists
855
      let user = await User.findOne({ where: { id, deleted: false } });
9✔
856
      // If it does not exist, return a 404 error
857
      if (user == null) {
9!
858
        res.status(404).json('Unknown user ID.');
×
859
        return;
×
860
      }
861

862
      user = {
9✔
863
        ...body,
864
      } as User;
865
      await User.update(parameters.id, user);
9✔
866
      res.status(200).json(
9✔
867
        await UserService.getSingleUser(asNumber(parameters.id)),
868
      );
869
    } catch (error) {
870
      this.logger.error('Could not update user:', error);
×
871
      res.status(500).json('Internal server error.');
×
872
    }
873
  }
874

875
  /**
876
   * DELETE /users/{id}
877
   * @summary Delete a single user
878
   * @operationId deleteUser
879
   * @tags users - Operations of user controller
880
   * @param {integer} id.path.required - The id of the user
881
   * @security JWT
882
   * @return 204 - User successfully deleted
883
   * @return {string} 400 - Cannot delete yourself
884
   */
885
  public async deleteUser(req: RequestWithToken, res: Response): Promise<void> {
886
    const parameters = req.params;
5✔
887
    this.logger.trace('Delete individual user', parameters, 'by user', req.token.user);
5✔
888

889
    if (req.token.user.id === parseInt(parameters.id, 10)) {
5✔
890
      res.status(400).json('Cannot delete yourself');
1✔
891
      return;
1✔
892
    }
893

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

904
      user.deleted = true;
2✔
905
      await user.save();
2✔
906
      res.status(204).json('User deleted');
2✔
907
    } catch (error) {
908
      this.logger.error('Could not create product:', error);
×
909
      res.status(500).json('Internal server error.');
×
910
    }
911
  }
912

913
  /**
914
   * GET /users/nfc/{nfcCode}
915
   * @summary Get a user using the nfc code
916
   * @operationId findUserNfc
917
   * @tags users - Operations of the user controller
918
   * @security JWT
919
   * @param {string} nfcCode.path.required - The nfc code of the user
920
   * @return {UserResponse} 200 - The requested user
921
   * @return {string} 404 - The user with the given nfc code does not exist
922
   */
923
  public async findUserNfc(req: RequestWithToken, res: Response): Promise<void> {
924
    const parameters = req.params;
3✔
925
    this.logger.trace('Find user nfc', parameters, 'by user', req.token.user);
3✔
926

927
    try {
3✔
928
      const nfcCode = String(parameters.nfcCode);
3✔
929
      const nfc = await NfcAuthenticator.findOne({ where: { nfcCode } });
3✔
930

931
      if (nfc === null) {
3✔
932
        res.status(404).json('Unknown nfc code');
1✔
933
        return;
1✔
934
      }
935

936
      res.status(200).json(parseUserToResponse(nfc.user));
2✔
937
    } catch (error) {
938
      this.logger.error('Could not find user using nfc:', error);
×
939
      res.status(500).json('Internal server error.');
×
940
    }
941
  }
942

943
  /**
944
   * POST /users/acceptTos
945
   * @summary Accept the Terms of Service if you have not accepted it yet
946
   * @operationId acceptTos
947
   * @tags users - Operations of the User controller
948
   * @param {AcceptTosRequest} request.body.required - "Tosrequest body"
949
   * @security JWT
950
   * @return 204 - ToS accepted
951
   * @return {string} 400 - ToS already accepted
952
   */
953
  public async acceptToS(req: RequestWithToken, res: Response): Promise<void> {
954
    this.logger.trace('Accept ToS for user', req.token.user);
3✔
955

956
    const { id } = req.token.user;
3✔
957
    const body = req.body as AcceptTosRequest;
3✔
958

959
    try {
3✔
960
      const user = await UserService.getSingleUser(id);
3✔
961
      if (user == null) {
3!
962
        res.status(404).json('User not found.');
×
963
        return;
×
964
      }
965

966
      const success = await UserService.acceptToS(id, body);
3✔
967
      if (!success) {
3✔
968
        res.status(400).json('User already accepted ToS.');
1✔
969
        return;
1✔
970
      }
971

972
      res.status(204).json();
2✔
973
      return;
2✔
974
    } catch (error) {
975
      this.logger.error('Could not accept ToS for user:', error);
×
976
      res.status(500).json('Internal server error.');
×
977
    }
978
  }
979

980
  /**
981
   * GET /users/{id}/products
982
   * @summary Get an user's products
983
   * @operationId getUsersProducts
984
   * @tags users - Operations of user controller
985
   * @param {integer} id.path.required - The id of the user
986
   * @param {integer} take.query - How many products the endpoint should return
987
   * @param {integer} skip.query - How many products should be skipped (for pagination)
988
   * @security JWT
989
   * @return {PaginatedProductResponse} 200 - List of products.
990
   */
991
  public async getUsersProducts(req: RequestWithToken, res: Response): Promise<void> {
992
    const parameters = req.params;
4✔
993
    this.logger.trace("Get user's products", parameters, 'by user', req.token.user);
4✔
994

995
    let take;
996
    let skip;
997
    try {
4✔
998
      const pagination = parseRequestPagination(req);
4✔
999
      take = pagination.take;
4✔
1000
      skip = pagination.skip;
4✔
1001
    } catch (e) {
1002
      res.status(400).send(e.message);
×
1003
      return;
×
1004
    }
1005

1006
    // Handle request
1007
    try {
4✔
1008
      const id = parseInt(parameters.id, 10);
4✔
1009
      const owner = await User.findOne({ where: { id, deleted: false } });
4✔
1010
      if (owner == null) {
4✔
1011
        res.status(404).json({});
1✔
1012
        return;
1✔
1013
      }
1014

1015
      const products = await ProductService.getProducts({}, { take, skip }, owner);
3✔
1016
      res.json(products);
3✔
1017
    } catch (error) {
1018
      this.logger.error('Could not return all products:', error);
×
1019
      res.status(500).json('Internal server error.');
×
1020
    }
1021
  }
1022

1023
  /**
1024
   * GET /users/{id}/containers
1025
   * @summary Returns the user's containers
1026
   * @operationId getUsersContainers
1027
   * @tags users - Operations of user controller
1028
   * @param {integer} id.path.required - The id of the user
1029
   * @security JWT
1030
   * @param {integer} take.query - How many containers the endpoint should return
1031
   * @param {integer} skip.query - How many containers should be skipped (for pagination)
1032
   * @return {PaginatedContainerResponse} 200 - All users updated containers
1033
   * @return {string} 404 - Not found error
1034
   * @return {string} 500 - Internal server error
1035
   */
1036
  public async getUsersContainers(req: RequestWithToken, res: Response): Promise<void> {
1037
    const { id } = req.params;
4✔
1038
    this.logger.trace("Get user's containers", id, 'by user', req.token.user);
4✔
1039

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

1051
    // handle request
1052
    try {
4✔
1053
      // Get the user object if it exists
1054
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1055
      // If it does not exist, return a 404 error
1056
      if (user == null) {
4✔
1057
        res.status(404).json('Unknown user ID.');
1✔
1058
        return;
1✔
1059
      }
1060

1061
      const containers = (await ContainerService
3✔
1062
        .getContainers({}, { take, skip }, user));
1063
      res.json(containers);
3✔
1064
    } catch (error) {
1065
      this.logger.error('Could not return containers:', error);
×
1066
      res.status(500).json('Internal server error.');
×
1067
    }
1068
  }
1069

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

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

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

1108
      const pointsOfSale = (await PointOfSaleService
3✔
1109
        .getPointsOfSale({}, { take, skip }, user));
1110
      res.json(pointsOfSale);
3✔
1111
    } catch (error) {
1112
      this.logger.error('Could not return point of sale:', error);
×
1113
      res.status(500).json('Internal server error.');
×
1114
    }
1115
  }
1116

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

1142
    // Parse the filters given in the query parameters. If there are any issues,
1143
    // the parse method will throw an exception. We will then return a 400 error.
1144
    let filters;
1145
    try {
4✔
1146
      filters = parseGetTransactionsFilters(req);
4✔
1147
    } catch (e) {
1148
      res.status(400).json(e.message);
×
1149
      return;
×
1150
    }
1151

1152
    let take;
1153
    let skip;
1154
    try {
4✔
1155
      const pagination = parseRequestPagination(req);
4✔
1156
      take = pagination.take;
4✔
1157
      skip = pagination.skip;
4✔
1158
    } catch (e) {
1159
      res.status(400).send(e.message);
×
1160
      return;
×
1161
    }
1162

1163
    try {
4✔
1164
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
4✔
1165
      if (user == null) {
4✔
1166
        res.status(404).json({});
1✔
1167
        return;
1✔
1168
      }
1169
      const transactions = await new TransactionService().getTransactions(filters, { take, skip }, user);
3✔
1170

1171
      res.status(200).json(transactions);
3✔
1172
    } catch (error) {
1173
      this.logger.error('Could not return all transactions:', error);
×
1174
      res.status(500).json('Internal server error.');
×
1175
    }
1176
  }
1177

1178
  /**
1179
   * GET /users/{id}/transactions/sales/report
1180
   * @summary Get sales report for the given user
1181
   * @operationId getUsersSalesReport
1182
   * @tags users - Operations of user controller
1183
   * @param {integer} id.path.required - The id of the user to get the sales report for
1184
   * @security JWT
1185
   * @param {string} fromDate.query.required - Start date for selected sales (inclusive)
1186
   * @param {string} tillDate.query.required - End date for selected sales (exclusive)
1187
   * @return {ReportResponse} 200 - The sales report of the user
1188
   * @return {string} 400 - Validation error
1189
   * @return {string} 404 - User not found error.
1190
   */
1191
  public async getUsersSalesReport(req: RequestWithToken, res: Response): Promise<void> {
1192
    const { id } = req.params;
7✔
1193
    this.logger.trace('Get sales report for user ', id, ' by user', req.token.user);
7✔
1194

1195
    let filters: { fromDate: Date, tillDate: Date };
1196
    try {
7✔
1197
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1198
    } catch (e) {
1199
      res.status(400).json(e.message);
1✔
1200
      return;
1✔
1201
    }
1202

1203
    try {
6✔
1204
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
6✔
1205
      if (user == null) {
6✔
1206
        res.status(404).json('Unknown user ID.');
1✔
1207
        return;
1✔
1208
      }
1209

1210
      const report = await (new SalesReportService()).getReport({ ...filters, forId: user.id });
5✔
1211
      res.status(200).json(ReportService.reportToResponse(report));
5✔
1212
    } catch (error) {
1213
      this.logger.error('Could not get sales report:', error);
×
1214
      res.status(500).json('Internal server error.');
×
1215
    }
1216
  }
1217

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

1239
    let filters: { fromDate: Date, tillDate: Date };
1240
    let description: string;
1241
    let fileType: ReturnFileType;
1242
    try {
7✔
1243
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1244
      description = String(req.query.description);
4✔
1245
      fileType = asReturnFileType(req.query.fileType);
4✔
1246
    } catch (e) {
1247
      res.status(400).json(e.message);
4✔
1248
      return;
4✔
1249
    }
1250

1251
    try {
3✔
1252
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1253
      if (user == null) {
3✔
1254
        res.status(404).json('Unknown user ID.');
1✔
1255
        return;
1✔
1256
      }
1257
      const service = new SalesReportService();
2✔
1258
      await reportPDFhelper(res)(service, filters, description, user.id, UserReportParametersType.Sales, fileType);
2✔
1259
    } catch (error) {
1260
      this.logger.error('Could not get sales report:', error);
1✔
1261
      if (error instanceof PdfError) {
1✔
1262
        res.status(502).json('PDF Generator service failed.');
1✔
1263
        return;
1✔
1264
      }
1265
      res.status(500).json('Internal server error.');
×
1266
    }
1267
  }
1268

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

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

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

1319
  /**
1320
   * GET /users/{id}/transactions/purchases/report
1321
   * @summary Get purchases report for the given user
1322
   * @operationId getUsersPurchasesReport
1323
   * @tags users - Operations of user controller
1324
   * @param {integer} id.path.required - The id of the user to get the purchases report for
1325
   * @security JWT
1326
   * @param {string} fromDate.query.required - Start date for selected purchases (inclusive)
1327
   * @param {string} tillDate.query.required - End date for selected purchases (exclusive)
1328
   * @return {ReportResponse} 200 - The purchases report of the user
1329
   * @return {string} 400 - Validation error
1330
   * @return {string} 404 - User not found error.
1331
   */
1332
  public async getUsersPurchasesReport(req: RequestWithToken, res: Response): Promise<void> {
1333
    const { id } = req.params;
4✔
1334
    this.logger.trace('Get purchases report for user ', id, ' by user', req.token.user);
4✔
1335

1336
    let filters: { fromDate: Date, tillDate: Date };
1337
    try {
4✔
1338
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
4✔
1339
    } catch (e) {
1340
      res.status(400).json(e.message);
1✔
1341
      return;
1✔
1342
    }
1343

1344
    try {
3✔
1345
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1346
      if (user == null) {
3✔
1347
        res.status(404).json('Unknown user ID.');
1✔
1348
        return;
1✔
1349
      }
1350

1351
      const report = await (new BuyerReportService()).getReport({ ...filters, forId: user.id });
2✔
1352
      res.status(200).json(ReportService.reportToResponse(report));
2✔
1353
    } catch (error) {
1354
      this.logger.error('Could not get sales report:', error);
×
1355
      res.status(500).json('Internal server error.');
×
1356
    }
1357
  }
1358

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

1378
    // Parse the filters given in the query parameters. If there are any issues,
1379
    // the parse method will throw an exception. We will then return a 400 error.
1380
    let filters;
1381
    try {
2✔
1382
      filters = parseGetTransferFilters(req);
2✔
1383
    } catch (e) {
1384
      res.status(400).json(e.message);
×
1385
      return;
×
1386
    }
1387

1388
    let take;
1389
    let skip;
1390
    try {
2✔
1391
      const pagination = parseRequestPagination(req);
2✔
1392
      take = pagination.take;
2✔
1393
      skip = pagination.skip;
2✔
1394
    } catch (e) {
1395
      res.status(400).send(e.message);
×
1396
      return;
×
1397
    }
1398

1399
    // handle request
1400
    try {
2✔
1401
      // Get the user object if it exists
1402
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
2✔
1403
      // If it does not exist, return a 404 error
1404
      if (user == null) {
2!
1405
        res.status(404).json('Unknown user ID.');
×
1406
        return;
×
1407
      }
1408

1409
      const transfers = (await new TransferService().getTransfers(
2✔
1410
        { ...filters }, { take, skip }, user,
1411
      ));
1412
      res.json(transfers);
2✔
1413
    } catch (error) {
1414
      this.logger.error('Could not return user transfers', error);
×
1415
      res.status(500).json('Internal server error.');
×
1416
    }
1417
  }
1418

1419
  /**
1420
   * GET /users/{id}/roles
1421
   * @summary Get all roles assigned to the user.
1422
   * @operationId getUserRoles
1423
   * @tags users - Operations of user controller
1424
   * @param {integer} id.path.required - The id of the user to get the roles from
1425
   * @security JWT
1426
   * @return {Array.<RoleWithPermissionsResponse>} 200 - The roles of the user
1427
   * @return {string} 404 - User not found error.
1428
   */
1429
  public async getUserRoles(req: RequestWithToken, res: Response): Promise<void> {
1430
    const parameters = req.params;
3✔
1431
    this.logger.trace('Get roles of user', parameters, 'by user', req.token.user);
3✔
1432

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

1443
      const rolesWithPermissions = await this.roleManager.getRoles(user, true);
2✔
1444
      const response = rolesWithPermissions.map((r) => RBACService.asRoleResponse(r));
2✔
1445
      res.status(200).json(response);
2✔
1446
    } catch (error) {
1447
      this.logger.error('Could not get roles of user:', error);
×
1448
      res.status(500).json('Internal server error.');
×
1449
    }
1450
  }
1451

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

1470
    let filters;
1471
    let take;
1472
    let skip;
1473
    try {
4✔
1474
      filters = parseGetFinancialMutationsFilters(req);
4✔
1475
      const pagination = parseRequestPagination(req);
4✔
1476
      take = pagination.take;
4✔
1477
      skip = pagination.skip;
4✔
1478
    } catch (e) {
1479
      res.status(400).send(e.message);
×
1480
      return;
×
1481
    }
1482

1483
    try {
4✔
1484
      const id = parseInt(parameters.id, 10);
4✔
1485
      // Get the user object if it exists
1486
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
1487
      // If it does not exist, return a 404 error
1488
      if (user == null) {
4!
1489
        res.status(404).json('Unknown user ID.');
×
1490
        return;
×
1491
      }
1492

1493
      const mutations = await UserService.getUserFinancialMutations(user, filters, { take, skip });
4✔
1494
      res.status(200).json(mutations);
4✔
1495
    } catch (error) {
1496
      this.logger.error('Could not get financial mutations of user:', error);
×
1497
      res.status(500).json('Internal server error.');
×
1498
    }
1499
  }
1500

1501
  /**
1502
   * GET /users/{id}/deposits
1503
   * @summary Get all deposits of a user that are still being processed by Stripe
1504
   * @operationId getUsersProcessingDeposits
1505
   * @tags users - Operations of user controller
1506
   * @param {integer} id.path.required - The id of the user to get the deposits from
1507
   * @security JWT
1508
   * @return {Array.<RoleResponse>} 200 - The processing deposits of a user
1509
   * @return {string} 404 - User not found error.
1510
   */
1511
  public async getUsersProcessingDeposits(req: RequestWithToken, res: Response): Promise<void> {
1512
    const parameters = req.params;
2✔
1513
    this.logger.trace('Get users processing deposits from user', parameters.id);
2✔
1514

1515
    try {
2✔
1516
      const id = parseInt(parameters.id, 10);
2✔
1517

1518
      const user = await User.findOne({ where: { id } });
2✔
1519
      if (user == null) {
2✔
1520
        res.status(404).json('Unknown user ID.');
1✔
1521
        return;
1✔
1522
      }
1523

1524
      const deposits = await StripeService.getProcessingStripeDepositsFromUser(id);
1✔
1525
      res.status(200).json(deposits);
1✔
1526
    } catch (error) {
1527
      this.logger.error('Could not get processing deposits of user:', error);
×
1528
      res.status(500).json('Internal server error.');
×
1529
    }
1530
  }
1531

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

1552
    let filters;
1553
    try {
6✔
1554
      filters = parseGetTransactionsFilters(req);
6✔
1555
    } catch (e) {
1556
      res.status(400).json(e.message);
1✔
1557
      return;
1✔
1558
    }
1559

1560
    try {
5✔
1561
      if ((filters.toId !== undefined && filters.fromId !== undefined) || (filters.toId === undefined && filters.fromId === undefined)) {
5✔
1562
        res.status(400).json('Need to provide either a toId or a fromId.');
2✔
1563
        return;
2✔
1564
      }
1565

1566
      const id = parseInt(parameters.id, 10);
3✔
1567

1568
      const user = await User.findOne({ where: { id } });
3✔
1569
      if (user == null) {
3✔
1570
        res.status(404).json('Unknown user ID.');
1✔
1571
        return;
1✔
1572
      }
1573

1574
      const report = await (new TransactionService()).getTransactionReportResponse(filters);
2✔
1575
      res.status(200).json(report);
2✔
1576
    } catch (e) {
1577
      res.status(500).send();
×
1578
      this.logger.error(e);
×
1579
    }
1580
  }
1581

1582
  /**
1583
   * POST /users/{id}/fines/waive
1584
   * @summary Waive all given user's fines
1585
   * @tags users - Operations of user controller
1586
   * @param {integer} id.path.required - The id of the user
1587
   * @param {WaiveFinesRequest} request.body
1588
   * Optional body, see https://github.com/GEWIS/sudosos-backend/pull/344
1589
   * @operationId waiveUserFines
1590
   * @security JWT
1591
   * @return 204 - Success
1592
   * @return {string} 400 - User has no fines.
1593
   * @return {string} 404 - User not found error.
1594
   */
1595
  public async waiveUserFines(req: RequestWithToken, res: Response): Promise<void> {
1596
    const { id: rawId } = req.params;
9✔
1597
    const body = req.body as WaiveFinesRequest;
9✔
1598
    this.logger.trace('Waive fines', body, 'of user', rawId, 'by', req.token.user);
9✔
1599

1600
    try {
9✔
1601
      const id = parseInt(rawId, 10);
9✔
1602

1603
      const user = await User.findOne({ where: { id }, relations: { currentFines: { fines: true } } });
9✔
1604
      if (user == null) {
9✔
1605
        res.status(404).json('Unknown user ID.');
1✔
1606
        return;
1✔
1607
      }
1608
      if (user.currentFines == null) {
8✔
1609
        res.status(400).json('User has no fines.');
1✔
1610
        return;
1✔
1611
      }
1612

1613
      const totalAmountOfFines = user.currentFines!.fines.reduce((total, f) => total.add(f.amount), Dinero());
14✔
1614
      // Backwards compatibility with old version, where you could only waive all user's fines
1615
      const amountToWaive = body?.amount ?? totalAmountOfFines.toObject();
7✔
1616
      if (amountToWaive.amount <= 0) {
7✔
1617
        res.status(400).json('Amount to waive cannot be zero or negative.');
2✔
1618
        return;
2✔
1619
      }
1620
      if (amountToWaive.amount > totalAmountOfFines.getAmount()) {
5✔
1621
        res.status(400).json('Amount to waive cannot be more than the total amount of fines.');
1✔
1622
        return;
1✔
1623
      }
1624

1625
      await new DebtorService().waiveFines(id, { amount: amountToWaive } as WaiveFinesParams);
4✔
1626
      res.status(204).send();
4✔
1627
    } catch (e) {
1628
      res.status(500).send();
×
1629
      this.logger.error(e);
×
1630
    }
1631
  }
1632

1633
  /**
1634
   * DELETE /users/{id}/roles/{roleId}
1635
   * @summary Deletes a role from a user
1636
   * @tags users - Operations of user controller
1637
   * @param {integer} id.path.required - The id of the user
1638
   * @param {integer} roleId.path.required - The id of the role
1639
   * @operationId deleteUserRole
1640
   * @security JWT
1641
   * @return 204 - Success
1642
   */
1643
  public async deleteUserRole(req: RequestWithToken, res: Response): Promise<void> {
1644
    const { id: rawUserId, roleId: rawRoleId } = req.params;
4✔
1645

1646
    const userId = parseInt(rawUserId, 10);
4✔
1647
    const roleId = parseInt(rawRoleId, 10);
4✔
1648

1649
    const user = await User.findOne({ where: { id: userId } });
4✔
1650
    if (!user) {
4✔
1651
      res.status(404).json('user not found');
1✔
1652
      return;
1✔
1653
    }
1654
    const role = await Role.findOne({ where: { id: roleId } });
3✔
1655
    if (!role) {
3✔
1656
      res.status(404).json('role not found');
1✔
1657
      return;
1✔
1658
    }
1659

1660
    await UserService.deleteUserRole(user, role);
2✔
1661
    res.status(204).send();
2✔
1662
  }
1663

1664
  /**
1665
   * POST /users/{id}/roles
1666
   * @summary Adds a role to a user
1667
   * @tags users - Operations of user controller
1668
   * @param {integer} id.path.required - The id of the user
1669
   * @param {AddRoleRequest} request.body.required
1670
   * @operationId addUserRole
1671
   * @security JWT
1672
   * @return 204 - Success
1673
   */
1674
  public async addUserRole(req: RequestWithToken, res: Response): Promise<void> {
1675
    const { id: rawUserId } = req.params;
2✔
1676
    const userId = parseInt(rawUserId, 10);
2✔
1677
    const { roleId } = req.body as AddRoleRequest;
2✔
1678

1679
    const user = await User.findOne({ where: { id: userId } });
2✔
1680
    if (!user) {
2!
1681
      res.status(404).json('user not found');
×
1682
      return;
×
1683
    }
1684
    const role = await Role.findOne({ where: { id: roleId } });
2✔
1685
    if (!role) {
2✔
1686
      res.status(404).json('role not found');
1✔
1687
      return;
1✔
1688
    }
1689

1690
    await UserService.addUserRole(user, role);
1✔
1691
    res.status(204).send();
1✔
1692
  }
1693

1694
  /**
1695
   * GET /users/{id}/wrapped
1696
   * @summary Get wrapped for a user
1697
   * @operationId getWrapped
1698
   * @tags users - Operations of user controller
1699
   * @param {integer} id.path.required - The id of the user
1700
   * @security JWT
1701
   * @return {WrappedResponse} 200 - The requested user's wrapped
1702
   * @return {string} 404 - Wrapped for user not found error
1703
   * @return {string} 500 - Internal server error
1704
   */
1705
  private async getUserWrapped(req: RequestWithToken, res: Response): Promise<void> {
1706
    const { id: rawUserId } = req.params;
4✔
1707

1708
    try {
4✔
1709
      const userId = parseInt(rawUserId, 10);
4✔
1710
    
1711
      const wrappedRes = await new WrappedService().getWrappedForUser(userId);
4✔
1712
      if (wrappedRes == null) {
4✔
1713
        res.status(404).json('Wrapped not found for user');
1✔
1714
        return;
1✔
1715
      }
1716

1717
      res.json(WrappedService.asWrappedResponse(wrappedRes));
3✔
1718
    } catch (error) {
1719
      res.status(500).json({ message: 'Internal server error' });
×
1720
    }
1721
  }
1722

1723
  /**
1724
   * POST /users/{id}/wrapped
1725
   * @summary Recompute wrapped for a user
1726
   * @operationId updateWrapped
1727
   * @tags users - Operations of user controller
1728
   * @param {integer} id.path.required - The id of the user
1729
   * @security JWT
1730
   * @return {WrappedResponse} 200 - The requested user's wrapped
1731
   * @return {string} 500 - Internal server error
1732
   */
1733
  private async computedWrapped(req: RequestWithToken, res: Response): Promise<void> {
1734
    const { id: rawUserId } = req.params;
2✔
1735

1736
    try {
2✔
1737
      const userId = parseInt(rawUserId, 10);
2✔
1738

1739
      res.json(await new WrappedService().updateWrapped({ ids: [userId] }));
2✔
1740
    } catch (error) {
1741
      res.status(500).json({ message: 'Internal server error' });
×
1742
    }
1743
  }
1744

1745
  /**
1746
   * GET /users/{id}/settings
1747
   * @summary Get all user settings
1748
   * @operationId getUserSettings
1749
   * @tags users - Operations of user controller
1750
   * @param {integer} id.path.required - The id of the user
1751
   * @security JWT
1752
   * @return {UserSettingsResponse} 200 - The user's settings
1753
   * @return {string} 404 - User not found
1754
   * @return {string} 500 - Internal server error
1755
   */
1756
  private async getUserSettings(req: RequestWithToken, res: Response): Promise<void> {
1757
    const { id: rawUserId } = req.params;
5✔
1758

1759
    try {
5✔
1760
      const userId = parseInt(rawUserId, 10);
5✔
1761
      
1762
      const user = await User.findOne({ where: { id: userId } });
5✔
1763
      if (!user) {
5✔
1764
        res.status(404).json('User not found.');
1✔
1765
        return;
1✔
1766
      }
1767

1768
      const store = new UserSettingsStore();
4✔
1769
      const settings = await store.getAllSettings(userId);
4✔
1770
      const response = UserSettingsStore.toResponse(settings);
4✔
1771
      res.json(response);
4✔
1772
    } catch (error) {
NEW
1773
      this.logger.error('Could not get user settings:', error);
×
NEW
1774
      res.status(500).json('Internal server error.');
×
1775
    }
1776
  }
1777

1778
  /**
1779
   * PATCH /users/{id}/settings
1780
   * @summary Update user settings
1781
   * @operationId patchUserSettings
1782
   * @tags users - Operations of user controller
1783
   * @param {integer} id.path.required - The id of the user
1784
   * @param {PatchUserSettingsRequest} request.body.required - The settings to update
1785
   * @security JWT
1786
   * @return {UserSettingsResponse} 200 - The updated user settings
1787
   * @return {string} 404 - User not found
1788
   * @return {string} 500 - Internal server error
1789
   */
1790
  private async patchUserSettings(req: RequestWithToken, res: Response): Promise<void> {
1791
    const { id: rawUserId } = req.params;
10✔
1792
    const body = req.body as PatchUserSettingsRequest;
10✔
1793

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

1803
      const store = new UserSettingsStore();
9✔
1804
      await store.setSettings(userId, body);
9✔
1805

1806
      const settings = await store.getAllSettings(userId);
9✔
1807

1808
      res.json(UserSettingsStore.toResponse(settings));
9✔
1809
    } catch (error) {
NEW
1810
      this.logger.error('Could not update user settings:', error);
×
NEW
1811
      res.status(500).json('Internal server error.');
×
1812
    }
1813
  }
1814
}
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