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

GEWIS / sudosos-backend / 26819060307

02 Jun 2026 12:15PM UTC coverage: 91.954% (-0.005%) from 91.959%
26819060307

push

github

web-flow
feat: add PaymentRequest HTTP API (#897)

* feat: add PaymentRequest controllers and DTOs

Authenticated controller (/payment-requests) and unauthenticated
share-link controller (/payment-requests-public). Both use the existing
service layer; the public surface is mounted before token middleware in
src/index.ts (StripeWebhookController pattern).

- PaymentRequestController: create, list (with get-own scoping), single
  fetch with request-scoped caching, cancel, start, mark-fulfilled.
- PaymentRequestPublicController: trimmed lookup + start endpoints. Uses
  PaymentRequestService.getPublicPaymentRequest for a lightweight load
  that skips audit-only relations.
- Request DTOs (CreatePaymentRequestRequest, MarkFulfilledExternallyRequest).
- UserController gains the GET /users/{id}/payment-requests endpoint that
  delegates to the service listing with forId scoped to the path user.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat: add PaymentRequest permissions to default roles

Regular users (User role) get get-own / create-own / update-own
on PaymentRequest so they can list, fetch, create, start, and
cancel their own payment-link rows. Super admin gets full access
via the standard `admin` permission bundle, which unlocks
mark-fulfilled-externally and cross-user listings.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test: add PaymentRequest controller tests

Covers PaymentRequestController (authenticated) and
PaymentRequestPublicController (unauthenticated share link):

- list/filter/pagination with RBAC own vs all,
- single-request fetch incl. cross-user 403,
- create (self vs other, admin escape hatch, 404 on unknown
  beneficiary, 400 on invalid expiresAt),
- cancel state machine (PENDING → CANCELLED, 409 from other
  states, 404 on unknown id),
- mark-fulfilled (admin-only via update:all policy, 400 on
  empty audit reason, 409 from PAID).

Public controller tests assert:
- trimmed response shape (no createdBy/cancelled... (continued)

4183 of 4774 branches covered (87.62%)

Branch coverage included in aggregate %.

298 of 379 new or added lines in 6 files covered. (78.63%)

2 existing lines in 1 file now uncovered.

21245 of 22879 relevant lines covered (92.86%)

859.36 hits per line

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

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

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

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

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

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

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

409
  /**
1✔
410
   * Function to determine which credentials are needed to GET
411
   *    'all' if user is not connected to User
412
   *    'organ' if user is connected to User via organ
413
   *    'own' if user is connected to User
414
   * @param req
415
   * @return whether User is connected to used token
416
   */
1✔
417
  static getRelation(req: RequestWithToken): string {
1✔
418
    if (userTokenInOrgan(req, asNumber(req.params.id))) return 'organ';
199✔
419
    return req.params.id === req.token.user.id.toString() ? 'own' : 'all';
199✔
420
  }
199✔
421

422
  static getAttributes(req: RequestWithToken): string[] {
1✔
423
    const attributes: string[] = [];
15✔
424
    const body = req.body as BaseUserRequest;
15✔
425
    for (const key in body) {
15✔
426
      if (body.hasOwnProperty(key)) {
17✔
427
        attributes.push(key);
17✔
428
      }
17✔
429
    }
17✔
430
    return attributes;
15✔
431
  }
15✔
432

433
  /**
1✔
434
   * Returns whether the token in the request is allowed to see the email field
435
   * of a User with the given relation (own/organ/all).
436
   */
1✔
437
  private async canSeeEmail(req: RequestWithToken, relation: string): Promise<boolean> {
1✔
438
    return this.roleManager.can(req.token.roles, 'get', relation, 'User', ['email']);
27✔
439
  }
27✔
440

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

459
    let take;
12✔
460
    let skip;
12✔
461
    let filters: UserFilterParameters;
12✔
462
    try {
12✔
463
      const pagination = parseRequestPagination(req);
12✔
464
      filters = parseGetUsersFilters(req);
12✔
465
      take = pagination.take;
12✔
466
      skip = pagination.skip;
12✔
467
    } catch (e) {
12!
468
      res.status(400).send(e.message);
×
469
      return;
×
470
    }
×
471

472
    try {
12✔
473
      const [users, count] = await UserService.getUsers(filters, { take, skip });
12✔
474
      const records = users.map((u) => asUserResponse(u, true));
12✔
475
      if (!await this.canSeeEmail(req, 'all')) {
12✔
476
        records.forEach((u) => { u.email = undefined; });
1✔
477
      }
1✔
478
      res.status(200).json({
12✔
479
        _pagination: { take, skip, count },
12✔
480
        records,
12✔
481
      });
12✔
482
    } catch (error) {
12!
483
      this.logger.error('Could not get users:', error);
×
484
      res.status(500).json('Internal server error.');
×
485
    }
×
486
  }
12✔
487

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

505
    // If it does not exist, return a 404 error
5✔
506
    const type = UserType[userType as keyof typeof UserType];
5✔
507
    if (!type || Number(userType)) {
5✔
508
      res.status(404).json('Unknown userType.');
1✔
509
      return;
1✔
510
    }
1✔
511

512
    try {
4✔
513
      req.query.type = userType;
4✔
514
      await this.getAllUsers(req, res);
4✔
515
    } catch (error) {
5!
516
      this.logger.error('Could not get users:', error);
×
517
      res.status(500).json('Internal server error.');
×
518
    }
×
519
  }
5✔
520

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

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

548
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
549
        updatePinRequest.pin.toString(), PinAuthenticator);
1✔
550
      res.status(204).json();
1✔
551
    } catch (error) {
2!
552
      this.logger.error('Could not update pin:', error);
×
553
      res.status(500).json('Internal server error.');
×
554
    }
×
555
  }
2✔
556

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

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

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

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

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

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

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

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

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

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

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

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

691

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

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

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

728
      await new AuthenticationService().setUserAuthenticationHash(user,
1✔
729
        updateLocalRequest.password, LocalAuthenticator);
1✔
730
      res.status(204).json();
1✔
731
    } catch (error) {
2!
732
      this.logger.error('Could not update local password:', error);
×
733
      res.status(500).json('Internal server error.');
×
734
    }
×
735
  }
2✔
736

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

754
    let take;
4✔
755
    let skip;
4✔
756
    try {
4✔
757
      const pagination = parseRequestPagination(req);
4✔
758
      take = pagination.take;
4✔
759
      skip = pagination.skip;
4✔
760
    } catch (e) {
4!
761
      res.status(400).send(e.message);
×
762
      return;
×
763
    }
×
764

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

775
      if (user.type !== UserType.ORGAN) {
4✔
776
        res.status(400).json('User is not of type Organ');
1✔
777
        return;
1✔
778
      }
1✔
779

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

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

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

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

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

845
    try {
4✔
846
      const user = await UserService.createUser(body);
4✔
847
      res.status(201).json(asUserResponse(user, true));
4✔
848
    } catch (error) {
4!
849
      this.logger.error('Could not create user:', error);
×
850
      res.status(500).json('Internal server error.');
×
851
    }
×
852
  }
4✔
853

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

870
    if (body.firstName !== undefined && body.firstName.length === 0) {
12✔
871
      res.status(400).json('firstName cannot be empty');
1✔
872
      return;
1✔
873
    }
1✔
874
    if (body.firstName !== undefined && body.firstName.length > 64) {
12✔
875
      res.status(400).json('firstName too long');
1✔
876
      return;
1✔
877
    }
1✔
878
    if (body.lastName !== undefined && body.lastName.length > 64) {
12✔
879
      res.status(400).json('lastName too long');
1✔
880
      return;
1✔
881
    }
1✔
882
    if (body.nickname !== undefined && body.nickname.length > 64) {
12✔
883
      res.status(400).json('nickname too long');
1✔
884
      return;
1✔
885
    }
1✔
886
    if (body.nickname === '') body.nickname = null;
12✔
887

888
    let parsedExpiryDate: Date | null | undefined = undefined;
8✔
889
    if (body.expiryDate !== undefined) {
12!
890
      if (body.expiryDate === null) {
×
891
        parsedExpiryDate = null;
×
892
      } else {
×
893
        parsedExpiryDate = new Date(body.expiryDate);
×
894
        if (isNaN(parsedExpiryDate.getTime())) {
×
895
          res.status(400).json('expiryDate is not a valid date');
×
896
          return;
×
897
        }
×
898
        const cap = new Date();
×
899
        cap.setMonth(cap.getMonth() + 18);
×
900
        if (parsedExpiryDate > cap) {
×
901
          res.status(400).json('expiryDate cannot be more than 18 months in the future');
×
902
          return;
×
903
        }
×
904
      }
×
905
    }
✔
906

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

917
      const isLocalUser = LocalUserTypes.includes(user.type);
8✔
918
      if (parsedExpiryDate !== undefined && !isLocalUser) {
12!
919
        res.status(400).json('expiryDate can only be set for local user accounts');
×
920
        return;
×
921
      }
✔
922

923
      user = { ...body } as unknown as User;
8✔
924
      if (parsedExpiryDate !== undefined) {
12!
925
        user.expiryDate = parsedExpiryDate;
×
926
        user.expiryNotificationSent = false;
×
927
      } else {
12✔
928
        delete (user as Partial<User>).expiryDate;
8✔
929
      }
8✔
930

931
      await User.update(parameters.id, user);
8✔
932
      const updatedUser = await UserService.getSingleUser(asNumber(parameters.id));
8✔
933
      res.status(200).json(asUserResponse(updatedUser, true));
8✔
934
    } catch (error) {
12!
935
      this.logger.error('Could not update user:', error);
×
936
      res.status(500).json('Internal server error.');
×
937
    }
×
938
  }
12✔
939

940
  /**
1✔
941
   * DELETE /users/{id}
942
   * @summary Delete a single user
943
   * @operationId deleteUser
944
   * @tags users - Operations of user controller
945
   * @param {integer} id.path.required - The id of the user
946
   * @security JWT
947
   * @return 204 - User successfully deleted
948
   * @return {string} 400 - Cannot delete yourself
949
   */
1✔
950
  public async deleteUser(req: RequestWithToken, res: Response): Promise<void> {
1✔
951
    const parameters = req.params;
5✔
952
    this.logger.trace('Delete individual user', parameters, 'by user', req.token.user);
5✔
953

954
    if (req.token.user.id === parseInt(parameters.id, 10)) {
5✔
955
      res.status(400).json('Cannot delete yourself');
1✔
956
      return;
1✔
957
    }
1✔
958

959
    try {
4✔
960
      const id = parseInt(parameters.id, 10);
4✔
961
      // Get the user object if it exists
4✔
962
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
963
      // If it does not exist, return a 404 error
4✔
964
      if (user == null) {
5✔
965
        res.status(404).json('Unknown user ID.');
2✔
966
        return;
2✔
967
      }
2✔
968

969
      user.deleted = true;
2✔
970
      await user.save();
2✔
971
      res.status(204).json('User deleted');
2✔
972
    } catch (error) {
5!
973
      this.logger.error('Could not create product:', error);
×
974
      res.status(500).json('Internal server error.');
×
975
    }
×
976
  }
5✔
977

978
  /**
1✔
979
   * GET /users/nfc/{nfcCode}
980
   * @summary Get a user using the nfc code
981
   * @operationId findUserNfc
982
   * @tags users - Operations of the user controller
983
   * @security JWT
984
   * @param {string} nfcCode.path.required - The nfc code of the user
985
   * @return {UserResponse} 200 - The requested user
986
   * @return {string} 404 - The user with the given nfc code does not exist
987
   */
1✔
988
  public async findUserNfc(req: RequestWithToken, res: Response): Promise<void> {
1✔
989
    const parameters = req.params;
3✔
990
    this.logger.trace('Find user nfc', parameters, 'by user', req.token.user);
3✔
991

992
    try {
3✔
993
      const nfcCode = String(parameters.nfcCode);
3✔
994
      const nfc = await NfcAuthenticator.findOne({ where: { nfcCode } });
3✔
995

996
      if (nfc === null) {
3✔
997
        res.status(404).json('Unknown nfc code');
1✔
998
        return;
1✔
999
      }
1✔
1000

1001
      const userResponse = parseUserToResponse(nfc.user);
2✔
1002
      if (!await this.canSeeEmail(req, 'all')) {
3!
1003
        userResponse.email = undefined;
×
1004
      }
✔
1005
      res.status(200).json(userResponse);
2✔
1006
    } catch (error) {
3!
1007
      this.logger.error('Could not find user using nfc:', error);
×
1008
      res.status(500).json('Internal server error.');
×
1009
    }
×
1010
  }
3✔
1011

1012
  /**
1✔
1013
   * POST /users/acceptTos
1014
   * @summary Accept the Terms of Service if you have not accepted it yet
1015
   * @operationId acceptTos
1016
   * @tags users - Operations of the User controller
1017
   * @param {AcceptTosRequest} request.body.required - "Tosrequest body"
1018
   * @security JWT
1019
   * @return 204 - ToS accepted
1020
   * @return {string} 400 - ToS already accepted
1021
   */
1✔
1022
  public async acceptToS(req: RequestWithToken, res: Response): Promise<void> {
1✔
1023
    this.logger.trace('Accept ToS for user', req.token.user);
3✔
1024

1025
    const { id } = req.token.user;
3✔
1026
    const body = req.body as AcceptTosRequest;
3✔
1027

1028
    try {
3✔
1029
      const user = await UserService.getSingleUser(id);
3✔
1030
      if (user == null) {
3!
1031
        res.status(404).json('User not found.');
×
1032
        return;
×
1033
      }
×
1034

1035
      const success = await UserService.acceptToS(id, body);
3✔
1036
      if (!success) {
3✔
1037
        res.status(400).json('User already accepted ToS.');
1✔
1038
        return;
1✔
1039
      }
1✔
1040

1041
      res.status(204).json();
2✔
1042
      return;
2✔
1043
    } catch (error) {
3!
1044
      this.logger.error('Could not accept ToS for user:', error);
×
1045
      res.status(500).json('Internal server error.');
×
1046
    }
×
1047
  }
3✔
1048

1049
  /**
1✔
1050
   * GET /users/{id}/products
1051
   * @summary Get an user's products
1052
   * @operationId getUsersProducts
1053
   * @tags users - Operations of user controller
1054
   * @param {integer} id.path.required - The id of the user
1055
   * @param {integer} take.query - How many products the endpoint should return
1056
   * @param {integer} skip.query - How many products should be skipped (for pagination)
1057
   * @security JWT
1058
   * @return {PaginatedProductResponse} 200 - List of products.
1059
   */
1✔
1060
  public async getUsersProducts(req: RequestWithToken, res: Response): Promise<void> {
1✔
1061
    const parameters = req.params;
4✔
1062
    this.logger.trace("Get user's products", parameters, 'by user', req.token.user);
4✔
1063

1064
    let take;
4✔
1065
    let skip;
4✔
1066
    try {
4✔
1067
      const pagination = parseRequestPagination(req);
4✔
1068
      take = pagination.take;
4✔
1069
      skip = pagination.skip;
4✔
1070
    } catch (e) {
4!
1071
      res.status(400).send(e.message);
×
1072
      return;
×
1073
    }
×
1074

1075
    // Handle request
4✔
1076
    try {
4✔
1077
      const id = parseInt(parameters.id, 10);
4✔
1078
      const owner = await User.findOne({ where: { id, deleted: false } });
4✔
1079
      if (owner == null) {
4✔
1080
        res.status(404).json({});
1✔
1081
        return;
1✔
1082
      }
1✔
1083

1084
      const [revisions, count] = await ProductService.getProducts({}, { take, skip }, owner);
3✔
1085
      const records = revisions.map((r) => ProductService.revisionToResponse(r));
3✔
1086
      res.json(toResponse(records, count, { take, skip }));
3✔
1087
    } catch (error) {
4!
1088
      this.logger.error('Could not return all products:', error);
×
1089
      res.status(500).json('Internal server error.');
×
1090
    }
×
1091
  }
4✔
1092

1093
  /**
1✔
1094
   * GET /users/{id}/containers
1095
   * @summary Returns the user's containers
1096
   * @operationId getUsersContainers
1097
   * @tags users - Operations of user controller
1098
   * @param {integer} id.path.required - The id of the user
1099
   * @security JWT
1100
   * @param {integer} take.query - How many containers the endpoint should return
1101
   * @param {integer} skip.query - How many containers should be skipped (for pagination)
1102
   * @return {PaginatedContainerResponse} 200 - All users updated containers
1103
   * @return {string} 404 - Not found error
1104
   * @return {string} 500 - Internal server error
1105
   */
1✔
1106
  public async getUsersContainers(req: RequestWithToken, res: Response): Promise<void> {
1✔
1107
    const { id } = req.params;
4✔
1108
    this.logger.trace("Get user's containers", id, 'by user', req.token.user);
4✔
1109

1110
    let take;
4✔
1111
    let skip;
4✔
1112
    try {
4✔
1113
      const pagination = parseRequestPagination(req);
4✔
1114
      take = pagination.take;
4✔
1115
      skip = pagination.skip;
4✔
1116
    } catch (e) {
4!
1117
      res.status(400).send(e.message);
×
1118
      return;
×
1119
    }
×
1120

1121
    // handle request
4✔
1122
    try {
4✔
1123
      // Get the user object if it exists
4✔
1124
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1125
      // If it does not exist, return a 404 error
4✔
1126
      if (user == null) {
4✔
1127
        res.status(404).json('Unknown user ID.');
1✔
1128
        return;
1✔
1129
      }
1✔
1130

1131
      const [revisions, count] = await ContainerService
3✔
1132
        .getContainers({}, { take, skip }, user);
3✔
1133
      const records = revisions.map((r) => ContainerService.revisionToResponse(r));
3✔
1134
      res.json(toResponse(records, count, { take, skip }));
3✔
1135
    } catch (error) {
4!
1136
      this.logger.error('Could not return containers:', error);
×
1137
      res.status(500).json('Internal server error.');
×
1138
    }
×
1139
  }
4✔
1140

1141
  /**
1✔
1142
   * GET /users/{id}/pointsofsale
1143
   * @summary Returns the user's Points of Sale
1144
   * @operationId getUsersPointsOfSale
1145
   * @tags users - Operations of user controller
1146
   * @param {integer} id.path.required - The id of the user
1147
   * @param {integer} take.query - How many points of sale the endpoint should return
1148
   * @param {integer} skip.query - How many points of sale should be skipped (for pagination)
1149
   * @security JWT
1150
   * @return {PaginatedPointOfSaleResponse} 200 - All users updated point of sales
1151
   * @return {string} 404 - Not found error
1152
   * @return {string} 500 - Internal server error
1153
   */
1✔
1154
  public async getUsersPointsOfSale(req: RequestWithToken, res: Response): Promise<void> {
1✔
1155
    const { id } = req.params;
4✔
1156
    this.logger.trace("Get user's points of sale", id, 'by user', req.token.user);
4✔
1157

1158
    let take;
4✔
1159
    let skip;
4✔
1160
    try {
4✔
1161
      const pagination = parseRequestPagination(req);
4✔
1162
      take = pagination.take;
4✔
1163
      skip = pagination.skip;
4✔
1164
    } catch (e) {
4!
1165
      res.status(400).send(e.message);
×
1166
      return;
×
1167
    }
×
1168

1169
    // handle request
4✔
1170
    try {
4✔
1171
      // Get the user object if it exists
4✔
1172
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1173
      // If it does not exist, return a 404 error
4✔
1174
      if (user == null) {
4✔
1175
        res.status(404).json('Unknown user ID.');
1✔
1176
        return;
1✔
1177
      }
1✔
1178

1179
      const [revisions, count] = await PointOfSaleService
3✔
1180
        .getPointsOfSale({}, { take, skip }, user);
3✔
1181
      const records = revisions.map((r) => PointOfSaleService.revisionToResponse(r));
3✔
1182
      res.json(toResponse(records, count, { take, skip }));
3✔
1183
    } catch (error) {
4!
1184
      this.logger.error('Could not return point of sale:', error);
×
1185
      res.status(500).json('Internal server error.');
×
1186
    }
×
1187
  }
4✔
1188

1189
  /**
1✔
1190
   * GET /users/{id}/transactions
1191
   * @summary Get transactions from a user.
1192
   * @operationId getUsersTransactions
1193
   * @tags users - Operations of user controller
1194
   * @param {integer} id.path.required - The id of the user that should be involved
1195
   * in all returned transactions
1196
   * @param {integer} fromId.query - From-user for selected transactions
1197
   * @param {integer} createdById.query - User that created selected transaction
1198
   * @param {integer} toId.query - To-user for selected transactions
1199
   * transactions. Requires ContainerId
1200
   * @param {integer} productId.query - Product ID for selected transactions
1201
   * @param {integer} productRevision.query - Product Revision for selected
1202
   * transactions. Requires ProductID
1203
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1204
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1205
   * @param {integer} take.query - How many transactions the endpoint should return
1206
   * @param {integer} skip.query - How many transactions should be skipped (for pagination)
1207
   * @security JWT
1208
   * @return {PaginatedBaseTransactionResponse} 200 - List of transactions.
1209
   */
1✔
1210
  public async getUsersTransactions(req: RequestWithToken, res: Response): Promise<void> {
1✔
1211
    const { id } = req.params;
4✔
1212
    this.logger.trace("Get user's", id, 'transactions by user', req.token.user);
4✔
1213

1214
    // Parse the filters given in the query parameters. If there are any issues,
4✔
1215
    // the parse method will throw an exception. We will then return a 400 error.
4✔
1216
    let filters;
4✔
1217
    try {
4✔
1218
      filters = parseGetTransactionsFilters(req);
4✔
1219
    } catch (e) {
4!
1220
      res.status(400).json(e.message);
×
1221
      return;
×
1222
    }
×
1223

1224
    let take;
4✔
1225
    let skip;
4✔
1226
    try {
4✔
1227
      const pagination = parseRequestPagination(req);
4✔
1228
      take = pagination.take;
4✔
1229
      skip = pagination.skip;
4✔
1230
    } catch (e) {
4!
1231
      res.status(400).send(e.message);
×
1232
      return;
×
1233
    }
×
1234

1235
    try {
4✔
1236
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
4✔
1237
      if (user == null) {
4✔
1238
        res.status(404).json({});
1✔
1239
        return;
1✔
1240
      }
1✔
1241
      const [records, count] = await new TransactionService().getTransactions(filters, { take, skip }, user);
3✔
1242

1243
      res.status(200).json(toResponse(records, count, { take, skip }));
3✔
1244
    } catch (error) {
4!
1245
      this.logger.error('Could not return all transactions:', error);
×
1246
      res.status(500).json('Internal server error.');
×
1247
    }
×
1248
  }
4✔
1249

1250
  /**
1✔
1251
   * GET /users/{id}/transactions/sales/report
1252
   * @summary Get sales report for the given user
1253
   * @operationId getUsersSalesReport
1254
   * @tags users - Operations of user controller
1255
   * @param {integer} id.path.required - The id of the user to get the sales report for
1256
   * @security JWT
1257
   * @param {string} fromDate.query.required - Start date for selected sales (inclusive)
1258
   * @param {string} tillDate.query.required - End date for selected sales (exclusive)
1259
   * @return {ReportResponse} 200 - The sales report of the user
1260
   * @return {string} 400 - Validation error
1261
   * @return {string} 404 - User not found error.
1262
   */
1✔
1263
  public async getUsersSalesReport(req: RequestWithToken, res: Response): Promise<void> {
1✔
1264
    const { id } = req.params;
7✔
1265
    this.logger.trace('Get sales report for user ', id, ' by user', req.token.user);
7✔
1266

1267
    let filters: { fromDate: Date, tillDate: Date };
7✔
1268
    try {
7✔
1269
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1270
    } catch (e) {
7✔
1271
      res.status(400).json(e.message);
1✔
1272
      return;
1✔
1273
    }
1✔
1274

1275
    try {
6✔
1276
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
6✔
1277
      if (user == null) {
7✔
1278
        res.status(404).json('Unknown user ID.');
1✔
1279
        return;
1✔
1280
      }
1✔
1281

1282
      const report = await (new SalesReportService()).getReport({ ...filters, forId: user.id });
5✔
1283
      res.status(200).json(ReportService.reportToResponse(report));
5✔
1284
    } catch (error) {
7!
1285
      this.logger.error('Could not get sales report:', error);
×
1286
      res.status(500).json('Internal server error.');
×
1287
    }
×
1288
  }
7✔
1289

1290
  /**
1✔
1291
   * GET /users/{id}/transactions/sales/report/pdf
1292
   * @summary Get sales report for the given user
1293
   * @operationId getUsersSalesReportPdf
1294
   * @tags users - Operations of user controller
1295
   * @param {integer} id.path.required - The id of the user to get the sales report for
1296
   * @security JWT
1297
   * @param {string} fromDate.query.required - Start date for selected sales (inclusive)
1298
   * @param {string} tillDate.query.required - End date for selected sales (exclusive)
1299
   * @param {string} description.query - Description of the report
1300
   * @param {string} fileType.query - enum:PDF,TEX - The file type of the report
1301
   * @return {string} 404 - User not found error.
1302
   * @returns {string} 200 - The requested report - application/pdf
1303
   * @return {string} 400 - Validation error
1304
   * @return {string} 500 - Internal server error
1305
   * @return {string} 502 - PDF generation failed
1306
   */
1✔
1307
  public async getUsersSalesReportPdf(req: RequestWithToken, res: Response): Promise<void> {
1✔
1308
    const { id } = req.params;
7✔
1309
    this.logger.trace('Get sales report pdf for user ', id, ' by user', req.token.user);
7✔
1310

1311
    let filters: { fromDate: Date, tillDate: Date };
7✔
1312
    let description: string;
7✔
1313
    let fileType: ReturnFileType;
7✔
1314
    try {
7✔
1315
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1316
      description = String(req.query.description);
7✔
1317
      fileType = asReturnFileType(req.query.fileType);
7✔
1318
    } catch (e) {
7✔
1319
      res.status(400).json(e.message);
4✔
1320
      return;
4✔
1321
    }
4✔
1322

1323
    try {
3✔
1324
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1325
      if (user == null) {
7✔
1326
        res.status(404).json('Unknown user ID.');
1✔
1327
        return;
1✔
1328
      }
1✔
1329
      const service = new SalesReportService();
2✔
1330
      await reportPDFhelper(res)(service, filters, description, user.id, UserReportParametersType.Sales, fileType);
2✔
1331
    } catch (error) {
1✔
1332
      this.logger.error('Could not get sales report:', error);
1✔
1333
      if (error instanceof PdfError) {
1✔
1334
        res.status(502).json('PDF Generator service failed.');
1✔
1335
        return;
1✔
1336
      }
1!
1337
      res.status(500).json('Internal server error.');
×
1338
    }
×
1339
  }
7✔
1340

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

1361
    let filters: { fromDate: Date, tillDate: Date };
7✔
1362
    let description: string;
7✔
1363
    let fileType: ReturnFileType;
7✔
1364
    try {
7✔
1365
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
7✔
1366
      description = String(req.query.description);
7✔
1367
      fileType = asReturnFileType(req.query.fileType);
7✔
1368
    } catch (e) {
7✔
1369
      res.status(400).json(e.message);
4✔
1370
      return;
4✔
1371
    }
4✔
1372

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

1391
  /**
1✔
1392
   * GET /users/{id}/transactions/purchases/report
1393
   * @summary Get purchases report for the given user
1394
   * @operationId getUsersPurchasesReport
1395
   * @tags users - Operations of user controller
1396
   * @param {integer} id.path.required - The id of the user to get the purchases report for
1397
   * @security JWT
1398
   * @param {string} fromDate.query.required - Start date for selected purchases (inclusive)
1399
   * @param {string} tillDate.query.required - End date for selected purchases (exclusive)
1400
   * @return {ReportResponse} 200 - The purchases report of the user
1401
   * @return {string} 400 - Validation error
1402
   * @return {string} 404 - User not found error.
1403
   */
1✔
1404
  public async getUsersPurchasesReport(req: RequestWithToken, res: Response): Promise<void> {
1✔
1405
    const { id } = req.params;
4✔
1406
    this.logger.trace('Get purchases report for user ', id, ' by user', req.token.user);
4✔
1407

1408
    let filters: { fromDate: Date, tillDate: Date };
4✔
1409
    try {
4✔
1410
      filters = asFromAndTillDate(req.query.fromDate, req.query.tillDate);
4✔
1411
    } catch (e) {
4✔
1412
      res.status(400).json(e.message);
1✔
1413
      return;
1✔
1414
    }
1✔
1415

1416
    try {
3✔
1417
      const user = await User.findOne({ where: { id: parseInt(id, 10) } });
3✔
1418
      if (user == null) {
4✔
1419
        res.status(404).json('Unknown user ID.');
1✔
1420
        return;
1✔
1421
      }
1✔
1422

1423
      const report = await (new BuyerReportService()).getReport({ ...filters, forId: user.id });
2✔
1424
      res.status(200).json(ReportService.reportToResponse(report));
2✔
1425
    } catch (error) {
4!
1426
      this.logger.error('Could not get sales report:', error);
×
1427
      res.status(500).json('Internal server error.');
×
1428
    }
×
1429
  }
4✔
1430

1431
  /**
1✔
1432
   * GET /users/{id}/transfers
1433
   * @summary Get transfers to or from an user.
1434
   * @operationId getUsersTransfers
1435
   * @tags users - Operations of user controller
1436
   * @param {integer} id.path.required - The id of the user that should be involved
1437
   * in all returned transfers
1438
   * @param {integer} take.query - How many transfers the endpoint should return
1439
   * @param {integer} skip.query - How many transfers should be skipped (for pagination)
1440
   * @param {integer} fromId.query - From-user for selected transfers
1441
   * @param {integer} toId.query - To-user for selected transfers
1442
   * @param {integer} id.query - ID of selected transfers
1443
   * @security JWT
1444
   * @return {PaginatedTransferResponse} 200 - List of transfers.
1445
   */
1✔
1446
  public async getUsersTransfers(req: RequestWithToken, res: Response): Promise<void> {
1✔
1447
    const { id } = req.params;
2✔
1448
    this.logger.trace("Get user's transfers", id, 'by user', req.token.user);
2✔
1449

1450
    // Parse the filters given in the query parameters. If there are any issues,
2✔
1451
    // the parse method will throw an exception. We will then return a 400 error.
2✔
1452
    let filters;
2✔
1453
    try {
2✔
1454
      filters = parseGetTransferFilters(req);
2✔
1455
    } catch (e) {
2!
1456
      res.status(400).json(e.message);
×
1457
      return;
×
1458
    }
×
1459

1460
    let take;
2✔
1461
    let skip;
2✔
1462
    try {
2✔
1463
      const pagination = parseRequestPagination(req);
2✔
1464
      take = pagination.take;
2✔
1465
      skip = pagination.skip;
2✔
1466
    } catch (e) {
2!
1467
      res.status(400).send(e.message);
×
1468
      return;
×
1469
    }
×
1470

1471
    // handle request
2✔
1472
    try {
2✔
1473
      // Get the user object if it exists
2✔
1474
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
2✔
1475
      // If it does not exist, return a 404 error
2✔
1476
      if (user == null) {
2!
1477
        res.status(404).json('Unknown user ID.');
×
1478
        return;
×
1479
      }
×
1480

1481
      const [transfers, count] = await new TransferService().getTransfers(
2✔
1482
        { ...filters }, { take, skip }, user,
2✔
1483
      );
1484
      const records = transfers.map((t) => TransferService.asTransferResponse(t));
2✔
1485
      res.json(toResponse(records, count, { take, skip }));
2✔
1486
    } catch (error) {
2!
1487
      this.logger.error('Could not return user transfers', error);
×
1488
      res.status(500).json('Internal server error.');
×
1489
    }
×
1490
  }
2✔
1491

1492
  /**
1✔
1493
   * GET /users/{id}/payment-requests
1494
   * @summary Get the PaymentRequests where the given user is the beneficiary.
1495
   *   Regular users can hit this for their own id; admins can hit it for any user.
1496
   * @operationId getUsersPaymentRequests
1497
   * @tags users - Operations of user controller
1498
   * @param {integer} id.path.required - The id of the beneficiary user.
1499
   * @param {integer} createdById.query - Filter by creator user id.
1500
   * @param {string} status.query - enum:PENDING,PAID,EXPIRED,CANCELLED - Comma-separated list of derived statuses.
1501
   * @param {string} fromDate.query - Filter requests created on or after this ISO date (inclusive).
1502
   * @param {string} tillDate.query - Filter requests created strictly before this ISO date (exclusive).
1503
   * @param {integer} take.query - How many rows the endpoint should return
1504
   * @param {integer} skip.query - How many rows to skip (for pagination)
1505
   * @security JWT
1506
   * @return {PaginatedBasePaymentRequestResponse} 200 - Payment requests for the user
1507
   * @return {string} 400 - Validation error
1508
   * @return {string} 404 - Unknown user id
1509
   */
1✔
1510
  public async getUsersPaymentRequests(req: RequestWithToken, res: Response): Promise<void> {
1✔
1511
    const { id } = req.params;
4✔
1512
    this.logger.trace("Get user's payment requests", id, 'by user', req.token.user);
4✔
1513

1514
    let filters;
4✔
1515
    let pagination;
4✔
1516
    try {
4✔
1517
      filters = parseGetPaymentRequestsFilters(req, { ignoreForId: true });
4✔
1518
      pagination = parseRequestPagination(req);
4✔
1519
    } catch (e) {
4!
NEW
1520
      res.status(400).send(e instanceof Error ? e.message : String(e));
×
NEW
1521
      return;
×
NEW
1522
    }
×
1523

1524
    try {
4✔
1525
      const user = await User.findOne({ where: { id: parseInt(id, 10), deleted: false } });
4✔
1526
      if (user == null) {
4✔
1527
        res.status(404).json('Unknown user ID.');
1✔
1528
        return;
1✔
1529
      }
1✔
1530

1531
      const service = new PaymentRequestService();
3✔
1532
      const [rows, count] = await service.getPaymentRequests(
3✔
1533
        { ...filters, forId: user.id }, pagination,
3✔
1534
      );
1535
      const records = rows.map((r) => PaymentRequestService.asBasePaymentRequestResponse(r));
3✔
1536
      res.json(toResponse(records, count, pagination));
3✔
1537
    } catch (e) {
4!
NEW
1538
      this.logger.error('Could not return user payment requests', e);
×
NEW
1539
      res.status(500).json('Internal server error.');
×
NEW
1540
    }
×
1541
  }
4✔
1542

1543
  /**
1✔
1544
   * GET /users/{id}/roles
1545
   * @summary Get all roles assigned to the user.
1546
   * @operationId getUserRoles
1547
   * @tags users - Operations of user controller
1548
   * @param {integer} id.path.required - The id of the user to get the roles from
1549
   * @security JWT
1550
   * @return {Array.<RoleWithPermissionsResponse>} 200 - The roles of the user
1551
   * @return {string} 404 - User not found error.
1552
   */
1✔
1553
  public async getUserRoles(req: RequestWithToken, res: Response): Promise<void> {
1✔
1554
    const parameters = req.params;
3✔
1555
    this.logger.trace('Get roles of user', parameters, 'by user', req.token.user);
3✔
1556

1557
    try {
3✔
1558
      const id = parseInt(parameters.id, 10);
3✔
1559
      // Get the user object if it exists
3✔
1560
      const user = await User.findOne({ where: { id, deleted: false } });
3✔
1561
      // If it does not exist, return a 404 error
3✔
1562
      if (user == null) {
3✔
1563
        res.status(404).json('Unknown user ID.');
1✔
1564
        return;
1✔
1565
      }
1✔
1566

1567
      const rolesWithPermissions = await this.roleManager.getRoles(user, true);
2✔
1568
      const response = rolesWithPermissions.map((r) => RBACService.asRoleResponse(r));
2✔
1569
      res.status(200).json(response);
2✔
1570
    } catch (error) {
3!
1571
      this.logger.error('Could not get roles of user:', error);
×
1572
      res.status(500).json('Internal server error.');
×
1573
    }
×
1574
  }
3✔
1575

1576
  /**
1✔
1577
   * GET /users/{id}/financialmutations
1578
   * @summary Get all financial mutations of a user (from or to).
1579
   * @operationId getUsersFinancialMutations
1580
   * @tags users - Operations of user controller
1581
   * @param {integer} id.path.required - The id of the user to get the mutations from
1582
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1583
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1584
   * @param {integer} take.query - How many transactions the endpoint should return
1585
   * @param {integer} skip.query - How many transactions should be skipped (for pagination)
1586
   * @security JWT
1587
   * @return {PaginatedFinancialMutationResponse} 200 - The financial mutations of the user
1588
   * @return {string} 404 - User not found error.
1589
   */
1✔
1590
  public async getUsersFinancialMutations(req: RequestWithToken, res: Response): Promise<void> {
1✔
1591
    const parameters = req.params;
4✔
1592
    this.logger.trace('Get financial mutations of user', parameters, 'by user', req.token.user);
4✔
1593

1594
    let filters;
4✔
1595
    let take;
4✔
1596
    let skip;
4✔
1597
    try {
4✔
1598
      filters = parseGetFinancialMutationsFilters(req);
4✔
1599
      const pagination = parseRequestPagination(req);
4✔
1600
      take = pagination.take;
4✔
1601
      skip = pagination.skip;
4✔
1602
    } catch (e) {
4!
1603
      res.status(400).send(e.message);
×
1604
      return;
×
1605
    }
×
1606

1607
    try {
4✔
1608
      const id = parseInt(parameters.id, 10);
4✔
1609
      // Get the user object if it exists
4✔
1610
      const user = await User.findOne({ where: { id, deleted: false } });
4✔
1611
      // If it does not exist, return a 404 error
4✔
1612
      if (user == null) {
4!
1613
        res.status(404).json('Unknown user ID.');
×
1614
        return;
×
1615
      }
×
1616

1617
      const mutations = await UserService.getUserFinancialMutations(user, filters, { take, skip });
4✔
1618
      res.status(200).json(mutations);
4✔
1619
    } catch (error) {
4!
1620
      this.logger.error('Could not get financial mutations of user:', error);
×
1621
      res.status(500).json('Internal server error.');
×
1622
    }
×
1623
  }
4✔
1624

1625
  /**
1✔
1626
   * GET /users/{id}/deposits
1627
   * @summary Get all deposits of a user that are still being processed by Stripe
1628
   * @operationId getUsersProcessingDeposits
1629
   * @tags users - Operations of user controller
1630
   * @param {integer} id.path.required - The id of the user to get the deposits from
1631
   * @security JWT
1632
   * @return {Array.<RoleResponse>} 200 - The processing deposits of a user
1633
   * @return {string} 404 - User not found error.
1634
   */
1✔
1635
  public async getUsersProcessingDeposits(req: RequestWithToken, res: Response): Promise<void> {
1✔
1636
    const parameters = req.params;
2✔
1637
    this.logger.trace('Get users processing deposits from user', parameters.id);
2✔
1638

1639
    try {
2✔
1640
      const id = parseInt(parameters.id, 10);
2✔
1641

1642
      const user = await User.findOne({ where: { id } });
2✔
1643
      if (user == null) {
2✔
1644
        res.status(404).json('Unknown user ID.');
1✔
1645
        return;
1✔
1646
      }
1✔
1647

1648
      const deposits = await StripeService.getProcessingStripeDepositsFromUser(id);
1✔
1649
      res.status(200).json(deposits.map((d) => StripeService.asStripeDepositResponse(d)));
1✔
1650
    } catch (error) {
2!
1651
      this.logger.error('Could not get processing deposits of user:', error);
×
1652
      res.status(500).json('Internal server error.');
×
1653
    }
×
1654
  }
2✔
1655

1656
  /**
1✔
1657
   * GET /users/{id}/transactions/report
1658
   * @summary Get transaction report for the given user
1659
   * @operationId getUsersTransactionsReport
1660
   * @tags users - Operations of user controller
1661
   * @param {integer} id.path.required - The id of the user to get the transaction report from
1662
   * @security JWT
1663
   * @return {Array.<TransactionReportResponse>} 200 - The transaction report of the user
1664
   * @param {string} fromDate.query - Start date for selected transactions (inclusive)
1665
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
1666
   * @param {integer} fromId.query - From-user for selected transactions
1667
   * @param {integer} toId.query - To-user for selected transactions
1668
   * @param {boolean} exclusiveToId.query - If all sub-transactions should be to the toId user, default true
1669
   * @deprecated - Use /users/{id}/transactions/sales/report or /users/{id}/transactions/purchases/report instead
1670
   * @return {string} 404 - User not found error.
1671
   */
1✔
1672
  public async getUsersTransactionsReport(req: RequestWithToken, res: Response): Promise<void> {
1✔
1673
    const parameters = req.params;
6✔
1674
    this.logger.trace('Get transaction report for user ', req.params.id, ' by user', req.token.user);
6✔
1675

1676
    let filters;
6✔
1677
    try {
6✔
1678
      filters = parseGetTransactionsFilters(req);
6✔
1679
    } catch (e) {
6✔
1680
      res.status(400).json(e.message);
1✔
1681
      return;
1✔
1682
    }
1✔
1683

1684
    try {
5✔
1685
      if ((filters.toId !== undefined && filters.fromId !== undefined) || (filters.toId === undefined && filters.fromId === undefined)) {
6✔
1686
        res.status(400).json('Need to provide either a toId or a fromId.');
2✔
1687
        return;
2✔
1688
      }
2✔
1689

1690
      const id = parseInt(parameters.id, 10);
3✔
1691

1692
      const user = await User.findOne({ where: { id } });
3✔
1693
      if (user == null) {
6✔
1694
        res.status(404).json('Unknown user ID.');
1✔
1695
        return;
1✔
1696
      }
1✔
1697

1698
      const report = await (new TransactionService()).getTransactionReportResponse(filters);
2✔
1699
      res.status(200).json(report);
2✔
1700
    } catch (e) {
6!
1701
      res.status(500).send();
×
1702
      this.logger.error(e);
×
1703
    }
×
1704
  }
6✔
1705

1706
  /**
1✔
1707
   * POST /users/{id}/fines/waive
1708
   * @summary Waive all given user's fines
1709
   * @tags users - Operations of user controller
1710
   * @param {integer} id.path.required - The id of the user
1711
   * @param {WaiveFinesRequest} request.body
1712
   * Optional body, see https://github.com/GEWIS/sudosos-backend/pull/344
1713
   * @operationId waiveUserFines
1714
   * @security JWT
1715
   * @return 204 - Success
1716
   * @return {string} 400 - User has no fines.
1717
   * @return {string} 404 - User not found error.
1718
   */
1✔
1719
  public async waiveUserFines(req: RequestWithToken, res: Response): Promise<void> {
1✔
1720
    const { id: rawId } = req.params;
9✔
1721
    const body = req.body as WaiveFinesRequest;
9✔
1722
    this.logger.trace('Waive fines', body, 'of user', rawId, 'by', req.token.user);
9✔
1723

1724
    try {
9✔
1725
      const id = parseInt(rawId, 10);
9✔
1726

1727
      const user = await User.findOne({ where: { id }, relations: { currentFines: { fines: true } } });
9✔
1728
      if (user == null) {
9✔
1729
        res.status(404).json('Unknown user ID.');
1✔
1730
        return;
1✔
1731
      }
1✔
1732
      if (user.currentFines == null) {
9✔
1733
        res.status(400).json('User has no fines.');
1✔
1734
        return;
1✔
1735
      }
1✔
1736

1737
      const totalAmountOfFines = user.currentFines!.fines.reduce((total, f) => total.add(f.amount), Dinero());
7✔
1738
      // Backwards compatibility with old version, where you could only waive all user's fines
7✔
1739
      const amountToWaive = body?.amount ?? totalAmountOfFines.toObject();
9✔
1740
      if (amountToWaive.amount <= 0) {
9✔
1741
        res.status(400).json('Amount to waive cannot be zero or negative.');
2✔
1742
        return;
2✔
1743
      }
2✔
1744
      if (amountToWaive.amount > totalAmountOfFines.getAmount()) {
9✔
1745
        res.status(400).json('Amount to waive cannot be more than the total amount of fines.');
1✔
1746
        return;
1✔
1747
      }
1✔
1748

1749
      await new DebtorService().waiveFines(id, { amount: amountToWaive } as WaiveFinesParams);
4✔
1750
      res.status(204).send();
4✔
1751
    } catch (e) {
9!
1752
      res.status(500).send();
×
1753
      this.logger.error(e);
×
1754
    }
×
1755
  }
9✔
1756

1757
  /**
1✔
1758
   * DELETE /users/{id}/roles/{roleId}
1759
   * @summary Deletes a role from a user
1760
   * @tags users - Operations of user controller
1761
   * @param {integer} id.path.required - The id of the user
1762
   * @param {integer} roleId.path.required - The id of the role
1763
   * @operationId deleteUserRole
1764
   * @security JWT
1765
   * @return 204 - Success
1766
   */
1✔
1767
  public async deleteUserRole(req: RequestWithToken, res: Response): Promise<void> {
1✔
1768
    const { id: rawUserId, roleId: rawRoleId } = req.params;
4✔
1769

1770
    const userId = parseInt(rawUserId, 10);
4✔
1771
    const roleId = parseInt(rawRoleId, 10);
4✔
1772

1773
    const user = await User.findOne({ where: { id: userId } });
4✔
1774
    if (!user) {
4✔
1775
      res.status(404).json('user not found');
1✔
1776
      return;
1✔
1777
    }
1✔
1778
    const role = await Role.findOne({ where: { id: roleId } });
3✔
1779
    if (!role) {
4✔
1780
      res.status(404).json('role not found');
1✔
1781
      return;
1✔
1782
    }
1✔
1783

1784
    await UserService.deleteUserRole(user, role);
2✔
1785
    res.status(204).send();
2✔
1786
  }
2✔
1787

1788
  /**
1✔
1789
   * POST /users/{id}/roles
1790
   * @summary Adds a role to a user
1791
   * @tags users - Operations of user controller
1792
   * @param {integer} id.path.required - The id of the user
1793
   * @param {AddRoleRequest} request.body.required
1794
   * @operationId addUserRole
1795
   * @security JWT
1796
   * @return 204 - Success
1797
   */
1✔
1798
  public async addUserRole(req: RequestWithToken, res: Response): Promise<void> {
1✔
1799
    const { id: rawUserId } = req.params;
2✔
1800
    const userId = parseInt(rawUserId, 10);
2✔
1801
    const { roleId } = req.body as AddRoleRequest;
2✔
1802

1803
    const user = await User.findOne({ where: { id: userId } });
2✔
1804
    if (!user) {
2!
1805
      res.status(404).json('user not found');
×
1806
      return;
×
1807
    }
×
1808
    const role = await Role.findOne({ where: { id: roleId } });
2✔
1809
    if (!role) {
2✔
1810
      res.status(404).json('role not found');
1✔
1811
      return;
1✔
1812
    }
1✔
1813

1814
    await UserService.addUserRole(user, role);
1✔
1815
    res.status(204).send();
1✔
1816
  }
1✔
1817

1818
  /**
1✔
1819
   * GET /users/{id}/wrapped
1820
   * @summary Get wrapped for a user
1821
   * @operationId getWrapped
1822
   * @tags users - Operations of user controller
1823
   * @param {integer} id.path.required - The id of the user
1824
   * @security JWT
1825
   * @return {WrappedResponse} 200 - The requested user's wrapped
1826
   * @return {string} 404 - Wrapped for user not found error
1827
   * @return {string} 500 - Internal server error
1828
   */
1✔
1829
  private async getUserWrapped(req: RequestWithToken, res: Response): Promise<void> {
1✔
1830
    const { id: rawUserId } = req.params;
4✔
1831

1832
    try {
4✔
1833
      const userId = parseInt(rawUserId, 10);
4✔
1834
    
1835
      const wrappedRes = await new WrappedService().getWrappedForUser(userId);
4✔
1836
      if (wrappedRes == null) {
4✔
1837
        res.status(404).json('Wrapped not found for user');
1✔
1838
        return;
1✔
1839
      }
1✔
1840

1841
      res.json(WrappedService.asWrappedResponse(wrappedRes));
3✔
1842
    } catch (error) {
4!
1843
      res.status(500).json({ message: 'Internal server error' });
×
1844
    }
×
1845
  }
4✔
1846

1847
  /**
1✔
1848
   * POST /users/{id}/wrapped
1849
   * @summary Recompute wrapped for a user
1850
   * @operationId updateWrapped
1851
   * @tags users - Operations of user controller
1852
   * @param {integer} id.path.required - The id of the user
1853
   * @security JWT
1854
   * @return {WrappedResponse} 200 - The requested user's wrapped
1855
   * @return {string} 500 - Internal server error
1856
   */
1✔
1857
  private async computedWrapped(req: RequestWithToken, res: Response): Promise<void> {
1✔
1858
    const { id: rawUserId } = req.params;
2✔
1859

1860
    try {
2✔
1861
      const userId = parseInt(rawUserId, 10);
2✔
1862

1863
      res.json(await new WrappedService().updateWrapped({ ids: [userId] }));
2✔
1864
    } catch (error) {
2!
1865
      res.status(500).json({ message: 'Internal server error' });
×
1866
    }
×
1867
  }
2✔
1868

1869
  /**
1✔
1870
   * GET /users/{id}/settings
1871
   * @summary Get all user settings
1872
   * @operationId getUserSettings
1873
   * @tags users - Operations of user controller
1874
   * @param {integer} id.path.required - The id of the user
1875
   * @security JWT
1876
   * @return {UserSettingsResponse} 200 - The user's settings
1877
   * @return {string} 404 - User not found
1878
   * @return {string} 500 - Internal server error
1879
   */
1✔
1880
  private async getUserSettings(req: RequestWithToken, res: Response): Promise<void> {
1✔
1881
    const { id: rawUserId } = req.params;
5✔
1882

1883
    try {
5✔
1884
      const userId = parseInt(rawUserId, 10);
5✔
1885
      
1886
      const user = await User.findOne({ where: { id: userId } });
5✔
1887
      if (!user) {
5✔
1888
        res.status(404).json('User not found.');
1✔
1889
        return;
1✔
1890
      }
1✔
1891

1892
      const store = new UserSettingsStore();
4✔
1893
      const settings = await store.getAllSettings(userId);
4✔
1894
      const response = UserSettingsStore.toResponse(settings);
4✔
1895
      res.json(response);
4✔
1896
    } catch (error) {
5!
1897
      this.logger.error('Could not get user settings:', error);
×
1898
      res.status(500).json('Internal server error.');
×
1899
    }
×
1900
  }
5✔
1901

1902
  /**
1✔
1903
   * PATCH /users/{id}/settings
1904
   * @summary Update user settings
1905
   * @operationId patchUserSettings
1906
   * @tags users - Operations of user controller
1907
   * @param {integer} id.path.required - The id of the user
1908
   * @param {PatchUserSettingsRequest} request.body.required - The settings to update
1909
   * @security JWT
1910
   * @return {UserSettingsResponse} 200 - The updated user settings
1911
   * @return {string} 404 - User not found
1912
   * @return {string} 500 - Internal server error
1913
   */
1✔
1914
  private async patchUserSettings(req: RequestWithToken, res: Response): Promise<void> {
1✔
1915
    const { id: rawUserId } = req.params;
10✔
1916
    const body = req.body as PatchUserSettingsRequest;
10✔
1917

1918
    try {
10✔
1919
      const userId = parseInt(rawUserId, 10);
10✔
1920
      
1921
      const user = await User.findOne({ where: { id: userId } });
10✔
1922
      if (!user) {
10✔
1923
        res.status(404).json('User not found.');
1✔
1924
        return;
1✔
1925
      }
1✔
1926

1927
      const store = new UserSettingsStore();
9✔
1928
      await store.setSettings(userId, body);
9✔
1929

1930
      const settings = await store.getAllSettings(userId);
9✔
1931

1932
      res.json(UserSettingsStore.toResponse(settings));
9✔
1933
    } catch (error) {
10!
1934
      this.logger.error('Could not update user settings:', error);
×
1935
      res.status(500).json('Internal server error.');
×
1936
    }
×
1937
  }
10✔
1938

1939
  /**
1✔
1940
   * PATCH /users/{id}/usertype
1941
   * @summary Update user type
1942
   * @operationId patchUserType
1943
   * @tags users - Operations of user controller
1944
   * @param {integer} id.path.required - The id of the user
1945
   * @param {PatchUserTypeRequest} request.body.required - The user type to update to
1946
   * @security JWT
1947
   * @return {UserResponse} 200 - The updated user
1948
   * @return {string} 404 - User not found
1949
   * @return {string} 400 - Bad request
1950
   * @return {string} 409 - Conflict error
1951
   * @return {string} 422 - Unprocessable entity
1952
   * @return {string} 500 - Internal server error
1953
   */
1✔
1954
  private async patchUserType(req: RequestWithToken, res: Response): Promise<void> {
1✔
1955
    const { id: rawUserId } = req.params;
5✔
1956
    const body = req.body as PatchUserTypeRequest;
5✔
1957

1958
    try {
5✔
1959
      const userId = parseInt(rawUserId, 10);
5✔
1960
      const userOptions = UserService.getOptions({ id: userId });
5✔
1961
      const user = await User.findOne(userOptions);
5✔
1962
      if (!user) {
5✔
1963
        res.status(404).json('User not found.');
1✔
1964
        return;
1✔
1965
      }
1✔
1966

1967
      const allowedTypes = [UserType.MEMBER, UserType.LOCAL_USER];
4✔
1968

1969
      if (!allowedTypes.includes(user.type)) {
5✔
1970
        res.status(422).json('It is not possible to change the user type for users of this type.');
1✔
1971
        return;
1✔
1972
      }
1✔
1973

1974
      if (!allowedTypes.includes(body.userType)) {
5✔
1975
        res.status(422).json(`User type can only be changed to [${allowedTypes}].`);
1✔
1976
        return;
1✔
1977
      }
1✔
1978

1979
      if (body.userType === user.type) {
5✔
1980
        res.status(409).json('User is already of this type.');
1✔
1981
        return;
1✔
1982
      }
1✔
1983

1984
      if (!user.email) {
5!
1985
        res.status(400).json('Cannot change user type of user without email.');
×
1986
        return;
×
1987
      }
✔
1988

1989
      if (body.userType === UserType.MEMBER && !user.memberUser) {
5!
1990
        res.status(400).json('Cannot change to MEMBER since no memberId is associated to this user.');
×
1991
        return;
×
1992
      }
✔
1993

1994
      await UserService.updateUserType(user, body.userType);
1✔
1995
      const updatedUser = await UserService.getSingleUser(userId);
1✔
1996
      res.status(200).json(asUserResponse(updatedUser, true));
1✔
1997
    } catch (e) {
5!
1998
      res.status(500).send('Internal server error.');
×
1999
      this.logger.error(e);
×
2000
    }
×
2001
  }
5✔
2002

2003
  /**
1✔
2004
   * GET /users/recently-charged
2005
   * @summary Get users recently charged by the caller via an authenticated point of sale.
2006
   * Returns distinct buyers ordered by most recent transaction first, intended for
2007
   * quick suggestions in the authenticated POS flow.
2008
   * @operationId getRecentlyChargedUsers
2009
   * @tags users - Operations of user controller
2010
   * @param {integer} take.query - Maximum number of users to return (default 50)
2011
   * @security JWT
2012
   * @return {Array.<UserResponse>} 200 - List of recently charged users.
2013
   */
1✔
2014
  public async getRecentlyChargedUsers(req: RequestWithToken, res: Response): Promise<void> {
1✔
2015
    this.logger.trace('Get recently charged users by user', req.token.user);
5✔
2016

2017
    let take: number;
5✔
2018
    try {
5✔
2019
      take = req.query.take !== undefined ? asNumber(req.query.take) : 50;
5✔
2020
      take = Math.min(Math.max(1, Math.trunc(take)), maxPagination());
5✔
2021
    } catch (e) {
5✔
2022
      res.status(400).send(e.message);
1✔
2023
      return;
1✔
2024
    }
1✔
2025

2026
    try {
4✔
2027
      const users = await new TransactionService().getRecentlyChargedUsers(req.token.user.id, take);
4✔
2028
      const records = users.map((u) => asUserResponse(u));
4✔
2029
      if (!await this.canSeeEmail(req, 'all')) {
5✔
2030
        records.forEach((u) => { u.email = undefined; });
1✔
2031
      }
1✔
2032
      res.status(200).json(records);
4✔
2033
    } catch (e) {
5!
2034
      res.status(500).send('Internal server error.');
×
2035
      this.logger.error(e);
×
2036
    }
×
2037
  }
5✔
2038
}
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