Skip to content

Commit d842374

Browse files
committed
feat(min): enforce minimum balance
1 parent b082de4 commit d842374

File tree

8 files changed

+77
-47
lines changed

8 files changed

+77
-47
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ next-env.d.ts
1010
.env.dev
1111
.env.prod
1212
*.pem
13-
*.mjs
13+
*.mjs
14+
*.xml

api/src/admin/admin.controller.ts

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { IsPublic } from '../auth/decorator/public.decorator';
44
import { ERROR_CODE } from '../exceptions';
55
import { ApiCreatedResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
66
import { ApiAppErrorResponse } from '../app.dto';
7+
import { ConfigModule } from '../config/config.module';
78

89
@Controller('admin')
910
@ApiTags('Authentication')
1011
export class AdminController {
11-
constructor(private admin: AdminService) {}
12+
constructor(private admin: AdminService, private config: ConfigModule) {}
1213

1314
@HttpCode(HttpStatus.CREATED)
1415
@IsPublic()
@@ -24,30 +25,30 @@ export class AdminController {
2425
ERROR_CODE.INVALID_TOKEN_FORMAT,
2526
'The token is not in the right format. This may happen if you did not use the syntax `Bearer {token there}`, or the token was not generated by the API',
2627
)
27-
async generateReport(@Headers() headers: Record<string, string>) {
28-
// this.admin.getUsersReport();
28+
async generateReport() {
29+
const users = await this.admin.getUsersReport();
30+
const ctrlSum = users.reduce((sum, user) => sum + Math.min(10000, user.balance), 0) / 100;
2931
return this.toXML({
3032
Document: {
3133
xmlns: 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.12',
3234
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
3335
CstmrCdtTrfInitn: {
3436
GrpHdr: {
35-
MsgId: { value: '0'.slice(0, 35) },
37+
MsgId: { value: `BUCKUTT-${Date.now()}`.slice(0, 35) },
3638
CreDtTm: { value: new Date().toISOString().slice(0, -5) },
37-
NbOfTxs: { value: 0 },
38-
CtrlSum: { value: 0 },
39+
NbOfTxs: { value: users.length },
40+
CtrlSum: { value: ctrlSum },
3941
InitgPty: {
40-
Nm: { value: 'Company Name'.slice(0, 140) },
41-
Id: { PrvtId: { Othr: { Id: { value: 'Company ID'.slice(0, 256) } } } },
42+
Nm: { value: this.config.XML_EXPORT_COMPANY_NAME.slice(0, 140) },
4243
},
4344
},
4445
PmtInf: [
4546
{
46-
PmtInfId: { value: '0'.slice(0, 35) },
47+
PmtInfId: { value: `BUCKUTT-P1-${Date.now()}`.slice(0, 35) },
4748
PmtMtd: { value: 'TRF' },
4849
BtchBookg: { value: false }, // Set to true to display only one entry in the bank
49-
NbOfTxs: { value: 0 },
50-
CtrlSum: { value: 0 },
50+
NbOfTxs: { value: users.length },
51+
CtrlSum: { value: ctrlSum },
5152
PmtTpInf: {
5253
InstrPrty: { value: 'NORM' },
5354
SvcLvl: {
@@ -59,53 +60,55 @@ export class AdminController {
5960
},
6061
ReqdExctnDt: { DtTm: { value: new Date().toISOString().slice(0, -5) } },
6162
Dbtr: {
62-
Nm: { value: 'Company Name'.slice(0, 140) },
63+
Nm: { value: this.config.XML_EXPORT_COMPANY_NAME.slice(0, 140) },
6364
PstlAdr: {
6465
Ctry: { value: 'FR' },
66+
AdrLine: [
67+
this.config.XML_EXPORT_COMPANY_ADDRESS.slice(0, 70),
68+
this.config.XML_EXPORT_COMPANY_ADDRESS_2.slice(0, 70),
69+
].map((v) => ({ value: v })),
6570
},
6671
},
6772
DbtrAcct: {
6873
Id: {
69-
IBAN: { value: 'FROMIBAN' },
74+
IBAN: { value: this.config.XML_EXPORT_COMPANY_IBAN },
7075
},
7176
},
7277
DbtrAgt: {
7378
FinInstnId: {
74-
BICFI: { value: 'FROMBIC' },
79+
BICFI: { value: this.config.XML_EXPORT_COMPANY_BIC },
7580
},
7681
},
7782
ChrgBr: { value: 'SLEV' },
78-
CdtTrfTxInf: [
79-
{
80-
PmtId: {
81-
EndToEndId: { value: '0'.slice(0, 35) },
82-
},
83-
Amt: {
84-
InstdAmt: {
85-
value: 0,
86-
Ccy: 'EUR',
87-
},
88-
},
89-
CdtrAgt: {
90-
FinInstnId: {
91-
BICFI: { value: 'TOBIC' },
92-
},
83+
CdtTrfTxInf: users.map((user) => ({
84+
PmtId: {
85+
EndToEndId: { value: `buckutt-user-${user.id}`.slice(0, 35) },
86+
},
87+
Amt: {
88+
InstdAmt: {
89+
value: Math.min(100, user.balance / 100),
90+
Ccy: 'EUR',
9391
},
94-
Cdtr: {
95-
Nm: { value: 'User Name'.slice(0, 140) },
92+
},
93+
CdtrAgt: {
94+
FinInstnId: {
95+
BICFI: { value: 'TOBIC' },
9696
},
97-
CdtrAcct: {
98-
Id: {
99-
IBAN: { value: 'TOIBAN' },
100-
},
97+
},
98+
Cdtr: {
99+
Nm: { value: sanitize(`${user.firstName} ${user.lastName.toUpperCase()}`).slice(0, 140) },
100+
},
101+
CdtrAcct: {
102+
Id: {
103+
IBAN: { value: user.iban },
101104
},
102-
RmtInf: {
103-
Ustrd: {
104-
value: 'Transaction description'.slice(0, 140),
105-
},
105+
},
106+
RmtInf: {
107+
Ustrd: {
108+
value: 'Remboursement BuckUTT'.slice(0, 140),
106109
},
107110
},
108-
],
111+
})),
109112
},
110113
],
111114
},
@@ -144,3 +147,7 @@ function isChild(
144147
): props is [key: string, value: XMLObject | Array<XMLObject>] {
145148
return props[0] === 'value' || ((props[1] instanceof Array || typeof props[1] === 'object') && props[1] !== null);
146149
}
150+
151+
function sanitize(str: string) {
152+
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
153+
}

api/src/admin/admin.service.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,21 @@ export class AdminService {
66
constructor(private prisma: PrismaService) {}
77

88
async getUsersReport() {
9-
return this.prisma.user.updateMany({
9+
const users = await this.prisma.user.findMany({
1010
where: {
1111
processed: null,
1212
iban: { not: null },
1313
},
14-
data: {
15-
processed: new Date(),
16-
},
14+
take: 250,
1715
});
16+
// await this.prisma.user.updateMany({
17+
// where: {
18+
// id: { in: users.map((u) => u.id) },
19+
// },
20+
// data: {
21+
// processed: new Date(),
22+
// },
23+
// });
24+
return users;
1825
}
1926
}

api/src/auth/auth.controller.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import { ApiBody, ApiCreatedResponse, ApiHeader, ApiOkResponse, ApiOperation, Ap
77
import AccessTokenResponse from './dto/res/access-token-res.dto';
88
import TokenValidityResDto from './dto/res/token-validity-res.dto';
99
import { ApiAppErrorResponse } from '../app.dto';
10+
import { ConfigModule } from '../config/config.module';
1011

1112
@Controller('auth')
1213
@ApiTags('Authentication')
1314
export class AuthController {
14-
constructor(private authService: AuthService) {}
15+
constructor(private authService: AuthService, private config: ConfigModule) {}
1516

1617
@HttpCode(HttpStatus.OK)
1718
@IsPublic()
@@ -34,6 +35,7 @@ export class AuthController {
3435
firstName: user.firstName,
3536
paymentMethodRegistered: user.iban ? user.ibanFoolproof : null,
3637
processed: !!user.processed,
38+
eligible: user.balance >= this.config.BALANCE_MIN_VALUE,
3739
};
3840
}
3941

@@ -74,6 +76,7 @@ export class AuthController {
7476
firstName: user?.firstName,
7577
paymentMethodRegistered: user?.iban ? user?.ibanFoolproof : null,
7678
processed: !!user?.processed,
79+
eligible: user.balance >= this.config.BALANCE_MIN_VALUE,
7780
};
7881
}
7982
}

api/src/auth/dto/res/access-token-res.dto.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export default class AccessTokenResDto {
44
currentBalance: number;
55
paymentMethodRegistered: string | null;
66
processed: boolean;
7+
eligible: boolean;
78
}

api/src/auth/dto/res/token-validity-res.dto.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export default class TokenValidityResDto {
44
currentBalance: number;
55
paymentMethodRegistered: string | null;
66
processed: boolean;
7+
eligible: boolean;
78
}

api/src/config/config.module.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export class ConfigModule {
2525
public readonly CRYPTO_PUBLIC_KEY: string;
2626
public readonly BALANCE_MIN_VALUE: number;
2727
public readonly LOCKER_SERVICE_KEY: string;
28+
public readonly XML_EXPORT_COMPANY_NAME: string;
29+
public readonly XML_EXPORT_COMPANY_IBAN: string;
30+
public readonly XML_EXPORT_COMPANY_BIC: string;
31+
public readonly XML_EXPORT_COMPANY_ADDRESS: string;
32+
public readonly XML_EXPORT_COMPANY_ADDRESS_2: string;
2833

2934
// DEV ENVIRONMENT ONLY
3035

@@ -40,6 +45,11 @@ export class ConfigModule {
4045
this.CRYPTO_PUBLIC_KEY = config.get('CRYPTO_PUBLIC_KEY');
4146
this.BALANCE_MIN_VALUE = Number(config.get('BALANCE_MIN_VALUE'));
4247
this.LOCKER_SERVICE_KEY = config.get('LOCKER_SERVICE_KEY');
48+
this.XML_EXPORT_COMPANY_NAME = config.get('XML_EXPORT_COMPANY_NAME');
49+
this.XML_EXPORT_COMPANY_IBAN = config.get('XML_EXPORT_COMPANY_IBAN');
50+
this.XML_EXPORT_COMPANY_BIC = config.get('XML_EXPORT_COMPANY_BIC');
51+
this.XML_EXPORT_COMPANY_ADDRESS = config.get('XML_EXPORT_COMPANY_ADDRESS');
52+
this.XML_EXPORT_COMPANY_ADDRESS_2 = config.get('XML_EXPORT_COMPANY_ADDRESS_2');
4353

4454
this._FAKER_SEED = isTestEnv ? Number(config.get('FAKER_SEED')) : undefined;
4555
}

api/src/user/user.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default class UsersController {
2121
const data = await this.usersService.consumeLocker(user, dto.data);
2222
if (!data) throw new AppException(ERROR_CODE.LOCKER_ERROR);
2323
if (user.processed) throw new AppException(ERROR_CODE.ALREADY_PROCESSED);
24-
if (user.balance < 1)
24+
if (user.balance < this.config.BALANCE_MIN_VALUE)
2525
throw new AppException(
2626
ERROR_CODE.USER_BALANCE_TOO_LOW,
2727
(this.config.BALANCE_MIN_VALUE / 100).toLocaleString('fr-FR', { currency: 'EUR', style: 'currency' }),

0 commit comments

Comments
 (0)