- sistemazioni Email : registrazione, invio invito, email di benvenuto
- fix circuito - profilo
This commit is contained in:
@@ -66,7 +66,7 @@ db.myelems.insertMany([
|
|||||||
"listcards": [],
|
"listcards": [],
|
||||||
"list": [],
|
"list": [],
|
||||||
"__v": 0,
|
"__v": 0,
|
||||||
"containerHtml": "<style>\nbody {\n font-family: Arial, sans-serif;\n margin: 0;\n padding: 20px;\n background-color: #f0f0f0;\n color: #333;\n}\n\nh1 {\n color: #0056b3;\n text-align: center;\n}\n\n\np, li {\n line-height: 1.6;\n}\n\n\nul {\n list-style-type: none;\n padding: 0;\n}\n\n\nli {\n background-color: #fff !important;\n margin-bottom: 10px !important;\n padding: 10px !important;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; \n}\n\n\na {\n color: #007bff;\n text-decoration: none;\n}\n\n\na:hover {\n text-decoration: underline;\n}\n\n\n.container {\n max-width: 800px;\n margin: 0 auto;\n background-color: #fff;\n padding: 20px;\n box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);\n}\n\n\n.strong {\n font-weight: bold;\n}\n</style>\n\n\n<p><strong>“Abitare gli Iblei”</strong> è una rete aperta che ha lo scopo di riunire tutte quelle persone che vogliono valorizzare e qualificare la vita nel territorio degli Iblei. </p>\n \n <p>Chi aderisce alla rete si riconosce in una <strong>Carta dei valori comuni</strong> e usa la rete per scambiare conoscenze, esperienze, risorse e prodotti sviluppati nell’ambito delle proprie iniziative (profit e non profit) individuali o collettive.</p>\n\n\n <p>L’area territoriale di questa rete è quella dei <strong>Monti Iblei orientali e occidentali</strong> (Noto, Avola, Canicattini, Siracusa, Palazzolo, Buccheri, Ferla, Modica, …).</p>\n\n\n <p>La rete <strong>“Abitare gli Iblei”</strong> offre i seguenti servizi utili per il territorio ed i suoi abitanti, frutto di una costruzione collettiva:</p>\n \n <ul>\n <li><strong>1. Mappa delle attività virtuose:</strong> permette di identificare attività pubbliche e private nel territorio che possono essere utili nella vita quotidiana. Queste attività possono riguardare artigiani, produttori o fornitori di servizi di cui almeno un membro della rete conosca la qualità e l’affidabilità (agricoltori, falegnami, fabbri, idraulici, imprese edili, strutture ricettive, …). Altre informazioni utili possono riguardare associazioni/istituzioni operanti in vari settori. – <strong>Accesso pubblico</strong></li>\n <li><strong>2. Calendario:</strong> permette di accedere ad annunci di eventi utili alla crescita culturale del territorio. La pubblicazione di eventi è riservata ai soli membri della rete che possono presentare iniziative anche di altri organizzatori. – <strong>Accesso pubblico</strong></li>\n <li><strong>3. Scambi di servizi, prodotti e ospitalità:</strong> questa funzione è riservata ai soli membri della rete e si realizza attraverso la Rete italiana di scambi orizzontali (RISO). – <strong>Accesso riservato</strong></li>\n <li><strong>4. Segnalazione di pericoli per il territorio:</strong> attraverso questa mappa è possibile segnalare incendi, immondizia abbandonata, discariche abusive, fonti di inquinamento per corsi d’acqua e spiagge, presenza di inquinamento nell’aria, … – <strong>Accesso riservato</strong></li>\n </ul>\n \n <p>Se vuoi aderire alla rete puoi richiederne la registrazione utilizzando questo Link <a href=\"#\"><strong>(Pagina in Costruzione)</strong></a>.</p>\n\n",
|
"containerHtml": "<style>\nbody {\n font-family: Arial, sans-serif;\n margin: 0;\n padding: 20px;\n background-color: #f0f0f0;\n color: #333;\n}\n\nh1 {\n color: #0056b3;\n text-align: center;\n}\n\n\np, li {\n line-height: 1.6;\n}\n\n\nul {\n list-style-type: none;\n padding: 0;\n}\n\n\nli {\n background-color: #fff !important;\n margin-bottom: 10px !important;\n padding: 10px !important;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; \n}\n\n\na {\n color: #007bff;\n text-decoration: none;\n}\n\n\na:hover {\n text-decoration: underline;\n}\n\n\n.container {\n max-width: 800px;\n margin: 0 auto;\n background-color: #fff;\n padding: 20px;\n box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);\n}\n\n\n.strong {\n font-weight: bold;\n}\n</style>\n\n\n<p><strong>“Abitare gli Iblei”</strong> è una rete aperta che ha lo scopo di riunire tutte quelle persone che vogliono valorizzare e qualificare la vita nel territorio degli Iblei. </p>\n \n <p>Chi aderisce alla rete si riconosce in una <strong>Carta dei valori comuni</strong> e usa la rete per scambiare conoscenze, esperienze, risorse e prodotti sviluppati nell’ambito delle proprie iniziative (profit e non profit) individuali o collettive.</p>\n\n\n <p>L’area territoriale di questa rete è quella dei <strong>Monti Iblei orientali e occidentali</strong> (Noto, Avola, Canicattini, Siracusa, Palazzolo, Buccheri, Ferla, Modica, …).</p>\n\n\n <p>La rete <strong>“Abitare gli Iblei”</strong> offre i seguenti servizi utili per il territorio ed i suoi abitanti, frutto di una costruzione collettiva:</p>\n \n <ul>\n <li><strong>1. Mappa delle attività virtuose:</strong> permette di identificare attività pubbliche e private nel territorio che possono essere utili nella vita quotidiana. Queste attività possono riguardare artigiani, produttori o fornitori di servizi di cui almeno un membro della rete conosca la qualità e l’affidabilità (agricoltori, falegnami, fabbri, idraulici, imprese edili, strutture ricettive, …). Altre informazioni utili possono riguardare associazioni/istituzioni operanti in vari settori. – <strong>Accesso pubblico</strong></li>\n <li><strong>2. Calendario:</strong> permette di accedere ad annunci di eventi utili alla crescita culturale del territorio. La pubblicazione di eventi è riservata ai soli membri della rete che possono presentare iniziative anche di altri organizzatori. – <strong>Accesso pubblico</strong></li>\n <li><strong>3. Scambi di servizi, prodotti e ospitalità:</strong> questa funzione è riservata ai soli membri della rete e si realizza attraverso la Rete Italiana scambi orizzontali (RISO). – <strong>Accesso riservato</strong></li>\n <li><strong>4. Segnalazione di pericoli per il territorio:</strong> attraverso questa mappa è possibile segnalare incendi, immondizia abbandonata, discariche abusive, fonti di inquinamento per corsi d’acqua e spiagge, presenza di inquinamento nell’aria, … – <strong>Accesso riservato</strong></li>\n </ul>\n \n <p>Se vuoi aderire alla rete puoi richiederne la registrazione utilizzando questo Link <a href=\"#\"><strong>(Pagina in Costruzione)</strong></a>.</p>\n\n",
|
||||||
"anim": {
|
"anim": {
|
||||||
"_id": new ObjectId("66db393e3b885ccdfaed28d6"),
|
"_id": new ObjectId("66db393e3b885ccdfaed28d6"),
|
||||||
"name": "",
|
"name": "",
|
||||||
|
|||||||
394
emails/RISO/reg_notifica_all_invitante/it/html.pug
Executable file
394
emails/RISO/reg_notifica_all_invitante/it/html.pug
Executable file
@@ -0,0 +1,394 @@
|
|||||||
|
doctype html
|
||||||
|
html(lang="it")
|
||||||
|
head
|
||||||
|
meta(charset="UTF-8")
|
||||||
|
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||||
|
style(type="text/css").
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 20px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-logo {
|
||||||
|
width: 120px;
|
||||||
|
height: auto;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header {
|
||||||
|
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 40px 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header h1 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header .subtitle {
|
||||||
|
margin: 8px 0 0 0;
|
||||||
|
font-size: 17px;
|
||||||
|
opacity: 0.95;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
font-size: 56px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-body {
|
||||||
|
padding: 32px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-box {
|
||||||
|
background: #fff8dc;
|
||||||
|
border-left: 4px solid #7cb342;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-box p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 17px;
|
||||||
|
color: #1a1a1a;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card {
|
||||||
|
background: linear-gradient(135deg, #f8fdf8 0%, #e8f5e9 100%);
|
||||||
|
border: 2px solid #7cb342;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card h3 {
|
||||||
|
font-size: 14px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #558b2f;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card .member-name {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #1a1a1a;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card .member-detail {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #555;
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card .member-detail strong {
|
||||||
|
color: #558b2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
background: #e8f5e9;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box p {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
color: #2e7d32;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-section {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-section h3 {
|
||||||
|
font-size: 17px;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-right: 10px;
|
||||||
|
min-width: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttprof-section {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-section {
|
||||||
|
text-align: center;
|
||||||
|
margin: 24px 0;
|
||||||
|
padding: 20px 0;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 16px 48px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||||
|
border-radius: 50px;
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 4px 12px rgba(124, 179, 66, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thank-you-box {
|
||||||
|
background: linear-gradient(135deg, #fff8dc 0%, #fef9f3 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thank-you-box p {
|
||||||
|
font-size: 17px;
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.7;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thank-you-box strong {
|
||||||
|
color: #558b2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-footer {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
background: #f8f9fa;
|
||||||
|
color: #777;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-footer p {
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-button {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||||
|
border-radius: 25px;
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 3px 10px rgba(124, 179, 66, 0.25);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
body {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header h1 {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-body {
|
||||||
|
padding: 20px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button {
|
||||||
|
padding: 14px 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card .member-name {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-item {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
|
.email-container
|
||||||
|
//- Header
|
||||||
|
.email-header
|
||||||
|
img.header-logo(src=baseurl+'/images/logo.png' alt=nomeapp+' - Rete Italiana Scambi Orizzontali')
|
||||||
|
h1 🎉 Il tuo invito è stato accettato!
|
||||||
|
p.subtitle Un nuovo membro si è unito a #{nomeapp}
|
||||||
|
|
||||||
|
//- Body
|
||||||
|
.email-body
|
||||||
|
//- Intro
|
||||||
|
.intro-text
|
||||||
|
| Ciao <strong>#{nomeInvitante}</strong>,<br>
|
||||||
|
| la persona che hai invitato, o che ha usato il tuo username come invitante, si è appena registrata su #{nomeapp}!
|
||||||
|
|
||||||
|
//- Card nuovo membro
|
||||||
|
.member-card
|
||||||
|
h3 👤 Nuovo Membro Registrato
|
||||||
|
.member-name #{nomeInvitato}
|
||||||
|
if emailInvitato
|
||||||
|
.member-detail
|
||||||
|
strong Email:
|
||||||
|
| #{emailInvitato}
|
||||||
|
if usernameInvitato
|
||||||
|
.member-detail
|
||||||
|
strong Username:
|
||||||
|
| #{usernameInvitato}
|
||||||
|
|
||||||
|
//- Bottone profilo
|
||||||
|
if usernameInvitato
|
||||||
|
.buttprof-section
|
||||||
|
a.profile-button(href=strlinksito + '/my/' + usernameInvitato target="_blank")
|
||||||
|
| 👤 Visualizza Profilo di #{usernameInvitato}
|
||||||
|
|
||||||
|
//- Ringraziamento
|
||||||
|
.thank-you-box
|
||||||
|
p
|
||||||
|
| 🙏 <strong>Grazie per aver contribuito alla crescita di #{nomeapp}!</strong>
|
||||||
|
p
|
||||||
|
| Ogni nuovo ingresso rende la nostra comunità più forte e ricca di opportunità.
|
||||||
|
| Il tuo invito aiuta #{nomeInvitato} a scoprire un nuovo modo di fare economia,
|
||||||
|
| basato su fiducia, comunità e scambi solidali.
|
||||||
|
|
||||||
|
//- Suggerimenti
|
||||||
|
.tips-section
|
||||||
|
h3 💡 Come puoi aiutare #{nomeInvitato}
|
||||||
|
.tip-item
|
||||||
|
span.tip-icon 🤝
|
||||||
|
span.tip-text
|
||||||
|
strong Connettiti con loro:
|
||||||
|
| Aiutali a sentirsi parte della comunità e presentali ad altri membri del circuito
|
||||||
|
.tip-item
|
||||||
|
span.tip-icon 📱
|
||||||
|
span.tip-text
|
||||||
|
strong Mostra come funziona #{nomeapp}:
|
||||||
|
| Spiega come creare annunci, usare i RIS e partecipare agli scambi nella tua zona
|
||||||
|
.tip-item
|
||||||
|
span.tip-icon 📲
|
||||||
|
span.tip-text
|
||||||
|
strong Gruppo Telegram:
|
||||||
|
| Incoraggiali a unirsi al gruppo Telegram del vostro circuito provinciale
|
||||||
|
.tip-item
|
||||||
|
span.tip-icon 🎯
|
||||||
|
span.tip-text
|
||||||
|
strong Profilo completo:
|
||||||
|
| Ricorda loro di completare il profilo per poter iniziare a scambiare
|
||||||
|
|
||||||
|
//- Info box
|
||||||
|
.info-box
|
||||||
|
p
|
||||||
|
| ✓ #{nomeInvitato} ha ricevuto un'email di benvenuto con tutte le istruzioni
|
||||||
|
p
|
||||||
|
| ✓ Dovrà completare il profilo prima di poter usare i RIS
|
||||||
|
p
|
||||||
|
| ✓ Il facilitatore locale valuterà l'abilitazione all'uso dei RIS
|
||||||
|
|
||||||
|
//- CTA
|
||||||
|
.cta-section
|
||||||
|
.cta-title Visita la Piattaforma
|
||||||
|
a.cta-button(href=strlinksito target="_blank") Vai su #{nomeapp}
|
||||||
|
|
||||||
|
//- Highlight box
|
||||||
|
.highlight-box
|
||||||
|
p
|
||||||
|
| 🌱 <strong>Costruiamo insieme un'economia più solidale!</strong><br>
|
||||||
|
| Continua a condividere #{nomeapp} con persone di fiducia della tua comunità.
|
||||||
|
| Più siamo, più scambi possibili ci sono per tutti!
|
||||||
|
|
||||||
|
//- Footer
|
||||||
|
.email-footer
|
||||||
|
.divider
|
||||||
|
p Hai ricevuto questa email perché hai invitato #{nomeInvitato} su #{nomeapp} oppure la persona ha usato il tuo username come invitante
|
||||||
|
p(style="margin-top: 12px; font-size: 12px;")
|
||||||
|
| #{new Date().getFullYear()} #{nomeapp} - Rete Italiana Scambi Orizzontali
|
||||||
|
p(style="margin-top: 12px; font-size: 12px;")
|
||||||
|
| 🍚 Comunità · Fiducia · Scambi Solidali · Sostenibilità
|
||||||
1
emails/RISO/reg_notifica_all_invitante/it/subject.pug
Executable file
1
emails/RISO/reg_notifica_all_invitante/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
|||||||
|
=`🎉 Il tuo invito è stato accettato su RISO da ${name ? ', ' + name : username} !`
|
||||||
343
emails/defaultSite/reg_notifica_all_invitante/it/html.pug
Executable file
343
emails/defaultSite/reg_notifica_all_invitante/it/html.pug
Executable file
@@ -0,0 +1,343 @@
|
|||||||
|
doctype html
|
||||||
|
html(lang="it")
|
||||||
|
head
|
||||||
|
meta(charset="UTF-8")
|
||||||
|
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||||
|
style(type="text/css").
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 20px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-logo {
|
||||||
|
width: 120px;
|
||||||
|
height: auto;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header {
|
||||||
|
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 40px 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header h1 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header .subtitle {
|
||||||
|
margin: 8px 0 0 0;
|
||||||
|
font-size: 17px;
|
||||||
|
opacity: 0.95;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
font-size: 56px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-body {
|
||||||
|
padding: 32px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-box {
|
||||||
|
background: #fff8dc;
|
||||||
|
border-left: 4px solid #7cb342;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-box p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 17px;
|
||||||
|
color: #1a1a1a;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card {
|
||||||
|
background: linear-gradient(135deg, #f8fdf8 0%, #e8f5e9 100%);
|
||||||
|
border: 2px solid #7cb342;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card h3 {
|
||||||
|
font-size: 14px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #558b2f;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card .member-name {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #1a1a1a;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card .member-detail {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #555;
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card .member-detail strong {
|
||||||
|
color: #558b2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
background: #e8f5e9;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box p {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
color: #2e7d32;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-section {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-section h3 {
|
||||||
|
font-size: 17px;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-right: 10px;
|
||||||
|
min-width: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-section {
|
||||||
|
text-align: center;
|
||||||
|
margin: 24px 0;
|
||||||
|
padding: 20px 0;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 16px 48px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||||
|
border-radius: 50px;
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 4px 12px rgba(124, 179, 66, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thank-you-box {
|
||||||
|
background: linear-gradient(135deg, #fff8dc 0%, #fef9f3 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thank-you-box p {
|
||||||
|
font-size: 17px;
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.7;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thank-you-box strong {
|
||||||
|
color: #558b2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-footer {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
background: #f8f9fa;
|
||||||
|
color: #777;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-footer p {
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
body {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header h1 {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-body {
|
||||||
|
padding: 20px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button {
|
||||||
|
padding: 14px 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card .member-name {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-item {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
|
.email-container
|
||||||
|
//- Header
|
||||||
|
.email-header
|
||||||
|
img.header-logo(src=baseurl+'/images/logo.png' alt=nomeapp+' - Rete Italiana Scambi Orizzontali')
|
||||||
|
.success-icon 🎉
|
||||||
|
h1 Il tuo invito è stato accettato!
|
||||||
|
p.subtitle Un nuovo membro si è unito a #{nomeapp}
|
||||||
|
|
||||||
|
//- Body
|
||||||
|
.email-body
|
||||||
|
//- Intro
|
||||||
|
.intro-text
|
||||||
|
| Ciao <strong>#{nomeInvitante}</strong>,<br>
|
||||||
|
| la persona che hai invitato, o che ha usato il tuo username come invitante, si è appena registrata su #{nomeapp}!
|
||||||
|
|
||||||
|
//- Card nuovo membro
|
||||||
|
.member-card
|
||||||
|
h3 👤 Nuovo Membro Registrato
|
||||||
|
.member-name #{nomeInvitato}
|
||||||
|
if emailInvitato
|
||||||
|
.member-detail
|
||||||
|
strong Email:
|
||||||
|
| #{emailInvitato}
|
||||||
|
if usernameInvitato
|
||||||
|
.member-detail
|
||||||
|
strong Username:
|
||||||
|
| #{usernameInvitato}
|
||||||
|
|
||||||
|
//- Ringraziamento
|
||||||
|
.thank-you-box
|
||||||
|
p
|
||||||
|
| 🙏 <strong>Grazie per aver contribuito alla crescita di #{nomeapp}!</strong>
|
||||||
|
p
|
||||||
|
| Ogni nuovo membro rende la nostra comunità più forte e ricca di opportunità.
|
||||||
|
| Il tuo invito ha aiutato #{nomeInvitato} a scoprire un nuovo modo di fare economia,
|
||||||
|
| basato su fiducia, comunità e scambi solidali.
|
||||||
|
|
||||||
|
//- Info box
|
||||||
|
.info-box
|
||||||
|
p
|
||||||
|
| ✓ #{nomeInvitato} ha ricevuto un'email di benvenuto con tutte le istruzioni
|
||||||
|
|
||||||
|
//- CTA
|
||||||
|
.cta-section
|
||||||
|
.cta-title Visita la Piattaforma
|
||||||
|
a.cta-button(href=strlinksito target="_blank") Vai su #{nomeapp}
|
||||||
|
|
||||||
|
//- Highlight box
|
||||||
|
.highlight-box
|
||||||
|
p
|
||||||
|
| 🌱 <strong>Costruiamo insieme un'economia più solidale!</strong><br>
|
||||||
|
| Continua a condividere #{nomeapp} con persone di fiducia della tua comunità.
|
||||||
|
| Più siamo, più scambi possibili ci sono per tutti!
|
||||||
|
|
||||||
|
//- Footer
|
||||||
|
.email-footer
|
||||||
|
.divider
|
||||||
|
p Hai ricevuto questa email perché hai invitato #{nomeInvitato} su #{nomeapp} oppure la persona ha usato il tuo username come invitante
|
||||||
|
p(style="margin-top: 12px; font-size: 12px;")
|
||||||
|
| #{new Date().getFullYear()} #{nomeapp}
|
||||||
|
p(style="margin-top: 12px; font-size: 12px;")
|
||||||
|
| 🍚 Comunità · Fiducia · Scambi Solidali · Sostenibilità
|
||||||
1
emails/defaultSite/reg_notifica_all_invitante/it/subject.pug
Executable file
1
emails/defaultSite/reg_notifica_all_invitante/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
|||||||
|
=`🎉 Il tuo invito è stato accettato su ${nomeapp} da ${name ? ', ' + name : username} !`
|
||||||
@@ -346,7 +346,7 @@ html(lang="it")
|
|||||||
span.benefit-text Connetterti con la tua comunità territoriale locale (circuito provinciale)
|
span.benefit-text Connetterti con la tua comunità territoriale locale (circuito provinciale)
|
||||||
.benefit-item
|
.benefit-item
|
||||||
span.benefit-icon 💰
|
span.benefit-icon 💰
|
||||||
span.benefit-text Usare i RIS, una moneta complementare che parte da zero (nessun debito iniziale!)
|
span.benefit-text Usare i RIS, uno strumento di scambio che parte da zero (nessun debito iniziale!)
|
||||||
.benefit-item
|
.benefit-item
|
||||||
span.benefit-icon 🌱
|
span.benefit-icon 🌱
|
||||||
span.benefit-text Ridurre la dipendenza dall'economia tradizionale e vivere in modo più sostenibile
|
span.benefit-text Ridurre la dipendenza dall'economia tradizionale e vivere in modo più sostenibile
|
||||||
|
|||||||
536
emails/reg_email_benvenuto_ammesso/it/html.pug
Executable file
536
emails/reg_email_benvenuto_ammesso/it/html.pug
Executable file
@@ -0,0 +1,536 @@
|
|||||||
|
doctype html
|
||||||
|
html(lang="it")
|
||||||
|
head
|
||||||
|
meta(charset="UTF-8")
|
||||||
|
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||||
|
style(type="text/css").
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 20px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-logo {
|
||||||
|
width: 80px;
|
||||||
|
height: auto;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header {
|
||||||
|
background: linear-gradient(135deg, #2E7D32 0%, #1B5E20 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 40px 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header h1 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header p {
|
||||||
|
margin: 8px 0 0 0;
|
||||||
|
font-size: 16px;
|
||||||
|
opacity: 0.95;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-body {
|
||||||
|
padding: 24px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.7;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-text strong {
|
||||||
|
color: #2E7D32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-message {
|
||||||
|
background: linear-gradient(135deg, #E8F5E9 0%, #C8E6C9 100%);
|
||||||
|
border-left: 4px solid #2E7D32;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-message h2 {
|
||||||
|
color: #1B5E20;
|
||||||
|
font-size: 20px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-message p {
|
||||||
|
color: #2E7D32;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credentials-box {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 4px solid #2E7D32;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credentials-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #555;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credential-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credential-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credential-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #666;
|
||||||
|
min-width: 120px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credential-value {
|
||||||
|
color: #1a1a1a;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
flex: 1;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credential-value a {
|
||||||
|
color: #2E7D32;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credential-value a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-section {
|
||||||
|
margin: 24px 0;
|
||||||
|
padding: 20px 0;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-title {
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-buttons-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 16px 28px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
background: linear-gradient(135deg, #2E7D32 0%, #1B5E20 100%);
|
||||||
|
border-radius: 50px;
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 4px 12px rgba(46, 125, 50, 0.3);
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 160px;
|
||||||
|
max-width: 200px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 16px rgba(46, 125, 50, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button-secondary {
|
||||||
|
background: linear-gradient(135deg, #388E3C 0%, #2E7D32 100%);
|
||||||
|
box-shadow: 0 4px 12px rgba(56, 142, 60, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button-secondary:hover {
|
||||||
|
box-shadow: 0 6px 16px rgba(56, 142, 60, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button-tertiary {
|
||||||
|
background: linear-gradient(135deg, #66BB6A 0%, #4CAF50 100%);
|
||||||
|
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button-tertiary:hover {
|
||||||
|
box-shadow: 0 6px 16px rgba(76, 175, 80, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-right: 6px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.next-steps {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.next-steps-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2E7D32;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
background: #f8fdf9;
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 3px solid #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-number {
|
||||||
|
background: linear-gradient(135deg, #4CAF50 0%, #388E3C 100%);
|
||||||
|
color: white;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-right: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #1B5E20;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
background: linear-gradient(135deg, #E8F5E9 0%, #C8E6C9 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid #A5D6A7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box p {
|
||||||
|
margin: 0;
|
||||||
|
color: #1B5E20;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box strong {
|
||||||
|
color: #2E7D32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-note {
|
||||||
|
background: #fff3e0;
|
||||||
|
border-left: 4px solid #FF9800;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-note p {
|
||||||
|
color: #E65100;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-footer {
|
||||||
|
padding: 20px 16px;
|
||||||
|
text-align: center;
|
||||||
|
background: #f8f9fa;
|
||||||
|
color: #777;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-footer p {
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
body {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header p {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-body {
|
||||||
|
padding: 20px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-message {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credentials-box {
|
||||||
|
padding: 16px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credential-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credential-label {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credential-value {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-buttons-wrapper {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button {
|
||||||
|
padding: 14px 24px;
|
||||||
|
font-size: 15px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-item {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-number {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content h3 {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content p {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
|
.email-container
|
||||||
|
.email-header
|
||||||
|
- var baseimg = baseurl + '/';
|
||||||
|
img.header-logo(src=baseimg+"images/logo.png" alt=nomeapp || 'Logo')
|
||||||
|
.welcome-icon 💚
|
||||||
|
h1 Benvenuti nella comunità RISO
|
||||||
|
p
|
||||||
|
strong #{name || username || 'Tu'}
|
||||||
|
| #{name || username ? ',' : ''} sei ufficialmente parte di qualcosa di speciale!
|
||||||
|
|
||||||
|
.email-body
|
||||||
|
.welcome-message
|
||||||
|
h2 🎉 Il tuo invitante #{nomeInvitante} ti ha ammesso!
|
||||||
|
p Sei ora un membro attivo della Rete Italiana di Scambio Orizzontale
|
||||||
|
p Inizia subito a scoprire beni, servizi e ospitalità nella tua comunità territoriale
|
||||||
|
|
||||||
|
.intro-text
|
||||||
|
| La tua comunità RISO ti aspetta! 🌱 Qui potrai
|
||||||
|
strong scambiare beni e servizi
|
||||||
|
| usando i
|
||||||
|
strong RIS
|
||||||
|
| , conoscere persone straordinarie e contribuire a costruire
|
||||||
|
strong un'economia più umana e solidale
|
||||||
|
| .
|
||||||
|
|
||||||
|
.credentials-box
|
||||||
|
.credentials-title 📋 I tuoi dati di accesso
|
||||||
|
|
||||||
|
if username
|
||||||
|
.credential-row
|
||||||
|
.credential-label Username:
|
||||||
|
.credential-value #{username}
|
||||||
|
|
||||||
|
if emailto
|
||||||
|
.credential-row
|
||||||
|
.credential-label Email:
|
||||||
|
.credential-value #{emailto}
|
||||||
|
|
||||||
|
if forgetpwd
|
||||||
|
.credential-row
|
||||||
|
.credential-label Password:
|
||||||
|
.credential-value
|
||||||
|
| (la password che hai inserito)
|
||||||
|
br
|
||||||
|
a(href=forgetpwd target="_blank") Hai dimenticato la password?
|
||||||
|
|
||||||
|
.cta-section
|
||||||
|
.cta-title 🚀 Accedi e inizia il tuo viaggio RISO
|
||||||
|
.cta-subtitle Scegli come vuoi accedere alla piattaforma
|
||||||
|
|
||||||
|
if strlinksito
|
||||||
|
.cta-buttons-wrapper
|
||||||
|
a.cta-button(href=strlinksito target="_blank")
|
||||||
|
span.button-icon 🌐
|
||||||
|
| Accedi da Web
|
||||||
|
|
||||||
|
a.cta-button.cta-button-secondary(href=strlinksito+'/installaapp' target="_blank")
|
||||||
|
span.button-icon 📱
|
||||||
|
| Installa l'App
|
||||||
|
|
||||||
|
a.cta-button.cta-button-tertiary(href=strlinksito+'/guida' target="_blank")
|
||||||
|
span.button-icon 📖
|
||||||
|
| Leggi la Guida
|
||||||
|
|
||||||
|
.next-steps
|
||||||
|
.next-steps-title 🎯 I tuoi primi passi nella comunità RISO
|
||||||
|
|
||||||
|
.step-item
|
||||||
|
.step-number 1
|
||||||
|
.step-content
|
||||||
|
h3 ✅ Completa il tuo profilo
|
||||||
|
p Aggiungi una foto, descrivi chi sei e cosa ti appassiona. Un profilo completo aiuta gli altri membri a conoscerti meglio!
|
||||||
|
|
||||||
|
.step-item
|
||||||
|
.step-number 2
|
||||||
|
.step-content
|
||||||
|
h3 📢 Pubblica il tuo primo annuncio
|
||||||
|
p Cosa puoi offrire? Beni, servizi o ospitalità - ogni contributo arricchisce la comunità. Ricorda di includere come strumento di scambio il RIS!
|
||||||
|
|
||||||
|
.step-item
|
||||||
|
.step-number 3
|
||||||
|
.step-content
|
||||||
|
h3 🔍 Esplora gli annunci
|
||||||
|
p Scopri cosa offrono gli altri membri nella tua area. Potresti trovare esattamente ciò che cerchi!
|
||||||
|
|
||||||
|
.step-item
|
||||||
|
.step-number 4
|
||||||
|
.step-content
|
||||||
|
h3 💬 Unisciti al gruppo territoriale
|
||||||
|
p Partecipa alle conversazioni, agli eventi e alle iniziative della tua comunità locale sulle chat Telegram di RISO
|
||||||
|
|
||||||
|
.info-box
|
||||||
|
p
|
||||||
|
strong 💰 Cos'è il RIS?
|
||||||
|
br
|
||||||
|
| Il RIS è l'unità di scambio della comunità RISO, basata sulla
|
||||||
|
strong fiducia reciproca
|
||||||
|
| . Parti da 0 RIS e accumula crediti offrendo i tuoi beni/servizi. Usalo per ottenere ciò di cui hai bisogno. Il bilancio totale della comunità è sempre zero - ogni credito corrisponde al debito di qualcun altro.
|
||||||
|
|
||||||
|
.community-note
|
||||||
|
p
|
||||||
|
| 🤝
|
||||||
|
strong Ricorda:
|
||||||
|
| Ogni scambio che fai, ogni annuncio che pubblichi, ogni interazione che hai rafforza la rete RISO. Non sei solo un utente -
|
||||||
|
strong sei un membro attivo
|
||||||
|
| di una comunità che crede nella solidarietà e nella condivisione!
|
||||||
|
|
||||||
|
.email-footer
|
||||||
|
.divider
|
||||||
|
p 💚 Benvenuto/a nella famiglia RISO!
|
||||||
|
p(style="margin-top: 8px;") Hai ricevuto questa email perché sei stato/a ammesso/a nella comunità #{nomeapp || 'RISO'}
|
||||||
|
p(style="margin-top: 12px; font-size: 12px;")
|
||||||
|
| © #{new Date().getFullYear()} #{nomeapp || 'RISO'} - Rete Italiana di Scambio Orizzontale
|
||||||
|
p(style="margin-top: 8px; font-size: 11px; color: #999;")
|
||||||
|
| Costruiamo insieme un'economia più umana e solidale
|
||||||
1
emails/reg_email_benvenuto_ammesso/it/subject.pug
Executable file
1
emails/reg_email_benvenuto_ammesso/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
|||||||
|
=`Benvenuto in ${nomeapp}`
|
||||||
@@ -310,54 +310,34 @@ html(lang="it")
|
|||||||
br
|
br
|
||||||
a(href=strlinkreg target="_blank") #{strlinkreg}
|
a(href=strlinkreg target="_blank") #{strlinkreg}
|
||||||
|
|
||||||
.info-box
|
.verification-process
|
||||||
|
.process-title
|
||||||
|
span.icon 📋
|
||||||
|
| Il tuo percorso di ingresso in RISO
|
||||||
|
|
||||||
|
.process-steps
|
||||||
|
.process-step
|
||||||
|
.step-badge 1
|
||||||
|
.step-content
|
||||||
|
h4 ✓ Verifica la tua email:
|
||||||
|
p Cliccando sul bottone qui sopra.
|
||||||
|
|
||||||
|
.process-step.active
|
||||||
|
.step-badge 2
|
||||||
|
.step-content
|
||||||
|
h4 ⏳ In attesa di ammissione
|
||||||
|
p Il tuo invitante vedrà la tua richiesta. Riceverai un'email appena sarai ammesso !
|
||||||
|
|
||||||
|
.process-step
|
||||||
|
.step-badge 3
|
||||||
|
.step-content
|
||||||
|
h4 🎉 Accesso alla piattaforma
|
||||||
|
p Potrai così accedere e completare il tuo profilo per iniziare a fare parte della comunità RISO.
|
||||||
|
|
||||||
|
.info-note
|
||||||
p
|
p
|
||||||
strong ✓ Dopo la verifica
|
strong 💡 Suggerimento:
|
||||||
| potrai accedere alla piattaforma utilizzando le tue credenziali e
|
| Se dovesse passare più di 24 ore, contatta il tuo invitante per ricordargli di ammetterti.
|
||||||
strong completare il tuo profilo.
|
|
||||||
|
|
||||||
.cta-section
|
|
||||||
- var numstep = 1;
|
|
||||||
if !verified_email
|
|
||||||
- var numstep = 2;
|
|
||||||
|
|
||||||
.cta-title 🚀 #{numstep}. Accedi e installa #{nomeapp}
|
|
||||||
|
|
||||||
if strlinksito
|
|
||||||
.cta-buttons-wrapper
|
|
||||||
a.cta-button(href=strlinksito target="_blank")
|
|
||||||
span.button-icon 🌐
|
|
||||||
| Accedi a #{nomeapp} da web
|
|
||||||
|
|
||||||
a.cta-button.cta-button-secondary(href=strlinksito+'/installaapp' target="_blank")
|
|
||||||
span.button-icon 📱
|
|
||||||
| Installa l'App
|
|
||||||
|
|
||||||
a.cta-button.cta-button-tertiary(href=strlinksito+'/guida' target="_blank")
|
|
||||||
span.button-icon 📖
|
|
||||||
| Vai alla Guida
|
|
||||||
|
|
||||||
.credentials-box
|
|
||||||
.credentials-title 📋 I tuoi dati di accesso
|
|
||||||
|
|
||||||
if username
|
|
||||||
.credential-row
|
|
||||||
.credential-label Username:
|
|
||||||
.credential-value #{username}
|
|
||||||
|
|
||||||
if emailto
|
|
||||||
.credential-row
|
|
||||||
.credential-label Email:
|
|
||||||
.credential-value #{emailto}
|
|
||||||
|
|
||||||
if forgetpwd
|
|
||||||
.credential-row
|
|
||||||
.credential-label Password:
|
|
||||||
.credential-value
|
|
||||||
| (la password che hai inserito)
|
|
||||||
br
|
|
||||||
a(href=forgetpwd target="_blank") Hai dimenticato la password?
|
|
||||||
|
|
||||||
|
|
||||||
.email-footer
|
.email-footer
|
||||||
.divider
|
.divider
|
||||||
|
|||||||
14
filelog.txt
14
filelog.txt
@@ -58,3 +58,17 @@ Dom 09/11 ORE 17:57: USER [surya4]: ciao
|
|||||||
Dom 09/11 ORE 18:36: USER [surya4]: ciao
|
Dom 09/11 ORE 18:36: USER [surya4]: ciao
|
||||||
|
|
||||||
Dom 09/11 ORE 18:43: USER [surya4]: ciao
|
Dom 09/11 ORE 18:43: USER [surya4]: ciao
|
||||||
|
|
||||||
|
Gio 20/11 ORE 19:12: USER [surya4]: ciao
|
||||||
|
|
||||||
|
Gio 20/11 ORE 19:15: USER [surya4]: ok
|
||||||
|
|
||||||
|
Gio 20/11 ORE 19:41: USER [surya4]: ok
|
||||||
|
|
||||||
|
Gio 20/11 ORE 19:52: USER [surya4]: ciao
|
||||||
|
|
||||||
|
Gio 20/11 ORE 20:55: USER [surya1977]: ciao
|
||||||
|
|
||||||
|
Gio 20/11 ORE 21:15: USER [surya1977]: ciao
|
||||||
|
|
||||||
|
Gio 20/11 ORE 21:24: USER [surya5]: ciao
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ process.env.LINK_REQUEST_NEWPASSWORD = '/requestnewpwd';
|
|||||||
process.env.ADD_NEW_SITE = '/addNewSite';
|
process.env.ADD_NEW_SITE = '/addNewSite';
|
||||||
process.env.LINK_UPDATE_PASSWORD = '/updatepassword';
|
process.env.LINK_UPDATE_PASSWORD = '/updatepassword';
|
||||||
process.env.LINK_UPDATE_PWD = '/updatepwd';
|
process.env.LINK_UPDATE_PWD = '/updatepwd';
|
||||||
process.env.LINK_CHECK_UPDATES = '/checkupdates';
|
|
||||||
process.env.KEY_APP_ID = 'KKPPAA5KJK435J3KSS9F9D8S9F8SD98F9SDF';
|
process.env.KEY_APP_ID = 'KKPPAA5KJK435J3KSS9F9D8S9F8SD98F9SDF';
|
||||||
|
|
||||||
console.log("Starting Node with: " + file);
|
console.log("Starting Node with: " + file);
|
||||||
|
|||||||
533
src/controllers/UserController.js
Normal file
533
src/controllers/UserController.js
Normal file
@@ -0,0 +1,533 @@
|
|||||||
|
const UserService = require('../services/UserService');
|
||||||
|
const AuthService = require('../services/AuthService');
|
||||||
|
const RegistrationService = require('../services/RegistrationService');
|
||||||
|
const { validateRegistration, validateLogin } = require('../validators/userValidators');
|
||||||
|
const telegrambot = require('../telegram/telegrambot');
|
||||||
|
const tools = require('../tools/general');
|
||||||
|
const server_constants = require('../tools/server_constants');
|
||||||
|
const shared_consts = require('../tools/shared_nodejs');
|
||||||
|
|
||||||
|
class UserController {
|
||||||
|
constructor() {
|
||||||
|
this.userService = new UserService();
|
||||||
|
this.authService = new AuthService();
|
||||||
|
this.registrationService = new RegistrationService();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register new user
|
||||||
|
* POST /users
|
||||||
|
*/
|
||||||
|
async register(req, res) {
|
||||||
|
try {
|
||||||
|
tools.mylog('POST /users - Registration');
|
||||||
|
|
||||||
|
// Validate input
|
||||||
|
const validationError = validateRegistration(req.body);
|
||||||
|
if (validationError) {
|
||||||
|
await tools.snooze(5000);
|
||||||
|
return res.status(400).send({
|
||||||
|
code: validationError.code,
|
||||||
|
msg: validationError.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const userData = this._extractUserData(req.body);
|
||||||
|
userData.ipaddr = tools.getiPAddressUser(req);
|
||||||
|
|
||||||
|
// Check security (IP bans, block words, etc.)
|
||||||
|
const securityCheck = await this._performSecurityChecks(userData, req);
|
||||||
|
if (securityCheck.blocked) {
|
||||||
|
return res.status(securityCheck.status).send({
|
||||||
|
code: securityCheck.code,
|
||||||
|
msg: securityCheck.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process registration
|
||||||
|
const result = await this.registrationService.registerUser(userData, req);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
return res.status(400).send({
|
||||||
|
code: result.code,
|
||||||
|
msg: result.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send response with tokens
|
||||||
|
res
|
||||||
|
.header('x-auth', result.token)
|
||||||
|
.header('x-refrtok', result.refreshToken)
|
||||||
|
.send(result.user);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in registration:', error.message);
|
||||||
|
res.status(400).send({
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
msg: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User login
|
||||||
|
* POST /users/login
|
||||||
|
*/
|
||||||
|
async login(req, res) {
|
||||||
|
try {
|
||||||
|
const { username, password, idapp, keyappid } = req.body;
|
||||||
|
|
||||||
|
// Validate API key
|
||||||
|
if (keyappid !== process.env.KEY_APP_ID) {
|
||||||
|
return res.status(400).send({ code: server_constants.RIS_CODE_ERR });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate input
|
||||||
|
const validationError = validateLogin(req.body);
|
||||||
|
if (validationError) {
|
||||||
|
return res.status(400).send({
|
||||||
|
code: validationError.code,
|
||||||
|
msg: validationError.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt login
|
||||||
|
const result = await this.authService.authenticate(
|
||||||
|
idapp,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
req
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
return res.status(result.status).send({
|
||||||
|
code: result.code,
|
||||||
|
msg: result.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send response with tokens
|
||||||
|
res
|
||||||
|
.header('x-auth', result.token)
|
||||||
|
.header('x-refrtok', result.refreshToken)
|
||||||
|
.send({
|
||||||
|
usertosend: result.user,
|
||||||
|
code: server_constants.RIS_CODE_OK,
|
||||||
|
subsExistonDb: result.subsExistonDb
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in login:', error.message);
|
||||||
|
res.status(400).send({
|
||||||
|
code: server_constants.RIS_CODE_LOGIN_ERR_GENERIC,
|
||||||
|
msgerr: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user profile
|
||||||
|
* POST /users/profile
|
||||||
|
*/
|
||||||
|
async getProfile(req, res) {
|
||||||
|
try {
|
||||||
|
const usernameOrig = req.user?.username || '';
|
||||||
|
const perm = req.user?.perm || tools.Perm.PERM_NONE;
|
||||||
|
const { username, idapp, idnotif } = req.body;
|
||||||
|
|
||||||
|
// Mark notification as read if present
|
||||||
|
if (idnotif) {
|
||||||
|
await this.userService.markNotificationAsRead(idapp, usernameOrig, idnotif);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user profile
|
||||||
|
const profile = await this.userService.getUserProfile(
|
||||||
|
idapp,
|
||||||
|
username,
|
||||||
|
usernameOrig,
|
||||||
|
perm
|
||||||
|
);
|
||||||
|
|
||||||
|
res.send(profile);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in getProfile:', error.message);
|
||||||
|
res.status(400).send({
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
msg: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user saldo (balance)
|
||||||
|
* POST /users/updatesaldo
|
||||||
|
*/
|
||||||
|
async updateSaldo(req, res) {
|
||||||
|
try {
|
||||||
|
const username = req.user.username;
|
||||||
|
const { idapp, circuitId, groupname, lastdr } = req.body;
|
||||||
|
|
||||||
|
const result = await this.userService.updateUserBalance(
|
||||||
|
idapp,
|
||||||
|
username,
|
||||||
|
circuitId,
|
||||||
|
groupname,
|
||||||
|
lastdr
|
||||||
|
);
|
||||||
|
|
||||||
|
res.send({ ris: result });
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in updateSaldo:', error.message);
|
||||||
|
res.status(400).send({
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
msg: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user's friends
|
||||||
|
* POST /users/friends
|
||||||
|
*/
|
||||||
|
async getFriends(req, res) {
|
||||||
|
try {
|
||||||
|
const username = req.user.username;
|
||||||
|
const { idapp } = req.body;
|
||||||
|
|
||||||
|
const friends = await this.userService.getUserFriends(idapp, username);
|
||||||
|
|
||||||
|
res.send(friends);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in getFriends:', error.message);
|
||||||
|
res.status(400).send({
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
msg: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute friend command (add, remove, etc.)
|
||||||
|
* POST /users/friends/cmd
|
||||||
|
*/
|
||||||
|
async executeFriendCommand(req, res) {
|
||||||
|
try {
|
||||||
|
const usernameLogged = req.user.username;
|
||||||
|
const { idapp, usernameOrig, usernameDest, cmd, value } = req.body;
|
||||||
|
|
||||||
|
// Security check
|
||||||
|
if (!this._canExecuteFriendCommand(req.user, usernameOrig, usernameDest, cmd)) {
|
||||||
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({
|
||||||
|
code: server_constants.RIS_CODE_ERR_UNAUTHORIZED,
|
||||||
|
msg: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.userService.executeFriendCommand(
|
||||||
|
req,
|
||||||
|
idapp,
|
||||||
|
usernameOrig,
|
||||||
|
usernameDest,
|
||||||
|
cmd,
|
||||||
|
value
|
||||||
|
);
|
||||||
|
|
||||||
|
res.send(result);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in executeFriendCommand:', error.message);
|
||||||
|
res.status(400).send({
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
msg: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user's groups
|
||||||
|
* POST /users/groups
|
||||||
|
*/
|
||||||
|
async getGroups(req, res) {
|
||||||
|
try {
|
||||||
|
const username = req.user.username;
|
||||||
|
const { idapp } = req.body;
|
||||||
|
|
||||||
|
const groups = await this.userService.getUserGroups(idapp, username, req);
|
||||||
|
|
||||||
|
res.send(groups);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in getGroups:', error.message);
|
||||||
|
res.status(400).send({
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
msg: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user's circuits
|
||||||
|
* POST /users/circuits
|
||||||
|
*/
|
||||||
|
async getCircuits(req, res) {
|
||||||
|
try {
|
||||||
|
const username = req.user.username;
|
||||||
|
const { idapp, nummovTodownload } = req.body;
|
||||||
|
|
||||||
|
const circuits = await this.userService.getUserCircuits(
|
||||||
|
idapp,
|
||||||
|
username,
|
||||||
|
req.user,
|
||||||
|
nummovTodownload
|
||||||
|
);
|
||||||
|
|
||||||
|
res.send(circuits);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in getCircuits:', error.message);
|
||||||
|
res.status(400).send({
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
msg: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh authentication token
|
||||||
|
* POST /users/newtok
|
||||||
|
*/
|
||||||
|
async refreshToken(req, res) {
|
||||||
|
try {
|
||||||
|
const { refreshToken } = req.body;
|
||||||
|
|
||||||
|
if (!refreshToken) {
|
||||||
|
return res.status(400).send({ error: 'Refresh token mancante' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.authService.refreshToken(refreshToken, req);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
return res.status(result.status).send({ error: result.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).send({
|
||||||
|
token: result.token,
|
||||||
|
refreshToken: result.refreshToken
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in refreshToken:', error.message);
|
||||||
|
res.status(500).send({ error: 'Errore interno del server' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout user (remove token)
|
||||||
|
* DELETE /users/me/token
|
||||||
|
*/
|
||||||
|
async logout(req, res) {
|
||||||
|
try {
|
||||||
|
await this.authService.removeToken(req.user, req.token);
|
||||||
|
res.status(200).send();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in logout:', error.message);
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if username exists
|
||||||
|
* GET /users/:idapp/:username
|
||||||
|
*/
|
||||||
|
async checkUsername(req, res) {
|
||||||
|
try {
|
||||||
|
const { username, idapp } = req.params;
|
||||||
|
|
||||||
|
const exists = await this.userService.checkUsernameExists(idapp, username);
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
return res.status(404).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).send();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in checkUsername:', error.message);
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user permissions
|
||||||
|
* POST /users/setperm
|
||||||
|
*/
|
||||||
|
async setPermissions(req, res) {
|
||||||
|
try {
|
||||||
|
const { idapp, username, perm } = req.body;
|
||||||
|
|
||||||
|
await this.userService.setUserPermissions(req.user._id, {
|
||||||
|
idapp,
|
||||||
|
username,
|
||||||
|
perm
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).send();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in setPermissions:', error.message);
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database operations (admin only)
|
||||||
|
* POST /users/dbop
|
||||||
|
*/
|
||||||
|
async executeDbOperation(req, res) {
|
||||||
|
try {
|
||||||
|
const { mydata, idapp } = req.body;
|
||||||
|
|
||||||
|
// Check permissions
|
||||||
|
if (!this._hasAdminPermissions(req.user)) {
|
||||||
|
return res.status(404).send({
|
||||||
|
code: server_constants.RIS_CODE_ERR_UNAUTHORIZED
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.userService.executeDbOperation(
|
||||||
|
idapp,
|
||||||
|
mydata,
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
);
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
code: server_constants.RIS_CODE_OK,
|
||||||
|
data: result
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in executeDbOperation:', error.message);
|
||||||
|
res.status(400).send({
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
msg: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get map information
|
||||||
|
* POST /users/infomap
|
||||||
|
*/
|
||||||
|
async getMapInfo(req, res) {
|
||||||
|
try {
|
||||||
|
const { idapp } = req.body;
|
||||||
|
|
||||||
|
const mapData = await this.userService.getMapInformation(idapp);
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
code: server_constants.RIS_CODE_OK,
|
||||||
|
ris: mapData
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in getMapInfo:', error.message);
|
||||||
|
res.status(400).send({
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
msg: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== PRIVATE HELPER METHODS =====
|
||||||
|
|
||||||
|
_extractUserData(body) {
|
||||||
|
const fields = [
|
||||||
|
'email', 'password', 'username', 'group', 'name',
|
||||||
|
'surname', 'idapp', 'keyappid', 'lang', 'profile',
|
||||||
|
'aportador_solidario'
|
||||||
|
];
|
||||||
|
|
||||||
|
const userData = {};
|
||||||
|
fields.forEach(field => {
|
||||||
|
if (body[field] !== undefined) {
|
||||||
|
userData[field] = body[field];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Normalize data
|
||||||
|
if (userData.email) userData.email = userData.email.toLowerCase().trim();
|
||||||
|
if (userData.username) userData.username = userData.username.trim();
|
||||||
|
if (userData.name) userData.name = userData.name.trim();
|
||||||
|
if (userData.surname) userData.surname = userData.surname.trim();
|
||||||
|
|
||||||
|
return userData;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _performSecurityChecks(userData, req) {
|
||||||
|
const { User } = require('../models/user');
|
||||||
|
|
||||||
|
// Check for blocked words
|
||||||
|
if (tools.blockwords(userData.username) ||
|
||||||
|
tools.blockwords(userData.name) ||
|
||||||
|
tools.blockwords(userData.surname)) {
|
||||||
|
await tools.snooze(5000);
|
||||||
|
return {
|
||||||
|
blocked: true,
|
||||||
|
status: 404,
|
||||||
|
code: server_constants.RIS_CODE_ERR
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check IP ban
|
||||||
|
const lastRecord = await User.getLastRec(userData.idapp);
|
||||||
|
if (lastRecord && process.env.LOCALE !== '1') {
|
||||||
|
if (lastRecord.ipaddr === userData.ipaddr && lastRecord.date_reg) {
|
||||||
|
const isTooRecent = tools.isdiffSecDateLess(lastRecord.date_reg, 3);
|
||||||
|
if (isTooRecent) {
|
||||||
|
const msg = `${userData.ipaddr}: [${userData.username}] ${userData.name} ${userData.surname}`;
|
||||||
|
tools.writeIPToBan(msg);
|
||||||
|
await telegrambot.sendMsgTelegramToTheAdmin(userData.idapp, '‼️ BAN: ' + msg, true);
|
||||||
|
await tools.snooze(5000);
|
||||||
|
|
||||||
|
return {
|
||||||
|
blocked: true,
|
||||||
|
status: 400,
|
||||||
|
code: server_constants.RIS_CODE_BANIP
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { blocked: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
_canExecuteFriendCommand(user, usernameOrig, usernameDest, cmd) {
|
||||||
|
const { User } = require('../models/user');
|
||||||
|
|
||||||
|
if (User.isAdmin(user.perm) || User.isManager(user.perm)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowedCommands = [
|
||||||
|
shared_consts.FRIENDSCMD.SETFRIEND,
|
||||||
|
shared_consts.FRIENDSCMD.SETHANDSHAKE
|
||||||
|
];
|
||||||
|
|
||||||
|
if (allowedCommands.includes(cmd)) {
|
||||||
|
return usernameOrig === user.username || usernameDest === user.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_hasAdminPermissions(user) {
|
||||||
|
const { User } = require('../models/user');
|
||||||
|
return User.isCollaboratore(user.perm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = UserController;
|
||||||
@@ -42,9 +42,9 @@ const authenticateMiddleware = async (req, res, next, withUser = false, lean = f
|
|||||||
req.statuscode2 = null;
|
req.statuscode2 = null;
|
||||||
|
|
||||||
// Gestione token scaduto
|
// Gestione token scaduto
|
||||||
if (user.code === server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED) {
|
if (user.code === server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED) {
|
||||||
return handleAuthFailure(req, res, next, {
|
return handleAuthFailure(req, res, next, {
|
||||||
code: server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED,
|
code: server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED,
|
||||||
message: 'TOKEN SCADUTO',
|
message: 'TOKEN SCADUTO',
|
||||||
logPrefix,
|
logPrefix,
|
||||||
noError,
|
noError,
|
||||||
@@ -81,7 +81,7 @@ function handleAuthFailure(req, res, next, { code, message, logPrefix, noError }
|
|||||||
|
|
||||||
if (noError) {
|
if (noError) {
|
||||||
req.statuscode2 = code;
|
req.statuscode2 = code;
|
||||||
console.log(` ## ${logPrefix} - ${message} (noError mode, continuing) ⚠️`);
|
// console.log(` ## ${logPrefix} - ${message} (noError mode, continuing) ⚠️`);
|
||||||
return next();
|
return next();
|
||||||
} else {
|
} else {
|
||||||
console.log(` ## SEND RES ${logPrefix} - ${message} ❌`);
|
console.log(` ## SEND RES ${logPrefix} - ${message} ❌`);
|
||||||
|
|||||||
150
src/middleware/securityMiddleware.js
Normal file
150
src/middleware/securityMiddleware.js
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
/**
|
||||||
|
* Security Middleware
|
||||||
|
* Handles rate limiting, IP blocking, and failed login attempts
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Dictionary to track failed login attempts
|
||||||
|
const failedLoginAttempts = {};
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
const MAX_FAILED_ATTEMPTS = 30;
|
||||||
|
const BLOCK_DURATION = 30 * 60 * 1000; // 30 minutes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block user after too many failed attempts
|
||||||
|
*/
|
||||||
|
function blockUser(username) {
|
||||||
|
failedLoginAttempts[username] = Date.now() + BLOCK_DURATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is blocked
|
||||||
|
*/
|
||||||
|
function isUserBlocked(username) {
|
||||||
|
const now = Date.now();
|
||||||
|
return failedLoginAttempts[username] && failedLoginAttempts[username] > now;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear failed attempts for user (on successful login)
|
||||||
|
*/
|
||||||
|
function clearFailedAttempts(username) {
|
||||||
|
delete failedLoginAttempts[username];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware to check if user is blocked before login
|
||||||
|
*/
|
||||||
|
function checkBlocked(req, res, next) {
|
||||||
|
const { username } = req.body;
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: 'Username mancante'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isUserBlocked(username)) {
|
||||||
|
const text = `Utente bloccato. Riprova più tardi. (username=${username})`;
|
||||||
|
console.log(text);
|
||||||
|
return res.status(403).json({
|
||||||
|
message: 'Utente bloccato. Riprova più tardi.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get failed attempts count for a user
|
||||||
|
*/
|
||||||
|
function getFailedAttempts(username) {
|
||||||
|
return failedLoginAttempts[username] || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment failed attempts
|
||||||
|
*/
|
||||||
|
function incrementFailedAttempts(username) {
|
||||||
|
if (typeof failedLoginAttempts[username] === 'number') {
|
||||||
|
failedLoginAttempts[username]++;
|
||||||
|
} else {
|
||||||
|
failedLoginAttempts[username] = 1;
|
||||||
|
}
|
||||||
|
return failedLoginAttempts[username];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user should be blocked after this attempt
|
||||||
|
*/
|
||||||
|
function shouldBlockUser(username) {
|
||||||
|
return (failedLoginAttempts[username] || 0) >= MAX_FAILED_ATTEMPTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware to rate limit API requests per IP
|
||||||
|
*/
|
||||||
|
const requestCounts = {};
|
||||||
|
const REQUEST_WINDOW = 60 * 1000; // 1 minute
|
||||||
|
const MAX_REQUESTS = 100; // per window
|
||||||
|
|
||||||
|
function rateLimitByIP(req, res, next) {
|
||||||
|
const tools = require('../tools/general');
|
||||||
|
const ip = tools.getiPAddressUser(req);
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
if (!requestCounts[ip]) {
|
||||||
|
requestCounts[ip] = {
|
||||||
|
count: 1,
|
||||||
|
resetTime: now + REQUEST_WINDOW
|
||||||
|
};
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now > requestCounts[ip].resetTime) {
|
||||||
|
// Reset window
|
||||||
|
requestCounts[ip] = {
|
||||||
|
count: 1,
|
||||||
|
resetTime: now + REQUEST_WINDOW
|
||||||
|
};
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestCounts[ip].count >= MAX_REQUESTS) {
|
||||||
|
return res.status(429).json({
|
||||||
|
message: 'Troppi tentativi. Riprova più tardi.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
requestCounts[ip].count++;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up old blocked users (run periodically)
|
||||||
|
*/
|
||||||
|
function cleanupBlockedUsers() {
|
||||||
|
const now = Date.now();
|
||||||
|
Object.keys(failedLoginAttempts).forEach(username => {
|
||||||
|
if (typeof failedLoginAttempts[username] === 'number' &&
|
||||||
|
failedLoginAttempts[username] < now) {
|
||||||
|
delete failedLoginAttempts[username];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run cleanup every 10 minutes
|
||||||
|
setInterval(cleanupBlockedUsers, 10 * 60 * 1000);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
checkBlocked,
|
||||||
|
blockUser,
|
||||||
|
isUserBlocked,
|
||||||
|
clearFailedAttempts,
|
||||||
|
getFailedAttempts,
|
||||||
|
incrementFailedAttempts,
|
||||||
|
shouldBlockUser,
|
||||||
|
rateLimitByIP,
|
||||||
|
MAX_FAILED_ATTEMPTS,
|
||||||
|
BLOCK_DURATION
|
||||||
|
};
|
||||||
@@ -197,7 +197,6 @@ const UserSchema = new mongoose.Schema({
|
|||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
aportador_solidario: {
|
aportador_solidario: {
|
||||||
// da cancellare
|
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
verified_by_aportador: {
|
verified_by_aportador: {
|
||||||
@@ -855,7 +854,7 @@ UserSchema.statics.findByToken = async function (token, typeaccess, con_auth, wi
|
|||||||
code = server_constants.RIS_CODE_OK;
|
code = server_constants.RIS_CODE_OK;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.expiredAt) {
|
if (err.expiredAt) {
|
||||||
code = server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED;
|
code = server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED;
|
||||||
if (con_auth) return { user: null, code };
|
if (con_auth) return { user: null, code };
|
||||||
} else {
|
} else {
|
||||||
console.error('Err findByToken:', err);
|
console.error('Err findByToken:', err);
|
||||||
@@ -888,7 +887,7 @@ UserSchema.statics.findByToken = async function (token, typeaccess, con_auth, wi
|
|||||||
|
|
||||||
if (checkExpiry && decoded.exp < currentTime) {
|
if (checkExpiry && decoded.exp < currentTime) {
|
||||||
console.log('🔴 Il token è scaduto, generazione del nuovo token...');
|
console.log('🔴 Il token è scaduto, generazione del nuovo token...');
|
||||||
code = server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED;
|
code = server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1001,10 +1000,9 @@ UserSchema.statics.setPwdComeQuellaDellAdmin = async function (mydata) {
|
|||||||
const User = this;
|
const User = this;
|
||||||
|
|
||||||
const userAdmin = await User.findOne({
|
const userAdmin = await User.findOne({
|
||||||
_id: mydata.myuserId
|
_id: mydata.myuserId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Verifica permessi admin
|
// Verifica permessi admin
|
||||||
if (!User.isAdmin(userAdmin.perm)) {
|
if (!User.isAdmin(userAdmin.perm)) {
|
||||||
throw new Error('Permessi insufficienti: solo gli admin possono modificare le password');
|
throw new Error('Permessi insufficienti: solo gli admin possono modificare le password');
|
||||||
@@ -1012,7 +1010,7 @@ UserSchema.statics.setPwdComeQuellaDellAdmin = async function (mydata) {
|
|||||||
|
|
||||||
// Trova l'utente da modificare
|
// Trova l'utente da modificare
|
||||||
const userfound = await User.findOne({
|
const userfound = await User.findOne({
|
||||||
_id: mydata._id
|
_id: mydata._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!userfound) {
|
if (!userfound) {
|
||||||
@@ -1032,10 +1030,9 @@ UserSchema.statics.ripristinaPwdPrec = async function (mydata) {
|
|||||||
const User = this;
|
const User = this;
|
||||||
|
|
||||||
const userAdmin = await User.findOne({
|
const userAdmin = await User.findOne({
|
||||||
_id: mydata.myuserId
|
_id: mydata.myuserId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Verifica permessi admin
|
// Verifica permessi admin
|
||||||
if (!User.isAdmin(userAdmin.perm)) {
|
if (!User.isAdmin(userAdmin.perm)) {
|
||||||
throw new Error('Permessi insufficienti: solo gli admin possono modificare le password');
|
throw new Error('Permessi insufficienti: solo gli admin possono modificare le password');
|
||||||
@@ -1043,7 +1040,7 @@ UserSchema.statics.ripristinaPwdPrec = async function (mydata) {
|
|||||||
|
|
||||||
// Trova l'utente da modificare
|
// Trova l'utente da modificare
|
||||||
const userfound = await User.findOne({
|
const userfound = await User.findOne({
|
||||||
_id: mydata._id
|
_id: mydata._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!userfound) {
|
if (!userfound) {
|
||||||
@@ -2281,6 +2278,15 @@ UserSchema.statics.getUsernameCircuitsByUsername = async function (idapp, userna
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Rimuovo l'Amicizia
|
// Rimuovo l'Amicizia
|
||||||
|
UserSchema.statics.removeUser = async function (id) {
|
||||||
|
const User = this;
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
return await User.deleteMany({ _id: id });
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
UserSchema.statics.removeFriend = async function (idapp, username, usernameDest) {
|
UserSchema.statics.removeFriend = async function (idapp, username, usernameDest) {
|
||||||
return await User.updateOne(
|
return await User.updateOne(
|
||||||
{ idapp, username },
|
{ idapp, username },
|
||||||
@@ -2828,6 +2834,10 @@ UserSchema.statics.setFriendsCmd = async function (req, idapp, usernameOrig, use
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (cmd === shared_consts.FRIENDSCMD.DELETE_USER) {
|
||||||
|
const id = await User.findOne({ idapp, username: usernameDest }, { username: 1 });
|
||||||
|
// Cancella Utente
|
||||||
|
ris = await User.removeUser(id);
|
||||||
} else if (cmd === shared_consts.FRIENDSCMD.UNBLOCK_USER) {
|
} else if (cmd === shared_consts.FRIENDSCMD.UNBLOCK_USER) {
|
||||||
username_worked = usernameDest;
|
username_worked = usernameDest;
|
||||||
|
|
||||||
@@ -4285,6 +4295,31 @@ UserSchema.statics.getNameSurnameByUsername = async function (idapp, username, r
|
|||||||
console.error('getNameSurnameByUsername', e);
|
console.error('getNameSurnameByUsername', e);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
UserSchema.statics.getNameSurnameEUsernameByUsername = async function (idapp, username, reale = false) {
|
||||||
|
const User = this;
|
||||||
|
|
||||||
|
return await User.findOne(
|
||||||
|
{
|
||||||
|
idapp,
|
||||||
|
username,
|
||||||
|
$or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
|
||||||
|
},
|
||||||
|
{ username: 1, name: 1, surname: 1 }
|
||||||
|
)
|
||||||
|
.then((rec) => {
|
||||||
|
let ris = rec.username;
|
||||||
|
if (!!rec) {
|
||||||
|
if (reale) {
|
||||||
|
if (!rec.name) return '';
|
||||||
|
}
|
||||||
|
ris = rec.name ? `${rec.name} ${rec.surname} - ${rec.username}` : `${rec.username}`;
|
||||||
|
}
|
||||||
|
return !!rec ? ris : '';
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error('getNameSurnameByUsername', e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
UserSchema.statics.getIdByUsername = async function (idapp, username) {
|
UserSchema.statics.getIdByUsername = async function (idapp, username) {
|
||||||
const User = this;
|
const User = this;
|
||||||
|
|||||||
575
src/modules/InvioNotifiche.js
Normal file
575
src/modules/InvioNotifiche.js
Normal file
@@ -0,0 +1,575 @@
|
|||||||
|
/**
|
||||||
|
* Classe per gestire l'invio di notifiche via Email e Telegram
|
||||||
|
* per gli eventi della piattaforma RISO
|
||||||
|
*/
|
||||||
|
|
||||||
|
class InvioNotifiche {
|
||||||
|
/**
|
||||||
|
* @param {Object} config - Configurazione per l'invio notifiche
|
||||||
|
* @param {Object} config.emailService - Servizio per invio email (es. nodemailer)
|
||||||
|
* @param {Object} config.telegramBot - Istanza del bot Telegram
|
||||||
|
* @param {String} config.adminTelegramId - ID Telegram dell'amministratore
|
||||||
|
* @param {String} config.adminEmail - Email dell'amministratore
|
||||||
|
* @param {String} config.baseUrl - URL base dell'applicazione
|
||||||
|
* @param {String} config.nomeApp - Nome dell'applicazione
|
||||||
|
* @param {Object} config.emailTemplates - Path ai template email PUG
|
||||||
|
*/
|
||||||
|
constructor(config) {
|
||||||
|
this.emailService = config.emailService;
|
||||||
|
this.telegramBot = config.telegramBot;
|
||||||
|
this.adminTelegramId = config.adminTelegramId;
|
||||||
|
this.adminEmail = config.adminEmail;
|
||||||
|
this.baseUrl = config.baseUrl;
|
||||||
|
this.nomeApp = config.nomeApp || 'RISO';
|
||||||
|
this.emailTemplates = config.emailTemplates || {};
|
||||||
|
|
||||||
|
// Logger (puoi sostituirlo con Winston o altro)
|
||||||
|
this.logger = config.logger || console;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// METODI PRIVATI - Invio Base
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invia una email
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _inviaEmail(destinatario, oggetto, htmlContent, templatePath = null, templateData = null) {
|
||||||
|
try {
|
||||||
|
let html = htmlContent;
|
||||||
|
|
||||||
|
// Se è specificato un template PUG, renderizzalo
|
||||||
|
if (templatePath && templateData) {
|
||||||
|
const pug = require('pug');
|
||||||
|
html = pug.renderFile(templatePath, templateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailOptions = {
|
||||||
|
from: `${this.nomeApp} <noreply@riso.app>`,
|
||||||
|
to: destinatario,
|
||||||
|
subject: oggetto,
|
||||||
|
html: html,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await this.emailService.sendMail(emailOptions);
|
||||||
|
|
||||||
|
this.logger.info(`Email inviata a ${destinatario}: ${oggetto}`);
|
||||||
|
return { success: true, messageId: result.messageId };
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Errore invio email a ${destinatario}:`, error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invia un messaggio Telegram
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _inviaTelegram(telegramId, messaggio, opzioni = {}) {
|
||||||
|
try {
|
||||||
|
if (!telegramId || telegramId === 0) {
|
||||||
|
this.logger.warn(`Telegram ID non valido: ${telegramId}`);
|
||||||
|
return { success: false, error: 'Telegram ID non valido' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions = {
|
||||||
|
parse_mode: 'HTML',
|
||||||
|
disable_web_page_preview: false,
|
||||||
|
...opzioni,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await this.telegramBot.sendMessage(telegramId, messaggio, defaultOptions);
|
||||||
|
|
||||||
|
this.logger.info(`Messaggio Telegram inviato a ${telegramId}`);
|
||||||
|
return { success: true, messageId: result.message_id };
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Errore invio Telegram a ${telegramId}:`, error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invia notifica sia via email che Telegram
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _inviaNotificaDoppia(destinatario, opzioniEmail, opzioniTelegram) {
|
||||||
|
const risultati = {
|
||||||
|
email: null,
|
||||||
|
telegram: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Invia email
|
||||||
|
if (destinatario.email) {
|
||||||
|
risultati.email = await this._inviaEmail(
|
||||||
|
destinatario.email,
|
||||||
|
opzioniEmail.oggetto,
|
||||||
|
opzioniEmail.html,
|
||||||
|
opzioniEmail.templatePath,
|
||||||
|
opzioniEmail.templateData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invia Telegram
|
||||||
|
if (destinatario.telegramId && destinatario.telegramId !== 0) {
|
||||||
|
risultati.telegram = await this._inviaTelegram(
|
||||||
|
destinatario.telegramId,
|
||||||
|
opzioniTelegram.messaggio,
|
||||||
|
opzioniTelegram.opzioni
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return risultati;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invia copia all'amministratore
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _inviaCopiaCopiaAdmin(oggetto, messaggio) {
|
||||||
|
const risultati = {
|
||||||
|
email: null,
|
||||||
|
telegram: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Email admin
|
||||||
|
if (this.adminEmail) {
|
||||||
|
risultati.email = await this._inviaEmail(this.adminEmail, `[ADMIN] ${oggetto}`, messaggio);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Telegram admin
|
||||||
|
if (this.adminTelegramId) {
|
||||||
|
risultati.telegram = await this._inviaTelegram(this.adminTelegramId, `🔔 <b>NOTIFICA ADMIN</b>\n\n${messaggio}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return risultati;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// EVENTO 1: REGISTRAZIONE UTENTE
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gestisce le notifiche dopo la registrazione
|
||||||
|
* @param {Object} utente - Dati dell'utente registrato
|
||||||
|
* @param {String} tokenVerifica - Token per verifica email
|
||||||
|
*/
|
||||||
|
async notificaRegistrazione(utente, tokenVerifica) {
|
||||||
|
try {
|
||||||
|
this.logger.info(`Notifica registrazione per utente: ${utente.username}`);
|
||||||
|
|
||||||
|
// Se l'email è già verificata, salta la verifica e vai direttamente alla richiesta ammissione
|
||||||
|
if (utente.verified_email === true) {
|
||||||
|
this.logger.info(`Email già verificata per ${utente.username}, salto verifica email`);
|
||||||
|
|
||||||
|
// Invia direttamente la richiesta di ammissione all'invitante
|
||||||
|
await this.notificaRichiestaAmmissione(utente);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Email già verificata, richiesta ammissione inviata',
|
||||||
|
emailVerificaInviata: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email non verificata: invia email di verifica
|
||||||
|
const linkVerifica = `${this.baseUrl}/verifica-email/${tokenVerifica}`;
|
||||||
|
|
||||||
|
const templateData = {
|
||||||
|
name: utente.name,
|
||||||
|
username: utente.username,
|
||||||
|
emailto: utente.email,
|
||||||
|
nomeapp: this.nomeApp,
|
||||||
|
baseurl: this.baseUrl,
|
||||||
|
linkVerifica: linkVerifica,
|
||||||
|
};
|
||||||
|
|
||||||
|
const risultato = await this._inviaEmail(
|
||||||
|
utente.email,
|
||||||
|
`Verifica il tuo indirizzo email - ${this.nomeApp}`,
|
||||||
|
null,
|
||||||
|
this.emailTemplates.verificaEmail,
|
||||||
|
templateData
|
||||||
|
);
|
||||||
|
|
||||||
|
// Notifica admin della nuova registrazione
|
||||||
|
const messaggioAdmin = `
|
||||||
|
📝 <b>Nuova Registrazione</b>
|
||||||
|
|
||||||
|
👤 <b>Username:</b> ${utente.username}
|
||||||
|
📧 <b>Email:</b> ${utente.email}
|
||||||
|
${utente.name ? `🏷️ <b>Nome:</b> ${utente.name}` : ''}
|
||||||
|
✅ <b>Email verificata:</b> ${utente.verified_email ? 'Sì' : 'No'}
|
||||||
|
📅 <b>Data:</b> ${new Date().toLocaleString('it-IT')}
|
||||||
|
|
||||||
|
${utente.verified_email ? "✓ Richiesta ammissione inviata all'invitante" : '⏳ In attesa verifica email'}
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
await this._inviaCopiaCopiaAdmin('Nuova Registrazione', messaggioAdmin);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Email di verifica inviata',
|
||||||
|
emailVerificaInviata: true,
|
||||||
|
risultato,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Errore in notificaRegistrazione:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// EVENTO 2: EMAIL VERIFICATA
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifica l'invitante che l'utente ha verificato l'email
|
||||||
|
* @param {Object} utente - Dati dell'utente che ha verificato l'email
|
||||||
|
*/
|
||||||
|
async notificaRichiestaAmmissione(utente) {
|
||||||
|
try {
|
||||||
|
this.logger.info(`Notifica richiesta ammissione per utente: ${utente.username}`);
|
||||||
|
|
||||||
|
// Recupera dati invitante (assumendo che tu abbia un metodo per recuperarlo)
|
||||||
|
const invitante = await this._getInvitante(utente.invitante_id);
|
||||||
|
|
||||||
|
if (!invitante) {
|
||||||
|
throw new Error(`Invitante non trovato per utente ${utente.username}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dati per email invitante
|
||||||
|
const templateDataInvitante = {
|
||||||
|
nomeInvitante: invitante.name || invitante.username,
|
||||||
|
nomeUtente: utente.name || utente.username,
|
||||||
|
usernameUtente: utente.username,
|
||||||
|
emailUtente: utente.email,
|
||||||
|
nomeapp: this.nomeApp,
|
||||||
|
baseurl: this.baseUrl,
|
||||||
|
linkAmmetti: `${this.baseUrl}/admin/ammetti-utente/${utente.id}`,
|
||||||
|
dataRegistrazione: new Date(utente.created_at).toLocaleDateString('it-IT'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Messaggio Telegram per invitante
|
||||||
|
const messaggioTelegramInvitante = `
|
||||||
|
🎉 <b>Nuovo Utente da Ammettere!</b>
|
||||||
|
|
||||||
|
Ciao ${invitante.name || invitante.username}!
|
||||||
|
|
||||||
|
L'utente che hai invitato ha completato la registrazione:
|
||||||
|
|
||||||
|
👤 <b>Nome:</b> ${utente.name || 'Non specificato'}
|
||||||
|
🔑 <b>Username:</b> ${utente.username}
|
||||||
|
📧 <b>Email:</b> ${utente.email}
|
||||||
|
📅 <b>Registrato il:</b> ${new Date(utente.created_at).toLocaleDateString('it-IT')}
|
||||||
|
|
||||||
|
✅ <b>Azione richiesta:</b> Accedi alla piattaforma per ammettere questo utente alla comunità RISO.
|
||||||
|
|
||||||
|
<a href="${this.baseUrl}/admin/ammetti-utente/${utente.id}">👉 Clicca qui per ammettere</a>
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
// Invia notifica all'invitante
|
||||||
|
const risultatiInvitante = await this._inviaNotificaDoppia(
|
||||||
|
{
|
||||||
|
email: invitante.email,
|
||||||
|
telegramId: invitante.teleg_id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
oggetto: `Nuovo utente da ammettere: ${utente.username}`,
|
||||||
|
templatePath: this.emailTemplates.richiestaAmmissione,
|
||||||
|
templateData: templateDataInvitante,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
messaggio: messaggioTelegramInvitante,
|
||||||
|
opzioni: {
|
||||||
|
reply_markup: {
|
||||||
|
inline_keyboard: [
|
||||||
|
[{ text: '✅ Ammetti Utente', url: `${this.baseUrl}/admin/ammetti-utente/${utente.id}` }],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Notifica admin
|
||||||
|
const messaggioAdmin = `
|
||||||
|
✅ <b>Email Verificata - Richiesta Ammissione</b>
|
||||||
|
|
||||||
|
👤 <b>Utente:</b> ${utente.username} (${utente.email})
|
||||||
|
👥 <b>Invitante:</b> ${invitante.username} (${invitante.email})
|
||||||
|
📅 <b>Data verifica:</b> ${new Date().toLocaleString('it-IT')}
|
||||||
|
|
||||||
|
📧 Notifica inviata all'invitante
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
await this._inviaCopiaCopiaAdmin('Richiesta Ammissione', messaggioAdmin);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Notifica richiesta ammissione inviata',
|
||||||
|
risultatiInvitante,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Errore in notificaRichiestaAmmissione:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// EVENTO 3: UTENTE AMMESSO
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invia email di benvenuto all'utente ammesso
|
||||||
|
* @param {Object} utente - Dati dell'utente ammesso
|
||||||
|
*/
|
||||||
|
async notificaUtenteAmmesso(utente) {
|
||||||
|
try {
|
||||||
|
this.logger.info(`Notifica utente ammesso: ${utente.username}`);
|
||||||
|
|
||||||
|
// Dati per email benvenuto
|
||||||
|
const templateData = {
|
||||||
|
name: utente.name,
|
||||||
|
username: utente.username,
|
||||||
|
emailto: utente.email,
|
||||||
|
nomeapp: this.nomeApp,
|
||||||
|
baseurl: this.baseUrl,
|
||||||
|
strlinksito: this.baseUrl,
|
||||||
|
forgetpwd: `${this.baseUrl}/reset-password`,
|
||||||
|
verified_email: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Invia email di benvenuto (template già esistente)
|
||||||
|
const risultatoEmail = await this._inviaEmail(
|
||||||
|
utente.email,
|
||||||
|
`💚 Benvenuto nella comunità ${this.nomeApp}!`,
|
||||||
|
null,
|
||||||
|
this.emailTemplates.benvenuto,
|
||||||
|
templateData
|
||||||
|
);
|
||||||
|
|
||||||
|
// Messaggio Telegram all'utente (se ha già Telegram collegato)
|
||||||
|
if (utente.teleg_id && utente.teleg_id !== 0) {
|
||||||
|
const messaggioTelegramUtente = `
|
||||||
|
🎉 <b>Benvenuto nella comunità RISO!</b>
|
||||||
|
|
||||||
|
Ciao ${utente.name || utente.username}!
|
||||||
|
|
||||||
|
Sei stato ammesso nella rete RISO! 🌱
|
||||||
|
|
||||||
|
Ora puoi:
|
||||||
|
✅ Completare il tuo profilo
|
||||||
|
📢 Pubblicare i tuoi primi annunci
|
||||||
|
🔍 Esplorare beni e servizi nella tua comunità
|
||||||
|
💬 Unirti al gruppo territoriale
|
||||||
|
|
||||||
|
<a href="${this.baseUrl}">👉 Accedi ora alla piattaforma</a>
|
||||||
|
|
||||||
|
Costruiamo insieme un'economia più umana e solidale! 💚
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
await this._inviaTelegram(utente.teleg_id, messaggioTelegramUtente, {
|
||||||
|
reply_markup: {
|
||||||
|
inline_keyboard: [[{ text: '🚀 Vai alla Piattaforma', url: this.baseUrl }]],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notifica admin
|
||||||
|
const messaggioAdmin = `
|
||||||
|
✅ <b>Utente Ammesso</b>
|
||||||
|
|
||||||
|
👤 <b>Username:</b> ${utente.username}
|
||||||
|
📧 <b>Email:</b> ${utente.email}
|
||||||
|
${utente.name ? `🏷️ <b>Nome:</b> ${utente.name}` : ''}
|
||||||
|
📅 <b>Ammesso il:</b> ${new Date().toLocaleString('it-IT')}
|
||||||
|
|
||||||
|
📧 Email di benvenuto inviata
|
||||||
|
${utente.teleg_id && utente.teleg_id !== 0 ? '📱 Notifica Telegram inviata' : '⏳ Telegram non ancora collegato'}
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
await this._inviaCopiaCopiaAdmin('Utente Ammesso', messaggioAdmin);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Notifica utente ammesso inviata',
|
||||||
|
risultatoEmail,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Errore in notificaUtenteAmmesso:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// EVENTO 4: PROFILO COMPLETATO + TELEGRAM VERIFICATO
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifica l'invitante che l'utente ha completato il profilo e verificato Telegram
|
||||||
|
* @param {Object} utente - Dati dell'utente che ha completato il profilo
|
||||||
|
*/
|
||||||
|
async notificaProfiloCompletato(utente) {
|
||||||
|
try {
|
||||||
|
this.logger.info(`Notifica profilo completato per utente: ${utente.username}`);
|
||||||
|
|
||||||
|
// Verifica che il profilo sia effettivamente completo e Telegram verificato
|
||||||
|
if (!utente.teleg_id || utente.teleg_id === 0) {
|
||||||
|
this.logger.warn(`Telegram non verificato per ${utente.username}`);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'Telegram non ancora verificato',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recupera invitante
|
||||||
|
const invitante = await this._getInvitante(utente.invitante_id);
|
||||||
|
|
||||||
|
if (!invitante) {
|
||||||
|
throw new Error(`Invitante non trovato per utente ${utente.username}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dati per email invitante
|
||||||
|
const templateDataInvitante = {
|
||||||
|
nomeInvitante: invitante.name || invitante.username,
|
||||||
|
nomeUtente: utente.name || utente.username,
|
||||||
|
usernameUtente: utente.username,
|
||||||
|
nomeapp: this.nomeApp,
|
||||||
|
baseurl: this.baseUrl,
|
||||||
|
linkProfilo: `${this.baseUrl}/utenti/${utente.username}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Messaggio Telegram per invitante
|
||||||
|
const messaggioTelegramInvitante = `
|
||||||
|
🎊 <b>Profilo Completato!</b>
|
||||||
|
|
||||||
|
Ciao ${invitante.name || invitante.username}!
|
||||||
|
|
||||||
|
L'utente che hai invitato ha completato il suo profilo ed è ora attivo su RISO:
|
||||||
|
|
||||||
|
👤 <b>Nome:</b> ${utente.name || utente.username}
|
||||||
|
🔑 <b>Username:</b> @${utente.username}
|
||||||
|
✅ <b>Telegram:</b> Verificato
|
||||||
|
📱 <b>Profilo:</b> Completato
|
||||||
|
|
||||||
|
L'utente è ora un membro attivo della comunità e può iniziare a pubblicare annunci!
|
||||||
|
|
||||||
|
<a href="${this.baseUrl}/utenti/${utente.username}">👉 Visualizza profilo</a>
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
// Invia notifica all'invitante
|
||||||
|
const risultatiInvitante = await this._inviaNotificaDoppia(
|
||||||
|
{
|
||||||
|
email: invitante.email,
|
||||||
|
telegramId: invitante.teleg_id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
oggetto: `${utente.username} ha completato il profilo su ${this.nomeApp}!`,
|
||||||
|
templatePath: this.emailTemplates.profiloCompletato,
|
||||||
|
templateData: templateDataInvitante,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
messaggio: messaggioTelegramInvitante,
|
||||||
|
opzioni: {
|
||||||
|
reply_markup: {
|
||||||
|
inline_keyboard: [[{ text: '👤 Visualizza Profilo', url: `${this.baseUrl}/utenti/${utente.username}` }]],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Notifica admin
|
||||||
|
const messaggioAdmin = `
|
||||||
|
✅ <b>Profilo Completato</b>
|
||||||
|
|
||||||
|
👤 <b>Utente:</b> ${utente.username}
|
||||||
|
📧 <b>Email:</b> ${utente.email}
|
||||||
|
📱 <b>Telegram:</b> Verificato (ID: ${utente.teleg_id})
|
||||||
|
👥 <b>Invitante:</b> ${invitante.username}
|
||||||
|
📅 <b>Data:</b> ${new Date().toLocaleString('it-IT')}
|
||||||
|
|
||||||
|
✅ L'utente è ora completamente attivo sulla piattaforma
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
await this._inviaCopiaCopiaAdmin('Profilo Completato', messaggioAdmin);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Notifica profilo completato inviata',
|
||||||
|
risultatiInvitante,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Errore in notificaProfiloCompletato:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// METODI HELPER
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recupera i dati dell'invitante (da implementare con il tuo DB)
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _getInvitante(invitanteId) {
|
||||||
|
// TODO: Implementa il recupero dell'invitante dal database
|
||||||
|
// Esempio con MongoDB:
|
||||||
|
// return await User.findById(invitanteId);
|
||||||
|
|
||||||
|
// Esempio con MySQL/PostgreSQL:
|
||||||
|
// return await db.query('SELECT * FROM users WHERE id = ?', [invitanteId]);
|
||||||
|
|
||||||
|
throw new Error('Metodo _getInvitante non implementato. Implementalo con il tuo database.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica se il profilo utente è completo
|
||||||
|
* @param {Object} utente
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
isProfiloCompleto(utente) {
|
||||||
|
// Definisci i criteri per considerare un profilo completo
|
||||||
|
return !!(
|
||||||
|
(utente.name && utente.email && utente.teleg_id && utente.teleg_id !== 0)
|
||||||
|
// Aggiungi altri campi necessari
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metodo principale per orchestrare le notifiche basate sugli eventi
|
||||||
|
* @param {String} evento - Tipo di evento
|
||||||
|
* @param {Object} dati - Dati dell'evento
|
||||||
|
*/
|
||||||
|
async gestisciEvento(evento, dati) {
|
||||||
|
try {
|
||||||
|
switch (evento) {
|
||||||
|
case 'REGISTRAZIONE':
|
||||||
|
return await this.notificaRegistrazione(dati.utente, dati.tokenVerifica);
|
||||||
|
|
||||||
|
case 'EMAIL_VERIFICATA':
|
||||||
|
return await this.notificaRichiestaAmmissione(dati.utente);
|
||||||
|
|
||||||
|
case 'UTENTE_AMMESSO':
|
||||||
|
return await this.notificaUtenteAmmesso(dati.utente);
|
||||||
|
|
||||||
|
case 'PROFILO_COMPLETATO':
|
||||||
|
// Verifica che Telegram sia effettivamente verificato
|
||||||
|
if (dati.utente.teleg_id && dati.utente.teleg_id !== 0) {
|
||||||
|
return await this.notificaProfiloCompletato(dati.utente);
|
||||||
|
} else {
|
||||||
|
this.logger.warn(`Profilo completato ma Telegram non verificato per ${dati.utente.username}`);
|
||||||
|
return { success: false, message: 'Telegram non verificato' };
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Evento non riconosciuto: ${evento}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Errore gestione evento ${evento}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = InvioNotifiche;
|
||||||
541
src/modules/InvioNotifiche.test.js
Normal file
541
src/modules/InvioNotifiche.test.js
Normal file
@@ -0,0 +1,541 @@
|
|||||||
|
/**
|
||||||
|
* TEST UNITARI PER LA CLASSE InvioNotifiche
|
||||||
|
*
|
||||||
|
* Framework: Jest
|
||||||
|
* Installazione: npm install --save-dev jest
|
||||||
|
* Esecuzione: npm test
|
||||||
|
*/
|
||||||
|
|
||||||
|
const InvioNotifiche = require('./InvioNotifiche');
|
||||||
|
|
||||||
|
// Mock dei servizi esterni
|
||||||
|
const mockEmailService = {
|
||||||
|
sendMail: jest.fn().mockResolvedValue({ messageId: 'mock-email-id' })
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTelegramBot = {
|
||||||
|
sendMessage: jest.fn().mockResolvedValue({ message_id: 123 })
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockLogger = {
|
||||||
|
info: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
error: jest.fn()
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('InvioNotifiche', () => {
|
||||||
|
let notifiche;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Reset dei mock prima di ogni test
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
// Inizializza classe con mock
|
||||||
|
notifiche = new InvioNotifiche({
|
||||||
|
emailService: mockEmailService,
|
||||||
|
telegramBot: mockTelegramBot,
|
||||||
|
adminTelegramId: '999999999',
|
||||||
|
adminEmail: 'paolo@riso.app',
|
||||||
|
baseUrl: 'https://riso.app',
|
||||||
|
nomeApp: 'RISO',
|
||||||
|
emailTemplates: {
|
||||||
|
verificaEmail: './templates/verifica.pug',
|
||||||
|
richiestaAmmissione: './templates/ammissione.pug',
|
||||||
|
benvenuto: './templates/benvenuto.pug',
|
||||||
|
profiloCompletato: './templates/profilo.pug'
|
||||||
|
},
|
||||||
|
logger: mockLogger
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock del metodo _getInvitante
|
||||||
|
notifiche._getInvitante = jest.fn().mockResolvedValue({
|
||||||
|
id: 5,
|
||||||
|
username: 'invitante',
|
||||||
|
email: 'invitante@example.com',
|
||||||
|
name: 'Marco Invitante',
|
||||||
|
teleg_id: 111111111
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TEST: notificaRegistrazione
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
describe('notificaRegistrazione', () => {
|
||||||
|
|
||||||
|
test('Invia email di verifica quando verified_email = false', async () => {
|
||||||
|
const utente = {
|
||||||
|
id: 1,
|
||||||
|
username: 'mario.rossi',
|
||||||
|
email: 'mario@example.com',
|
||||||
|
name: 'Mario Rossi',
|
||||||
|
verified_email: false,
|
||||||
|
invitante_id: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await notifiche.notificaRegistrazione(utente, 'token123');
|
||||||
|
|
||||||
|
// Verifica che l'email sia stata inviata
|
||||||
|
expect(mockEmailService.sendMail).toHaveBeenCalledTimes(2); // utente + admin
|
||||||
|
expect(result.emailVerificaInviata).toBe(true);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
|
||||||
|
// Verifica logging
|
||||||
|
expect(mockLogger.info).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('Notifica registrazione')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Salta verifica email quando verified_email = true', async () => {
|
||||||
|
const utente = {
|
||||||
|
id: 1,
|
||||||
|
username: 'mario.rossi',
|
||||||
|
email: 'mario@example.com',
|
||||||
|
name: 'Mario Rossi',
|
||||||
|
verified_email: true,
|
||||||
|
invitante_id: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await notifiche.notificaRegistrazione(utente, null);
|
||||||
|
|
||||||
|
// Verifica che NON sia stata inviata email di verifica
|
||||||
|
expect(result.emailVerificaInviata).toBe(false);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
|
||||||
|
// Verifica che sia stata chiamata notificaRichiestaAmmissione
|
||||||
|
expect(notifiche._getInvitante).toHaveBeenCalledWith(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Gestisce errori correttamente', async () => {
|
||||||
|
mockEmailService.sendMail.mockRejectedValueOnce(new Error('SMTP Error'));
|
||||||
|
|
||||||
|
const utente = {
|
||||||
|
id: 1,
|
||||||
|
username: 'mario.rossi',
|
||||||
|
email: 'mario@example.com',
|
||||||
|
verified_email: false,
|
||||||
|
invitante_id: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
notifiche.notificaRegistrazione(utente, 'token123')
|
||||||
|
).rejects.toThrow();
|
||||||
|
|
||||||
|
expect(mockLogger.error).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TEST: notificaRichiestaAmmissione
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
describe('notificaRichiestaAmmissione', () => {
|
||||||
|
|
||||||
|
test('Invia notifica email e telegram all\'invitante', async () => {
|
||||||
|
const utente = {
|
||||||
|
id: 1,
|
||||||
|
username: 'mario.rossi',
|
||||||
|
email: 'mario@example.com',
|
||||||
|
name: 'Mario Rossi',
|
||||||
|
invitante_id: 5,
|
||||||
|
created_at: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await notifiche.notificaRichiestaAmmissione(utente);
|
||||||
|
|
||||||
|
// Verifica chiamate
|
||||||
|
expect(notifiche._getInvitante).toHaveBeenCalledWith(5);
|
||||||
|
expect(mockEmailService.sendMail).toHaveBeenCalled();
|
||||||
|
expect(mockTelegramBot.sendMessage).toHaveBeenCalled();
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Gestisce invitante non trovato', async () => {
|
||||||
|
notifiche._getInvitante.mockResolvedValueOnce(null);
|
||||||
|
|
||||||
|
const utente = {
|
||||||
|
id: 1,
|
||||||
|
username: 'mario.rossi',
|
||||||
|
invitante_id: 999
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
notifiche.notificaRichiestaAmmissione(utente)
|
||||||
|
).rejects.toThrow('Invitante non trovato');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Invia notifica anche se Telegram fallisce', async () => {
|
||||||
|
mockTelegramBot.sendMessage.mockRejectedValueOnce(new Error('Telegram Error'));
|
||||||
|
|
||||||
|
const utente = {
|
||||||
|
id: 1,
|
||||||
|
username: 'mario.rossi',
|
||||||
|
email: 'mario@example.com',
|
||||||
|
invitante_id: 5,
|
||||||
|
created_at: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await notifiche.notificaRichiestaAmmissione(utente);
|
||||||
|
|
||||||
|
// Email dovrebbe essere comunque inviata
|
||||||
|
expect(mockEmailService.sendMail).toHaveBeenCalled();
|
||||||
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('Errore invio Telegram'),
|
||||||
|
expect.any(Error)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TEST: notificaUtenteAmmesso
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
describe('notificaUtenteAmmesso', () => {
|
||||||
|
|
||||||
|
test('Invia email di benvenuto all\'utente', async () => {
|
||||||
|
const utente = {
|
||||||
|
id: 1,
|
||||||
|
username: 'mario.rossi',
|
||||||
|
email: 'mario@example.com',
|
||||||
|
name: 'Mario Rossi',
|
||||||
|
teleg_id: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await notifiche.notificaUtenteAmmesso(utente);
|
||||||
|
|
||||||
|
expect(mockEmailService.sendMail).toHaveBeenCalled();
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
|
||||||
|
// Verifica oggetto email
|
||||||
|
const emailCall = mockEmailService.sendMail.mock.calls[0][0];
|
||||||
|
expect(emailCall.subject).toContain('Benvenuto');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Invia anche Telegram se utente ha teleg_id', async () => {
|
||||||
|
const utente = {
|
||||||
|
id: 1,
|
||||||
|
username: 'mario.rossi',
|
||||||
|
email: 'mario@example.com',
|
||||||
|
name: 'Mario Rossi',
|
||||||
|
teleg_id: 123456789
|
||||||
|
};
|
||||||
|
|
||||||
|
await notifiche.notificaUtenteAmmesso(utente);
|
||||||
|
|
||||||
|
expect(mockTelegramBot.sendMessage).toHaveBeenCalledWith(
|
||||||
|
123456789,
|
||||||
|
expect.stringContaining('Benvenuto'),
|
||||||
|
expect.any(Object)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Salta Telegram se teleg_id = 0', async () => {
|
||||||
|
const utente = {
|
||||||
|
id: 1,
|
||||||
|
username: 'mario.rossi',
|
||||||
|
email: 'mario@example.com',
|
||||||
|
teleg_id: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
await notifiche.notificaUtenteAmmesso(utente);
|
||||||
|
|
||||||
|
// Solo email, no Telegram
|
||||||
|
expect(mockTelegramBot.sendMessage).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TEST: notificaProfiloCompletato
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
describe('notificaProfiloCompletato', () => {
|
||||||
|
|
||||||
|
test('Invia notifica se Telegram è verificato', async () => {
|
||||||
|
const utente = {
|
||||||
|
id: 1,
|
||||||
|
username: 'mario.rossi',
|
||||||
|
email: 'mario@example.com',
|
||||||
|
name: 'Mario Rossi',
|
||||||
|
teleg_id: 123456789,
|
||||||
|
invitante_id: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await notifiche.notificaProfiloCompletato(utente);
|
||||||
|
|
||||||
|
expect(notifiche._getInvitante).toHaveBeenCalledWith(5);
|
||||||
|
expect(mockEmailService.sendMail).toHaveBeenCalled();
|
||||||
|
expect(mockTelegramBot.sendMessage).toHaveBeenCalled();
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Non invia se Telegram non verificato', async () => {
|
||||||
|
const utente = {
|
||||||
|
id: 1,
|
||||||
|
username: 'mario.rossi',
|
||||||
|
email: 'mario@example.com',
|
||||||
|
teleg_id: 0,
|
||||||
|
invitante_id: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await notifiche.notificaProfiloCompletato(utente);
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
expect(result.message).toContain('Telegram non ancora verificato');
|
||||||
|
expect(mockEmailService.sendMail).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TEST: gestisciEvento
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
describe('gestisciEvento', () => {
|
||||||
|
|
||||||
|
test('Gestisce evento REGISTRAZIONE', async () => {
|
||||||
|
const spy = jest.spyOn(notifiche, 'notificaRegistrazione');
|
||||||
|
|
||||||
|
await notifiche.gestisciEvento('REGISTRAZIONE', {
|
||||||
|
utente: { username: 'test' },
|
||||||
|
tokenVerifica: 'token123'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Gestisce evento EMAIL_VERIFICATA', async () => {
|
||||||
|
const spy = jest.spyOn(notifiche, 'notificaRichiestaAmmissione');
|
||||||
|
|
||||||
|
await notifiche.gestisciEvento('EMAIL_VERIFICATA', {
|
||||||
|
utente: { username: 'test', invitante_id: 5 }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Gestisce evento UTENTE_AMMESSO', async () => {
|
||||||
|
const spy = jest.spyOn(notifiche, 'notificaUtenteAmmesso');
|
||||||
|
|
||||||
|
await notifiche.gestisciEvento('UTENTE_AMMESSO', {
|
||||||
|
utente: { username: 'test' }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Gestisce evento PROFILO_COMPLETATO solo se Telegram verificato', async () => {
|
||||||
|
const spy = jest.spyOn(notifiche, 'notificaProfiloCompletato');
|
||||||
|
|
||||||
|
// Con Telegram verificato
|
||||||
|
await notifiche.gestisciEvento('PROFILO_COMPLETATO', {
|
||||||
|
utente: { username: 'test', teleg_id: 123, invitante_id: 5 }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
spy.mockClear();
|
||||||
|
|
||||||
|
// Senza Telegram
|
||||||
|
const result = await notifiche.gestisciEvento('PROFILO_COMPLETATO', {
|
||||||
|
utente: { username: 'test', teleg_id: 0 }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Lancia errore per evento non riconosciuto', async () => {
|
||||||
|
await expect(
|
||||||
|
notifiche.gestisciEvento('EVENTO_INESISTENTE', {})
|
||||||
|
).rejects.toThrow('Evento non riconosciuto');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TEST: isProfiloCompleto
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
describe('isProfiloCompleto', () => {
|
||||||
|
|
||||||
|
test('Ritorna true se profilo completo', () => {
|
||||||
|
const utente = {
|
||||||
|
name: 'Mario Rossi',
|
||||||
|
email: 'mario@example.com',
|
||||||
|
teleg_id: 123456789
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(notifiche.isProfiloCompleto(utente)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Ritorna false se manca name', () => {
|
||||||
|
const utente = {
|
||||||
|
email: 'mario@example.com',
|
||||||
|
teleg_id: 123456789
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(notifiche.isProfiloCompleto(utente)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Ritorna false se manca email', () => {
|
||||||
|
const utente = {
|
||||||
|
name: 'Mario Rossi',
|
||||||
|
teleg_id: 123456789
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(notifiche.isProfiloCompleto(utente)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Ritorna false se Telegram non verificato', () => {
|
||||||
|
const utente = {
|
||||||
|
name: 'Mario Rossi',
|
||||||
|
email: 'mario@example.com',
|
||||||
|
teleg_id: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(notifiche.isProfiloCompleto(utente)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TEST: Metodi Privati
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
describe('Metodi privati', () => {
|
||||||
|
|
||||||
|
test('_inviaEmail invia email correttamente', async () => {
|
||||||
|
const result = await notifiche._inviaEmail(
|
||||||
|
'test@example.com',
|
||||||
|
'Test Subject',
|
||||||
|
'<p>Test HTML</p>'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(mockEmailService.sendMail).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
to: 'test@example.com',
|
||||||
|
subject: 'Test Subject',
|
||||||
|
html: '<p>Test HTML</p>'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('_inviaTelegram invia messaggio correttamente', async () => {
|
||||||
|
const result = await notifiche._inviaTelegram(
|
||||||
|
123456789,
|
||||||
|
'Test message'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(mockTelegramBot.sendMessage).toHaveBeenCalledWith(
|
||||||
|
123456789,
|
||||||
|
'Test message',
|
||||||
|
expect.any(Object)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('_inviaTelegram gestisce ID non valido', async () => {
|
||||||
|
const result = await notifiche._inviaTelegram(0, 'Test');
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
expect(result.error).toContain('non valido');
|
||||||
|
expect(mockLogger.warn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('_inviaCopiaCopiaAdmin invia a email e telegram admin', async () => {
|
||||||
|
await notifiche._inviaCopiaCopiaAdmin('Test', 'Message');
|
||||||
|
|
||||||
|
expect(mockEmailService.sendMail).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
to: 'admin@riso.app',
|
||||||
|
subject: '[ADMIN] Test'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockTelegramBot.sendMessage).toHaveBeenCalledWith(
|
||||||
|
'999999999',
|
||||||
|
expect.stringContaining('NOTIFICA ADMIN'),
|
||||||
|
expect.any(Object)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TEST: Integrazione
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
describe('Test di Integrazione', () => {
|
||||||
|
let notifiche;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
notifiche = new InvioNotifiche({
|
||||||
|
emailService: mockEmailService,
|
||||||
|
telegramBot: mockTelegramBot,
|
||||||
|
adminTelegramId: '999999999',
|
||||||
|
adminEmail: 'admin@riso.app',
|
||||||
|
baseUrl: 'https://riso.app',
|
||||||
|
nomeApp: 'RISO',
|
||||||
|
emailTemplates: {},
|
||||||
|
logger: mockLogger
|
||||||
|
});
|
||||||
|
|
||||||
|
notifiche._getInvitante = jest.fn().mockResolvedValue({
|
||||||
|
id: 5,
|
||||||
|
username: 'invitante',
|
||||||
|
email: 'invitante@example.com',
|
||||||
|
teleg_id: 111111111
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Flusso completo: Registrazione → Verifica → Ammissione → Profilo', async () => {
|
||||||
|
const utente = {
|
||||||
|
id: 1,
|
||||||
|
username: 'mario.rossi',
|
||||||
|
email: 'mario@example.com',
|
||||||
|
name: 'Mario Rossi',
|
||||||
|
verified_email: false,
|
||||||
|
invitante_id: 5,
|
||||||
|
teleg_id: 0,
|
||||||
|
created_at: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. Registrazione
|
||||||
|
await notifiche.notificaRegistrazione(utente, 'token123');
|
||||||
|
expect(mockEmailService.sendMail).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// 2. Verifica email
|
||||||
|
utente.verified_email = true;
|
||||||
|
await notifiche.notificaRichiestaAmmissione(utente);
|
||||||
|
expect(notifiche._getInvitante).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// 3. Ammissione
|
||||||
|
await notifiche.notificaUtenteAmmesso(utente);
|
||||||
|
expect(mockEmailService.sendMail).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// 4. Profilo completato
|
||||||
|
utente.teleg_id = 123456789;
|
||||||
|
await notifiche.notificaProfiloCompletato(utente);
|
||||||
|
expect(mockTelegramBot.sendMessage).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Verifica che tutte le notifiche admin siano state inviate
|
||||||
|
const adminCalls = mockEmailService.sendMail.mock.calls.filter(
|
||||||
|
call => call[0].subject.includes('[ADMIN]')
|
||||||
|
);
|
||||||
|
expect(adminCalls.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configurazione package.json per Jest
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"test:coverage": "jest --coverage"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"coveragePathIgnorePatterns": ["/node_modules/"],
|
||||||
|
"testMatch": ["**/__tests__/**/*.js", "**/?(*.)+(spec|test).js"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -2303,7 +2303,7 @@ router.post('/exec', authenticate, async (req, res) => {
|
|||||||
|
|
||||||
if (!User.isAdmin(req.user.perm) || tokcheck !== TOKCHECK) {
|
if (!User.isAdmin(req.user.perm) || tokcheck !== TOKCHECK) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = '';
|
let result = '';
|
||||||
@@ -2335,7 +2335,7 @@ router.post('/cloudflare', authenticate, async (req, res) => {
|
|||||||
|
|
||||||
if (!User.isAdmin(req.user.perm) || tokcheck !== TOKCHECK) {
|
if (!User.isAdmin(req.user.perm) || tokcheck !== TOKCHECK) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = '';
|
let result = '';
|
||||||
@@ -2403,7 +2403,7 @@ router.post('/miab', authenticate, async (req, res) => {
|
|||||||
|
|
||||||
if (!User.isAdmin(req.user.perm) || tokcheck !== TOKCHECK) {
|
if (!User.isAdmin(req.user.perm) || tokcheck !== TOKCHECK) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = '';
|
let result = '';
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ router.post('/', authenticate, async (req, res) => {
|
|||||||
|
|
||||||
if ((!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm)) && (username) !== req.user.username) {
|
if ((!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm)) && (username) !== req.user.username) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
let aportador_solidario = req.user.aportador_solidario;
|
let aportador_solidario = req.user.aportador_solidario;
|
||||||
let aportador_solidario_nome_completo = req.user.aportador_solidario_nome_completo;
|
let aportador_solidario_nome_completo = req.user.aportador_solidario_nome_completo;
|
||||||
@@ -55,7 +55,7 @@ router.post('/downline', authenticate, async (req, res) => {
|
|||||||
|
|
||||||
if ((!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm)) && (username) !== req.user.username) {
|
if ((!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm)) && (username) !== req.user.username) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
let aportador_solidario = req.user.aportador_solidario;
|
let aportador_solidario = req.user.aportador_solidario;
|
||||||
let aportador_solidario_nome_completo = req.user.aportador_solidario_nome_completo;
|
let aportador_solidario_nome_completo = req.user.aportador_solidario_nome_completo;
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ router.post('/sendmailreg', authenticate, async (req, res) => {
|
|||||||
const diffTime = Math.abs(now - user.lasttime_email_sent_verify);
|
const diffTime = Math.abs(now - user.lasttime_email_sent_verify);
|
||||||
const diffMinutes = Math.ceil(diffTime / (1000 * 60));
|
const diffMinutes = Math.ceil(diffTime / (1000 * 60));
|
||||||
|
|
||||||
if (diffMinutes < 1) {
|
if (diffMinutes < 5) {
|
||||||
return res.status(200).send({ email_inviata: false, error: 'Attendi qualche minuto prima di reinviare nuovamente.' });
|
return res.status(200).send({ email_inviata: false, error: 'Attendi qualche minuto prima di reinviare nuovamente.' });
|
||||||
}
|
}
|
||||||
ris = await sendemail.sendEmail_Registration(user.lang, user.email, user, user.idapp, user.linkreg);
|
ris = await sendemail.sendEmail_Registration(user.lang, user.email, user, user.idapp, user.linkreg);
|
||||||
@@ -396,7 +396,7 @@ router.post('/settable', authenticate, async (req, res) => {
|
|||||||
(await !tools.ModificheConsentite(req, params.table, fieldsvalue, mydata ? mydata._id : ''))
|
(await !tools.ModificheConsentite(req, params.table, fieldsvalue, mydata ? mydata._id : ''))
|
||||||
) {
|
) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shared_consts.TABLES_USER_ID.includes(params.table)) {
|
if (shared_consts.TABLES_USER_ID.includes(params.table)) {
|
||||||
@@ -827,7 +827,7 @@ router.post('/getexp', authenticate, (req, res) => {
|
|||||||
|
|
||||||
if (!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm) && !User.isFacilitatore(req.user.perm)) {
|
if (!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm) && !User.isFacilitatore(req.user.perm)) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1334,7 +1334,7 @@ router.patch('/chval', authenticate, async (req, res) => {
|
|||||||
!(mydata.table === 'accounts' && (await Account.canEditAccountAdmins(req.user.username, mydata.id)))
|
!(mydata.table === 'accounts' && (await Account.canEditAccountAdmins(req.user.username, mydata.id)))
|
||||||
) {
|
) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const camporequisiti = UserCost.FIELDS_REQUISITI.includes(Object.keys(fieldsvalue)[0]);
|
const camporequisiti = UserCost.FIELDS_REQUISITI.includes(Object.keys(fieldsvalue)[0]);
|
||||||
@@ -1617,7 +1617,7 @@ router.patch('/askfunz', authenticate, async (req, res) => {
|
|||||||
req.user._id.toString() !== id
|
req.user._id.toString() !== id
|
||||||
) {
|
) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1676,7 +1676,7 @@ router.patch('/callfunz', authenticate, async (req, res) => {
|
|||||||
req.user._id.toString() !== id
|
req.user._id.toString() !== id
|
||||||
) {
|
) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1755,7 +1755,7 @@ router.delete('/delrec/:table/:id', authenticate, async (req, res) => {
|
|||||||
(await !tools.ModificheConsentite(req, tablename, fields, id, req.user))
|
(await !tools.ModificheConsentite(req, tablename, fields, id, req.user))
|
||||||
) {
|
) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
let cancellato = false;
|
let cancellato = false;
|
||||||
@@ -1863,7 +1863,7 @@ router.post('/duprec/:table/:id', authenticate, async (req, res) => {
|
|||||||
const mytable = globalTables.getTableByTableName(tablename);
|
const mytable = globalTables.getTableByTableName(tablename);
|
||||||
|
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if (!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm)) {
|
/* if (!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm)) {
|
||||||
@@ -1902,7 +1902,7 @@ router.post('/duprec/:table/:id', authenticate, async (req, res) => {
|
|||||||
|
|
||||||
router.get('/loadsite/:userId/:idapp', authenticate_noerror_WithUserLean, async (req, res) => {
|
router.get('/loadsite/:userId/:idapp', authenticate_noerror_WithUserLean, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
// if ((req.statuscode2 = server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED)) {
|
// if ((req.statuscode2 = server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED)) {
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@@ -2031,8 +2031,8 @@ async function load(req, res, version = '0') {
|
|||||||
// Estrazione e validazione degli input
|
// Estrazione e validazione degli input
|
||||||
const userId = req.user ? req.user._id.toString() : req.params.userId || '0';
|
const userId = req.user ? req.user._id.toString() : req.params.userId || '0';
|
||||||
const idapp = req.params.idapp;
|
const idapp = req.params.idapp;
|
||||||
/*const status = req.code === server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED
|
/*const status = req.code === server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED
|
||||||
? server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED
|
? server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED
|
||||||
: 200;*/
|
: 200;*/
|
||||||
|
|
||||||
let status = req.code;
|
let status = req.code;
|
||||||
@@ -2251,11 +2251,10 @@ async function load(req, res, version = '0') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get(process.env.LINK_CHECK_UPDATES, authenticate_noerror, async (req, res) => {
|
router.get('/checkupdates', authenticate_noerror, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const idapp = req.query.idapp;
|
const idapp = req.query.idapp;
|
||||||
|
|
||||||
// console.log("POST " + process.env.LINK_CHECK_UPDATES + " userId=" + userId);
|
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
if (req.code === 1) return res.status(200).send();
|
if (req.code === 1) return res.status(200).send();
|
||||||
else return res.status(req.code).send();
|
else return res.status(req.code).send();
|
||||||
|
|||||||
@@ -391,7 +391,7 @@ router.post('/testemail', authenticate, async (req, res) => {
|
|||||||
|
|
||||||
if (!User.isAdmin(req.user.perm)) {
|
if (!User.isAdmin(req.user.perm)) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
idapp = req.body.idapp;
|
idapp = req.body.idapp;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -498,8 +498,19 @@ module.exports = {
|
|||||||
const strlinkreg = tools.getHostByIdApp(idapp) + `/invitetoreg/${dati.token}`;
|
const strlinkreg = tools.getHostByIdApp(idapp) + `/invitetoreg/${dati.token}`;
|
||||||
return strlinkreg;
|
return strlinkreg;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getPathEmail(idapp, email_template) {
|
||||||
|
const RISO_TEMPLATES = ['reg_notifica_all_invitante'];
|
||||||
|
|
||||||
|
if (RISO_TEMPLATES.includes(email_template)) {
|
||||||
|
return tools.RISO_STR_PATH + '/' + email_template;
|
||||||
|
}
|
||||||
|
return 'defaultSite/' + email_template;
|
||||||
|
},
|
||||||
sendEmail_Registration: async function (lang, emailto, user, idapp, idreg) {
|
sendEmail_Registration: async function (lang, emailto, user, idapp, idreg) {
|
||||||
try {
|
try {
|
||||||
|
const nomecognomeInvitante = await User.getNameSurnameByUsername(idapp, user.aportador_solidario, true);
|
||||||
|
|
||||||
let mylocalsconf = {
|
let mylocalsconf = {
|
||||||
idapp,
|
idapp,
|
||||||
dataemail: await this.getdataemail(idapp),
|
dataemail: await this.getdataemail(idapp),
|
||||||
@@ -511,17 +522,39 @@ module.exports = {
|
|||||||
forgetpwd: tools.getHostByIdApp(idapp) + '/requestresetpwd',
|
forgetpwd: tools.getHostByIdApp(idapp) + '/requestresetpwd',
|
||||||
emailto: emailto,
|
emailto: emailto,
|
||||||
verified_email: user.verified_email,
|
verified_email: user.verified_email,
|
||||||
|
usernameInvitante: user.aportador_solidario,
|
||||||
|
nomeInvitante: nomecognomeInvitante.trim(),
|
||||||
|
nomeInvitato: await User.getNameSurnameEUsernameByUsername(idapp, user.username),
|
||||||
user,
|
user,
|
||||||
};
|
};
|
||||||
|
|
||||||
mylocalsconf = this.setParamsForTemplate(user, mylocalsconf);
|
mylocalsconf = this.setParamsForTemplate(user, mylocalsconf);
|
||||||
|
|
||||||
await this.sendEmail_base(
|
let quale_email_inviare = '';
|
||||||
tools.getpathregByIdApp(idapp, lang),
|
|
||||||
emailto,
|
if (user.verified_email) {
|
||||||
mylocalsconf,
|
// se l'utente è già stato verificata la sua email, allora gli mando direttamente la email di invito.
|
||||||
tools.getreplyToEmailByIdApp(idapp)
|
quale_email_inviare = 'reg_email_benvenuto_ammesso/' + lang;
|
||||||
);
|
} else {
|
||||||
|
// altrimenti gli mando l'email con la richiesta di Verifica email
|
||||||
|
quale_email_inviare = tools.getpathregByIdApp(idapp, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Invia una email al nuovo utente
|
||||||
|
await this.sendEmail_base(quale_email_inviare, emailto, mylocalsconf, tools.getreplyToEmailByIdApp(idapp));
|
||||||
|
|
||||||
|
if (user.verified_email && user.aportador_solidario) {
|
||||||
|
const pathemail = this.getPathEmail(idapp, 'reg_notifica_all_invitante');
|
||||||
|
|
||||||
|
// Manda anche una email al suo Invitante
|
||||||
|
const recaportador = await User.getUserShortDataByUsername(idapp, user.aportador_solidario);
|
||||||
|
const ris = await this.sendEmail_base(
|
||||||
|
pathemail + '/' + tools.LANGADMIN,
|
||||||
|
recaportador.email,
|
||||||
|
mylocalsconf,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Send to the Admin an Email
|
// Send to the Admin an Email
|
||||||
const ris = await this.sendEmail_base(
|
const ris = await this.sendEmail_base(
|
||||||
@@ -539,7 +572,7 @@ module.exports = {
|
|||||||
let aportador = mylocalsconf.aportador_solidario ? ' (da ' + mylocalsconf.aportador_solidario + ')' : '';
|
let aportador = mylocalsconf.aportador_solidario ? ' (da ' + mylocalsconf.aportador_solidario + ')' : '';
|
||||||
|
|
||||||
const numutenti = await User.getNumUsers(mylocalsconf.idapp);
|
const numutenti = await User.getNumUsers(mylocalsconf.idapp);
|
||||||
tools.sendNotifToAdmin(mylocalsconf.idapp, true, '++Reg [' + numutenti + '] ' + nometot + aportador);
|
tools.sendNotifToAdmin(mylocalsconf.idapp, true, '++Registrazione [' + numutenti + '] ' + nometot + aportador);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ris;
|
return ris;
|
||||||
@@ -548,6 +581,34 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
sendEmail_InvitaAmico: async function (lang, emailto, user, idapp, dati) {
|
sendEmail_InvitaAmico: async function (lang, emailto, user, idapp, dati) {
|
||||||
|
try {
|
||||||
|
const nomecognomeInvitante = await User.getNameSurnameByUsername(idapp, dati.usernameInvitante, true);
|
||||||
|
|
||||||
|
let mylocalsconf = {
|
||||||
|
idapp,
|
||||||
|
dataemail: await this.getdataemail(idapp),
|
||||||
|
baseurl: tools.getHostByIdApp(idapp),
|
||||||
|
locale: lang,
|
||||||
|
nomeapp: tools.getNomeAppByIdApp(idapp),
|
||||||
|
strlinksito: tools.getHostByIdApp(idapp),
|
||||||
|
//strlinkreg: this.getlinkReg(idapp, idreg),
|
||||||
|
linkRegistrazione: this.getlinkInvitoReg(idapp, dati),
|
||||||
|
emailto: emailto,
|
||||||
|
usernameInvitante: dati.usernameInvitante,
|
||||||
|
nomeInvitante: nomecognomeInvitante.trim(),
|
||||||
|
messaggioPersonalizzato: dati.messaggioPersonalizzato,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ris = await this.sendEmail_base('invitaamico/' + lang, emailto, mylocalsconf, '');
|
||||||
|
|
||||||
|
await telegrambot.notifyToTelegram(telegrambot.phase.INVITA_AMICO, mylocalsconf);
|
||||||
|
|
||||||
|
return ris;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Err sendEmail_InvitaAmico', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sendEmail_Utente_Ammesso: async function (lang, emailto, user, idapp, dati) {
|
||||||
try {
|
try {
|
||||||
let mylocalsconf = {
|
let mylocalsconf = {
|
||||||
idapp,
|
idapp,
|
||||||
@@ -560,25 +621,15 @@ module.exports = {
|
|||||||
linkRegistrazione: this.getlinkInvitoReg(idapp, dati),
|
linkRegistrazione: this.getlinkInvitoReg(idapp, dati),
|
||||||
emailto: emailto,
|
emailto: emailto,
|
||||||
usernameInvitante: dati.usernameInvitante,
|
usernameInvitante: dati.usernameInvitante,
|
||||||
messaggioPersonalizzato: dati.messaggioPersonalizzato,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ris = await this.sendEmail_base('invitaamico/' + lang, emailto, mylocalsconf, '');
|
const ris = await this.sendEmail_base('reg_email_benvenuto_ammesso/' + lang, emailto, mylocalsconf, '');
|
||||||
|
|
||||||
// await telegrambot.notifyToTelegram(telegrambot.phase.REGISTRATION, mylocalsconf);
|
await telegrambot.notifyToTelegram(telegrambot.phase.AMMETTI_UTENTE, mylocalsconf);
|
||||||
|
|
||||||
if (tools.getConfSiteOptionEnabledByIdApp(mylocalsconf.idapp, shared_consts.ConfSite.Notif_Reg_Push_Admin)) {
|
|
||||||
const nometot = tools.getNomeCognomeEUserNameByUser(mylocalsconf);
|
|
||||||
|
|
||||||
let aportador = mylocalsconf.aportador_solidario ? ' (da ' + mylocalsconf.aportador_solidario + ')' : '';
|
|
||||||
|
|
||||||
const numutenti = await User.getNumUsers(mylocalsconf.idapp);
|
|
||||||
//tools.sendNotifToAdmin(mylocalsconf.idapp, true, '++Reg [' + numutenti + '] ' + nometot + aportador);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ris;
|
return ris;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Err sendEmail_Registration', e);
|
console.error('Err sendEmail_InvitaAmico', e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1584,7 +1635,7 @@ module.exports = {
|
|||||||
smtpTransport,
|
smtpTransport,
|
||||||
previewonly
|
previewonly
|
||||||
);
|
);
|
||||||
console.log(' ...email inviata?', risult);
|
// console.log(' ...email inviata?', risult);
|
||||||
|
|
||||||
return risult;
|
return risult;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ router.post('/settable', authenticate, async (req, res) => {
|
|||||||
(await !tools.ModificheConsentite(req, params.table, fieldsvalue, mydata ? mydata._id : ''))
|
(await !tools.ModificheConsentite(req, params.table, fieldsvalue, mydata ? mydata._id : ''))
|
||||||
) {
|
) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shared_consts.TABLES_USER_ID.includes(params.table)) {
|
if (shared_consts.TABLES_USER_ID.includes(params.table)) {
|
||||||
@@ -790,7 +790,7 @@ router.post('/getexp', authenticate, (req, res) => {
|
|||||||
|
|
||||||
if (!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm) && !User.isFacilitatore(req.user.perm)) {
|
if (!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm) && !User.isFacilitatore(req.user.perm)) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1293,7 +1293,7 @@ router.patch('/chval', authenticate, async (req, res) => {
|
|||||||
!(mydata.table === 'accounts' && (await Account.canEditAccountAdmins(req.user.username, mydata.id)))
|
!(mydata.table === 'accounts' && (await Account.canEditAccountAdmins(req.user.username, mydata.id)))
|
||||||
) {
|
) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const camporequisiti = UserCost.FIELDS_REQUISITI.includes(Object.keys(fieldsvalue)[0]);
|
const camporequisiti = UserCost.FIELDS_REQUISITI.includes(Object.keys(fieldsvalue)[0]);
|
||||||
@@ -1576,7 +1576,7 @@ router.patch('/askfunz', authenticate, async (req, res) => {
|
|||||||
req.user._id.toString() !== id
|
req.user._id.toString() !== id
|
||||||
) {
|
) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1635,7 +1635,7 @@ router.patch('/callfunz', authenticate, async (req, res) => {
|
|||||||
req.user._id.toString() !== id
|
req.user._id.toString() !== id
|
||||||
) {
|
) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1714,7 +1714,7 @@ router.delete('/delrec/:table/:id', authenticate, async (req, res) => {
|
|||||||
(await !tools.ModificheConsentite(req, tablename, fields, id, req.user))
|
(await !tools.ModificheConsentite(req, tablename, fields, id, req.user))
|
||||||
) {
|
) {
|
||||||
// If without permissions, exit
|
// If without permissions, exit
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
let cancellato = false;
|
let cancellato = false;
|
||||||
@@ -1822,7 +1822,7 @@ router.post('/duprec/:table/:id', authenticate, async (req, res) => {
|
|||||||
const mytable = globalTables.getTableByTableName(tablename);
|
const mytable = globalTables.getTableByTableName(tablename);
|
||||||
|
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if (!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm)) {
|
/* if (!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm)) {
|
||||||
@@ -1986,8 +1986,8 @@ async function load(req, res, version = '0') {
|
|||||||
// Estrazione e validazione degli input
|
// Estrazione e validazione degli input
|
||||||
const userId = req.user ? req.user._id.toString() : req.params.userId || '0';
|
const userId = req.user ? req.user._id.toString() : req.params.userId || '0';
|
||||||
const idapp = req.params.idapp;
|
const idapp = req.params.idapp;
|
||||||
/*const status = req.code === server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED
|
/*const status = req.code === server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED
|
||||||
? server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED
|
? server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED
|
||||||
: 200;*/
|
: 200;*/
|
||||||
|
|
||||||
let status = req.code;
|
let status = req.code;
|
||||||
@@ -2206,7 +2206,7 @@ async function load(req, res, version = '0') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get(process.env.LINK_CHECK_UPDATES, authenticate_noerror, async (req, res) => {
|
router.get('/checkupdates', authenticate_noerror, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const idapp = req.query.idapp;
|
const idapp = req.query.idapp;
|
||||||
|
|
||||||
|
|||||||
240
src/services/AuthService.js
Normal file
240
src/services/AuthService.js
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
const { User } = require('../models/user');
|
||||||
|
const Subscription = require('../models/subscribers');
|
||||||
|
const tools = require('../tools/general');
|
||||||
|
const server_constants = require('../tools/server_constants');
|
||||||
|
const telegrambot = require('../telegram/telegrambot');
|
||||||
|
|
||||||
|
class AuthService {
|
||||||
|
constructor() {
|
||||||
|
this.failedLoginAttempts = {};
|
||||||
|
this.MAX_FAILED_ATTEMPTS = 30;
|
||||||
|
this.BLOCK_DURATION = 30 * 60 * 1000; // 30 minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate user with username and password
|
||||||
|
*/
|
||||||
|
async authenticate(idapp, username, password, req) {
|
||||||
|
try {
|
||||||
|
// Check if user is blocked
|
||||||
|
if (this.isUserBlocked(username)) {
|
||||||
|
const text = `Utente bloccato. Riprova più tardi. (username=${username})`;
|
||||||
|
console.log(text);
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
status: 403,
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
message: text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find user by credentials
|
||||||
|
const user = await User.findByCredentials(idapp, username, password);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return await this._handleFailedLogin(idapp, username, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset failed attempts on successful login
|
||||||
|
delete this.failedLoginAttempts[username];
|
||||||
|
|
||||||
|
// Generate tokens
|
||||||
|
const { token, refreshToken } = await user.generateAuthToken(req);
|
||||||
|
|
||||||
|
// Prepare user data to send
|
||||||
|
const userToSend = this._prepareUserData(user);
|
||||||
|
|
||||||
|
// Check subscription
|
||||||
|
const subsExistonDb = await this._checkSubscription(
|
||||||
|
user._id,
|
||||||
|
req.get('User-Agent')
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: false,
|
||||||
|
token,
|
||||||
|
refreshToken,
|
||||||
|
user: userToSend,
|
||||||
|
subsExistonDb
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in authenticate:', error.message);
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
status: 400,
|
||||||
|
code: server_constants.RIS_CODE_LOGIN_ERR_GENERIC,
|
||||||
|
message: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh authentication token
|
||||||
|
*/
|
||||||
|
async refreshToken(refreshToken, req) {
|
||||||
|
try {
|
||||||
|
if (!refreshToken) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
status: 400,
|
||||||
|
message: 'Refresh token mancante'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.findByRefreshTokenAnyAccess(refreshToken);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
status: 403,
|
||||||
|
message: 'Refresh token non valido'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { token, refreshToken: newRefreshToken } = await user.generateAuthToken(req);
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: false,
|
||||||
|
token,
|
||||||
|
refreshToken: newRefreshToken
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in refreshToken:', error.message);
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
status: 500,
|
||||||
|
message: 'Errore interno del server'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove authentication token (logout)
|
||||||
|
*/
|
||||||
|
async removeToken(user, token) {
|
||||||
|
try {
|
||||||
|
await user.removeToken(token);
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in removeToken:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is blocked due to too many failed attempts
|
||||||
|
*/
|
||||||
|
isUserBlocked(username) {
|
||||||
|
const now = Date.now();
|
||||||
|
return this.failedLoginAttempts[username] &&
|
||||||
|
this.failedLoginAttempts[username] > now;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block user after too many failed attempts
|
||||||
|
*/
|
||||||
|
blockUser(username) {
|
||||||
|
this.failedLoginAttempts[username] = Date.now() + this.BLOCK_DURATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle failed login attempt
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _handleFailedLogin(idapp, username, req) {
|
||||||
|
// Check database for too many wrong attempts
|
||||||
|
const loginCheck = await User.tooManyLoginWrong(idapp, username, true);
|
||||||
|
|
||||||
|
if (loginCheck.troppilogin) {
|
||||||
|
const text = `Troppe richieste di Login ERRATE: ${username} [IP: ${tools.getiPAddressUser(req)}] Tentativi: ${loginCheck.retry_pwd}`;
|
||||||
|
await telegrambot.sendMsgTelegramToTheManagers(idapp, text);
|
||||||
|
console.log('/login', text);
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
status: 400,
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
message: text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await tools.snooze(2000);
|
||||||
|
|
||||||
|
// Track failed attempts in memory
|
||||||
|
if (!this.failedLoginAttempts[username]) {
|
||||||
|
this.failedLoginAttempts[username] = 1;
|
||||||
|
} else {
|
||||||
|
this.failedLoginAttempts[username]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numAttempts = this.failedLoginAttempts[username];
|
||||||
|
|
||||||
|
// Notify after multiple failed attempts
|
||||||
|
if (numAttempts > 2) {
|
||||||
|
const msg = `Tentativo (${numAttempts}) di Login ERRATO [${username}]\n[IP: ${tools.getiPAddressUser(req)}]`;
|
||||||
|
tools.mylogshow(msg);
|
||||||
|
await telegrambot.sendMsgTelegramToTheAdmin(idapp, msg, true);
|
||||||
|
tools.writeErrorLog(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block user after max attempts
|
||||||
|
if (this.failedLoginAttempts[username] >= this.MAX_FAILED_ATTEMPTS) {
|
||||||
|
this.blockUser(username);
|
||||||
|
const text = `Troppi tentativi di accesso falliti. Utente bloccato (${username}) [IP: ${tools.getiPAddressUser(req)}]`;
|
||||||
|
tools.mylogshow(text);
|
||||||
|
await telegrambot.sendMsgTelegramToTheManagers(idapp, text);
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
status: 403,
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
message: text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
status: 401,
|
||||||
|
code: server_constants.RIS_CODE_LOGIN_ERR,
|
||||||
|
message: 'Credenziali non valide'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare user data for response
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_prepareUserData(user) {
|
||||||
|
const shared_consts = require('../tools/shared_nodejs');
|
||||||
|
const userToSend = new User();
|
||||||
|
|
||||||
|
shared_consts.fieldsUserToChange().forEach(field => {
|
||||||
|
userToSend[field] = user[field];
|
||||||
|
});
|
||||||
|
|
||||||
|
return userToSend;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if subscription exists
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _checkSubscription(userId, userAgent) {
|
||||||
|
try {
|
||||||
|
const subscription = await Subscription.findOne({
|
||||||
|
userId,
|
||||||
|
access: 'auth',
|
||||||
|
browser: userAgent
|
||||||
|
}).lean();
|
||||||
|
|
||||||
|
return !!subscription;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking subscription:', error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AuthService;
|
||||||
286
src/services/RegistrationService.js
Normal file
286
src/services/RegistrationService.js
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
const { User } = require('../models/user');
|
||||||
|
const ListaInvitiEmail = require('../models/listainvitiemail');
|
||||||
|
const tools = require('../tools/general');
|
||||||
|
const shared_consts = require('../tools/shared_nodejs');
|
||||||
|
const server_constants = require('../tools/server_constants');
|
||||||
|
const reg = require('../reg/registration');
|
||||||
|
const telegrambot = require('../telegram/telegrambot');
|
||||||
|
const sendemail = require('../sendemail');
|
||||||
|
|
||||||
|
class RegistrationService {
|
||||||
|
/**
|
||||||
|
* Register a new user
|
||||||
|
*/
|
||||||
|
async registerUser(userData, req) {
|
||||||
|
try {
|
||||||
|
const user = new User(userData);
|
||||||
|
|
||||||
|
// Set default aportador if needed
|
||||||
|
this._normalizeAportadorSolidario(user);
|
||||||
|
|
||||||
|
// Generate registration link
|
||||||
|
user.linkreg = reg.getlinkregByEmail(user.idapp, user.email, user.username);
|
||||||
|
user.verified_email = false;
|
||||||
|
|
||||||
|
// Check if user is part of an invitation
|
||||||
|
await this._checkEmailInvitation(user);
|
||||||
|
|
||||||
|
// Set timestamps
|
||||||
|
user.lasttimeonline = new Date();
|
||||||
|
user.date_reg = new Date();
|
||||||
|
user.aportador_iniziale = user.aportador_solidario;
|
||||||
|
|
||||||
|
// Handle registration token
|
||||||
|
const regexpire = req.body['regexpire'];
|
||||||
|
const skipVerification = regexpire ? await User.getifRegTokenIsValid(user.idapp, regexpire) : false;
|
||||||
|
|
||||||
|
if (!tools.getAskToVerifyReg(user.idapp) || skipVerification) {
|
||||||
|
user.verified_by_aportador = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default group
|
||||||
|
const idMyGroupSite = tools.getidMyGroupBySite(user.idapp);
|
||||||
|
user.idMyGroup = idMyGroupSite || '';
|
||||||
|
|
||||||
|
// Validate aportador (inviter)
|
||||||
|
const aportadorValidation = await this._validateAportador(user);
|
||||||
|
if (aportadorValidation.error) {
|
||||||
|
return aportadorValidation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for existing user
|
||||||
|
const existingUserCheck = await this._checkExistingUser(user);
|
||||||
|
if (existingUserCheck.error) {
|
||||||
|
return existingUserCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle already registered but not verified case
|
||||||
|
if (existingUserCheck.notYetVerified) {
|
||||||
|
return await this._handleNotYetVerified(user, existingUserCheck.existingUser, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save new user
|
||||||
|
return await this._saveNewUser(user, req, regexpire);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in registerUser:', error.message);
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
message: error.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize aportador solidario field
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_normalizeAportadorSolidario(user) {
|
||||||
|
if (user.aportador_solidario === 'tuo_username' || user.aportador_solidario === '{username}') {
|
||||||
|
user.aportador_solidario = 'surya1977';
|
||||||
|
}
|
||||||
|
|
||||||
|
user.aportador_solidario = user.aportador_solidario.trim().replace('@', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if email is part of an invitation
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _checkEmailInvitation(user) {
|
||||||
|
const invitation = await ListaInvitiEmail.findOne({ email: user.email });
|
||||||
|
|
||||||
|
if (invitation) {
|
||||||
|
user.verified_email = true;
|
||||||
|
user.verified_by_aportador = true;
|
||||||
|
|
||||||
|
invitation.registered = true;
|
||||||
|
invitation.userIdRegistered = user._id;
|
||||||
|
await invitation.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate aportador solidario (inviter)
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _validateAportador(user) {
|
||||||
|
let id_aportador = await User.getIdByUsername(user.idapp, user.aportador_solidario);
|
||||||
|
|
||||||
|
// Try to find by Telegram username if not found
|
||||||
|
if (!id_aportador) {
|
||||||
|
const userAportador = await User.getUserByUsernameTelegram(user.idapp, user.aportador_solidario);
|
||||||
|
|
||||||
|
if (userAportador) {
|
||||||
|
id_aportador = userAportador._id;
|
||||||
|
user.aportador_solidario = userAportador.username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get correct username (case-sensitive)
|
||||||
|
if (id_aportador) {
|
||||||
|
user.aportador_solidario = await User.getRealUsernameByUsername(user.idapp, user.aportador_solidario);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if aportador is required and valid
|
||||||
|
if (!id_aportador && tools.getAskToVerifyReg(user.idapp)) {
|
||||||
|
const msg = `Il link di registrazione non sembra risultare valido.<br>invitante: ${user.aportador_solidario}<br>username: ${user.username}`;
|
||||||
|
|
||||||
|
await telegrambot.sendMsgTelegramToTheManagers(user.idapp, msg);
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
code: server_constants.RIS_CODE_USER_APORTADOR_NOT_VALID,
|
||||||
|
message: msg,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { error: false, id_aportador };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user already exists
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _checkExistingUser(user) {
|
||||||
|
let notYetVerified = false;
|
||||||
|
let existingUser = null;
|
||||||
|
|
||||||
|
// Check by credentials (username + password)
|
||||||
|
const userByCredentials = await User.findByCredentials(user.idapp, user.username, user.password, true);
|
||||||
|
|
||||||
|
// Check by username
|
||||||
|
const userByUsername = await User.findByUsername(user.idapp, user.username);
|
||||||
|
|
||||||
|
if (userByUsername) {
|
||||||
|
// Check if it's a user waiting for verification
|
||||||
|
if (tools.getAskToVerifyReg(userByUsername.idapp)) {
|
||||||
|
if (!userByUsername.verified_by_aportador && userByUsername.profile.teleg_id > 0 && userByCredentials) {
|
||||||
|
notYetVerified = true;
|
||||||
|
existingUser = userByCredentials;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!notYetVerified) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
code: server_constants.RIS_CODE_USERNAME_ALREADY_EXIST,
|
||||||
|
message: 'Username già esistente',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check by email (only if not waiting for verification)
|
||||||
|
if (!notYetVerified) {
|
||||||
|
const userByEmail = await User.findByEmail(user.idapp, user.email);
|
||||||
|
|
||||||
|
if (userByEmail) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
code: server_constants.RIS_CODE_EMAIL_ALREADY_EXIST,
|
||||||
|
message: 'Email già esistente',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check by cell phone + name + surname
|
||||||
|
const userByCell = await User.findByCellAndNameSurname(user.idapp, user.profile.cell, user.name, user.surname);
|
||||||
|
|
||||||
|
if (userByCell && user.name !== '' && user.surname !== '' && user.profile.cell !== '') {
|
||||||
|
console.log('UTENTE GIA ESISTENTE:', user);
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
code: server_constants.RIS_CODE_USER_ALREADY_EXIST,
|
||||||
|
message: 'Utente già registrato',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: false,
|
||||||
|
notYetVerified,
|
||||||
|
existingUser,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle user that registered but not yet verified
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _handleNotYetVerified(newUser, existingUser, req) {
|
||||||
|
const id_aportador = await User.getIdByUsername(newUser.idapp, newUser.aportador_solidario);
|
||||||
|
|
||||||
|
if (id_aportador) {
|
||||||
|
// Update aportador for existing user
|
||||||
|
await User.setaportador_solidario(newUser.idapp, newUser.username, newUser.aportador_solidario);
|
||||||
|
|
||||||
|
const myuser = await User.findOne({ _id: existingUser._id });
|
||||||
|
|
||||||
|
if (myuser) {
|
||||||
|
// Ask confirmation from inviter
|
||||||
|
await telegrambot.askConfirmationUser(myuser.idapp, shared_consts.CallFunz.REGISTRATION, myuser);
|
||||||
|
|
||||||
|
const { token, refreshToken } = await myuser.generateAuthToken(req);
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: false,
|
||||||
|
token,
|
||||||
|
refreshToken,
|
||||||
|
user: myuser,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
message: 'Errore nella registrazione',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save new user and send notifications
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _saveNewUser(user, req, regexpire = '') {
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
const savedUser = await User.findByUsername(user.idapp, user.username, false);
|
||||||
|
|
||||||
|
if (!savedUser) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
message: "Errore nel salvare l'utente",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate tokens
|
||||||
|
const { token, refreshToken } = await savedUser.generateAuthToken(req);
|
||||||
|
|
||||||
|
if (!savedUser.verified_by_aportador) {
|
||||||
|
// Send confirmation request to inviter
|
||||||
|
await telegrambot.askConfirmationUser(
|
||||||
|
user.idapp,
|
||||||
|
shared_consts.CallFunz.REGISTRATION,
|
||||||
|
user,
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
regexpire
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send registration email
|
||||||
|
await sendemail.sendEmail_Registration(user.lang, user.email, user, user.idapp, user.linkreg);
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: false,
|
||||||
|
token,
|
||||||
|
refreshToken,
|
||||||
|
user: savedUser,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = RegistrationService;
|
||||||
467
src/services/UserService.js
Normal file
467
src/services/UserService.js
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
const { User } = require('../models/user');
|
||||||
|
const { MyGroup } = require('../models/mygroup');
|
||||||
|
const { Circuit } = require('../models/circuit');
|
||||||
|
const { SendNotif } = require('../models/sendnotif');
|
||||||
|
const { Province } = require('../models/province');
|
||||||
|
const tools = require('../tools/general');
|
||||||
|
const shared_consts = require('../tools/shared_nodejs');
|
||||||
|
const server_constants = require('../tools/server_constants');
|
||||||
|
const CronMod = require('../modules/CronMod');
|
||||||
|
|
||||||
|
class UserService {
|
||||||
|
/**
|
||||||
|
* Get user profile with friends
|
||||||
|
*/
|
||||||
|
async getUserProfile(idapp, username, usernameOrig, perm) {
|
||||||
|
try {
|
||||||
|
const profile = await User.getUserProfileByUsername(
|
||||||
|
idapp,
|
||||||
|
username,
|
||||||
|
usernameOrig,
|
||||||
|
false,
|
||||||
|
perm
|
||||||
|
);
|
||||||
|
|
||||||
|
const friends = await User.getFriendsByUsername(idapp, usernameOrig);
|
||||||
|
|
||||||
|
// Get extra info if viewing own profile
|
||||||
|
if (username === usernameOrig) {
|
||||||
|
const userProfile = await User.getExtraInfoByUsername(idapp, profile.username);
|
||||||
|
profile.profile = userProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: profile,
|
||||||
|
friends
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in getUserProfile:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user activities profile (public version)
|
||||||
|
*/
|
||||||
|
async getUserActivitiesProfile(idapp, username, usernameOrig, perm, isAuthenticated) {
|
||||||
|
try {
|
||||||
|
const profile = await User.getUserProfileByUsername(
|
||||||
|
idapp,
|
||||||
|
username,
|
||||||
|
usernameOrig,
|
||||||
|
false,
|
||||||
|
perm
|
||||||
|
);
|
||||||
|
|
||||||
|
const friends = await User.getFriendsByUsername(idapp, usernameOrig);
|
||||||
|
|
||||||
|
let userProfile;
|
||||||
|
if (isAuthenticated) {
|
||||||
|
userProfile = await User.getExtraInfoByUsername(idapp, profile.username);
|
||||||
|
} else {
|
||||||
|
userProfile = await User.getProfilePerActivitiesByUsername(idapp, profile.username);
|
||||||
|
// Hide sensitive data for non-authenticated users
|
||||||
|
profile.aportador_solidario = '';
|
||||||
|
profile.date_reg = '';
|
||||||
|
profile.email = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.profile = userProfile;
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: profile,
|
||||||
|
friends
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in getUserActivitiesProfile:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user balance and get notifications
|
||||||
|
*/
|
||||||
|
async updateUserBalance(idapp, username, circuitId, groupname, lastdr = '') {
|
||||||
|
try {
|
||||||
|
const userProfile = await User.getExtraInfoByUsername(idapp, username);
|
||||||
|
|
||||||
|
const arrRecNotif = await SendNotif.findAllNotifByUsernameIdAndIdApp(
|
||||||
|
username,
|
||||||
|
lastdr,
|
||||||
|
idapp,
|
||||||
|
shared_consts.LIMIT_NOTIF_FOR_USER,
|
||||||
|
shared_consts.QualiNotifs.OTHERS
|
||||||
|
);
|
||||||
|
|
||||||
|
const arrRecNotifCoins = await SendNotif.findAllNotifByUsernameIdAndIdApp(
|
||||||
|
username,
|
||||||
|
lastdr,
|
||||||
|
idapp,
|
||||||
|
shared_consts.LIMIT_NOTIFCOINS_FOR_USER,
|
||||||
|
shared_consts.QualiNotifs.CIRCUITS
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
userprofile: userProfile,
|
||||||
|
arrrecnotif: arrRecNotif,
|
||||||
|
arrrecnotifcoins: arrRecNotifCoins
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in updateUserBalance:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user's friends list
|
||||||
|
*/
|
||||||
|
async getUserFriends(idapp, username) {
|
||||||
|
try {
|
||||||
|
return await User.getFriendsByUsername(idapp, username);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in getUserFriends:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user's groups
|
||||||
|
*/
|
||||||
|
async getUserGroups(idapp, username, req) {
|
||||||
|
try {
|
||||||
|
return await MyGroup.getGroupsByUsername(idapp, username, req);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in getUserGroups:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user's circuits
|
||||||
|
*/
|
||||||
|
async getUserCircuits(idapp, username, user, nummovTodownload) {
|
||||||
|
try {
|
||||||
|
return await Circuit.getCircuitsByUsername(
|
||||||
|
idapp,
|
||||||
|
username,
|
||||||
|
user,
|
||||||
|
nummovTodownload
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in getUserCircuits:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute friend command (add, remove, handshake, etc.)
|
||||||
|
*/
|
||||||
|
async executeFriendCommand(req, idapp, usernameOrig, usernameDest, cmd, value) {
|
||||||
|
try {
|
||||||
|
// Normalize usernames
|
||||||
|
const realUsernameOrig = await User.getRealUsernameByUsername(idapp, usernameOrig);
|
||||||
|
const realUsernameDest = await User.getRealUsernameByUsername(idapp, usernameDest);
|
||||||
|
|
||||||
|
return await User.setFriendsCmd(
|
||||||
|
req,
|
||||||
|
idapp,
|
||||||
|
realUsernameOrig,
|
||||||
|
realUsernameDest,
|
||||||
|
cmd,
|
||||||
|
value
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in executeFriendCommand:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute group command
|
||||||
|
*/
|
||||||
|
async executeGroupCommand(idapp, usernameOrig, groupnameDest, cmd, value, usernameLogged) {
|
||||||
|
try {
|
||||||
|
return await User.setGroupsCmd(
|
||||||
|
idapp,
|
||||||
|
usernameOrig,
|
||||||
|
groupnameDest,
|
||||||
|
cmd,
|
||||||
|
value,
|
||||||
|
usernameLogged
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in executeGroupCommand:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute circuit command
|
||||||
|
*/
|
||||||
|
async executeCircuitCommand(idapp, usernameOrig, circuitname, cmd, value, usernameLogged, extrarec) {
|
||||||
|
try {
|
||||||
|
const result = await User.setCircuitCmd(
|
||||||
|
idapp,
|
||||||
|
usernameOrig,
|
||||||
|
circuitname,
|
||||||
|
cmd,
|
||||||
|
value,
|
||||||
|
usernameLogged,
|
||||||
|
extrarec
|
||||||
|
);
|
||||||
|
|
||||||
|
// Mark notification as read if present
|
||||||
|
if (extrarec?.idnotif) {
|
||||||
|
await this.markNotificationAsRead(idapp, usernameOrig, extrarec.idnotif);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in executeCircuitCommand:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark notification as read
|
||||||
|
*/
|
||||||
|
async markNotificationAsRead(idapp, username, idnotif) {
|
||||||
|
try {
|
||||||
|
if (idnotif) {
|
||||||
|
await SendNotif.setNotifAsRead(idapp, username, idnotif);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in markNotificationAsRead:', error.message);
|
||||||
|
// Non-critical error, don't throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if username exists
|
||||||
|
*/
|
||||||
|
async checkUsernameExists(idapp, username) {
|
||||||
|
try {
|
||||||
|
let user = await User.findByUsername(idapp, username, false, true);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
user = await User.findByUsernameTelegram(idapp, username, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!user;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in checkUsernameExists:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set user permissions
|
||||||
|
*/
|
||||||
|
async setUserPermissions(userId, data) {
|
||||||
|
try {
|
||||||
|
await User.setPermissionsById(userId, data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in setUserPermissions:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute database operation (admin only)
|
||||||
|
*/
|
||||||
|
async executeDbOperation(idapp, mydata, req, res) {
|
||||||
|
try {
|
||||||
|
const cronMod = new CronMod();
|
||||||
|
return await cronMod.eseguiDbOp(idapp, mydata, req, res);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in executeDbOperation:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute user-specific database operation
|
||||||
|
*/
|
||||||
|
async executeUserDbOperation(idapp, mydata, username) {
|
||||||
|
try {
|
||||||
|
let result = await User.DbOp(idapp, mydata);
|
||||||
|
|
||||||
|
// Handle specific operations
|
||||||
|
await this._handleSpecificDbOperations(mydata);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return await User.updateMyData(result, idapp, username);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in executeUserDbOperation:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get map information (provinces with user counts)
|
||||||
|
*/
|
||||||
|
async getMapInformation(idapp) {
|
||||||
|
try {
|
||||||
|
const query = [
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: 'users',
|
||||||
|
localField: 'prov',
|
||||||
|
foreignField: 'profile.resid_province',
|
||||||
|
as: 'users'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$addFields: {
|
||||||
|
userCount: { $size: '$users' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: 'provinces',
|
||||||
|
localField: 'prov',
|
||||||
|
foreignField: 'prov',
|
||||||
|
as: 'provinceInfo'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$addFields: {
|
||||||
|
provinceDescr: { $arrayElemAt: ['$provinceInfo.descr', 0] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$project: {
|
||||||
|
_id: 0,
|
||||||
|
province: '$prov',
|
||||||
|
descr: '$provinceDescr',
|
||||||
|
userCount: 1,
|
||||||
|
lat: 1,
|
||||||
|
long: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return await Province.aggregate(query);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in getMapInformation:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send command to user
|
||||||
|
*/
|
||||||
|
async sendCommand(req, idapp, usernameOrig, usernameDest, cmd, value) {
|
||||||
|
try {
|
||||||
|
const realUsernameOrig = await User.getRealUsernameByUsername(idapp, usernameOrig);
|
||||||
|
const realUsernameDest = await User.getRealUsernameByUsername(idapp, usernameDest);
|
||||||
|
|
||||||
|
return await User.sendCmd(
|
||||||
|
req,
|
||||||
|
idapp,
|
||||||
|
realUsernameOrig,
|
||||||
|
realUsernameDest,
|
||||||
|
cmd,
|
||||||
|
value
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in sendCommand:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user panel information (admin/manager view)
|
||||||
|
*/
|
||||||
|
async getUserPanelInfo(idapp, username) {
|
||||||
|
try {
|
||||||
|
const user = await User.findOne(
|
||||||
|
{ idapp, username },
|
||||||
|
{
|
||||||
|
username: 1,
|
||||||
|
name: 1,
|
||||||
|
surname: 1,
|
||||||
|
email: 1,
|
||||||
|
verified_by_aportador: 1,
|
||||||
|
aportador_solidario: 1,
|
||||||
|
lasttimeonline: 1,
|
||||||
|
deleted: 1,
|
||||||
|
sospeso: 1,
|
||||||
|
blocked: 1,
|
||||||
|
reported: 1,
|
||||||
|
username_who_report: 1,
|
||||||
|
date_report: 1,
|
||||||
|
profile: 1
|
||||||
|
}
|
||||||
|
).lean();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('Utente non trovato');
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in getUserPanelInfo:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle specific database operations
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _handleSpecificDbOperations(mydata) {
|
||||||
|
const operations = {
|
||||||
|
'saveStepTut': () => User.findOneAndUpdate(
|
||||||
|
{ _id: mydata._id },
|
||||||
|
{ $set: { 'profile.stepTutorial': mydata.value } }
|
||||||
|
),
|
||||||
|
'noNameSurname': () => User.findOneAndUpdate(
|
||||||
|
{ _id: mydata._id },
|
||||||
|
{ $set: { 'profile.noNameSurname': mydata.value } }
|
||||||
|
),
|
||||||
|
'telegram_verification_skipped': () => User.findOneAndUpdate(
|
||||||
|
{ _id: mydata._id },
|
||||||
|
{ $set: { 'profile.telegram_verification_skipped': mydata.value } }
|
||||||
|
),
|
||||||
|
'noCircuit': () => User.findOneAndUpdate(
|
||||||
|
{ _id: mydata._id },
|
||||||
|
{ $set: { 'profile.noCircuit': mydata.value } }
|
||||||
|
),
|
||||||
|
'noCircIta': () => User.findOneAndUpdate(
|
||||||
|
{ _id: mydata._id },
|
||||||
|
{ $set: { 'profile.noCircIta': mydata.value } }
|
||||||
|
),
|
||||||
|
'insert_circuito_ita': () => User.findOneAndUpdate(
|
||||||
|
{ _id: mydata._id },
|
||||||
|
{ $set: { 'profile.insert_circuito_ita': mydata.value } }
|
||||||
|
),
|
||||||
|
'noFoto': () => User.findOneAndUpdate(
|
||||||
|
{ _id: mydata._id },
|
||||||
|
{ $set: { 'profile.noFoto': mydata.value } }
|
||||||
|
),
|
||||||
|
'pwdLikeAdmin': () => User.setPwdComeQuellaDellAdmin(mydata),
|
||||||
|
'ripristinaPwdPrec': () => User.ripristinaPwdPrec(mydata)
|
||||||
|
};
|
||||||
|
|
||||||
|
const operation = operations[mydata.dbop];
|
||||||
|
if (operation) {
|
||||||
|
await operation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = UserService;
|
||||||
@@ -652,7 +652,7 @@ const txt_pt = {
|
|||||||
|
|
||||||
const TelegramBot = require('node-telegram-bot-api');
|
const TelegramBot = require('node-telegram-bot-api');
|
||||||
|
|
||||||
const ADMIN_IDTELEGRAM_TEST = [5356627050, 5022837609, 12429864]; //Surya A.
|
const ADMIN_IDTELEGRAM_TEST = [5356627050, 5022837609, 12429864]; //Surya A.
|
||||||
|
|
||||||
const MyTelegramBot = {
|
const MyTelegramBot = {
|
||||||
ADMIN_IDTELEGRAM_SERVER: '12429864', //Paolo
|
ADMIN_IDTELEGRAM_SERVER: '12429864', //Paolo
|
||||||
@@ -662,7 +662,8 @@ const MyTelegramBot = {
|
|||||||
phase: {
|
phase: {
|
||||||
REGISTRATION: 1,
|
REGISTRATION: 1,
|
||||||
ISCRIZIONE_CONACREIS: 2,
|
ISCRIZIONE_CONACREIS: 2,
|
||||||
ISCRIZIONE_ARCADEI: 4,
|
INVITA_AMICO: 5,
|
||||||
|
AMMETTI_UTENTE: 10,
|
||||||
},
|
},
|
||||||
|
|
||||||
getAppTelegram: function () {
|
getAppTelegram: function () {
|
||||||
@@ -793,6 +794,8 @@ const MyTelegramBot = {
|
|||||||
let nome = tools.getNomeCognomeEUserNameByUser(mylocalsconf.user);
|
let nome = tools.getNomeCognomeEUserNameByUser(mylocalsconf.user);
|
||||||
text = printf(getstr(langdest, 'MSG_APORTADOR_USER_REGISTERED'), nome, numutenti, aportador);
|
text = printf(getstr(langdest, 'MSG_APORTADOR_USER_REGISTERED'), nome, numutenti, aportador);
|
||||||
}
|
}
|
||||||
|
} else if (phase === this.phase.INVITA_AMICO) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let addtext = '';
|
let addtext = '';
|
||||||
@@ -915,6 +918,19 @@ const MyTelegramBot = {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
send_notif = true;
|
send_notif = true;
|
||||||
|
} else if (myfunc === shared_consts.CallFunz.VERIFICA_TELEGRAM) {
|
||||||
|
if (telegid > 0) {
|
||||||
|
cl.setPhotoProfile(myuser, telegid, false);
|
||||||
|
|
||||||
|
const rismsg = await MsgTemplate.getMsgByLang(
|
||||||
|
idapp,
|
||||||
|
myuser,
|
||||||
|
shared_consts.TypeMsgTemplate.MSG_VERIFICA_TELEGRAM_COMPLETATA,
|
||||||
|
myuser.lang
|
||||||
|
);
|
||||||
|
|
||||||
|
await cl.sendMsgLog(telegid, rismsg.body);
|
||||||
|
}
|
||||||
} else if (myfunc === shared_consts.CallFunz.RICHIESTA_GRUPPO) {
|
} else if (myfunc === shared_consts.CallFunz.RICHIESTA_GRUPPO) {
|
||||||
msg_notifpush = printf(getstr(langdest, 'MSG_ACCEPT_NEWENTRY_INGROUP'), name);
|
msg_notifpush = printf(getstr(langdest, 'MSG_ACCEPT_NEWENTRY_INGROUP'), name);
|
||||||
domanda = printf(getstr(langdest, 'MSG_ACCEPT_NEWENTRY_INGROUP'), name) + '<br>' + struserinfomsg;
|
domanda = printf(getstr(langdest, 'MSG_ACCEPT_NEWENTRY_INGROUP'), name) + '<br>' + struserinfomsg;
|
||||||
@@ -1748,10 +1764,15 @@ class Telegram {
|
|||||||
|
|
||||||
if (rec.user) {
|
if (rec.user) {
|
||||||
if (!rec.user.verified_by_aportador) {
|
if (!rec.user.verified_by_aportador) {
|
||||||
MyTelegramBot.askConfirmationUser(this.idapp, shared_consts.CallFunz.REGISTRATION, rec.user);
|
const recuser = this.getRecInMemById(msg.chat.id);
|
||||||
|
MyTelegramBot.askConfirmationUser(this.idapp, shared_consts.CallFunz.REGISTRATION, recuser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rec.cmdAfterVerified === shared_consts.CallFunz.VERIFICA_TELEGRAM) {
|
||||||
|
await MyTelegramBot.askConfirmationUser(this.idapp, shared_consts.CallFunz.VERIFICA_TELEGRAM, rec.user);
|
||||||
|
}
|
||||||
|
|
||||||
/*} else {
|
/*} else {
|
||||||
if (!rec.user.profile.username_telegram) {
|
if (!rec.user.profile.username_telegram) {
|
||||||
return this.checkIfUsernameTelegramSet(msg, rec.user);
|
return this.checkIfUsernameTelegramSet(msg, rec.user);
|
||||||
@@ -1905,8 +1926,8 @@ class Telegram {
|
|||||||
risp = 'Siiiii ! Davvero! ' + emo.DREAM;
|
risp = 'Siiiii ! Davvero! ' + emo.DREAM;
|
||||||
} else if (MsgBot.PAROLACCE.find((rec) => testo.indexOf(rec) > -1)) {
|
} else if (MsgBot.PAROLACCE.find((rec) => testo.indexOf(rec) > -1)) {
|
||||||
risp = "Da te non me l'aspettavo proprio !! " + emo.INNOCENT + emo.CROSS_ROSSA;
|
risp = "Da te non me l'aspettavo proprio !! " + emo.INNOCENT + emo.CROSS_ROSSA;
|
||||||
} else if (MsgBot.OK.find((rec) => testo.indexOf(rec) > -1)) {
|
// } else if (MsgBot.OK.find((rec) => testo.indexOf(rec) > -1)) {
|
||||||
risp = '👍🏻';
|
// risp = '👍🏻';
|
||||||
} else if (MsgBot.CUORE.find((rec) => testo.indexOf(rec) > -1)) {
|
} else if (MsgBot.CUORE.find((rec) => testo.indexOf(rec) > -1)) {
|
||||||
risp = '❤️💚💜';
|
risp = '❤️💚💜';
|
||||||
} else if (MsgBot.HAHA.find((rec) => testo.indexOf(rec) > -1) && testo.length < 8) {
|
} else if (MsgBot.HAHA.find((rec) => testo.indexOf(rec) > -1) && testo.length < 8) {
|
||||||
@@ -3308,9 +3329,25 @@ class Telegram {
|
|||||||
console.log(recuser.username, " SI E' VERIFICATO CON TELEGRAM !");
|
console.log(recuser.username, " SI E' VERIFICATO CON TELEGRAM !");
|
||||||
let username = recuser.name;
|
let username = recuser.name;
|
||||||
|
|
||||||
|
if (!msg.from.username) {
|
||||||
|
rec.cmdAfterVerified === shared_consts.CallFunz.VERIFICA_TELEGRAM;
|
||||||
|
} else {
|
||||||
|
await this.verificaTelegramCompleted(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async verificaTelegramCompleted(msg) {
|
||||||
|
try {
|
||||||
|
const rec = this.getRecInMem(msg);
|
||||||
|
const id = msg.chat.id;
|
||||||
|
const recuser = this.getRecInMemById(id);
|
||||||
|
|
||||||
|
if (recuser) {
|
||||||
await User.setUsernameTelegram(
|
await User.setUsernameTelegram(
|
||||||
this.idapp,
|
this.idapp,
|
||||||
recuser._id,
|
recuser.user._id,
|
||||||
msg.from.username || '',
|
msg.from.username || '',
|
||||||
msg.from.first_name || '',
|
msg.from.first_name || '',
|
||||||
msg.from.last_name || ''
|
msg.from.last_name || ''
|
||||||
@@ -3320,12 +3357,12 @@ class Telegram {
|
|||||||
rec.datemenu_updated = null;
|
rec.datemenu_updated = null;
|
||||||
rec.menuDb = null;
|
rec.menuDb = null;
|
||||||
|
|
||||||
if (!!msg.from.username) {
|
const user = await User.UserByIdTelegram(this.idapp, msg.chat.id);
|
||||||
await MyTelegramBot.askConfirmationUser(this.idapp, shared_consts.CallFunz.REGISTRATION, recuser);
|
|
||||||
} else {
|
await MyTelegramBot.askConfirmationUser(this.idapp, shared_consts.CallFunz.VERIFICA_TELEGRAM, user);
|
||||||
console.log(" ... MA GLI MANCA L'USERNAME TELEGRAM !! ");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4557,6 +4594,9 @@ if (true) {
|
|||||||
`${userDest.username}`
|
`${userDest.username}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Invia una email alla persona che è stata ammessa
|
||||||
|
|
||||||
|
|
||||||
await local_sendMsgTelegram(user.idapp, data.username, msgOrig);
|
await local_sendMsgTelegram(user.idapp, data.username, msgOrig);
|
||||||
await local_sendMsgTelegram(user.idapp, data.userDest, msgDest);
|
await local_sendMsgTelegram(user.idapp, data.userDest, msgDest);
|
||||||
// Invia questo msg anche all'Admin
|
// Invia questo msg anche all'Admin
|
||||||
|
|||||||
@@ -467,6 +467,7 @@ module.exports = {
|
|||||||
AYNI: '7',
|
AYNI: '7',
|
||||||
CNM: '10',
|
CNM: '10',
|
||||||
RISO: '13',
|
RISO: '13',
|
||||||
|
RISO_STR_PATH: 'RISO',
|
||||||
FIOREDELLAVITA: '15',
|
FIOREDELLAVITA: '15',
|
||||||
PIUCHEBUONO: '17',
|
PIUCHEBUONO: '17',
|
||||||
MACRO: '18',
|
MACRO: '18',
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ module.exports = Object.freeze({
|
|||||||
|
|
||||||
RIS_CODE_HTTP_INVALID_TOKEN: 401,
|
RIS_CODE_HTTP_INVALID_TOKEN: 401,
|
||||||
RIS_CODE_HTTP_FORBIDDEN_PERMESSI: 403,
|
RIS_CODE_HTTP_FORBIDDEN_PERMESSI: 403,
|
||||||
RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED: 408,
|
RIS_CODE_HTTP_TOKEN_EXPIRED: 408,
|
||||||
|
|
||||||
RIS_CODE_TOKEN_RESETPASSWORD_NOT_FOUND: -23,
|
RIS_CODE_TOKEN_RESETPASSWORD_NOT_FOUND: -23,
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ module.exports = {
|
|||||||
UNBLOCK_USER: 156,
|
UNBLOCK_USER: 156,
|
||||||
REPORT_USER: 158,
|
REPORT_USER: 158,
|
||||||
FIND_PEOPLE: 166,
|
FIND_PEOPLE: 166,
|
||||||
|
DELETE_USER: 170,
|
||||||
},
|
},
|
||||||
|
|
||||||
GROUPSCMD: {
|
GROUPSCMD: {
|
||||||
@@ -595,6 +596,7 @@ module.exports = {
|
|||||||
MS_SHARE_LINK: 2000,
|
MS_SHARE_LINK: 2000,
|
||||||
MSG_BENV_REGISTRATO: 2020,
|
MSG_BENV_REGISTRATO: 2020,
|
||||||
MSG_INVITE_WHATSAPP: 2040,
|
MSG_INVITE_WHATSAPP: 2040,
|
||||||
|
MSG_VERIFICA_TELEGRAM_COMPLETATA: 2050,
|
||||||
},
|
},
|
||||||
|
|
||||||
TypeSend: {
|
TypeSend: {
|
||||||
|
|||||||
186
src/validators/UserValidators.js
Normal file
186
src/validators/UserValidators.js
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
|
||||||
|
const tools = require('../tools/general');
|
||||||
|
const server_constants = require('../tools/server_constants');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate registration data
|
||||||
|
*/
|
||||||
|
function validateRegistration(body) {
|
||||||
|
const { username, email, password, name, surname } = body;
|
||||||
|
|
||||||
|
// Check required fields
|
||||||
|
if (!username || !email || !password) {
|
||||||
|
return {
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
message: 'Campi obbligatori mancanti'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check minimum lengths
|
||||||
|
if (email.length < 6) {
|
||||||
|
return {
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
message: 'Email troppo corta'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username.length < 4) {
|
||||||
|
return {
|
||||||
|
code: server_constants.RIS_CODE_USERNAME_NOT_VALID,
|
||||||
|
message: 'Username deve essere almeno 4 caratteri'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length < 5) {
|
||||||
|
return {
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
message: 'Password deve essere almeno 5 caratteri'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check username format
|
||||||
|
if (!tools.isAlphaNumericAndSpecialCharacter(username)) {
|
||||||
|
return {
|
||||||
|
code: server_constants.RIS_CODE_USERNAME_NOT_VALID,
|
||||||
|
message: 'Username contiene caratteri non validi'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for blocked words
|
||||||
|
if (tools.blockwords(username) || tools.blockwords(name) || tools.blockwords(surname)) {
|
||||||
|
return {
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
message: 'Contenuto non valido'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate login data
|
||||||
|
*/
|
||||||
|
function validateLogin(body) {
|
||||||
|
const { username, password, idapp } = body;
|
||||||
|
|
||||||
|
// Check required fields
|
||||||
|
if (!username || !password || !idapp) {
|
||||||
|
return {
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
message: 'Campi obbligatori mancanti'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check minimum lengths
|
||||||
|
if (username.length < 3) {
|
||||||
|
return {
|
||||||
|
code: server_constants.RIS_CODE_LOGIN_ERR,
|
||||||
|
message: 'Username troppo corto'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length < 4) {
|
||||||
|
return {
|
||||||
|
code: server_constants.RIS_CODE_LOGIN_ERR,
|
||||||
|
message: 'Password troppo corta'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate email format
|
||||||
|
*/
|
||||||
|
function validateEmail(email) {
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
return emailRegex.test(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate phone number format
|
||||||
|
*/
|
||||||
|
function validatePhoneNumber(phone) {
|
||||||
|
if (!phone) return true; // Optional field
|
||||||
|
|
||||||
|
// Remove spaces and special characters
|
||||||
|
const cleanPhone = phone.replace(/[\s\-\(\)]/g, '');
|
||||||
|
|
||||||
|
// Check if it's numeric and reasonable length
|
||||||
|
return /^\+?\d{8,15}$/.test(cleanPhone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate user profile data
|
||||||
|
*/
|
||||||
|
function validateProfileData(profileData) {
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
if (profileData.email && !validateEmail(profileData.email)) {
|
||||||
|
errors.push('Email non valida');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profileData.cell && !validatePhoneNumber(profileData.cell)) {
|
||||||
|
errors.push('Numero di telefono non valido');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profileData.username) {
|
||||||
|
if (profileData.username.length < 4) {
|
||||||
|
errors.push('Username deve essere almeno 4 caratteri');
|
||||||
|
}
|
||||||
|
if (!tools.isAlphaNumericAndSpecialCharacter(profileData.username)) {
|
||||||
|
errors.push('Username contiene caratteri non validi');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.length > 0 ? {
|
||||||
|
code: server_constants.RIS_CODE_ERR,
|
||||||
|
message: errors.join(', ')
|
||||||
|
} : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate permissions value
|
||||||
|
*/
|
||||||
|
function validatePermissions(perm) {
|
||||||
|
const validPermissions = [0, 1, 2, 3, 4, 5]; // Based on your Perm enum
|
||||||
|
return validPermissions.includes(perm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate circuit/group command
|
||||||
|
*/
|
||||||
|
function validateCommand(cmd, validCommands) {
|
||||||
|
return validCommands.includes(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize user input (remove dangerous characters)
|
||||||
|
*/
|
||||||
|
function sanitizeInput(input) {
|
||||||
|
if (typeof input !== 'string') return input;
|
||||||
|
|
||||||
|
return input
|
||||||
|
.trim()
|
||||||
|
.replace(/[<>]/g, '') // Remove potential XSS
|
||||||
|
.replace(/['";]/g, ''); // Remove SQL injection chars
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate MongoDB ObjectId
|
||||||
|
*/
|
||||||
|
function validateObjectId(id) {
|
||||||
|
return /^[0-9a-fA-F]{24}$/.test(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
validateRegistration,
|
||||||
|
validateLogin,
|
||||||
|
validateEmail,
|
||||||
|
validatePhoneNumber,
|
||||||
|
validateProfileData,
|
||||||
|
validatePermissions,
|
||||||
|
validateCommand,
|
||||||
|
sanitizeInput,
|
||||||
|
validateObjectId
|
||||||
|
};
|
||||||
@@ -1 +1 @@
|
|||||||
1.2.79
|
1.2.84
|
||||||
Reference in New Issue
Block a user