Skip to content

Commit 29f14a6

Browse files
authored
Merge pull request #20 from ricash-org/develop
Last merge on main---from develop
2 parents 62b1747 + b71e453 commit 29f14a6

12 files changed

Lines changed: 803 additions & 302 deletions

File tree

README.md

Lines changed: 345 additions & 250 deletions
Large diffs are not rendered by default.

dist/controllers/notificationController.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,37 @@ const ContactSchema = zod_1.z.object({
1010
email: zod_1.z.string().email(),
1111
phone: zod_1.z.string().min(8),
1212
});
13+
const AlertContactSchema = zod_1.z.object({
14+
phone: zod_1.z.string().min(8),
15+
email: zod_1.z.string().email().optional(),
16+
});
1317
const TransferNotificationSchema = zod_1.z.object({
1418
type: zod_1.z.literal("transfer"),
1519
sender: ContactSchema,
1620
receiver: ContactSchema,
1721
amount: zod_1.z.number().positive(),
1822
content: zod_1.z.string().min(1),
1923
});
24+
const AlertNotificationSchema = zod_1.z.object({
25+
type: zod_1.z.enum(["alert_securite", "ALERT_SECURITE"]),
26+
user: AlertContactSchema,
27+
content: zod_1.z.string().min(1),
28+
});
2029
const SimpleNotificationSchema = zod_1.z.object({
2130
type: zod_1.z
2231
.string()
2332
.min(1)
24-
.refine((value) => value !== "transfer", {
25-
message: 'Utiliser le schéma "transfer" lorsque type = "transfer".',
33+
.refine((value) => value !== "transfer" &&
34+
value !== "alert_securite" &&
35+
value !== "ALERT_SECURITE", {
36+
message: 'Utiliser le schéma "transfer" ou "alert_securite" selon le type.',
2637
}),
2738
user: ContactSchema,
2839
content: zod_1.z.string().min(1),
2940
});
3041
const NotificationBodySchema = zod_1.z.union([
3142
TransferNotificationSchema,
43+
AlertNotificationSchema,
3244
SimpleNotificationSchema,
3345
]);
3446
const envoyerNotification = async (req, res) => {

dist/controllers/optController.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ const Notification_1 = require("../entities/Notification");
66
const otpService_1 = require("../services/otpService");
77
const otpService = new otpService_1.OtpService();
88
const GenerateOtpSchema = zod_1.z.object({
9-
utilisateurId: zod_1.z.string().min(1),
10-
canalNotification: zod_1.z.enum(["SMS", "EMAIL"]),
11-
email: zod_1.z.string().email(),
9+
utilisateurId: zod_1.z.string().min(1).optional(),
1210
phone: zod_1.z.string().min(8),
1311
});
1412
const generateOtp = async (req, res) => {
@@ -21,11 +19,9 @@ const generateOtp = async (req, res) => {
2119
errors: parsed.error.flatten(),
2220
});
2321
}
24-
const { utilisateurId, canalNotification, email, phone } = parsed.data;
25-
const canalEnum = canalNotification === "SMS"
26-
? Notification_1.CanalNotification.SMS
27-
: Notification_1.CanalNotification.EMAIL;
28-
const result = await otpService.createOtp(utilisateurId, canalEnum, email, phone);
22+
const { phone } = parsed.data;
23+
const utilisateurId = parsed.data.utilisateurId ?? phone;
24+
const result = await otpService.createOtp(utilisateurId, Notification_1.CanalNotification.SMS, phone);
2925
res.json(result);
3026
}
3127
catch (error) {

dist/entities/Notification.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,68 @@ exports.Notification = exports.StatutNotification = exports.CanalNotification =
1313
const typeorm_1 = require("typeorm");
1414
var TypeNotification;
1515
(function (TypeNotification) {
16+
// Existing types
1617
TypeNotification["CONFIRMATION_TRANSFERT"] = "CONFIRMATION_TRANSFERT";
18+
TypeNotification["CONFIRMATION_DEPOT"] = "CONFIRMATION_DEPOT";
1719
TypeNotification["CONFIRMATION_RETRAIT"] = "CONFIRMATION_RETRAIT";
1820
TypeNotification["RETRAIT_REUSSI"] = "RETRAIT_REUSSI";
1921
TypeNotification["DEPOT_REUSSI"] = "DEPOT_REUSSI";
2022
TypeNotification["ALERT_SECURITE"] = "ALERT_SECURITE";
2123
TypeNotification["VERIFICATION_KYC"] = "VERIFICATION_KYC";
2224
TypeNotification["VERIFICATION_EMAIL"] = "VERIFICATION_EMAIL";
2325
TypeNotification["VERIFICATION_TELEPHONE"] = "VERIFICATION_TELEPHONE";
26+
// 1. ADMIN MANAGEMENT
27+
TypeNotification["ADMIN_CREE"] = "ADMIN_CREE";
28+
TypeNotification["ADMIN_MIS_A_JOUR"] = "ADMIN_MIS_A_JOUR";
29+
TypeNotification["ADMIN_SUPPRIME"] = "ADMIN_SUPPRIME";
30+
// 2. AGENT WORKFLOW
31+
TypeNotification["AGENT_INSCRIPTION"] = "AGENT_INSCRIPTION";
32+
TypeNotification["AGENT_EN_ATTENTE_VALIDATION"] = "AGENT_EN_ATTENTE_VALIDATION";
33+
TypeNotification["AGENT_VALIDE"] = "AGENT_VALIDE";
34+
TypeNotification["AGENT_REJETE"] = "AGENT_REJETE";
35+
// 3. CLIENT
36+
TypeNotification["CLIENT_INSCRIPTION"] = "CLIENT_INSCRIPTION";
37+
TypeNotification["CLIENT_COMPTE_ACTIF"] = "CLIENT_COMPTE_ACTIF";
38+
// 4. AUTHENTICATION AND SECURITY
39+
TypeNotification["CONNEXION_REUSSIE"] = "CONNEXION_REUSSIE";
40+
TypeNotification["ECHEC_CONNEXION"] = "ECHEC_CONNEXION";
41+
TypeNotification["DECONNEXION"] = "DECONNEXION";
42+
TypeNotification["NOUVEL_APPAREIL"] = "NOUVEL_APPAREIL";
43+
TypeNotification["CHANGEMENT_MOT_DE_PASSE"] = "CHANGEMENT_MOT_DE_PASSE";
44+
TypeNotification["CHANGEMENT_EMAIL"] = "CHANGEMENT_EMAIL";
45+
TypeNotification["CHANGEMENT_TELEPHONE"] = "CHANGEMENT_TELEPHONE";
46+
TypeNotification["COMPTE_BLOQUE"] = "COMPTE_BLOQUE";
47+
TypeNotification["COMPTE_DEBLOQUE"] = "COMPTE_DEBLOQUE";
48+
// 5. TRANSACTIONS
49+
TypeNotification["TRANSFERT_ENVOYE"] = "TRANSFERT_ENVOYE";
50+
TypeNotification["TRANSFERT_RECU"] = "TRANSFERT_RECU";
51+
TypeNotification["ECHEC_TRANSFERT"] = "ECHEC_TRANSFERT";
52+
TypeNotification["DEPOT_EN_COURS"] = "DEPOT_EN_COURS";
53+
TypeNotification["ECHEC_DEPOT"] = "ECHEC_DEPOT";
54+
TypeNotification["RETRAIT_EN_COURS"] = "RETRAIT_EN_COURS";
55+
TypeNotification["ECHEC_RETRAIT"] = "ECHEC_RETRAIT";
56+
// 6. OTP AND VERIFICATION
57+
TypeNotification["OTP_ENVOYE"] = "OTP_ENVOYE";
58+
TypeNotification["OTP_VALIDE"] = "OTP_VALIDE";
59+
TypeNotification["OTP_EXPIRE"] = "OTP_EXPIRE";
60+
TypeNotification["OTP_INVALIDE"] = "OTP_INVALIDE";
61+
// 7. KYC
62+
TypeNotification["KYC_EN_COURS"] = "KYC_EN_COURS";
63+
TypeNotification["KYC_VALIDE"] = "KYC_VALIDE";
64+
TypeNotification["KYC_REJETE"] = "KYC_REJETE";
65+
// 8. PAYMENT
66+
TypeNotification["PAIEMENT_REUSSI"] = "PAIEMENT_REUSSI";
67+
TypeNotification["PAIEMENT_ECHOUE"] = "PAIEMENT_ECHOUE";
68+
TypeNotification["FACTURE_GENEREE"] = "FACTURE_GENEREE";
69+
TypeNotification["FACTURE_PAYEE"] = "FACTURE_PAYEE";
70+
// 9. FRAUD AND ALERTS
71+
TypeNotification["TENTATIVE_FRAUDE"] = "TENTATIVE_FRAUDE";
72+
TypeNotification["TRANSACTION_SUSPECTE"] = "TRANSACTION_SUSPECTE";
73+
TypeNotification["ACTIVITE_INHABITUELLE"] = "ACTIVITE_INHABITUELLE";
74+
// 10. SYSTEM
75+
TypeNotification["MAINTENANCE"] = "MAINTENANCE";
76+
TypeNotification["MISE_A_JOUR_SYSTEME"] = "MISE_A_JOUR_SYSTEME";
77+
TypeNotification["ANNONCE"] = "ANNONCE";
2478
})(TypeNotification || (exports.TypeNotification = TypeNotification = {}));
2579
var CanalNotification;
2680
(function (CanalNotification) {

dist/services/notificationService.js

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ class NotificationService {
3838
// }
3939
// }
4040
mapStringToTypeNotification(type) {
41+
const normalized = type.trim().toUpperCase();
42+
if (Object.values(Notification_1.TypeNotification).includes(normalized)) {
43+
return normalized;
44+
}
4145
switch (type) {
4246
case "transfer":
4347
return Notification_1.TypeNotification.CONFIRMATION_TRANSFERT;
@@ -114,6 +118,58 @@ class NotificationService {
114118
email: notifEmail,
115119
};
116120
}
121+
async sendSmsPriorityToContact(contact, content, type, role, extraContext) {
122+
const context = { ...(extraContext || {}), role };
123+
const notifSms = this.notifRepo.create({
124+
utilisateurId: contact.phone,
125+
typeNotification: type,
126+
canal: Notification_1.CanalNotification.SMS,
127+
context,
128+
message: content,
129+
destinationPhone: contact.phone,
130+
statut: Notification_1.StatutNotification.EN_COURS,
131+
});
132+
await this.notifRepo.save(notifSms);
133+
try {
134+
await client.messages.create({
135+
body: content,
136+
from: process.env.TWILIO_PHONE_NUMBER,
137+
to: contact.phone,
138+
});
139+
notifSms.statut = Notification_1.StatutNotification.ENVOYEE;
140+
}
141+
catch (error) {
142+
notifSms.statut = Notification_1.StatutNotification.ECHEC;
143+
console.error("Erreur d'envoi SMS :", error);
144+
}
145+
await this.notifRepo.save(notifSms);
146+
let notifEmail;
147+
if (contact.email) {
148+
notifEmail = this.notifRepo.create({
149+
utilisateurId: contact.email,
150+
typeNotification: type,
151+
canal: Notification_1.CanalNotification.EMAIL,
152+
context,
153+
message: content,
154+
destinationEmail: contact.email,
155+
statut: Notification_1.StatutNotification.EN_COURS,
156+
});
157+
await this.notifRepo.save(notifEmail);
158+
try {
159+
await (0, mailService_1.sendEmail)(contact.email, "Notification", content);
160+
notifEmail.statut = Notification_1.StatutNotification.ENVOYEE;
161+
}
162+
catch (error) {
163+
notifEmail.statut = Notification_1.StatutNotification.ECHEC;
164+
console.error("Erreur d'envoi email :", error);
165+
}
166+
await this.notifRepo.save(notifEmail);
167+
}
168+
return {
169+
sms: notifSms,
170+
...(notifEmail ? { email: notifEmail } : {}),
171+
};
172+
}
117173
/**
118174
* Endpoint HTTP (Postman) :
119175
* - dépend UNIQUEMENT des coordonnées fournies dans le JSON
@@ -131,6 +187,15 @@ class NotificationService {
131187
receiver: receiverResult,
132188
};
133189
}
190+
if (payload.type === "alert_securite" ||
191+
payload.type === "ALERT_SECURITE") {
192+
const alertPayload = payload;
193+
const type = this.mapStringToTypeNotification(alertPayload.type);
194+
const userResult = await this.sendSmsPriorityToContact(alertPayload.user, alertPayload.content, type, "USER");
195+
return {
196+
user: userResult,
197+
};
198+
}
134199
const simplePayload = payload;
135200
const type = this.mapStringToTypeNotification(simplePayload.type);
136201
const userResult = await this.sendMultiChannelToContact(simplePayload.user, simplePayload.content, type, "USER");
@@ -158,6 +223,63 @@ class NotificationService {
158223
if (!destinationEmail && !destinationPhone) {
159224
throw new Error(`Aucun contact (email ou téléphone) disponible pour l'utilisateur ${data.utilisateurId}`);
160225
}
226+
// Priorité SMS pour les alertes sécurité: SMS d'abord si numéro disponible,
227+
// puis email en second canal si disponible.
228+
if (data.typeNotification === Notification_1.TypeNotification.ALERT_SECURITE) {
229+
const context = data.context;
230+
let smsNotif;
231+
let emailNotif;
232+
if (destinationPhone) {
233+
smsNotif = this.notifRepo.create({
234+
utilisateurId: data.utilisateurId,
235+
typeNotification: data.typeNotification,
236+
canal: Notification_1.CanalNotification.SMS,
237+
context,
238+
message,
239+
destinationPhone,
240+
statut: Notification_1.StatutNotification.EN_COURS,
241+
});
242+
await this.notifRepo.save(smsNotif);
243+
try {
244+
await client.messages.create({
245+
body: message,
246+
from: process.env.TWILIO_PHONE_NUMBER,
247+
to: destinationPhone,
248+
});
249+
smsNotif.statut = Notification_1.StatutNotification.ENVOYEE;
250+
}
251+
catch (error) {
252+
smsNotif.statut = Notification_1.StatutNotification.ECHEC;
253+
console.error("Erreur d'envoi SMS :", error);
254+
}
255+
await this.notifRepo.save(smsNotif);
256+
}
257+
if (destinationEmail) {
258+
emailNotif = this.notifRepo.create({
259+
utilisateurId: data.utilisateurId,
260+
typeNotification: data.typeNotification,
261+
canal: Notification_1.CanalNotification.EMAIL,
262+
context,
263+
message,
264+
destinationEmail,
265+
statut: Notification_1.StatutNotification.EN_COURS,
266+
});
267+
await this.notifRepo.save(emailNotif);
268+
try {
269+
await (0, mailService_1.sendEmail)(destinationEmail, "RICASH NOTIFICATION", message);
270+
emailNotif.statut = Notification_1.StatutNotification.ENVOYEE;
271+
}
272+
catch (error) {
273+
emailNotif.statut = Notification_1.StatutNotification.ECHEC;
274+
console.error("Erreur d'envoi email :", error);
275+
}
276+
await this.notifRepo.save(emailNotif);
277+
}
278+
return {
279+
...(smsNotif ? { sms: smsNotif } : {}),
280+
...(emailNotif ? { email: emailNotif } : {}),
281+
};
282+
}
161283
// 4. Validation spécifique au canal demandé
162284
if (data.canal === Notification_1.CanalNotification.EMAIL && !destinationEmail) {
163285
throw new Error(`Canal EMAIL demandé mais aucune adresse email valide pour l'utilisateur ${data.utilisateurId}`);

dist/services/otpService.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,25 @@ class OtpService {
1313
generateCode() {
1414
return Math.floor(1000 + Math.random() * 9000).toString(); // 4chiffres
1515
}
16-
async createOtp(utilisateurId, canalNotification, email, phone) {
16+
async createOtp(utilisateurId, canalNotification, phone) {
1717
const code = this.generateCode();
1818
const expiration = new Date(Date.now() + this.expirationDelay);
19-
const destinationEmail = email;
2019
const destinationPhone = phone;
2120
const otp = this.otpRepo.create({
2221
utilisateurId, // identifiant métier
2322
canal: canalNotification,
2423
code,
2524
expiration,
26-
destinationEmail,
2725
destinationPhone,
2826
});
2927
await this.otpRepo.save(otp);
30-
// Détermination automatique du type de notification
31-
const notifType = canalNotification === "EMAIL"
32-
? Notification_1.TypeNotification.VERIFICATION_EMAIL
33-
: Notification_1.TypeNotification.VERIFICATION_TELEPHONE;
28+
const notifType = Notification_1.TypeNotification.VERIFICATION_TELEPHONE;
3429
// message standard inter-services (aligné sur InterServices / NotificationEvent)
3530
const message = {
3631
utilisateurId,
3732
typeNotification: notifType,
3833
canal: canalNotification,
3934
context: { code },
40-
email: destinationEmail,
4135
phone: destinationPhone,
4236
metadata: {
4337
service: "notification-service:otp",

src/controllers/notificationController.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ const ContactSchema = z.object({
1010
phone: z.string().min(8),
1111
});
1212

13+
const AlertContactSchema = z.object({
14+
phone: z.string().min(8),
15+
email: z.string().email().optional(),
16+
});
17+
1318
const TransferNotificationSchema = z.object({
1419
type: z.literal("transfer"),
1520
sender: ContactSchema,
@@ -18,19 +23,33 @@ const TransferNotificationSchema = z.object({
1823
content: z.string().min(1),
1924
});
2025

26+
const AlertNotificationSchema = z.object({
27+
type: z.enum(["alert_securite", "ALERT_SECURITE"]),
28+
user: AlertContactSchema,
29+
content: z.string().min(1),
30+
});
31+
2132
const SimpleNotificationSchema = z.object({
2233
type: z
2334
.string()
2435
.min(1)
25-
.refine((value) => value !== "transfer", {
26-
message: 'Utiliser le schéma "transfer" lorsque type = "transfer".',
27-
}),
36+
.refine(
37+
(value) =>
38+
value !== "transfer" &&
39+
value !== "alert_securite" &&
40+
value !== "ALERT_SECURITE",
41+
{
42+
message:
43+
'Utiliser le schéma "transfer" ou "alert_securite" selon le type.',
44+
},
45+
),
2846
user: ContactSchema,
2947
content: z.string().min(1),
3048
});
3149

3250
const NotificationBodySchema = z.union([
3351
TransferNotificationSchema,
52+
AlertNotificationSchema,
3453
SimpleNotificationSchema,
3554
]);
3655

src/controllers/optController.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import { OtpService } from "../services/otpService";
66
const otpService = new OtpService();
77

88
const GenerateOtpSchema = z.object({
9-
utilisateurId: z.string().min(1),
10-
canalNotification: z.enum(["SMS", "EMAIL"]),
11-
email: z.string().email(),
9+
utilisateurId: z.string().min(1).optional(),
1210
phone: z.string().min(8),
1311
});
1412

@@ -24,17 +22,12 @@ export const generateOtp = async (req: Request, res: Response) => {
2422
});
2523
}
2624

27-
const { utilisateurId, canalNotification, email, phone } = parsed.data;
28-
29-
const canalEnum =
30-
canalNotification === "SMS"
31-
? CanalNotification.SMS
32-
: CanalNotification.EMAIL;
25+
const { phone } = parsed.data;
26+
const utilisateurId = parsed.data.utilisateurId ?? phone;
3327

3428
const result = await otpService.createOtp(
3529
utilisateurId,
36-
canalEnum,
37-
email,
30+
CanalNotification.SMS,
3831
phone,
3932
);
4033
res.json(result);

0 commit comments

Comments
 (0)