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

GEWIS / sudosos-backend / 14285340495

05 Apr 2025 08:05PM UTC coverage: 85.028% (+0.02%) from 85.01%
14285340495

Pull #510

github

web-flow
Merge 156b16a52 into 6604ca487
Pull Request #510: fix: balance-controller

1274 of 1559 branches covered (81.72%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

6870 of 8019 relevant lines covered (85.67%)

1089.95 hits per line

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

75.0
/src/controller/balance-controller.ts
1
/**
2
 *  SudoSOS back-end API service.
3
 *  Copyright (C) 2024  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 balance-controller.
23
 *
24
 * @module balance
25
 */
26

27
import log4js, { Logger } from 'log4js';
2✔
28
import { Response } from 'express';
29
import BaseController, { BaseControllerOptions } from './base-controller';
2✔
30
import Policy from './policy';
31
import { RequestWithToken } from '../middleware/token-middleware';
32
import User from '../entity/user/user';
2✔
33
import BalanceService, { asBalanceOrderColumn, GetBalanceParameters } from '../service/balance-service';
2✔
34
import UserController from './user-controller';
2✔
35
import { asArrayOfUserTypes, asBoolean, asDate, asDinero } from '../helpers/validators';
2✔
36
import { asOrderingDirection } from '../helpers/ordering';
2✔
37
import { parseRequestPagination } from '../helpers/pagination';
2✔
38

39
export default class BalanceController extends BaseController {
2✔
40
  private logger: Logger = log4js.getLogger('BalanceController');
2✔
41

42
  /**
43
   * Creates a new balance controller instance.
44
   * @param options - The options passed to the base controller.
45
   */
46
  public constructor(options: BaseControllerOptions) {
47
    super(options);
2✔
48
    this.logger.level = process.env.LOG_LEVEL;
2✔
49
  }
50

51
  /**
52
   * @inheritdoc
53
   */
54
  public getPolicy(): Policy {
55
    return {
2✔
56
      '/': {
57
        GET: {
58
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'own', 'Balance', ['*']),
1✔
59
          handler: this.getOwnBalance.bind(this),
60
        },
61
      },
62
      '/all': {
63
        GET: {
64
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'Balance', ['*']),
23✔
65
          handler: this.getAllBalances.bind(this),
66
        },
67
      },
68
      '/:id(\\d+)': {
69
        GET: {
70
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', UserController.getRelation(req), 'Balance', ['*']),
3✔
71
          handler: this.getBalance.bind(this),
72
        },
73
      },
74
      '/summary': {
75
        GET: {
76
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'Balance', ['*']),
2✔
77
          handler: this.calculateTotalBalances.bind(this),
78
        },
79
      },
80
    };
81
  }
82

83
  /**
84
   * GET /balances
85
   * @summary Get balance of the current user
86
   * @operationId getBalances
87
   * @tags balance - Operations of balance controller
88
   * @security JWT
89
   * @return {BalanceResponse} 200 - The requested user's balance
90
   * @return {string} 400 - Validation error
91
   * @return {string} 404 - Not found error
92
   * @return {string} 500 - Internal server error
93
   */
94
  // eslint-disable-next-line class-methods-use-this
95
  private async getOwnBalance(req: RequestWithToken, res: Response): Promise<void> {
96
    try {
1✔
97
      res.json(await new BalanceService().getBalance(req.token.user.id));
1✔
98
    } catch (error) {
99
      this.logger.error(`Could not get balance of user with id ${req.token.user.id}`, error);
×
100
      res.status(500).json('Internal server error.');
×
101
    }
102
  }
103

104
  /**
105
   * GET /balances/all
106
   * @summary Get balance of all users
107
   * @operationId getAllBalance
108
   * @tags balance - Operations of balance controller
109
   * @security JWT
110
   * @param {string} date.query - Timestamp to get balances for
111
   * @param {integer} minBalance.query - Minimum balance
112
   * @param {integer} maxBalance.query - Maximum balance
113
   * @param {boolean} hasFine.query - Only users with(out) fines
114
   * @param {integer} minFine.query - Minimum fine
115
   * @param {integer} maxFine.query - Maximum fine
116
   * @param {Array<string|number>} userTypes.query - enum:MEMBER,ORGAN,VOUCHER,LOCAL_USER,LOCAL_ADMIN,INVOICE,AUTOMATIC_INVOICE - Filter based on user type.
117
   * @param {string} orderBy.query - Column to order balance by - eg: id,amount
118
   * @param {string} orderDirection.query - enum:ASC,DESC - Order direction
119
   * @param {boolean} allowDeleted.query - Whether to include deleted users
120
   * @param {integer} take.query - How many transactions the endpoint should return
121
   * @param {integer} skip.query - How many transactions should be skipped (for pagination)
122
   * @return {PaginatedBalanceResponse} 200 - The requested user's balance
123
   * @return {string} 400 - Validation error
124
   * @return {string} 500 - Internal server error
125
   */
126
  private async getAllBalances(req: RequestWithToken, res: Response): Promise<void> {
127
    this.logger.trace('Get all balances by', req.token.user);
22✔
128

129
    let params: GetBalanceParameters;
130
    let take;
131
    let skip;
132
    try {
22✔
133
      params = {
22✔
134
        date: asDate(req.query.date),
135
        minBalance: asDinero(req.query.minBalance),
136
        maxBalance: asDinero(req.query.maxBalance),
137
        hasFine: asBoolean(req.query.hasFine),
138
        minFine: asDinero(req.query.minFine),
139
        maxFine: asDinero(req.query.maxFine),
140
        userTypes: asArrayOfUserTypes(req.query.userTypes),
141
        orderBy: asBalanceOrderColumn(req.query.orderBy),
142
        orderDirection: asOrderingDirection(req.query.orderDirection),
143
        allowDeleted: asBoolean(req.query.allowDeleted),
144
      };
145
      const pagination = parseRequestPagination(req);
22✔
146
      take = pagination.take;
22✔
147
      skip = pagination.skip;
22✔
148
    } catch (error) {
149
      res.status(400).json(error.message);
×
150
      return;
×
151
    }
152

153
    try {
22✔
154
      const result = await new BalanceService().getBalances(params, { take, skip });
22✔
155
      res.json(result);
22✔
156
    } catch (error) {
157
      this.logger.error('Could not get balances', error);
×
158
      res.status(500).json('Internal server error.');
×
159
    }
160
  }
161

162
  /**
163
   * GET /balances/{id}
164
   * @summary Retrieves the requested balance
165
   * @operationId getBalanceId
166
   * @tags balance - Operations of balance controller
167
   * @param {integer} id.path.required - The id of the user for which the saldo is requested
168
   * @security JWT
169
   * @return {BalanceResponse} 200 - The requested user's balance
170
   * @return {string} 400 - Validation error
171
   * @return {string} 404 - Not found error
172
   * @return {string} 500 - Internal server error
173
   */
174
  private async getBalance(req: RequestWithToken, res: Response): Promise<void> {
175
    try {
2✔
176
      const userId = Number.parseInt(req.params.id, 10);
2✔
177
      if (await User.findOne({ where: { id: userId, deleted: false } })) {
2✔
178
        res.json(await new BalanceService().getBalance(userId));
1✔
179
      } else {
180
        res.status(404).json('User does not exist');
1✔
181
      }
182
    } catch (error) {
183
      const id = req?.params?.id ?? req.token.user.id;
×
184
      this.logger.error(`Could not get balance of user with id ${id}`, error);
×
185
      res.status(500).json('Internal server error.');
×
186
    }
187
  }
188

189
  /**
190
   * GET /balances/summary
191
   * @summary Get the calculated total balances in SudoSOS
192
   * @operationId calculateTotalBalances
193
   * @tags balance - Operations of balance controller
194
   * @security JWT
195
   * @param {string} date.query.required - The date for which to calculate the balance.
196
   * @return {TotalBalanceResponse} 200 - The requested user's balance
197
   * @return {string} 400 - Validation error
198
   * @return {string} 404 - Not found error
199
   * @return {string} 500 - Internal server error
200
   */
201
  private async calculateTotalBalances(req: RequestWithToken, res: Response): Promise<void> {
202
    try {
1✔
203
      const date = asDate(req.query.date);
1✔
204

205
      const balances = await new BalanceService().calculateTotalBalances(date);
1✔
206
      res.json(balances);
1✔
207
    } catch (error) {
208
      this.logger.error('Could not calculate the total balances', error);
×
UNCOV
209
      res.status(500).json('Internal server error.');
×
210
    }
211
  }
212
}
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