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

GEWIS / sudosos-backend / 21095597836

17 Jan 2026 02:14PM UTC coverage: 89.593% (-0.03%) from 89.623%
21095597836

push

github

web-flow
fix: return baseUser with eligable inactive cost (#697)

1657 of 1999 branches covered (82.89%)

Branch coverage included in aggregate %.

6 of 7 new or added lines in 2 files covered. (85.71%)

2 existing lines in 1 file now uncovered.

8545 of 9388 relevant lines covered (91.02%)

1029.29 hits per line

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

82.91
/src/controller/inactive-administrative-cost-controller.ts
1
/**
2
 *  SudoSOS back-end API service.
3
 *  Copyright (C) 2026 Study association GEWIS
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU Affero General Public License as published
7
 *  by the Free Software Foundation, either version 3 of the License, or
8
 *  (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU Affero General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU Affero General Public License
16
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
 *
18
 *  @license
19
 */
20

21
/**
22
 * This is the module page of the inactive-administrative-cost-controller
23
 * @module internal/controllers
24
 */
25

26
import BaseController, { BaseControllerOptions } from './base-controller';
2✔
27
import log4js, { Logger } from 'log4js';
2✔
28
import { Response } from 'express';
29
import Policy from './policy';
30
import { RequestWithToken } from '../middleware/token-middleware';
31
import { parseRequestPagination } from '../helpers/pagination';
2✔
32
import InactiveAdministrativeCostService, { parseInactiveAdministrativeCostFilterParameters, InactiveAdministrativeCostFilterParameters } from '../service/inactive-administrative-cost-service';
2✔
33
import { PaginatedInactiveAdministrativeCostResponse } from './response/inactive-administrative-cost-response';
34
import { isFail } from '../helpers/specification-validation';
2✔
35
import {
36
  CreateInactiveAdministrativeCostRequest,
37
  HandoutInactiveAdministrativeCostsRequest,
38
} from './request/inactive-administrative-cost-request';
39
import { NotImplementedError } from '../errors';
2✔
40
import verifyValidUserId from './request/validators/inactive-administrative-cost-request-spec';
2✔
41
import InactiveAdministrativeCost from '../entity/transactions/inactive-administrative-cost';
42
import User from '../entity/user/user';
2✔
43
import { In } from 'typeorm';
2✔
44
import { asBoolean } from '../helpers/validators';
2✔
45

46

47
export default class InactiveAdministrativeCostController extends BaseController {
2✔
48
  /**
49
   * Reference to the logger instance.
50
   */
51
  private logger: Logger = log4js.getLogger('InactiveAdministrativeCostController');
2✔
52

53
  /**
54
   * Creates a new InactiveAdministrativeCost controller instance
55
   * @param options - The options passed to the base controller.
56
   */
57
  public constructor(options: BaseControllerOptions) {
58
    super(options);
2✔
59
    this.logger.level = process.env.LOG_LEVEL;
2✔
60
  }
61

62
  /**
63
     * @inheritDoc
64
     */
65
  getPolicy(): Policy {
66
    return {
2✔
67
      '/': {
68
        GET: {
69
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'InactiveAdministrativeCost', ['*']),
6✔
70
          handler: this.getAllInactiveAdministrativeCosts.bind(this),
71
        },
72
        POST: {
73
          body: { modelName: 'CreateInactiveAdministrativeCostRequest' },
74
          policy: async (req) => this.roleManager.can(req.token.roles, 'create', 'all', 'InactiveAdministrativeCost', ['*']),
4✔
75
          handler: this.createInactiveAdministrativeCost.bind(this),
76
        },
77
      },
78
      '/:id(\\d+)': {
79
        GET: {
80
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'InactiveAdministrativeCost', ['*']),
4✔
81
          handler: this.getSingleInactiveAdministrativeCost.bind(this),
82
        },
83
        DELETE: {
84
          policy: async (req) => this.roleManager.can(req.token.roles, 'delete', 'all', 'InactiveAdministrativeCost', ['*']),
3✔
85
          handler: this.deleteInactiveAdministrativeCost.bind(this),
86
        },
87
      },
88
      '/eligible-users': {
89
        GET: {
90
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'InactiveAdministrativeCost', ['*']),
2✔
91
          handler: this.checkInactiveUsers.bind(this),
92
        },
93
      },
94
      '/notify': {
95
        POST: {
96
          policy: async (req) => this.roleManager.can(req.token.roles, 'notify', 'all', 'InactiveAdministrativeCost', ['*']),
3✔
97
          handler: this.notifyInactiveUsers.bind(this),
98
          body: { modelName: 'HandoutInactiveAdministrativeCostsRequest' },
99
        },
100
      },
101
      '/handout': {
102
        POST: {
103
          policy: async (req) => this.roleManager.can(req.token.roles, 'create', 'all', 'InactiveAdministrativeCost', ['*']),
3✔
104
          handler: this.handoutInactiveAdministrativeCost.bind(this),
105
          body: { modelName: 'HandoutInactiveAdministrativeCostsRequest' },
106
        },
107
      },
108
    };
109
  }
110

111
  /**
112
   * GET /inactive-administrative-costs
113
   * @summary Returns all inactive administrative costs in the system.
114
   * @operationId getAllInactiveAdministrativeCosts
115
   * @tags inactiveAdministrativeCosts - Operations of the invoices controller
116
   * @security JWT
117
   * @param {integer} fromId.query - Filter on the id of the user
118
   * @param {integer} inactiveAdministrativeCostId.query - Filter on the id of entity
119
   * @return {PaginatedInactiveAdministrativeCostResponse} 200 - All existing inactive administrative costs
120
   * @return {string} 400 - Validation Error
121
   * @return {string} 500 - Internal server error
122
   */
123
  public async getAllInactiveAdministrativeCosts(req: RequestWithToken, res: Response): Promise<void> {
124
    const { body } = req;
5✔
125
    this.logger.trace('Get all inactive administrative costs', body, 'by user', req.token.user);
5✔
126

127
    let take;
128
    let skip;
129
    let filter: InactiveAdministrativeCostFilterParameters;
130

131
    try {
5✔
132
      const pagination = parseRequestPagination(req);
5✔
133
      filter = parseInactiveAdministrativeCostFilterParameters(req);
5✔
134
      take = pagination.take;
4✔
135
      skip = pagination.skip;
4✔
136
    } catch (e) {
137
      res.status(400).send(e.message);
1✔
138
      return;
1✔
139
    }
140

141
    // Handle request
142
    try {
4✔
143
      const inactiveAdministrativeCosts: PaginatedInactiveAdministrativeCostResponse = await new InactiveAdministrativeCostService().getPaginatedInactiveAdministrativeCosts(
4✔
144
        filter, { take, skip },
145
      );
146
      res.json(inactiveAdministrativeCosts);
4✔
147
    } catch (error) {
148
      this.logger.error('Could not return all inactive administrative costs', error);
×
149
      res.status(500).json('Internal server error.');
×
150
    }
151
  }
152

153
  /**
154
   * GET /inactive-administrative-costs/{id}
155
   * @summary Returns a single inactive administrative cost entity
156
   * @operationId getInactiveAdministrativeCosts
157
   * @param {integer} id.path.required - The id of the requested inactive administrative cost
158
   * @tags inactiveAdministrativeCosts - Operations of the invoices controller
159
   * @security JWT
160
   * @return {BaseInactiveAdministrativeCostResponse} 200 - All existing inactive administrative cost
161
   * @return {string} 404 - InactiveAdministrativeCost not found
162
   * @return {string} 500 - Internal server error
163
   */
164
  public async getSingleInactiveAdministrativeCost(req: RequestWithToken, res: Response): Promise<void> {
165
    const { id } = req.params;
3✔
166
    const inactiveAdministrativeCostId = parseInt(id, 10);
3✔
167
    this.logger.trace('Get inactive administrative costs', inactiveAdministrativeCostId, 'by user', req.token.user);
3✔
168

169
    try {
3✔
170
      const inactiveAdministrativeCosts: InactiveAdministrativeCost[] = await new InactiveAdministrativeCostService().getInactiveAdministrativeCosts(
3✔
171
        { inactiveAdministrativeCostId },
172
      );
173

174
      const inactiveAdministrativeCost = inactiveAdministrativeCosts[0];
3✔
175
      if (!inactiveAdministrativeCost) {
3✔
176
        res.status(404).json('Unknown inactive administrative cost ID.');
1✔
177
        return;
1✔
178
      }
179
      const response = InactiveAdministrativeCostService.asInactiveAdministrativeCostResponse(inactiveAdministrativeCost);
2✔
180

181
      res.json(response);
2✔
182
    } catch (error) {
183
      this.logger.error('Could not return inactive administrative cost', error);
×
184
      res.status(500).json('Internal server error.');
×
185
    }
186

187
  }
188

189
  /**
190
   * POST /inactive-administrative-costs
191
   * @summary Adds and inactive administrative cost to the system.
192
   * @operationId createInactiveAdministrativeCosts
193
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
194
   * @security JWT
195
   * @param {CreateInactiveAdministrativeCostRequest} request.body.required -
196
   * The inactive administrative cost which should be created
197
   * @return {BaseInactiveAdministrativeCostResponse} 200 - The created inactive administrative cost entity
198
   * @return {string} 400 - Validation error
199
   * @return {string} 500 - Internal server error
200
   */
201
  public async createInactiveAdministrativeCost(req: RequestWithToken, res: Response): Promise<void> {
202
    const body   = req.body as CreateInactiveAdministrativeCostRequest;
3✔
203
    this.logger.trace('Create InactiveAdministrativeCosts', body, 'by user', req.token.user);
3✔
204

205
    // handle request
206
    try {
3✔
207
      const validation = await verifyValidUserId(body);
3✔
208
      if (isFail(validation)) {
3✔
209
        res.status(400).json(validation.fail.value);
2✔
210
        return;
2✔
211
      }
212

213
      const inactiveAdministrativeCost = await new InactiveAdministrativeCostService().createInactiveAdministrativeCost(body);
1✔
214
      res.json(InactiveAdministrativeCostService.asInactiveAdministrativeCostResponse(inactiveAdministrativeCost));
1✔
215
    } catch (error) {
216
      if (error instanceof NotImplementedError) {
×
217
        res.status(501).json(error.message);
×
218
        return;
×
219
      }
220
      this.logger.error('Could not create inactive administrative cost:', error);
×
221
      res.status(500).json('Internal server error.');
×
222
    }
223
  }
224

225
  /**
226
   * DELETE /inactive-administrative-costs/{id}
227
   * @summary Deletes an inactive administrative cost.
228
   * @operationId deleteInactiveAdministrativeCost
229
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
230
   * @security JWT
231
   * @param {integer} id.path.required - The id of the inactive administrative cost which should be deleted.
232
   * @return {string} 404 - Invoice not found
233
   * @return 204 - Deletion success
234
   * @return {string} 500 - Internal server error
235
   */
236
  public async deleteInactiveAdministrativeCost(req: RequestWithToken, res: Response): Promise<void> {
237
    const { id } = req.params;
2✔
238
    const inactiveAdministrativeCostId = parseInt(id, 10);
2✔
239
    this.logger.trace('Delete inactive administrative costs', inactiveAdministrativeCostId, 'by user', req.token.user);
2✔
240

241
    try {
2✔
242
      const inactiveAdministrativeCost = await new InactiveAdministrativeCostService().deleteInactiveAdministrativeCost(inactiveAdministrativeCostId);
2✔
243
      if (!inactiveAdministrativeCost) {
2✔
244
        res.status(404).json('InactiveAdministrativeCost not found.');
1✔
245
        return;
1✔
246
      }
247
      res.status(204).send();
1✔
248
    } catch (error) {
UNCOV
249
      this.logger.error('Could not delete InactiveAdministrativeCost:', error);
×
UNCOV
250
      res.status(500).json('Internal server error.');
×
251
    }
252
  }
253

254
  /**
255
   * GET /inactive-administrative-costs/eligible-users
256
   * @summary Find all users who are eligible for notification or creation of inactive administrative cost
257
   * @operationId getInactiveAdministrativeCostsEligibleUsers
258
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
259
   * @security JWT
260
   * @param {boolean} notification.query - Whether to check for notification or for fine.
261
   * @return {Array<UserToInactiveAdministrativeCostResponse>} 200 - List of eligible users
262
   * @return {string} 500 - Internal server error
263
   */
264
  public async checkInactiveUsers(req: RequestWithToken, res: Response): Promise<void> {
265
    const { body } = req;
1✔
266
    this.logger.trace('Check Inactive Users', body, 'by user', req.token.user);
1✔
267

268
    try {
1✔
269
      const notification = asBoolean(req.query.notification);
1✔
270

271
      const usersResponses = await new InactiveAdministrativeCostService().checkInactiveUsers({ notification });
1✔
272

273
      res.json(usersResponses);
1✔
274
    } catch (error) {
275
      this.logger.error('Could not check inactive users:', error);
×
276
      res.status(500).json('Internal server error.');
×
277
    }
278
  }
279

280
  /**
281
   * POST /inactive-administrative-costs/notify
282
   * @summary Notify all users which will pay administrative costs within a year
283
   * @operationId notifyInactiveAdministrativeCostsUsers
284
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
285
   * @security JWT
286
   * @param {HandoutInactiveAdministrativeCostsRequest} request.body.required -
287
   * The users that should be notified
288
   * @return 204 - Success
289
   * @return {string} 400 - Validation error
290
   * @return {string} 500 - Internal server error
291
   */
292
  public async notifyInactiveUsers(req: RequestWithToken, res: Response): Promise<void> {
293
    const body = req.body as HandoutInactiveAdministrativeCostsRequest;
2✔
294
    this.logger.trace('Notify Inactive Users', body, 'by user', req.token.user);
2✔
295

296
    try {
2✔
297
      if (!Array.isArray(body.userIds)) throw new Error('userIds is not an Array.');
2!
298
      
299
      const users = await User.find({ where: { id: In(body.userIds) } });
2✔
300
      if (users.length !== body.userIds.length) throw new Error('userIds is not a valid array of user IDs');
2✔
301
    } catch (error) {
302
      res.status(400).json(error.message);
1✔
303
      return ;
1✔
304
    }
305

306
    try {
1✔
307
      await new InactiveAdministrativeCostService().sendInactiveNotification(body);
1✔
308
      res.status(204).send();
1✔
309
    } catch (error) {
310
      this.logger.error('Could not check inactive users:', error);
×
311
      res.status(500).json('Internal server error.');
×
312
    }
313
  }
314

315
  /**
316
   * POST /inactive-administrative-costs/handout
317
   * @summary Handout inactive administrative costs to all users who are eligible.
318
   * @operationId handoutInactiveAdministrativeCostsUsers
319
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
320
   * @security JWT
321
   * @param {HandoutInactiveAdministrativeCostsRequest} request.body.required -
322
   * The users that should be fined
323
   * @return 204 - Success
324
   * @return {string} 400 - Validation error
325
   * @return {string} 500 - Internal server error
326
   */
327
  public async handoutInactiveAdministrativeCost(req: RequestWithToken, res: Response): Promise<void> {
328
    const body = req.body as HandoutInactiveAdministrativeCostsRequest;
2✔
329
    this.logger.trace('Handout InactiveAdministrativeCosts', body, 'by user', req.token.user);
2✔
330

331
    try {
2✔
332
      if (!Array.isArray(body.userIds)) throw new Error('userIds is not an Array.');
2!
333

334
      const users = await User.find({ where: { id: In(body.userIds) } });
2✔
335
      if (users.length !== body.userIds.length) throw new Error('userIds is not a valid array of user IDs');
2✔
336
    } catch (error) {
337
      res.status(400).json(error.message);
1✔
338
      return ;
1✔
339
    }
340

341
    try {
1✔
342
      const inactiveAdministrativeCosts = await new InactiveAdministrativeCostService().handOutInactiveAdministrativeCost(body);
1✔
343
      const response = InactiveAdministrativeCostService.toArrayResponse(inactiveAdministrativeCosts);
1✔
344

345
      res.status(200).send(response);
1✔
346
    } catch (error) {
347
      this.logger.error('Could not check inactive users:', error);
×
348
      res.status(500).json('Internal server error.');
×
349
    }
350
  }
351

352

353
}
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