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

GEWIS / sudosos-backend / 27945865144

22 Jun 2026 10:20AM UTC coverage: 92.043% (+0.02%) from 92.024%
27945865144

Pull #968

github

web-flow
Merge 121f5df82 into 3c823bbd4
Pull Request #968: Release: update `main` from `develop`

4236 of 4839 branches covered (87.54%)

Branch coverage included in aggregate %.

165 of 177 new or added lines in 20 files covered. (93.22%)

8 existing lines in 1 file now uncovered.

21837 of 23488 relevant lines covered (92.97%)

841.95 hits per line

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

85.3
/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, { LocalUserTypes, 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 PaymentRequestService, { parseGetPaymentRequestsFilters } from '../service/payment-request-service';
46
import AuthenticationService from '../service/authentication-service';
47
import TokenHandler from '../authentication/token-handler';
48
import RBACService from '../service/rbac-service';
49
import { updatePinRequestSpecFactory } from './request/validators/update-pin-request-spec';
50
import UpdatePinRequest from './request/update-pin-request';
51
import UserService, {
52
  AcceptTosResult,
53
  asUserResponse,
54
  parseGetFinancialMutationsFilters,
55
  parseGetUsersFilters,
56
  UserFilterParameters,
57
} from '../service/user-service';
58
import { asFromAndTillDate, asNumber, asReturnFileType } from '../helpers/validators';
59
import { createUserRequestSpecFactory } from './request/validators/user-request-spec';
60
import userTokenInOrgan from '../helpers/token-helper';
61
import { globalAsyncValidatorRegistry } from '../middleware/async-validator-registry';
62
import { parseUserToResponse } from '../helpers/revision-to-response';
63
import { AcceptTosRequest } from './request/accept-tos-request';
64
import PinAuthenticator from '../entity/authenticator/pin-authenticator';
65
import LocalAuthenticator from '../entity/authenticator/local-authenticator';
66
import UpdateLocalRequest from './request/update-local-request';
67
import { updateLocalRequestSpecFactory } from './request/validators/update-local-request-spec';
68
import StripeService from '../service/stripe-service';
69
import { updateNfcRequestSpecFactory } from './request/validators/update-nfc-request-spec';
70
import UpdateNfcRequest from './request/update-nfc-request';
71
import NfcAuthenticator from '../entity/authenticator/nfc-authenticator';
72
import KeyAuthenticator from '../entity/authenticator/key-authenticator';
73
import UpdateKeyResponse from './response/update-key-response';
74
import { randomBytes } from 'crypto';
75
import DebtorService, { WaiveFinesParams } from '../service/debtor-service';
76
import ReportService, { BuyerReportService, SalesReportService } from '../service/report-service';
77
import { ReturnFileType, UserReportParametersType } from 'pdf-generator-client';
78
import { reportPDFhelper } from '../helpers/express-pdf';
79
import { PdfError } from '../errors';
80
import { WaiveFinesRequest } from './request/debtor-request';
81
import Dinero from 'dinero.js';
82
import Role from '../entity/rbac/role';
83
import WrappedService from '../service/wrapped-service';
84
import UserSettingsStore from '../user-settings/user-settings-store';
85
import { PatchUserSettingsRequest } from './request/user-request';
86
import TermsOfServiceService from '../service/terms-of-service-service';
87
import { UserTosResponse } from './response/terms-of-service-response';
88

89
export default class UserController extends BaseController {
1✔
90
  private logger: Logger = log4js.getLogger('UserController');
1✔
91

92
  /**
93
   * Reference to the token handler of the application.
94
   */
95
  private tokenHandler: TokenHandler;
96

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

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

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

421
  /**
1✔
422
   * Function to determine which credentials are needed to GET
423
   *    'all' if user is not connected to User
424
   *    'organ' if user is connected to User via organ
425
   *    'own' if user is connected to User
426
   * @param req
427
   * @return whether User is connected to used token
428
   */
1✔
429
  static getRelation(req: RequestWithToken): string {
1✔
430
    if (userTokenInOrgan(req, asNumber(req.params.id))) return 'organ';
203✔
431
    return req.params.id === req.token.user.id.toString() ? 'own' : 'all';
203✔
432
  }
203✔
433

434
  static getAttributes(req: RequestWithToken): string[] {
1✔
435
    const attributes: string[] = [];
15✔
436
    const body = req.body as BaseUserRequest;
15✔
437
    for (const key in body) {
15✔
438
      if (body.hasOwnProperty(key)) {
17✔
439
        attributes.push(key);
17✔
440
      }
17✔
441
    }
17✔
442
    return attributes;
15✔
443
  }
15✔
444

445
  /**
1✔
446
   * Returns whether the token in the request is allowed to see the email field
447
   * of a User with the given relation (own/organ/all).
448
   */
1✔
449
  private async canSeeEmail(req: RequestWithToken, relation: string): Promise<boolean> {
1✔
450
    return this.roleManager.can(req.token.roles, 'get', relation, 'User', ['email']);
27✔
451
  }
27✔
452

453
  /**
1✔
454
   * GET /users
455
   * @summary Get a list of all users
456
   * @operationId getAllUsers
457
   * @tags users - Operations of user controller
458
   * @security JWT
459
   * @param {integer} take.query - How many users the endpoint should return
460
   * @param {integer} skip.query - How many users should be skipped (for pagination)
461
   * @param {string} search.query - Filter based on first name, last name, nickname & email
462
   * @param {boolean} active.query - Filter based if the user is active
463
   * @param {boolean} ofAge.query - Filter based if the user is 18+
464
   * @param {integer} id.query - Filter based on user ID
465
   * @param {string} type.query - enum:MEMBER,ORGAN,VOUCHER,LOCAL_USER,LOCAL_ADMIN,INVOICE,AUTOMATIC_INVOICE - Filter based on user type.
466
   * @return {PaginatedUserResponse} 200 - A list of all users
467
   */
1✔
468
  public async getAllUsers(req: RequestWithToken, res: Response): Promise<void> {
1✔
469
    this.logger.trace('Get all users by user', req.token.user);
12✔
470

471
    let take;
12✔
472
    let skip;
12✔
473
    let filters: UserFilterParameters;
12✔
474
    try {
12✔
475
      const pagination = parseRequestPagination(req);
12✔
476
      filters = parseGetUsersFilters(req);
12✔
477
      take = pagination.take;
12✔
478
      skip = pagination.skip;
12✔
479
    } catch (e) {
12!
480
      res.status(400).send(e.message);
×
481
      return;
×
482
    }
×
483

484
    try {
12✔
485
      const [users, count] = await UserService.getUsers(filters, { take, skip });
12✔
486
      const records = users.map((u) => asUserResponse(u, true));
12✔
487
      if (!await this.canSeeEmail(req, 'all')) {
12✔
488
        records.forEach((u) => { u.email = undefined; });
1✔
489
      }
1✔
490
      res.status(200).json({
12✔
491
        _pagination: { take, skip, count },
12✔
492
        records,
12✔
493
      });
12✔
494
    } catch (error) {
12!
495
      this.logger.error('Could not get users:', error);
×
496
      res.status(500).json('Internal server error.');
×
497
    }
×
498
  }
12✔
499

500
  /**
1✔
501
   * GET /users/usertype/{userType}
502
   * @summary Get all users of user type
503
   * @operationId getAllUsersOfUserType
504
   * @tags users - Operations of user controller
505
   * @param {string} userType.path.required - The userType of the requested users
506
   * @security JWT
507
   * @param {integer} take.query - How many users the endpoint should return
508
   * @param {integer} skip.query - How many users should be skipped (for pagination)
509
   * @return {PaginatedUserResponse} 200 - A list of all users
510
   * @return {string} 404 - Nonexistent usertype
511
   */
1✔
512
  public async getAllUsersOfUserType(req: RequestWithToken, res: Response): Promise<void> {
1✔
513
    const parameters = req.params;
5✔
514
    this.logger.trace('Get all users of userType', parameters, 'by user', req.token.user);
5✔
515
    const userType = req.params.userType.toUpperCase();
5✔
516

517
    // If it does not exist, return a 404 error
5✔
518
    const type = UserType[userType as keyof typeof UserType];
5✔
519
    if (!type || Number(userType)) {
5✔
520
      res.status(404).json('Unknown userType.');
1✔
521
      return;
1✔
522
    }
1✔
523

524
    try {
4✔
525
      req.query.type = userType;
4✔
526
      await this.getAllUsers(req, res);
4✔
527
    } catch (error) {
5!
528
      this.logger.error('Could not get users:', error);
×
529
      res.status(500).json('Internal server error.');
×
530
    }
×
531
  }
5✔
532

533
  /**
1✔
534
   * PUT /users/{id}/authenticator/pin
535
   * @summary Put an users pin code
536
   * @operationId updateUserPin
537
   * @tags users - Operations of user controller
538
   * @param {integer} id.path.required - The id of the user
539
   * @param {UpdatePinRequest} request.body.required -
540
   *    The PIN code to update to
541
   * @security JWT
542
   * @return 204 - Update success
543
   * @return {string} 400 - Validation Error
544
   * @return {string} 404 - Nonexistent user id
545
   */
1✔
546
  public async updateUserPin(req: RequestWithToken, res: Response): Promise<void> {
1✔
547
    const { params } = req;
2✔
548
    const updatePinRequest = req.body as UpdatePinRequest;
2✔
549
    this.logger.trace('Update user pin', params, 'by user', req.token.user);
2✔
550

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

560
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
561
        updatePinRequest.pin.toString(), PinAuthenticator);
1✔
562
      res.status(204).json();
1✔
563
    } catch (error) {
2!
564
      this.logger.error('Could not update pin:', error);
×
565
      res.status(500).json('Internal server error.');
×
566
    }
×
567
  }
2✔
568

569
  /**
1✔
570
   * PUT /users/{id}/authenticator/nfc
571
   * @summary Put a users NFC code
572
   * @operationId updateUserNfc
573
   * @tags users - Operations of user controller
574
   * @param {integer} id.path.required - The id of the user
575
   * @param {UpdateNfcRequest} request.body.required -
576
   *    The NFC code to update to
577
   * @security JWT
578
   * @return 204 - Update success
579
   * @return {string} 400 - Validation Error
580
   * @return {string} 404 - Nonexistent user id
581
   */
1✔
582
  public async updateUserNfc(req: RequestWithToken, res: Response): Promise<void> {
1✔
583
    const { params } = req;
8✔
584
    const updateNfcRequest = req.body as UpdateNfcRequest;
8✔
585
    this.logger.trace('Update user NFC', params, 'by user', req.token.user);
8✔
586

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

596
      await new AuthenticationService().setUserAuthenticationNfc(user,
7✔
597
        updateNfcRequest.nfcCode.toString(), NfcAuthenticator);
7✔
598
      res.status(204).json();
7✔
599
    } catch (error) {
8!
600
      this.logger.error('Could not update NFC:', error);
×
601
      res.status(500).json('Internal server error.');
×
602
    }
×
603
  }
8✔
604

605
  /**
1✔
606
   * DELETE /users/{id}/authenticator/nfc
607
   * @summary Delete a nfc code
608
   * @operationId deleteUserNfc
609
   * @tags users - Operations of user controller
610
   * @param {integer} id.path.required - The id of the user
611
   * @security JWT
612
   * @return 200 - Delete nfc success
613
   * @return {string} 400 - Validation Error
614
   * @return {string} 403 - Nonexistent user nfc
615
   * @return {string} 404 - Nonexistent user id
616
   */
1✔
617
  public async deleteUserNfc(req: RequestWithToken, res: Response): Promise<void> {
1✔
618
    const parameters = req.params;
9✔
619
    this.logger.trace('Delete user NFC', parameters, 'by user', req.token.user);
9✔
620

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

630
      if (await NfcAuthenticator.count({ where: { userId: parseInt(parameters.id, 10) } }) == 0) {
9✔
631
        res.status(403).json('No saved nfc');
3✔
632
        return;
3✔
633
      }
3✔
634

635
      await NfcAuthenticator.delete(parseInt(parameters.id, 10));
3✔
636
      res.status(204).json();
3✔
637
    } catch (error) {
9!
638
      this.logger.error('Could not update NFC:', error);
×
639
      res.status(500).json('Internal server error.');
×
640
    }
×
641
  }
9✔
642

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

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

668
      const generatedKey = randomBytes(128).toString('hex');
1✔
669
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
670
        generatedKey, KeyAuthenticator);
1✔
671
      const response = { key: generatedKey } as UpdateKeyResponse;
1✔
672
      res.status(200).json(response);
1✔
673
    } catch (error) {
2!
674
      this.logger.error('Could not update key:', error);
×
675
      res.status(500).json('Internal server error.');
×
676
    }
×
677
  }
2✔
678

679
  /**
1✔
680
   * Delete /users/{id}/authenticator/key
681
   * @summary Delete a users key code
682
   * @operationId deleteUserKey
683
   * @tags users - Operations of user controller
684
   * @param {integer} id.path.required - The id of the user
685
   * @security JWT
686
   * @return  200 - Deletion succesfull
687
   * @return {string} 400 - Validation Error
688
   * @return {string} 404 - Nonexistent user id
689
   */
1✔
690
  public async deleteUserKey(req: RequestWithToken, res: Response): Promise<void> {
1✔
691
    const { params } = req;
2✔
692
    this.logger.trace('Delete user key', params, 'by user', req.token.user);
2✔
693

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

703

704
      await KeyAuthenticator.delete(parseInt(params.id, 10));
1✔
705
      res.status(204).json();
1✔
706
    } catch (error) {
2!
707
      this.logger.error('Could not delete key:', error);
×
708
      res.status(500).json('Internal server error.');
×
709
    }
×
710
  }
2✔
711

712
  /**
1✔
713
   * PUT /users/{id}/authenticator/local
714
   * @summary Put a user's local password
715
   * @operationId updateUserLocalPassword
716
   * @tags users - Operations of user controller
717
   * @param {integer} id.path.required - The id of the user
718
   * @param {UpdateLocalRequest} request.body.required -
719
   *    The password update
720
   * @security JWT
721
   * @return 204 - Update success
722
   * @return {string} 400 - Validation Error
723
   * @return {string} 404 - Nonexistent user id
724
   */
1✔
725
  public async updateUserLocalPassword(req: RequestWithToken, res: Response): Promise<void> {
1✔
726
    const parameters = req.params;
2✔
727
    const updateLocalRequest = req.body as UpdateLocalRequest;
2✔
728
    this.logger.trace('Update user local password', parameters, 'by user', req.token.user);
2✔
729

730
    try {
2✔
731
      const id = Number.parseInt(parameters.id, 10);
2✔
732
      // Get the user object if it exists
2✔
733
      const user = await User.findOne({ where: { id, deleted: false } });
2✔
734
      // If it does not exist, return a 404 error
2✔
735
      if (user == null) {
2✔
736
        res.status(404).json('Unknown user ID.');
1✔
737
        return;
1✔
738
      }
1✔
739

740
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
741
        updateLocalRequest.password, LocalAuthenticator);
1✔
742
      res.status(204).json();
1✔
743
    } catch (error) {
2!
744
      this.logger.error('Could not update local password:', error);
×
745
      res.status(500).json('Internal server error.');
×
746
    }
×
747
  }
2✔
748

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

766
    let take;
4✔
767
    let skip;
4✔
768
    try {
4✔
769
      const pagination = parseRequestPagination(req);
4✔
770
      take = pagination.take;
4✔
771
      skip = pagination.skip;
4✔
772
    } catch (e) {
4!
773
      res.status(400).send(e.message);
×
774
      return;
×
775
    }
×
776

777
    try {
4✔
778
      const organId = asNumber(parameters.id);
4✔
779
      // Get the user object if it exists
4✔
780
      const user = await User.findOne({ where: { id: organId } });
4✔
781
      // If it does not exist, return a 404 error
4✔
782
      if (user == null) {
4✔
783
        res.status(404).json('Unknown user ID.');
1✔
784
        return;
1✔
785
      }
1✔
786

787
      if (user.type !== UserType.ORGAN) {
4✔
788
        res.status(400).json('User is not of type Organ');
1✔
789
        return;
1✔
790
      }
1✔
791

792
      const [members, count] = await UserService.getUsers({ organId }, { take, skip });
2✔
793
      const records = members.map((u) => asUserResponse(u, true));
2✔
794
      if (!await this.canSeeEmail(req, UserController.getRelation(req))) {
4!
795
        records.forEach((u) => { u.email = undefined; });
×
796
      }
✔
797
      res.status(200).json({
2✔
798
        _pagination: { take, skip, count },
2✔
799
        records,
2✔
800
      });
2✔
801
    } catch (error) {
4!
802
      this.logger.error('Could not get organ members:', error);
×
803
      res.status(500).json('Internal server error.');
×
804
    }
×
805
  }
4✔
806

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

821
    try {
10✔
822
      // Get the user object if it exists
10✔
823
      const user = await UserService.getSingleUser(asNumber(parameters.id));
10✔
824
      // If it does not exist, return a 404 error
10✔
825
      if (user == null) {
10✔
826
        res.status(404).json('Unknown user ID.');
3✔
827
        return;
3✔
828
      }
3✔
829

830
      const response = asUserResponse(user, true);
7✔
831
      if (!await this.canSeeEmail(req, UserController.getRelation(req))) {
10✔
832
        response.email = undefined;
1✔
833
      }
1✔
834
      res.status(200).json(response);
7✔
835
    } catch (error) {
10!
836
      this.logger.error('Could not get individual user:', error);
×
837
      res.status(500).json('Internal server error.');
×
838
    }
×
839
  }
10✔
840

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

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

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

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

900
    let parsedExpiryDate: Date | null | undefined = undefined;
8✔
901
    if (body.expiryDate !== undefined) {
12!
902
      if (body.expiryDate === null) {
×
903
        parsedExpiryDate = null;
×
904
      } else {
×
905
        parsedExpiryDate = new Date(body.expiryDate);
×
906
        if (isNaN(parsedExpiryDate.getTime())) {
×
907
          res.status(400).json('expiryDate is not a valid date');
×
908
          return;
×
909
        }
×
910
        const cap = new Date();
×
911
        cap.setMonth(cap.getMonth() + 18);
×
912
        if (parsedExpiryDate > cap) {
×
913
          res.status(400).json('expiryDate cannot be more than 18 months in the future');
×
914
          return;
×
915
        }
×
916
      }
×
917
    }
✔
918

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

929
      const isLocalUser = LocalUserTypes.includes(user.type);
8✔
930
      if (parsedExpiryDate !== undefined && !isLocalUser) {
12!
931
        res.status(400).json('expiryDate can only be set for local user accounts');
×
932
        return;
×
933
      }
✔
934

935
      user = { ...body } as unknown as User;
8✔
936
      if (parsedExpiryDate !== undefined) {
12!
937
        user.expiryDate = parsedExpiryDate;
×
938
        user.expiryNotificationSent = false;
×
939
      } else {
12✔
940
        delete (user as Partial<User>).expiryDate;
8✔
941
      }
8✔
942

943
      await User.update(parameters.id, user);
8✔
944
      const updatedUser = await UserService.getSingleUser(asNumber(parameters.id));
8✔
945
      res.status(200).json(asUserResponse(updatedUser, true));
8✔
946
    } catch (error) {
12!
947
      this.logger.error('Could not update user:', error);
×
948
      res.status(500).json('Internal server error.');
×
949
    }
×
950
  }
12✔
951

952
  /**
1✔
953
   * DELETE /users/{id}
954
   * @summary Delete a single user
955
   * @operationId deleteUser
956
   * @tags users - Operations of user controller
957
   * @param {integer} id.path.required - The id of the user
958
   * @security JWT
959
   * @return 204 - User successfully deleted
960
   * @return {string} 400 - Cannot delete yourself
961
   */
1✔
962
  public async deleteUser(req: RequestWithToken, res: Response): Promise<void> {
1✔
963
    const parameters = req.params;
5✔
964
    this.logger.trace('Delete individual user', parameters, 'by user', req.token.user);
5✔
965

966
    if (req.token.user.id === parseInt(parameters.id, 10)) {
5✔
967
      res.status(400).json('Cannot delete yourself');
1✔
968
      return;
1✔
969
    }
1✔
970

971
    try {
4✔
972
      const id = parseInt(parameters.id, 10);
4✔
973
      // Get the user object if it exists
4✔
974
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
975
      // If it does not exist, return a 404 error
4✔
976
      if (user == null) {
5✔
977
        res.status(404).json('Unknown user ID.');
2✔
978
        return;
2✔
979
      }
2✔
980

981
      user.deleted = true;
2✔
982
      await user.save();
2✔
983
      res.status(204).json('User deleted');
2✔
984
    } catch (error) {
5!
985
      this.logger.error('Could not create product:', error);
×
986
      res.status(500).json('Internal server error.');
×
987
    }
×
988
  }
5✔
989

990
  /**
1✔
991
   * GET /users/nfc/{nfcCode}
992
   * @summary Get a user using the nfc code
993
   * @operationId findUserNfc
994
   * @tags users - Operations of the user controller
995
   * @security JWT
996
   * @param {string} nfcCode.path.required - The nfc code of the user
997
   * @return {UserResponse} 200 - The requested user
998
   * @return {string} 404 - The user with the given nfc code does not exist
999
   */
1✔
1000
  public async findUserNfc(req: RequestWithToken, res: Response): Promise<void> {
1✔
1001
    const parameters = req.params;
3✔
1002
    this.logger.trace('Find user nfc', parameters, 'by user', req.token.user);
3✔
1003

1004
    try {
3✔
1005
      const nfcCode = String(parameters.nfcCode);
3✔
1006
      const nfc = await NfcAuthenticator.findOne({ where: { nfcCode } });
3✔
1007

1008
      if (nfc === null) {
3✔
1009
        res.status(404).json('Unknown nfc code');
1✔
1010
        return;
1✔
1011
      }
1✔
1012

1013
      const userResponse = parseUserToResponse(nfc.user);
2✔
1014
      if (!await this.canSeeEmail(req, 'all')) {
3!
1015
        userResponse.email = undefined;
×
1016
      }
✔
1017
      res.status(200).json(userResponse);
2✔
1018
    } catch (error) {
3!
1019
      this.logger.error('Could not find user using nfc:', error);
×
1020
      res.status(500).json('Internal server error.');
×
1021
    }
×
1022
  }
3✔
1023

1024
  /**
1✔
1025
   * POST /users/acceptTos
1026
   * @summary Accept the current Terms of Service version
1027
   * @operationId acceptTos
1028
   * @tags users - Operations of the User controller
1029
   * @param {AcceptTosRequest} request.body.required - "Tosrequest body"
1030
   * @security JWT
1031
   * @return 204 - ToS accepted
1032
   * @return {string} 400 - Given version is not the current TOS version, or this version was already accepted
1033
   * @return {string} 404 - User not found
1034
   */
1✔
1035
  public async acceptToS(req: RequestWithToken, res: Response): Promise<void> {
1✔
1036
    this.logger.trace('Accept ToS for user', req.token.user);
4✔
1037

1038
    const { id } = req.token.user;
4✔
1039
    const body = req.body as AcceptTosRequest;
4✔
1040

1041
    try {
4✔
1042
      const result = await UserService.acceptToS(id, body);
4✔
1043
      switch (result) {
4✔
1044
        case AcceptTosResult.USER_NOT_FOUND:
4!
NEW
1045
          res.status(404).json('User not found.');
×
NEW
1046
          return;
×
1047
        case AcceptTosResult.NOT_CURRENT_VERSION:
4✔
1048
          res.status(400).json('Given version is not the current Terms of Service version.');
1✔
1049
          return;
1✔
1050
        case AcceptTosResult.ALREADY_ACCEPTED:
4✔
1051
          res.status(400).json('User already accepted this ToS version.');
1✔
1052
          return;
1✔
1053
        case AcceptTosResult.SUCCESS:
4✔
1054
          res.status(204).json();
2✔
1055
          return;
2✔
1056
      }
4✔
1057
    } catch (error) {
4!
NEW
1058
      this.logger.error('Could not accept ToS for user:', error);
×
NEW
1059
      res.status(500).json('Internal server error.');
×
NEW
1060
    }
×
1061
  }
4✔
1062

1063
  /**
1✔
1064
   * GET /users/{id}/tos
1065
   * @summary Get a user's terms of service status and acceptance history
1066
   * @operationId getUserTos
1067
   * @tags users - Operations of the User controller
1068
   * @param {integer} id.path.required - The id of the user
1069
   * @security JWT
1070
   * @return {UserTosResponse} 200 - The user's TOS status and acceptance history
1071
   * @return {string} 404 - User not found
1072
   */
1✔
1073
  public async getUserTos(req: RequestWithToken, res: Response): Promise<void> {
1✔
1074
    const parameters = req.params;
3✔
1075
    this.logger.trace('Get user TOS status', parameters, 'by user', req.token.user);
3✔
1076

1077
    try {
3✔
1078
      const id = parseInt(parameters.id, 10);
3✔
1079
      const user = await User.findOne({ where: { id, deleted: false } });
3✔
1080
      if (user == null) {
3✔
1081
        res.status(404).json('User not found.');
1✔
1082
        return;
1✔
1083
      }
1✔
1084

1085
      const [status, currentVersion, acceptances] = await Promise.all([
2✔
1086
        TermsOfServiceService.getUserTosStatus(user),
2✔
1087
        TermsOfServiceService.getCurrentVersion(),
2✔
1088
        TermsOfServiceService.getAcceptances(id),
2✔
1089
      ]);
2✔
1090

1091
      const response: UserTosResponse = {
2✔
1092
        status,
2✔
1093
        currentVersion,
2✔
1094
        acceptances: acceptances.map((acceptance) => ({
2✔
1095
          versionNumber: acceptance.versionNumber,
2✔
1096
          acceptedAt: acceptance.createdAt.toISOString(),
2✔
1097
        })),
2✔
1098
      };
2✔
1099
      res.json(response);
2✔
1100
    } catch (error) {
3!
NEW
1101
      this.logger.error('Could not get user TOS status:', error);
×
1102
      res.status(500).json('Internal server error.');
×
1103
    }
×
1104
  }
3✔
1105

1106
  /**
1✔
1107
   * GET /users/{id}/products
1108
   * @summary Get an user's products
1109
   * @operationId getUsersProducts
1110
   * @tags users - Operations of user controller
1111
   * @param {integer} id.path.required - The id of the user
1112
   * @param {integer} take.query - How many products the endpoint should return
1113
   * @param {integer} skip.query - How many products should be skipped (for pagination)
1114
   * @security JWT
1115
   * @return {PaginatedProductResponse} 200 - List of products.
1116
   */
1✔
1117
  public async getUsersProducts(req: RequestWithToken, res: Response): Promise<void> {
1✔
1118
    const parameters = req.params;
4✔
1119
    this.logger.trace("Get user's products", parameters, 'by user', req.token.user);
4✔
1120

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

1132
    // Handle request
4✔
1133
    try {
4✔
1134
      const id = parseInt(parameters.id, 10);
4✔
1135
      const owner = await User.findOne({ where: { id, deleted: false } });
4✔
1136
      if (owner == null) {
4✔
1137
        res.status(404).json({});
1✔
1138
        return;
1✔
1139
      }
1✔
1140

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

1150
  /**
1✔
1151
   * GET /users/{id}/containers
1152
   * @summary Returns the user's containers
1153
   * @operationId getUsersContainers
1154
   * @tags users - Operations of user controller
1155
   * @param {integer} id.path.required - The id of the user
1156
   * @security JWT
1157
   * @param {integer} take.query - How many containers the endpoint should return
1158
   * @param {integer} skip.query - How many containers should be skipped (for pagination)
1159
   * @return {PaginatedContainerResponse} 200 - All users updated containers
1160
   * @return {string} 404 - Not found error
1161
   * @return {string} 500 - Internal server error
1162
   */
1✔
1163
  public async getUsersContainers(req: RequestWithToken, res: Response): Promise<void> {
1✔
1164
    const { id } = req.params;
4✔
1165
    this.logger.trace("Get user's containers", id, 'by user', req.token.user);
4✔
1166

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

1178
    // handle request
4✔
1179
    try {
4✔
1180
      // Get the user object if it exists
4✔
1181
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1182
      // If it does not exist, return a 404 error
4✔
1183
      if (user == null) {
4✔
1184
        res.status(404).json('Unknown user ID.');
1✔
1185
        return;
1✔
1186
      }
1✔
1187

1188
      const [revisions, count] = await ContainerService
3✔
1189
        .getContainers({}, { take, skip }, user);
3✔
1190
      const records = revisions.map((r) => ContainerService.revisionToResponse(r));
3✔
1191
      res.json(toResponse(records, count, { take, skip }));
3✔
1192
    } catch (error) {
4!
1193
      this.logger.error('Could not return containers:', error);
×
1194
      res.status(500).json('Internal server error.');
×
1195
    }
×
1196
  }
4✔
1197

1198
  /**
1✔
1199
   * GET /users/{id}/pointsofsale
1200
   * @summary Returns the user's Points of Sale
1201
   * @operationId getUsersPointsOfSale
1202
   * @tags users - Operations of user controller
1203
   * @param {integer} id.path.required - The id of the user
1204
   * @param {integer} take.query - How many points of sale the endpoint should return
1205
   * @param {integer} skip.query - How many points of sale should be skipped (for pagination)
1206
   * @security JWT
1207
   * @return {PaginatedPointOfSaleResponse} 200 - All users updated point of sales
1208
   * @return {string} 404 - Not found error
1209
   * @return {string} 500 - Internal server error
1210
   */
1✔
1211
  public async getUsersPointsOfSale(req: RequestWithToken, res: Response): Promise<void> {
1✔
1212
    const { id } = req.params;
4✔
1213
    this.logger.trace("Get user's points of sale", id, 'by user', req.token.user);
4✔
1214

1215
    let take;
4✔
1216
    let skip;
4✔
1217
    try {
4✔
1218
      const pagination = parseRequestPagination(req);
4✔
1219
      take = pagination.take;
4✔
1220
      skip = pagination.skip;
4✔
1221
    } catch (e) {
4!
1222
      res.status(400).send(e.message);
×
1223
      return;
×
1224
    }
×
1225

1226
    // handle request
4✔
1227
    try {
4✔
1228
      // Get the user object if it exists
4✔
1229
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1230
      // If it does not exist, return a 404 error
4✔
1231
      if (user == null) {
4✔
1232
        res.status(404).json('Unknown user ID.');
1✔
1233
        return;
1✔
1234
      }
1✔
1235

1236
      const [revisions, count] = await PointOfSaleService
3✔
1237
        .getPointsOfSale({}, { take, skip }, user);
3✔
1238
      const records = revisions.map((r) => PointOfSaleService.revisionToResponse(r));
3✔
1239
      res.json(toResponse(records, count, { take, skip }));
3✔
1240
    } catch (error) {
4!
1241
      this.logger.error('Could not return point of sale:', error);
×
1242
      res.status(500).json('Internal server error.');
×
1243
    }
×
1244
  }
4✔
1245

1246
  /**
1✔
1247
   * GET /users/{id}/transactions
1248
   * @summary Get transactions from a user.
1249
   * @operationId getUsersTransactions
1250
   * @tags users - Operations of user controller
1251
   * @param {integer} id.path.required - The id of the user that should be involved
1252
   * in all returned transactions
1253
   * @param {integer} fromId.query - From-user for selected transactions
1254
   * @param {integer} createdById.query - User that created selected transaction
1255
   * @param {integer} toId.query - To-user for selected transactions
1256
   * transactions. Requires ContainerId
1257
   * @param {integer} productId.query - Product ID for selected transactions
1258
   * @param {integer} productRevision.query - Product Revision for selected
1259
   * transactions. Requires ProductID
1260
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1261
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1262
   * @param {integer} take.query - How many transactions the endpoint should return
1263
   * @param {integer} skip.query - How many transactions should be skipped (for pagination)
1264
   * @security JWT
1265
   * @return {PaginatedBaseTransactionResponse} 200 - List of transactions.
1266
   */
1✔
1267
  public async getUsersTransactions(req: RequestWithToken, res: Response): Promise<void> {
1✔
1268
    const { id } = req.params;
4✔
1269
    this.logger.trace("Get user's", id, 'transactions by user', req.token.user);
4✔
1270

1271
    // Parse the filters given in the query parameters. If there are any issues,
4✔
1272
    // the parse method will throw an exception. We will then return a 400 error.
4✔
1273
    let filters;
4✔
1274
    try {
4✔
1275
      filters = parseGetTransactionsFilters(req);
4✔
1276
    } catch (e) {
4!
1277
      res.status(400).json(e.message);
×
1278
      return;
×
1279
    }
×
1280

1281
    let take;
4✔
1282
    let skip;
4✔
1283
    try {
4✔
1284
      const pagination = parseRequestPagination(req);
4✔
1285
      take = pagination.take;
4✔
1286
      skip = pagination.skip;
4✔
1287
    } catch (e) {
4!
1288
      res.status(400).send(e.message);
×
1289
      return;
×
1290
    }
×
1291

1292
    try {
4✔
1293
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
4✔
1294
      if (user == null) {
4✔
1295
        res.status(404).json({});
1✔
1296
        return;
1✔
1297
      }
1✔
1298
      const [records, count] = await new TransactionService().getTransactions(filters, { take, skip }, user);
3✔
1299

1300
      res.status(200).json(toResponse(records, count, { take, skip }));
3✔
1301
    } catch (error) {
4!
1302
      this.logger.error('Could not return all transactions:', error);
×
1303
      res.status(500).json('Internal server error.');
×
1304
    }
×
1305
  }
4✔
1306

1307
  /**
1✔
1308
   * GET /users/{id}/transactions/sales/report
1309
   * @summary Get sales report for the given user
1310
   * @operationId getUsersSalesReport
1311
   * @tags users - Operations of user controller
1312
   * @param {integer} id.path.required - The id of the user to get the sales report for
1313
   * @security JWT
1314
   * @param {string} fromDate.query.required - Start date for selected sales (inclusive)
1315
   * @param {string} tillDate.query.required - End date for selected sales (exclusive)
1316
   * @return {ReportResponse} 200 - The sales report of the user
1317
   * @return {string} 400 - Validation error
1318
   * @return {string} 404 - User not found error.
1319
   */
1✔
1320
  public async getUsersSalesReport(req: RequestWithToken, res: Response): Promise<void> {
1✔
1321
    const { id } = req.params;
7✔
1322
    this.logger.trace('Get sales report for user ', id, ' by user', req.token.user);
7✔
1323

1324
    let filters: { fromDate: Date, tillDate: Date };
7✔
1325
    try {
7✔
1326
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1327
    } catch (e) {
7✔
1328
      res.status(400).json(e.message);
1✔
1329
      return;
1✔
1330
    }
1✔
1331

1332
    try {
6✔
1333
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
6✔
1334
      if (user == null) {
7✔
1335
        res.status(404).json('Unknown user ID.');
1✔
1336
        return;
1✔
1337
      }
1✔
1338

1339
      const report = await (new SalesReportService()).getReport({ ...filters, forId: user.id });
5✔
1340
      res.status(200).json(ReportService.reportToResponse(report));
5✔
1341
    } catch (error) {
7!
1342
      this.logger.error('Could not get sales report:', error);
×
1343
      res.status(500).json('Internal server error.');
×
1344
    }
×
1345
  }
7✔
1346

1347
  /**
1✔
1348
   * GET /users/{id}/transactions/sales/report/pdf
1349
   * @summary Get sales report for the given user
1350
   * @operationId getUsersSalesReportPdf
1351
   * @tags users - Operations of user controller
1352
   * @param {integer} id.path.required - The id of the user to get the sales report for
1353
   * @security JWT
1354
   * @param {string} fromDate.query.required - Start date for selected sales (inclusive)
1355
   * @param {string} tillDate.query.required - End date for selected sales (exclusive)
1356
   * @param {string} description.query - Description of the report
1357
   * @param {string} fileType.query - enum:PDF,TEX - The file type of the report
1358
   * @return {string} 404 - User not found error.
1359
   * @returns {string} 200 - The requested report - application/pdf
1360
   * @return {string} 400 - Validation error
1361
   * @return {string} 500 - Internal server error
1362
   * @return {string} 502 - PDF generation failed
1363
   */
1✔
1364
  public async getUsersSalesReportPdf(req: RequestWithToken, res: Response): Promise<void> {
1✔
1365
    const { id } = req.params;
7✔
1366
    this.logger.trace('Get sales report pdf for user ', id, ' by user', req.token.user);
7✔
1367

1368
    let filters: { fromDate: Date, tillDate: Date };
7✔
1369
    let description: string;
7✔
1370
    let fileType: ReturnFileType;
7✔
1371
    try {
7✔
1372
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1373
      description = String(req.query.description);
7✔
1374
      fileType = asReturnFileType(req.query.fileType);
7✔
1375
    } catch (e) {
7✔
1376
      res.status(400).json(e.message);
4✔
1377
      return;
4✔
1378
    }
4✔
1379

1380
    try {
3✔
1381
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1382
      if (user == null) {
7✔
1383
        res.status(404).json('Unknown user ID.');
1✔
1384
        return;
1✔
1385
      }
1✔
1386
      const service = new SalesReportService();
2✔
1387
      await reportPDFhelper(res)(service, filters, description, user.id, UserReportParametersType.Sales, fileType);
2✔
1388
    } catch (error) {
1✔
1389
      this.logger.error('Could not get sales report:', error);
1✔
1390
      if (error instanceof PdfError) {
1✔
1391
        res.status(502).json('PDF Generator service failed.');
1✔
1392
        return;
1✔
1393
      }
1!
1394
      res.status(500).json('Internal server error.');
×
1395
    }
×
1396
  }
7✔
1397

1398
  /**
1✔
1399
   * GET /users/{id}/transactions/purchases/report/pdf
1400
   * @summary Get purchase report pdf for the given user
1401
   * @operationId getUsersPurchaseReportPdf
1402
   * @tags users - Operations of user controller
1403
   * @param {integer} id.path.required - The id of the user to get the purchase report for
1404
   * @security JWT
1405
   * @param {string} fromDate.query.required - Start date for selected purchases (inclusive)
1406
   * @param {string} tillDate.query.required - End date for selected purchases (exclusive)
1407
   * @param {string} fileType.query - enum:PDF,TEX - The file type of the report
1408
   * @return {string} 404 - User not found error.
1409
   * @returns {string} 200 - The requested report - application/pdf
1410
   * @return {string} 400 - Validation error
1411
   * @return {string} 500 - Internal server error
1412
   * @return {string} 502 - PDF generation failed
1413
   */
1✔
1414
  public async getUsersPurchaseReportPdf(req: RequestWithToken, res: Response): Promise<void> {
1✔
1415
    const { id } = req.params;
7✔
1416
    this.logger.trace('Get purchase report pdf for user ', id, ' by user', req.token.user);
7✔
1417

1418
    let filters: { fromDate: Date, tillDate: Date };
7✔
1419
    let description: string;
7✔
1420
    let fileType: ReturnFileType;
7✔
1421
    try {
7✔
1422
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1423
      description = String(req.query.description);
7✔
1424
      fileType = asReturnFileType(req.query.fileType);
7✔
1425
    } catch (e) {
7✔
1426
      res.status(400).json(e.message);
4✔
1427
      return;
4✔
1428
    }
4✔
1429

1430
    try {
3✔
1431
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1432
      if (user == null) {
7✔
1433
        res.status(404).json('Unknown user ID.');
1✔
1434
        return;
1✔
1435
      }
1✔
1436
      const service = new BuyerReportService();
2✔
1437
      await (reportPDFhelper(res))(service, filters, description, user.id, UserReportParametersType.Purchases, fileType);
2✔
1438
    } catch (error) {
1✔
1439
      this.logger.error('Could not get sales report:', error);
1✔
1440
      if (error instanceof PdfError) {
1✔
1441
        res.status(502).json('PDF Generator service failed.');
1✔
1442
        return;
1✔
1443
      }
1!
1444
      res.status(500).json('Internal server error.');
×
1445
    }
×
1446
  }
7✔
1447

1448
  /**
1✔
1449
   * GET /users/{id}/transactions/purchases/report
1450
   * @summary Get purchases report for the given user
1451
   * @operationId getUsersPurchasesReport
1452
   * @tags users - Operations of user controller
1453
   * @param {integer} id.path.required - The id of the user to get the purchases report for
1454
   * @security JWT
1455
   * @param {string} fromDate.query.required - Start date for selected purchases (inclusive)
1456
   * @param {string} tillDate.query.required - End date for selected purchases (exclusive)
1457
   * @return {ReportResponse} 200 - The purchases report of the user
1458
   * @return {string} 400 - Validation error
1459
   * @return {string} 404 - User not found error.
1460
   */
1✔
1461
  public async getUsersPurchasesReport(req: RequestWithToken, res: Response): Promise<void> {
1✔
1462
    const { id } = req.params;
4✔
1463
    this.logger.trace('Get purchases report for user ', id, ' by user', req.token.user);
4✔
1464

1465
    let filters: { fromDate: Date, tillDate: Date };
4✔
1466
    try {
4✔
1467
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
4✔
1468
    } catch (e) {
4✔
1469
      res.status(400).json(e.message);
1✔
1470
      return;
1✔
1471
    }
1✔
1472

1473
    try {
3✔
1474
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1475
      if (user == null) {
4✔
1476
        res.status(404).json('Unknown user ID.');
1✔
1477
        return;
1✔
1478
      }
1✔
1479

1480
      const report = await (new BuyerReportService()).getReport({ ...filters, forId: user.id });
2✔
1481
      res.status(200).json(ReportService.reportToResponse(report));
2✔
1482
    } catch (error) {
4!
1483
      this.logger.error('Could not get sales report:', error);
×
1484
      res.status(500).json('Internal server error.');
×
1485
    }
×
1486
  }
4✔
1487

1488
  /**
1✔
1489
   * GET /users/{id}/transfers
1490
   * @summary Get transfers to or from an user.
1491
   * @operationId getUsersTransfers
1492
   * @tags users - Operations of user controller
1493
   * @param {integer} id.path.required - The id of the user that should be involved
1494
   * in all returned transfers
1495
   * @param {integer} take.query - How many transfers the endpoint should return
1496
   * @param {integer} skip.query - How many transfers should be skipped (for pagination)
1497
   * @param {integer} fromId.query - From-user for selected transfers
1498
   * @param {integer} toId.query - To-user for selected transfers
1499
   * @param {integer} id.query - ID of selected transfers
1500
   * @security JWT
1501
   * @return {PaginatedTransferResponse} 200 - List of transfers.
1502
   */
1✔
1503
  public async getUsersTransfers(req: RequestWithToken, res: Response): Promise<void> {
1✔
1504
    const { id } = req.params;
2✔
1505
    this.logger.trace("Get user's transfers", id, 'by user', req.token.user);
2✔
1506

1507
    // Parse the filters given in the query parameters. If there are any issues,
2✔
1508
    // the parse method will throw an exception. We will then return a 400 error.
2✔
1509
    let filters;
2✔
1510
    try {
2✔
1511
      filters = parseGetTransferFilters(req);
2✔
1512
    } catch (e) {
2!
1513
      res.status(400).json(e.message);
×
1514
      return;
×
1515
    }
×
1516

1517
    let take;
2✔
1518
    let skip;
2✔
1519
    try {
2✔
1520
      const pagination = parseRequestPagination(req);
2✔
1521
      take = pagination.take;
2✔
1522
      skip = pagination.skip;
2✔
1523
    } catch (e) {
2!
1524
      res.status(400).send(e.message);
×
1525
      return;
×
1526
    }
×
1527

1528
    // handle request
2✔
1529
    try {
2✔
1530
      // Get the user object if it exists
2✔
1531
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
2✔
1532
      // If it does not exist, return a 404 error
2✔
1533
      if (user == null) {
2!
1534
        res.status(404).json('Unknown user ID.');
×
1535
        return;
×
1536
      }
×
1537

1538
      const [transfers, count] = await new TransferService().getTransfers(
2✔
1539
        { ...filters }, { take, skip }, user,
2✔
1540
      );
1541
      const records = transfers.map((t) => TransferService.asTransferResponse(t));
2✔
1542
      res.json(toResponse(records, count, { take, skip }));
2✔
1543
    } catch (error) {
2!
1544
      this.logger.error('Could not return user transfers', error);
×
1545
      res.status(500).json('Internal server error.');
×
1546
    }
×
1547
  }
2✔
1548

1549
  /**
1✔
1550
   * GET /users/{id}/payment-requests
1551
   * @summary Get the PaymentRequests where the given user is the beneficiary.
1552
   *   Regular users can hit this for their own id; admins can hit it for any user.
1553
   * @operationId getUsersPaymentRequests
1554
   * @tags users - Operations of user controller
1555
   * @param {integer} id.path.required - The id of the beneficiary user.
1556
   * @param {integer} createdById.query - Filter by creator user id.
1557
   * @param {string} status.query - enum:PENDING,PAID,EXPIRED,CANCELLED - Comma-separated list of derived statuses.
1558
   * @param {string} fromDate.query - Filter requests created on or after this ISO date (inclusive).
1559
   * @param {string} tillDate.query - Filter requests created strictly before this ISO date (exclusive).
1560
   * @param {integer} take.query - How many rows the endpoint should return
1561
   * @param {integer} skip.query - How many rows to skip (for pagination)
1562
   * @security JWT
1563
   * @return {PaginatedBasePaymentRequestResponse} 200 - Payment requests for the user
1564
   * @return {string} 400 - Validation error
1565
   * @return {string} 404 - Unknown user id
1566
   */
1✔
1567
  public async getUsersPaymentRequests(req: RequestWithToken, res: Response): Promise<void> {
1✔
1568
    const { id } = req.params;
4✔
1569
    this.logger.trace("Get user's payment requests", id, 'by user', req.token.user);
4✔
1570

1571
    let filters;
4✔
1572
    let pagination;
4✔
1573
    try {
4✔
1574
      filters = parseGetPaymentRequestsFilters(req, { ignoreForId: true });
4✔
1575
      pagination = parseRequestPagination(req);
4✔
1576
    } catch (e) {
4!
1577
      res.status(400).send(e instanceof Error ? e.message : String(e));
×
1578
      return;
×
1579
    }
×
1580

1581
    try {
4✔
1582
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1583
      if (user == null) {
4✔
1584
        res.status(404).json('Unknown user ID.');
1✔
1585
        return;
1✔
1586
      }
1✔
1587

1588
      const service = new PaymentRequestService();
3✔
1589
      const [rows, count] = await service.getPaymentRequests(
3✔
1590
        { ...filters, forId: user.id }, pagination,
3✔
1591
      );
1592
      const records = rows.map((r) => PaymentRequestService.asBasePaymentRequestResponse(r));
3✔
1593
      res.json(toResponse(records, count, pagination));
3✔
1594
    } catch (e) {
4!
1595
      this.logger.error('Could not return user payment requests', e);
×
1596
      res.status(500).json('Internal server error.');
×
1597
    }
×
1598
  }
4✔
1599

1600
  /**
1✔
1601
   * GET /users/{id}/roles
1602
   * @summary Get all roles assigned to the user.
1603
   * @operationId getUserRoles
1604
   * @tags users - Operations of user controller
1605
   * @param {integer} id.path.required - The id of the user to get the roles from
1606
   * @security JWT
1607
   * @return {Array.<RoleWithPermissionsResponse>} 200 - The roles of the user
1608
   * @return {string} 404 - User not found error.
1609
   */
1✔
1610
  public async getUserRoles(req: RequestWithToken, res: Response): Promise<void> {
1✔
1611
    const parameters = req.params;
3✔
1612
    this.logger.trace('Get roles of user', parameters, 'by user', req.token.user);
3✔
1613

1614
    try {
3✔
1615
      const id = parseInt(parameters.id, 10);
3✔
1616
      // Get the user object if it exists
3✔
1617
      const user = await User.findOne({ where: { id, deleted: false } });
3✔
1618
      // If it does not exist, return a 404 error
3✔
1619
      if (user == null) {
3✔
1620
        res.status(404).json('Unknown user ID.');
1✔
1621
        return;
1✔
1622
      }
1✔
1623

1624
      const rolesWithPermissions = await this.roleManager.getRoles(user, true);
2✔
1625
      const response = rolesWithPermissions.map((r) => RBACService.asRoleResponse(r));
2✔
1626
      res.status(200).json(response);
2✔
1627
    } catch (error) {
3!
1628
      this.logger.error('Could not get roles of user:', error);
×
1629
      res.status(500).json('Internal server error.');
×
1630
    }
×
1631
  }
3✔
1632

1633
  /**
1✔
1634
   * GET /users/{id}/financialmutations
1635
   * @summary Get all financial mutations of a user (from or to).
1636
   * @operationId getUsersFinancialMutations
1637
   * @tags users - Operations of user controller
1638
   * @param {integer} id.path.required - The id of the user to get the mutations from
1639
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1640
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1641
   * @param {integer} take.query - How many transactions the endpoint should return
1642
   * @param {integer} skip.query - How many transactions should be skipped (for pagination)
1643
   * @security JWT
1644
   * @return {PaginatedFinancialMutationResponse} 200 - The financial mutations of the user
1645
   * @return {string} 404 - User not found error.
1646
   */
1✔
1647
  public async getUsersFinancialMutations(req: RequestWithToken, res: Response): Promise<void> {
1✔
1648
    const parameters = req.params;
4✔
1649
    this.logger.trace('Get financial mutations of user', parameters, 'by user', req.token.user);
4✔
1650

1651
    let filters;
4✔
1652
    let take;
4✔
1653
    let skip;
4✔
1654
    try {
4✔
1655
      filters = parseGetFinancialMutationsFilters(req);
4✔
1656
      const pagination = parseRequestPagination(req);
4✔
1657
      take = pagination.take;
4✔
1658
      skip = pagination.skip;
4✔
1659
    } catch (e) {
4!
1660
      res.status(400).send(e.message);
×
1661
      return;
×
1662
    }
×
1663

1664
    try {
4✔
1665
      const id = parseInt(parameters.id, 10);
4✔
1666
      // Get the user object if it exists
4✔
1667
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
1668
      // If it does not exist, return a 404 error
4✔
1669
      if (user == null) {
4!
1670
        res.status(404).json('Unknown user ID.');
×
1671
        return;
×
1672
      }
×
1673

1674
      const mutations = await UserService.getUserFinancialMutations(user, filters, { take, skip });
4✔
1675
      res.status(200).json(mutations);
4✔
1676
    } catch (error) {
4!
1677
      this.logger.error('Could not get financial mutations of user:', error);
×
1678
      res.status(500).json('Internal server error.');
×
1679
    }
×
1680
  }
4✔
1681

1682
  /**
1✔
1683
   * GET /users/{id}/deposits
1684
   * @summary Get all deposits of a user that are still being processed by Stripe
1685
   * @operationId getUsersProcessingDeposits
1686
   * @tags users - Operations of user controller
1687
   * @param {integer} id.path.required - The id of the user to get the deposits from
1688
   * @security JWT
1689
   * @return {Array.<RoleResponse>} 200 - The processing deposits of a user
1690
   * @return {string} 404 - User not found error.
1691
   */
1✔
1692
  public async getUsersProcessingDeposits(req: RequestWithToken, res: Response): Promise<void> {
1✔
1693
    const parameters = req.params;
2✔
1694
    this.logger.trace('Get users processing deposits from user', parameters.id);
2✔
1695

1696
    try {
2✔
1697
      const id = parseInt(parameters.id, 10);
2✔
1698

1699
      const user = await User.findOne({ where: { id } });
2✔
1700
      if (user == null) {
2✔
1701
        res.status(404).json('Unknown user ID.');
1✔
1702
        return;
1✔
1703
      }
1✔
1704

1705
      const deposits = await StripeService.getProcessingStripeDepositsFromUser(id);
1✔
1706
      res.status(200).json(deposits.map((d) => StripeService.asStripeDepositResponse(d)));
1✔
1707
    } catch (error) {
2!
1708
      this.logger.error('Could not get processing deposits of user:', error);
×
1709
      res.status(500).json('Internal server error.');
×
1710
    }
×
1711
  }
2✔
1712

1713
  /**
1✔
1714
   * GET /users/{id}/transactions/report
1715
   * @summary Get transaction report for the given user
1716
   * @operationId getUsersTransactionsReport
1717
   * @tags users - Operations of user controller
1718
   * @param {integer} id.path.required - The id of the user to get the transaction report from
1719
   * @security JWT
1720
   * @return {Array.<TransactionReportResponse>} 200 - The transaction report of the user
1721
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1722
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1723
   * @param {integer} fromId.query - From-user for selected transactions
1724
   * @param {integer} toId.query - To-user for selected transactions
1725
   * @param {boolean} exclusiveToId.query - If all sub-transactions should be to the toId user, default true
1726
   * @deprecated - Use /users/{id}/transactions/sales/report or /users/{id}/transactions/purchases/report instead
1727
   * @return {string} 404 - User not found error.
1728
   */
1✔
1729
  public async getUsersTransactionsReport(req: RequestWithToken, res: Response): Promise<void> {
1✔
1730
    const parameters = req.params;
6✔
1731
    this.logger.trace('Get transaction report for user ', req.params.id, ' by user', req.token.user);
6✔
1732

1733
    let filters;
6✔
1734
    try {
6✔
1735
      filters = parseGetTransactionsFilters(req);
6✔
1736
    } catch (e) {
6✔
1737
      res.status(400).json(e.message);
1✔
1738
      return;
1✔
1739
    }
1✔
1740

1741
    try {
5✔
1742
      if ((filters.toId !== undefined && filters.fromId !== undefined) || (filters.toId === undefined && filters.fromId === undefined)) {
6✔
1743
        res.status(400).json('Need to provide either a toId or a fromId.');
2✔
1744
        return;
2✔
1745
      }
2✔
1746

1747
      const id = parseInt(parameters.id, 10);
3✔
1748

1749
      const user = await User.findOne({ where: { id } });
3✔
1750
      if (user == null) {
6✔
1751
        res.status(404).json('Unknown user ID.');
1✔
1752
        return;
1✔
1753
      }
1✔
1754

1755
      const report = await (new TransactionService()).getTransactionReportResponse(filters);
2✔
1756
      res.status(200).json(report);
2✔
1757
    } catch (e) {
6!
1758
      res.status(500).send();
×
1759
      this.logger.error(e);
×
1760
    }
×
1761
  }
6✔
1762

1763
  /**
1✔
1764
   * POST /users/{id}/fines/waive
1765
   * @summary Waive all given user's fines
1766
   * @tags users - Operations of user controller
1767
   * @param {integer} id.path.required - The id of the user
1768
   * @param {WaiveFinesRequest} request.body
1769
   * Optional body, see https://github.com/GEWIS/sudosos-backend/pull/344
1770
   * @operationId waiveUserFines
1771
   * @security JWT
1772
   * @return 204 - Success
1773
   * @return {string} 400 - User has no fines.
1774
   * @return {string} 404 - User not found error.
1775
   */
1✔
1776
  public async waiveUserFines(req: RequestWithToken, res: Response): Promise<void> {
1✔
1777
    const { id: rawId } = req.params;
9✔
1778
    const body = req.body as WaiveFinesRequest;
9✔
1779
    this.logger.trace('Waive fines', body, 'of user', rawId, 'by', req.token.user);
9✔
1780

1781
    try {
9✔
1782
      const id = parseInt(rawId, 10);
9✔
1783

1784
      const user = await User.findOne({ where: { id }, relations: { currentFines: { fines: true } } });
9✔
1785
      if (user == null) {
9✔
1786
        res.status(404).json('Unknown user ID.');
1✔
1787
        return;
1✔
1788
      }
1✔
1789
      if (user.currentFines == null) {
9✔
1790
        res.status(400).json('User has no fines.');
1✔
1791
        return;
1✔
1792
      }
1✔
1793

1794
      const totalAmountOfFines = user.currentFines!.fines.reduce((total, f) => total.add(f.amount), Dinero());
7✔
1795
      // Backwards compatibility with old version, where you could only waive all user's fines
7✔
1796
      const amountToWaive = body?.amount ?? totalAmountOfFines.toObject();
9✔
1797
      if (amountToWaive.amount <= 0) {
9✔
1798
        res.status(400).json('Amount to waive cannot be zero or negative.');
2✔
1799
        return;
2✔
1800
      }
2✔
1801
      if (amountToWaive.amount > totalAmountOfFines.getAmount()) {
9✔
1802
        res.status(400).json('Amount to waive cannot be more than the total amount of fines.');
1✔
1803
        return;
1✔
1804
      }
1✔
1805

1806
      await new DebtorService().waiveFines(id, { amount: amountToWaive } as WaiveFinesParams);
4✔
1807
      res.status(204).send();
4✔
1808
    } catch (e) {
9!
1809
      res.status(500).send();
×
1810
      this.logger.error(e);
×
1811
    }
×
1812
  }
9✔
1813

1814
  /**
1✔
1815
   * DELETE /users/{id}/roles/{roleId}
1816
   * @summary Deletes a role from a user
1817
   * @tags users - Operations of user controller
1818
   * @param {integer} id.path.required - The id of the user
1819
   * @param {integer} roleId.path.required - The id of the role
1820
   * @operationId deleteUserRole
1821
   * @security JWT
1822
   * @return 204 - Success
1823
   */
1✔
1824
  public async deleteUserRole(req: RequestWithToken, res: Response): Promise<void> {
1✔
1825
    const { id: rawUserId, roleId: rawRoleId } = req.params;
4✔
1826

1827
    const userId = parseInt(rawUserId, 10);
4✔
1828
    const roleId = parseInt(rawRoleId, 10);
4✔
1829

1830
    const user = await User.findOne({ where: { id: userId } });
4✔
1831
    if (!user) {
4✔
1832
      res.status(404).json('user not found');
1✔
1833
      return;
1✔
1834
    }
1✔
1835
    const role = await Role.findOne({ where: { id: roleId } });
3✔
1836
    if (!role) {
4✔
1837
      res.status(404).json('role not found');
1✔
1838
      return;
1✔
1839
    }
1✔
1840

1841
    await UserService.deleteUserRole(user, role);
2✔
1842
    res.status(204).send();
2✔
1843
  }
2✔
1844

1845
  /**
1✔
1846
   * POST /users/{id}/roles
1847
   * @summary Adds a role to a user
1848
   * @tags users - Operations of user controller
1849
   * @param {integer} id.path.required - The id of the user
1850
   * @param {AddRoleRequest} request.body.required
1851
   * @operationId addUserRole
1852
   * @security JWT
1853
   * @return 204 - Success
1854
   */
1✔
1855
  public async addUserRole(req: RequestWithToken, res: Response): Promise<void> {
1✔
1856
    const { id: rawUserId } = req.params;
2✔
1857
    const userId = parseInt(rawUserId, 10);
2✔
1858
    const { roleId } = req.body as AddRoleRequest;
2✔
1859

1860
    const user = await User.findOne({ where: { id: userId } });
2✔
1861
    if (!user) {
2!
1862
      res.status(404).json('user not found');
×
1863
      return;
×
1864
    }
×
1865
    const role = await Role.findOne({ where: { id: roleId } });
2✔
1866
    if (!role) {
2✔
1867
      res.status(404).json('role not found');
1✔
1868
      return;
1✔
1869
    }
1✔
1870

1871
    await UserService.addUserRole(user, role);
1✔
1872
    res.status(204).send();
1✔
1873
  }
1✔
1874

1875
  /**
1✔
1876
   * GET /users/{id}/wrapped
1877
   * @summary Get wrapped for a user
1878
   * @operationId getWrapped
1879
   * @tags users - Operations of user controller
1880
   * @param {integer} id.path.required - The id of the user
1881
   * @security JWT
1882
   * @return {WrappedResponse} 200 - The requested user's wrapped
1883
   * @return {string} 404 - Wrapped for user not found error
1884
   * @return {string} 500 - Internal server error
1885
   */
1✔
1886
  private async getUserWrapped(req: RequestWithToken, res: Response): Promise<void> {
1✔
1887
    const { id: rawUserId } = req.params;
4✔
1888

1889
    try {
4✔
1890
      const userId = parseInt(rawUserId, 10);
4✔
1891
    
1892
      const wrappedRes = await new WrappedService().getWrappedForUser(userId);
4✔
1893
      if (wrappedRes == null) {
4✔
1894
        res.status(404).json('Wrapped not found for user');
1✔
1895
        return;
1✔
1896
      }
1✔
1897

1898
      res.json(WrappedService.asWrappedResponse(wrappedRes));
3✔
1899
    } catch (error) {
4!
1900
      res.status(500).json({ message: 'Internal server error' });
×
1901
    }
×
1902
  }
4✔
1903

1904
  /**
1✔
1905
   * POST /users/{id}/wrapped
1906
   * @summary Recompute wrapped for a user
1907
   * @operationId updateWrapped
1908
   * @tags users - Operations of user controller
1909
   * @param {integer} id.path.required - The id of the user
1910
   * @security JWT
1911
   * @return {WrappedResponse} 200 - The requested user's wrapped
1912
   * @return {string} 500 - Internal server error
1913
   */
1✔
1914
  private async computedWrapped(req: RequestWithToken, res: Response): Promise<void> {
1✔
1915
    const { id: rawUserId } = req.params;
2✔
1916

1917
    try {
2✔
1918
      const userId = parseInt(rawUserId, 10);
2✔
1919

1920
      res.json(await new WrappedService().updateWrapped({ ids: [userId] }));
2✔
1921
    } catch (error) {
2!
1922
      res.status(500).json({ message: 'Internal server error' });
×
1923
    }
×
1924
  }
2✔
1925

1926
  /**
1✔
1927
   * GET /users/{id}/settings
1928
   * @summary Get all user settings
1929
   * @operationId getUserSettings
1930
   * @tags users - Operations of user controller
1931
   * @param {integer} id.path.required - The id of the user
1932
   * @security JWT
1933
   * @return {UserSettingsResponse} 200 - The user's settings
1934
   * @return {string} 404 - User not found
1935
   * @return {string} 500 - Internal server error
1936
   */
1✔
1937
  private async getUserSettings(req: RequestWithToken, res: Response): Promise<void> {
1✔
1938
    const { id: rawUserId } = req.params;
5✔
1939

1940
    try {
5✔
1941
      const userId = parseInt(rawUserId, 10);
5✔
1942
      
1943
      const user = await User.findOne({ where: { id: userId } });
5✔
1944
      if (!user) {
5✔
1945
        res.status(404).json('User not found.');
1✔
1946
        return;
1✔
1947
      }
1✔
1948

1949
      const store = new UserSettingsStore();
4✔
1950
      const settings = await store.getAllSettings(userId);
4✔
1951
      const response = UserSettingsStore.toResponse(settings);
4✔
1952
      res.json(response);
4✔
1953
    } catch (error) {
5!
1954
      this.logger.error('Could not get user settings:', error);
×
1955
      res.status(500).json('Internal server error.');
×
1956
    }
×
1957
  }
5✔
1958

1959
  /**
1✔
1960
   * PATCH /users/{id}/settings
1961
   * @summary Update user settings
1962
   * @operationId patchUserSettings
1963
   * @tags users - Operations of user controller
1964
   * @param {integer} id.path.required - The id of the user
1965
   * @param {PatchUserSettingsRequest} request.body.required - The settings to update
1966
   * @security JWT
1967
   * @return {UserSettingsResponse} 200 - The updated user settings
1968
   * @return {string} 404 - User not found
1969
   * @return {string} 500 - Internal server error
1970
   */
1✔
1971
  private async patchUserSettings(req: RequestWithToken, res: Response): Promise<void> {
1✔
1972
    const { id: rawUserId } = req.params;
10✔
1973
    const body = req.body as PatchUserSettingsRequest;
10✔
1974

1975
    try {
10✔
1976
      const userId = parseInt(rawUserId, 10);
10✔
1977
      
1978
      const user = await User.findOne({ where: { id: userId } });
10✔
1979
      if (!user) {
10✔
1980
        res.status(404).json('User not found.');
1✔
1981
        return;
1✔
1982
      }
1✔
1983

1984
      const store = new UserSettingsStore();
9✔
1985
      await store.setSettings(userId, body);
9✔
1986

1987
      const settings = await store.getAllSettings(userId);
9✔
1988

1989
      res.json(UserSettingsStore.toResponse(settings));
9✔
1990
    } catch (error) {
10!
1991
      this.logger.error('Could not update user settings:', error);
×
1992
      res.status(500).json('Internal server error.');
×
1993
    }
×
1994
  }
10✔
1995

1996
  /**
1✔
1997
   * PATCH /users/{id}/usertype
1998
   * @summary Update user type
1999
   * @operationId patchUserType
2000
   * @tags users - Operations of user controller
2001
   * @param {integer} id.path.required - The id of the user
2002
   * @param {PatchUserTypeRequest} request.body.required - The user type to update to
2003
   * @security JWT
2004
   * @return {UserResponse} 200 - The updated user
2005
   * @return {string} 404 - User not found
2006
   * @return {string} 400 - Bad request
2007
   * @return {string} 409 - Conflict error
2008
   * @return {string} 422 - Unprocessable entity
2009
   * @return {string} 500 - Internal server error
2010
   */
1✔
2011
  private async patchUserType(req: RequestWithToken, res: Response): Promise<void> {
1✔
2012
    const { id: rawUserId } = req.params;
5✔
2013
    const body = req.body as PatchUserTypeRequest;
5✔
2014

2015
    try {
5✔
2016
      const userId = parseInt(rawUserId, 10);
5✔
2017
      const userOptions = UserService.getOptions({ id: userId });
5✔
2018
      const user = await User.findOne(userOptions);
5✔
2019
      if (!user) {
5✔
2020
        res.status(404).json('User not found.');
1✔
2021
        return;
1✔
2022
      }
1✔
2023

2024
      const allowedTypes = [UserType.MEMBER, UserType.LOCAL_USER];
4✔
2025

2026
      if (!allowedTypes.includes(user.type)) {
5✔
2027
        res.status(422).json('It is not possible to change the user type for users of this type.');
1✔
2028
        return;
1✔
2029
      }
1✔
2030

2031
      if (!allowedTypes.includes(body.userType)) {
5✔
2032
        res.status(422).json(`User type can only be changed to [${allowedTypes}].`);
1✔
2033
        return;
1✔
2034
      }
1✔
2035

2036
      if (body.userType === user.type) {
5✔
2037
        res.status(409).json('User is already of this type.');
1✔
2038
        return;
1✔
2039
      }
1✔
2040

2041
      if (!user.email) {
5!
2042
        res.status(400).json('Cannot change user type of user without email.');
×
2043
        return;
×
2044
      }
✔
2045

2046
      if (body.userType === UserType.MEMBER && !user.memberUser) {
5!
2047
        res.status(400).json('Cannot change to MEMBER since no memberId is associated to this user.');
×
2048
        return;
×
2049
      }
✔
2050

2051
      await UserService.updateUserType(user, body.userType);
1✔
2052
      const updatedUser = await UserService.getSingleUser(userId);
1✔
2053
      res.status(200).json(asUserResponse(updatedUser, true));
1✔
2054
    } catch (e) {
5!
2055
      res.status(500).send('Internal server error.');
×
2056
      this.logger.error(e);
×
2057
    }
×
2058
  }
5✔
2059

2060
  /**
1✔
2061
   * GET /users/recently-charged
2062
   * @summary Get users recently charged by the caller via an authenticated point of sale.
2063
   * Returns distinct buyers ordered by most recent transaction first, intended for
2064
   * quick suggestions in the authenticated POS flow.
2065
   * @operationId getRecentlyChargedUsers
2066
   * @tags users - Operations of user controller
2067
   * @param {integer} take.query - Maximum number of users to return (default 50)
2068
   * @security JWT
2069
   * @return {Array.<UserResponse>} 200 - List of recently charged users.
2070
   */
1✔
2071
  public async getRecentlyChargedUsers(req: RequestWithToken, res: Response): Promise<void> {
1✔
2072
    this.logger.trace('Get recently charged users by user', req.token.user);
5✔
2073

2074
    let take: number;
5✔
2075
    try {
5✔
2076
      take = req.query.take !== undefined ? asNumber(req.query.take) : 50;
5✔
2077
      take = Math.min(Math.max(1, Math.trunc(take)), maxPagination());
5✔
2078
    } catch (e) {
5✔
2079
      res.status(400).send(e.message);
1✔
2080
      return;
1✔
2081
    }
1✔
2082

2083
    try {
4✔
2084
      const users = await new TransactionService().getRecentlyChargedUsers(req.token.user.id, take);
4✔
2085
      const records = users.map((u) => asUserResponse(u));
4✔
2086
      if (!await this.canSeeEmail(req, 'all')) {
5✔
2087
        records.forEach((u) => { u.email = undefined; });
1✔
2088
      }
1✔
2089
      res.status(200).json(records);
4✔
2090
    } catch (e) {
5!
2091
      res.status(500).send('Internal server error.');
×
2092
      this.logger.error(e);
×
2093
    }
×
2094
  }
5✔
2095
}
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