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

GEWIS / sudosos-backend / 25753937432

12 May 2026 09:17AM UTC coverage: 88.117% (-1.0%) from 89.089%
25753937432

push

github

web-flow
chore(deps): fix missing dependencies for running docs:dev (#911)

3925 of 4574 branches covered (85.81%)

Branch coverage included in aggregate %.

20093 of 22683 relevant lines covered (88.58%)

1125.83 hits per line

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

86.43
/src/controller/user-controller.ts
1
/**
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
 */
1✔
20

21
/**
1✔
22
 * This is the module page of user-controller.
23
 *
24
 * @module users
25
 */
1✔
26

27
import { Response } from 'express';
28
import log4js, { Logger } from 'log4js';
29
import BaseController, { BaseControllerOptions } from './base-controller';
30
import Policy from './policy';
31
import { RequestWithToken } from '../middleware/token-middleware';
32
import User, { UserType } from '../entity/user/user';
33
import BaseUserRequest, {
34
  AddRoleRequest,
35
  CreateUserRequest,
36
  PatchUserTypeRequest,
37
  UpdateUserRequest,
38
} from './request/user-request';
39
import { maxPagination, parseRequestPagination, toResponse } from '../helpers/pagination';
40
import ProductService from '../service/product-service';
41
import PointOfSaleService from '../service/point-of-sale-service';
42
import TransactionService, { parseGetTransactionsFilters } from '../service/transaction-service';
43
import ContainerService from '../service/container-service';
44
import TransferService, { parseGetTransferFilters } from '../service/transfer-service';
45
import AuthenticationService from '../service/authentication-service';
46
import TokenHandler from '../authentication/token-handler';
47
import RBACService from '../service/rbac-service';
48
import { updatePinRequestSpecFactory } from './request/validators/update-pin-request-spec';
49
import UpdatePinRequest from './request/update-pin-request';
50
import UserService, {
51
  asUserResponse,
52
  parseGetFinancialMutationsFilters,
53
  parseGetUsersFilters,
54
  UserFilterParameters,
55
} from '../service/user-service';
56
import { asFromAndTillDate, asNumber, asReturnFileType } from '../helpers/validators';
57
import { createUserRequestSpecFactory } from './request/validators/user-request-spec';
58
import userTokenInOrgan from '../helpers/token-helper';
59
import { globalAsyncValidatorRegistry } from '../middleware/async-validator-registry';
60
import { parseUserToResponse } from '../helpers/revision-to-response';
61
import { AcceptTosRequest } from './request/accept-tos-request';
62
import PinAuthenticator from '../entity/authenticator/pin-authenticator';
63
import LocalAuthenticator from '../entity/authenticator/local-authenticator';
64
import UpdateLocalRequest from './request/update-local-request';
65
import { updateLocalRequestSpecFactory } from './request/validators/update-local-request-spec';
66
import StripeService from '../service/stripe-service';
67
import { updateNfcRequestSpecFactory } from './request/validators/update-nfc-request-spec';
68
import UpdateNfcRequest from './request/update-nfc-request';
69
import NfcAuthenticator from '../entity/authenticator/nfc-authenticator';
70
import KeyAuthenticator from '../entity/authenticator/key-authenticator';
71
import UpdateKeyResponse from './response/update-key-response';
72
import { randomBytes } from 'crypto';
73
import DebtorService, { WaiveFinesParams } from '../service/debtor-service';
74
import ReportService, { BuyerReportService, SalesReportService } from '../service/report-service';
75
import { ReturnFileType, UserReportParametersType } from 'pdf-generator-client';
76
import { reportPDFhelper } from '../helpers/express-pdf';
77
import { PdfError } from '../errors';
78
import { WaiveFinesRequest } from './request/debtor-request';
79
import Dinero from 'dinero.js';
80
import Role from '../entity/rbac/role';
81
import WrappedService from '../service/wrapped-service';
82
import UserSettingsStore from '../user-settings/user-settings-store';
83
import { PatchUserSettingsRequest } from './request/user-request';
84

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

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

93
  /**
1✔
94
   * Create a new user controller instance.
95
   * @param options - The options passed to the base controller.
96
   * @param tokenHandler
97
   */
1✔
98
  public constructor(
1✔
99
    options: BaseControllerOptions,
3✔
100
    tokenHandler: TokenHandler,
3✔
101
  ) {
3✔
102
    super(options);
3✔
103
    this.configureLogger(this.logger);
3✔
104
    this.tokenHandler = tokenHandler;
3✔
105
    globalAsyncValidatorRegistry.register('CreateUserRequest', createUserRequestSpecFactory);
3✔
106
    globalAsyncValidatorRegistry.register('UpdatePinRequest', updatePinRequestSpecFactory);
3✔
107
    globalAsyncValidatorRegistry.register('UpdateNfcRequest', updateNfcRequestSpecFactory);
3✔
108
    globalAsyncValidatorRegistry.register('UpdateLocalRequest', updateLocalRequestSpecFactory);
3✔
109
  }
3✔
110

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

682

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

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

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

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

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

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

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

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

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

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

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

809
      const response = asUserResponse(user, true);
7✔
810
      if (!await this.canSeeEmail(req, UserController.getRelation(req))) {
10✔
811
        response.email = undefined;
1✔
812
      }
1✔
813
      res.status(200).json(response);
7✔
814
    } catch (error) {
10!
815
      this.logger.error('Could not get individual user:', error);
×
816
      res.status(500).json('Internal server error.');
×
817
    }
×
818
  }
10✔
819

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

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

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

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

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

889
      user = {
8✔
890
        ...body,
8✔
891
      } as User;
8✔
892
      await User.update(parameters.id, user);
8✔
893
      const updatedUser = await UserService.getSingleUser(asNumber(parameters.id));
8✔
894
      res.status(200).json(asUserResponse(updatedUser, true));
8✔
895
    } catch (error) {
12!
896
      this.logger.error('Could not update user:', error);
×
897
      res.status(500).json('Internal server error.');
×
898
    }
×
899
  }
12✔
900

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

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

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

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

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

953
    try {
3✔
954
      const nfcCode = String(parameters.nfcCode);
3✔
955
      const nfc = await NfcAuthenticator.findOne({ where: { nfcCode } });
3✔
956

957
      if (nfc === null) {
3✔
958
        res.status(404).json('Unknown nfc code');
1✔
959
        return;
1✔
960
      }
1✔
961

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

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

986
    const { id } = req.token.user;
3✔
987
    const body = req.body as AcceptTosRequest;
3✔
988

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

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

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

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

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

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

1045
      const [revisions, count] = await ProductService.getProducts({}, { take, skip }, owner);
3✔
1046
      const records = revisions.map((r) => ProductService.revisionToResponse(r));
3✔
1047
      res.json(toResponse(records, count, { take, skip }));
3✔
1048
    } catch (error) {
4!
1049
      this.logger.error('Could not return all products:', error);
×
1050
      res.status(500).json('Internal server error.');
×
1051
    }
×
1052
  }
4✔
1053

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

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

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

1092
      const [revisions, count] = await ContainerService
3✔
1093
        .getContainers({}, { take, skip }, user);
3✔
1094
      const records = revisions.map((r) => ContainerService.revisionToResponse(r));
3✔
1095
      res.json(toResponse(records, count, { take, skip }));
3✔
1096
    } catch (error) {
4!
1097
      this.logger.error('Could not return containers:', error);
×
1098
      res.status(500).json('Internal server error.');
×
1099
    }
×
1100
  }
4✔
1101

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

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

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

1140
      const [revisions, count] = await PointOfSaleService
3✔
1141
        .getPointsOfSale({}, { take, skip }, user);
3✔
1142
      const records = revisions.map((r) => PointOfSaleService.revisionToResponse(r));
3✔
1143
      res.json(toResponse(records, count, { take, skip }));
3✔
1144
    } catch (error) {
4!
1145
      this.logger.error('Could not return point of sale:', error);
×
1146
      res.status(500).json('Internal server error.');
×
1147
    }
×
1148
  }
4✔
1149

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1904
      await UserService.updateUserType(user, body.userType);
1✔
1905
      const updatedUser = await UserService.getSingleUser(userId);
1✔
1906
      res.status(200).json(asUserResponse(updatedUser, true));
1✔
1907
    } catch (e) {
5!
1908
      res.status(500).send('Internal server error.');
×
1909
      this.logger.error(e);
×
1910
    }
×
1911
  }
5✔
1912

1913
  /**
1✔
1914
   * GET /users/recently-charged
1915
   * @summary Get users recently charged by the caller via an authenticated point of sale.
1916
   * Returns distinct buyers ordered by most recent transaction first, intended for
1917
   * quick suggestions in the authenticated POS flow.
1918
   * @operationId getRecentlyChargedUsers
1919
   * @tags users - Operations of user controller
1920
   * @param {integer} take.query - Maximum number of users to return (default 50)
1921
   * @security JWT
1922
   * @return {Array.<UserResponse>} 200 - List of recently charged users.
1923
   */
1✔
1924
  public async getRecentlyChargedUsers(req: RequestWithToken, res: Response): Promise<void> {
1✔
1925
    this.logger.trace('Get recently charged users by user', req.token.user);
5✔
1926

1927
    let take: number;
5✔
1928
    try {
5✔
1929
      take = req.query.take !== undefined ? asNumber(req.query.take) : 50;
5✔
1930
      take = Math.min(Math.max(1, Math.trunc(take)), maxPagination());
5✔
1931
    } catch (e) {
5✔
1932
      res.status(400).send(e.message);
1✔
1933
      return;
1✔
1934
    }
1✔
1935

1936
    try {
4✔
1937
      const users = await new TransactionService().getRecentlyChargedUsers(req.token.user.id, take);
4✔
1938
      const records = users.map((u) => asUserResponse(u));
4✔
1939
      if (!await this.canSeeEmail(req, 'all')) {
5✔
1940
        records.forEach((u) => { u.email = undefined; });
1✔
1941
      }
1✔
1942
      res.status(200).json(records);
4✔
1943
    } catch (e) {
5!
1944
      res.status(500).send('Internal server error.');
×
1945
      this.logger.error(e);
×
1946
    }
×
1947
  }
5✔
1948
}
1✔
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