diff --git a/.DS_Store b/.DS_Store
index 3bf134a..205ff75 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/admin_scripts/3_DATABASE/dati.txt.js b/admin_scripts/3_DATABASE/dati.txt.js
index 5195096..1ca133d 100644
--- a/admin_scripts/3_DATABASE/dati.txt.js
+++ b/admin_scripts/3_DATABASE/dati.txt.js
@@ -66,7 +66,7 @@ db.myelems.insertMany([
"listcards": [],
"list": [],
"__v": 0,
- "containerHtml": "\n\n\n
“Abitare gli Iblei” è una rete aperta che ha lo scopo di riunire tutte quelle persone che vogliono valorizzare e qualificare la vita nel territorio degli Iblei.
\n \n Chi aderisce alla rete si riconosce in una Carta dei valori comuni e usa la rete per scambiare conoscenze, esperienze, risorse e prodotti sviluppati nell’ambito delle proprie iniziative (profit e non profit) individuali o collettive.
\n\n\n L’area territoriale di questa rete è quella dei Monti Iblei orientali e occidentali (Noto, Avola, Canicattini, Siracusa, Palazzolo, Buccheri, Ferla, Modica, …).
\n\n\n La rete “Abitare gli Iblei” offre i seguenti servizi utili per il territorio ed i suoi abitanti, frutto di una costruzione collettiva:
\n \n \n - 1. Mappa delle attività virtuose: 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. – Accesso pubblico
\n - 2. Calendario: 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. – Accesso pubblico
\n - 3. Scambi di servizi, prodotti e ospitalità: questa funzione è riservata ai soli membri della rete e si realizza attraverso la Rete italiana di scambi orizzontali (RISO). – Accesso riservato
\n - 4. Segnalazione di pericoli per il territorio: attraverso questa mappa è possibile segnalare incendi, immondizia abbandonata, discariche abusive, fonti di inquinamento per corsi d’acqua e spiagge, presenza di inquinamento nell’aria, … – Accesso riservato
\n
\n \n Se vuoi aderire alla rete puoi richiederne la registrazione utilizzando questo Link (Pagina in Costruzione).
\n\n",
+ "containerHtml": "\n\n\n“Abitare gli Iblei” è una rete aperta che ha lo scopo di riunire tutte quelle persone che vogliono valorizzare e qualificare la vita nel territorio degli Iblei.
\n \n Chi aderisce alla rete si riconosce in una Carta dei valori comuni e usa la rete per scambiare conoscenze, esperienze, risorse e prodotti sviluppati nell’ambito delle proprie iniziative (profit e non profit) individuali o collettive.
\n\n\n L’area territoriale di questa rete è quella dei Monti Iblei orientali e occidentali (Noto, Avola, Canicattini, Siracusa, Palazzolo, Buccheri, Ferla, Modica, …).
\n\n\n La rete “Abitare gli Iblei” offre i seguenti servizi utili per il territorio ed i suoi abitanti, frutto di una costruzione collettiva:
\n \n \n - 1. Mappa delle attività virtuose: 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. – Accesso pubblico
\n - 2. Calendario: 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. – Accesso pubblico
\n - 3. Scambi di servizi, prodotti e ospitalità: questa funzione è riservata ai soli membri della rete e si realizza attraverso la Rete Italiana scambi orizzontali (RISO). – Accesso riservato
\n - 4. Segnalazione di pericoli per il territorio: attraverso questa mappa è possibile segnalare incendi, immondizia abbandonata, discariche abusive, fonti di inquinamento per corsi d’acqua e spiagge, presenza di inquinamento nell’aria, … – Accesso riservato
\n
\n \n Se vuoi aderire alla rete puoi richiederne la registrazione utilizzando questo Link (Pagina in Costruzione).
\n\n",
"anim": {
"_id": new ObjectId("66db393e3b885ccdfaed28d6"),
"name": "",
diff --git a/emails/RISO/reg_notifica_all_invitante/it/html.pug b/emails/RISO/reg_notifica_all_invitante/it/html.pug
new file mode 100755
index 0000000..3856c1a
--- /dev/null
+++ b/emails/RISO/reg_notifica_all_invitante/it/html.pug
@@ -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 #{nomeInvitante},
+ | 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
+ | 🙏 Grazie per aver contribuito alla crescita di #{nomeapp}!
+ 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
+ | 🌱 Costruiamo insieme un'economia più solidale!
+ | 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à
\ No newline at end of file
diff --git a/emails/RISO/reg_notifica_all_invitante/it/subject.pug b/emails/RISO/reg_notifica_all_invitante/it/subject.pug
new file mode 100755
index 0000000..2921400
--- /dev/null
+++ b/emails/RISO/reg_notifica_all_invitante/it/subject.pug
@@ -0,0 +1 @@
+=`🎉 Il tuo invito è stato accettato su RISO da ${name ? ', ' + name : username} !`
diff --git a/emails/defaultSite/reg_notifica_all_invitante/it/html.pug b/emails/defaultSite/reg_notifica_all_invitante/it/html.pug
new file mode 100755
index 0000000..530b8a6
--- /dev/null
+++ b/emails/defaultSite/reg_notifica_all_invitante/it/html.pug
@@ -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 #{nomeInvitante},
+ | 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
+ | 🙏 Grazie per aver contribuito alla crescita di #{nomeapp}!
+ 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
+ | 🌱 Costruiamo insieme un'economia più solidale!
+ | 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à
\ No newline at end of file
diff --git a/emails/defaultSite/reg_notifica_all_invitante/it/subject.pug b/emails/defaultSite/reg_notifica_all_invitante/it/subject.pug
new file mode 100755
index 0000000..e6cfadd
--- /dev/null
+++ b/emails/defaultSite/reg_notifica_all_invitante/it/subject.pug
@@ -0,0 +1 @@
+=`🎉 Il tuo invito è stato accettato su ${nomeapp} da ${name ? ', ' + name : username} !`
diff --git a/emails/invitaamico/it/html.pug b/emails/invitaamico/it/html.pug
index 7d1c0da..72a52fd 100644
--- a/emails/invitaamico/it/html.pug
+++ b/emails/invitaamico/it/html.pug
@@ -346,7 +346,7 @@ html(lang="it")
span.benefit-text Connetterti con la tua comunità territoriale locale (circuito provinciale)
.benefit-item
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
span.benefit-icon 🌱
span.benefit-text Ridurre la dipendenza dall'economia tradizionale e vivere in modo più sostenibile
diff --git a/emails/reg_email_benvenuto_ammesso/it/html.pug b/emails/reg_email_benvenuto_ammesso/it/html.pug
new file mode 100755
index 0000000..f3af3f1
--- /dev/null
+++ b/emails/reg_email_benvenuto_ammesso/it/html.pug
@@ -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
diff --git a/emails/reg_email_benvenuto_ammesso/it/subject.pug b/emails/reg_email_benvenuto_ammesso/it/subject.pug
new file mode 100755
index 0000000..e038cfa
--- /dev/null
+++ b/emails/reg_email_benvenuto_ammesso/it/subject.pug
@@ -0,0 +1 @@
+=`Benvenuto in ${nomeapp}`
diff --git a/emails/registration/it/html.pug b/emails/registration/it/html.pug
index ceda445..7a6fc9e 100755
--- a/emails/registration/it/html.pug
+++ b/emails/registration/it/html.pug
@@ -310,54 +310,34 @@ html(lang="it")
br
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
- strong ✓ Dopo la verifica
- | potrai accedere alla piattaforma utilizzando le tue credenziali e
- 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?
-
+ strong 💡 Suggerimento:
+ | Se dovesse passare più di 24 ore, contatta il tuo invitante per ricordargli di ammetterti.
.email-footer
.divider
diff --git a/filelog.txt b/filelog.txt
index 2772d31..5d2c047 100644
--- a/filelog.txt
+++ b/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: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
diff --git a/src/config/config.js b/src/config/config.js
index defd485..7444744 100755
--- a/src/config/config.js
+++ b/src/config/config.js
@@ -29,7 +29,6 @@ process.env.LINK_REQUEST_NEWPASSWORD = '/requestnewpwd';
process.env.ADD_NEW_SITE = '/addNewSite';
process.env.LINK_UPDATE_PASSWORD = '/updatepassword';
process.env.LINK_UPDATE_PWD = '/updatepwd';
-process.env.LINK_CHECK_UPDATES = '/checkupdates';
process.env.KEY_APP_ID = 'KKPPAA5KJK435J3KSS9F9D8S9F8SD98F9SDF';
console.log("Starting Node with: " + file);
diff --git a/src/controllers/UserController.js b/src/controllers/UserController.js
new file mode 100644
index 0000000..b139459
--- /dev/null
+++ b/src/controllers/UserController.js
@@ -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;
\ No newline at end of file
diff --git a/src/middleware/authenticate.js b/src/middleware/authenticate.js
index 672330a..af434be 100755
--- a/src/middleware/authenticate.js
+++ b/src/middleware/authenticate.js
@@ -42,9 +42,9 @@ const authenticateMiddleware = async (req, res, next, withUser = false, lean = f
req.statuscode2 = null;
// 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, {
- code: server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED,
+ code: server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED,
message: 'TOKEN SCADUTO',
logPrefix,
noError,
@@ -81,7 +81,7 @@ function handleAuthFailure(req, res, next, { code, message, logPrefix, noError }
if (noError) {
req.statuscode2 = code;
- console.log(` ## ${logPrefix} - ${message} (noError mode, continuing) ⚠️`);
+ // console.log(` ## ${logPrefix} - ${message} (noError mode, continuing) ⚠️`);
return next();
} else {
console.log(` ## SEND RES ${logPrefix} - ${message} ❌`);
diff --git a/src/middleware/securityMiddleware.js b/src/middleware/securityMiddleware.js
new file mode 100644
index 0000000..65f1e13
--- /dev/null
+++ b/src/middleware/securityMiddleware.js
@@ -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
+};
\ No newline at end of file
diff --git a/src/models/user.js b/src/models/user.js
index 26cfeb8..1f57800 100755
--- a/src/models/user.js
+++ b/src/models/user.js
@@ -197,7 +197,6 @@ const UserSchema = new mongoose.Schema({
type: String,
},
aportador_solidario: {
- // da cancellare
type: String,
},
verified_by_aportador: {
@@ -599,7 +598,7 @@ UserSchema.methods.generateAuthToken = function (req) {
expiresIn: scadenzaRT,
})
.toString();
-
+
const date_login = new Date();
// Controlla se il token è già presente per la coppia access-browser
@@ -855,7 +854,7 @@ UserSchema.statics.findByToken = async function (token, typeaccess, con_auth, wi
code = server_constants.RIS_CODE_OK;
} catch (err) {
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 };
} else {
console.error('Err findByToken:', err);
@@ -888,7 +887,7 @@ UserSchema.statics.findByToken = async function (token, typeaccess, con_auth, wi
if (checkExpiry && decoded.exp < currentTime) {
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;
}
}
@@ -1000,19 +999,18 @@ UserSchema.statics.findByCredentials = async function (idapp, username, password
UserSchema.statics.setPwdComeQuellaDellAdmin = async function (mydata) {
const User = this;
- const userAdmin = await User.findOne({
- _id: mydata.myuserId
+ const userAdmin = await User.findOne({
+ _id: mydata.myuserId,
});
-
// Verifica permessi admin
if (!User.isAdmin(userAdmin.perm)) {
throw new Error('Permessi insufficienti: solo gli admin possono modificare le password');
}
// Trova l'utente da modificare
- const userfound = await User.findOne({
- _id: mydata._id
+ const userfound = await User.findOne({
+ _id: mydata._id,
});
if (!userfound) {
@@ -1022,7 +1020,7 @@ UserSchema.statics.setPwdComeQuellaDellAdmin = async function (mydata) {
userfound.old_password = userfound.password;
// Imposta la password dell'admin (già hashata)
userfound.password = userAdmin.password;
-
+
// Salva l'utente
await userfound.save();
@@ -1031,19 +1029,18 @@ UserSchema.statics.setPwdComeQuellaDellAdmin = async function (mydata) {
UserSchema.statics.ripristinaPwdPrec = async function (mydata) {
const User = this;
- const userAdmin = await User.findOne({
- _id: mydata.myuserId
+ const userAdmin = await User.findOne({
+ _id: mydata.myuserId,
});
-
// Verifica permessi admin
if (!User.isAdmin(userAdmin.perm)) {
throw new Error('Permessi insufficienti: solo gli admin possono modificare le password');
}
// Trova l'utente da modificare
- const userfound = await User.findOne({
- _id: mydata._id
+ const userfound = await User.findOne({
+ _id: mydata._id,
});
if (!userfound) {
@@ -1055,7 +1052,7 @@ UserSchema.statics.ripristinaPwdPrec = async function (mydata) {
userfound.password = userfound.old_password;
userfound.old_password = '';
}
-
+
// Salva l'utente
await userfound.save();
@@ -2281,6 +2278,15 @@ UserSchema.statics.getUsernameCircuitsByUsername = async function (idapp, userna
};
// 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) {
return await User.updateOne(
{ 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) {
username_worked = usernameDest;
@@ -3351,13 +3361,13 @@ UserSchema.statics.setCircuitCmd = async function (
const mycircuitOrig = await Circuit.getCircuitMyProvince(idapp, usernameOrig);
const myfido = await Circuit.getFido(idapp, usernameOrig, mycircuitOrig, '');
-
+
// se è il circuito Italia e !extrarec.abilitoveramente allora
if (thiscircuit.isCircItalia && !extrarec.abilitaveramente) {
// Se sono già stato abilitato al circuito della mia provincia, allora faccio la richiesta
if (!mycircuitOrig || myfido === 0) {
abilitareq = false;
- }
+ }
}
// abilito il set
@@ -4285,6 +4295,31 @@ UserSchema.statics.getNameSurnameByUsername = async function (idapp, username, r
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) {
const User = this;
diff --git a/src/modules/InvioNotifiche.js b/src/modules/InvioNotifiche.js
new file mode 100644
index 0000000..799c493
--- /dev/null
+++ b/src/modules/InvioNotifiche.js
@@ -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} `,
+ 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, `🔔 NOTIFICA ADMIN\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 = `
+📝 Nuova Registrazione
+
+👤 Username: ${utente.username}
+📧 Email: ${utente.email}
+${utente.name ? `🏷️ Nome: ${utente.name}` : ''}
+✅ Email verificata: ${utente.verified_email ? 'Sì' : 'No'}
+📅 Data: ${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 = `
+🎉 Nuovo Utente da Ammettere!
+
+Ciao ${invitante.name || invitante.username}!
+
+L'utente che hai invitato ha completato la registrazione:
+
+👤 Nome: ${utente.name || 'Non specificato'}
+🔑 Username: ${utente.username}
+📧 Email: ${utente.email}
+📅 Registrato il: ${new Date(utente.created_at).toLocaleDateString('it-IT')}
+
+✅ Azione richiesta: Accedi alla piattaforma per ammettere questo utente alla comunità RISO.
+
+👉 Clicca qui per ammettere
+ `.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 = `
+✅ Email Verificata - Richiesta Ammissione
+
+👤 Utente: ${utente.username} (${utente.email})
+👥 Invitante: ${invitante.username} (${invitante.email})
+📅 Data verifica: ${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 = `
+🎉 Benvenuto nella comunità RISO!
+
+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
+
+👉 Accedi ora alla piattaforma
+
+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 = `
+✅ Utente Ammesso
+
+👤 Username: ${utente.username}
+📧 Email: ${utente.email}
+${utente.name ? `🏷️ Nome: ${utente.name}` : ''}
+📅 Ammesso il: ${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 = `
+🎊 Profilo Completato!
+
+Ciao ${invitante.name || invitante.username}!
+
+L'utente che hai invitato ha completato il suo profilo ed è ora attivo su RISO:
+
+👤 Nome: ${utente.name || utente.username}
+🔑 Username: @${utente.username}
+✅ Telegram: Verificato
+📱 Profilo: Completato
+
+L'utente è ora un membro attivo della comunità e può iniziare a pubblicare annunci!
+
+👉 Visualizza profilo
+ `.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 = `
+✅ Profilo Completato
+
+👤 Utente: ${utente.username}
+📧 Email: ${utente.email}
+📱 Telegram: Verificato (ID: ${utente.teleg_id})
+👥 Invitante: ${invitante.username}
+📅 Data: ${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;
diff --git a/src/modules/InvioNotifiche.test.js b/src/modules/InvioNotifiche.test.js
new file mode 100644
index 0000000..98d8b99
--- /dev/null
+++ b/src/modules/InvioNotifiche.test.js
@@ -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',
+ 'Test HTML
'
+ );
+
+ expect(result.success).toBe(true);
+ expect(mockEmailService.sendMail).toHaveBeenCalledWith(
+ expect.objectContaining({
+ to: 'test@example.com',
+ subject: 'Test Subject',
+ html: 'Test HTML
'
+ })
+ );
+ });
+
+ 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"]
+ }
+}
+*/
diff --git a/src/router/admin_router.js b/src/router/admin_router.js
index b49a39d..e999fcb 100755
--- a/src/router/admin_router.js
+++ b/src/router/admin_router.js
@@ -2303,7 +2303,7 @@ router.post('/exec', authenticate, async (req, res) => {
if (!User.isAdmin(req.user.perm) || tokcheck !== TOKCHECK) {
// 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 = '';
@@ -2335,7 +2335,7 @@ router.post('/cloudflare', authenticate, async (req, res) => {
if (!User.isAdmin(req.user.perm) || tokcheck !== TOKCHECK) {
// 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 = '';
@@ -2403,7 +2403,7 @@ router.post('/miab', authenticate, async (req, res) => {
if (!User.isAdmin(req.user.perm) || tokcheck !== TOKCHECK) {
// 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 = '';
diff --git a/src/router/dashboard_router.js b/src/router/dashboard_router.js
index bead09b..977c188 100755
--- a/src/router/dashboard_router.js
+++ b/src/router/dashboard_router.js
@@ -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 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_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 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_nome_completo = req.user.aportador_solidario_nome_completo;
diff --git a/src/router/index_router.js b/src/router/index_router.js
index 13392fa..fe12bdf 100755
--- a/src/router/index_router.js
+++ b/src/router/index_router.js
@@ -334,7 +334,7 @@ router.post('/sendmailreg', authenticate, async (req, res) => {
const diffTime = Math.abs(now - user.lasttime_email_sent_verify);
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.' });
}
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 : ''))
) {
// 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)) {
@@ -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 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 {
@@ -1334,7 +1334,7 @@ router.patch('/chval', authenticate, async (req, res) => {
!(mydata.table === 'accounts' && (await Account.canEditAccountAdmins(req.user.username, mydata.id)))
) {
// 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]);
@@ -1617,7 +1617,7 @@ router.patch('/askfunz', authenticate, async (req, res) => {
req.user._id.toString() !== id
) {
// 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
) {
// 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))
) {
// 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;
@@ -1863,7 +1863,7 @@ router.post('/duprec/:table/:id', authenticate, async (req, res) => {
const mytable = globalTables.getTableByTableName(tablename);
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)) {
@@ -1902,7 +1902,7 @@ router.post('/duprec/:table/:id', authenticate, async (req, res) => {
router.get('/loadsite/:userId/:idapp', authenticate_noerror_WithUserLean, async (req, res) => {
try {
- // if ((req.statuscode2 = server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED)) {
+ // if ((req.statuscode2 = server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED)) {
// return;
// }
@@ -2031,8 +2031,8 @@ async function load(req, res, version = '0') {
// Estrazione e validazione degli input
const userId = req.user ? req.user._id.toString() : req.params.userId || '0';
const idapp = req.params.idapp;
- /*const status = req.code === server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED
- ? server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED
+ /*const status = req.code === server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED
+ ? server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED
: 200;*/
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 {
const idapp = req.query.idapp;
- // console.log("POST " + process.env.LINK_CHECK_UPDATES + " userId=" + userId);
if (!req.user) {
if (req.code === 1) return res.status(200).send();
else return res.status(req.code).send();
diff --git a/src/router/newsletter_router.js b/src/router/newsletter_router.js
index cf40070..a1e815b 100755
--- a/src/router/newsletter_router.js
+++ b/src/router/newsletter_router.js
@@ -391,7 +391,7 @@ router.post('/testemail', authenticate, async (req, res) => {
if (!User.isAdmin(req.user.perm)) {
// 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;
diff --git a/src/router/users_router.js b/src/router/users_router.js
index 442fbf2..3e78cb7 100755
--- a/src/router/users_router.js
+++ b/src/router/users_router.js
@@ -1,455 +1,196 @@
const express = require('express');
const router = express.Router();
-const { User } = require('../models/user');
-
-const ListaInvitiEmail = require('../models/listainvitiemail');
-
-// const { Nave } = require('../models/nave');
-const Hours = require('../models/hours');
-//const { NavePersistente } = require('../models/navepersistente');
-//const { ListaIngresso } = require('../models/listaingresso');
-//const { Graduatoria } = require('../models/graduatoria');
-// const { ExtraList } = require('../models/extralist');
-const { ObjectId } = require('mongodb');
-
-const sendemail = require('../sendemail');
-
-const { Settings } = require('../models/settings');
-const CronMod = require('../modules/CronMod');
-
-const { SendNotif } = require('../models/sendnotif');
-const { MyElem } = require('../models/myelem');
-
-const { MyBot } = require('../models/bot');
-
-const tools = require('../tools/general');
-const shared_consts = require('../tools/shared_nodejs');
-
-const server_constants = require('../tools/server_constants');
-
-const telegrambot = require('../telegram/telegrambot');
-
-const _ = require('lodash');
-
-const reg = require('../reg/registration');
-
+const UserController = require('../controllers/UserController');
const { authenticate, authenticate_noerror, authenticate_withUser } = require('../middleware/authenticate');
+const { checkBlocked } = require('../middleware/securityMiddleware');
-const Cart = require('../models/cart');
-const CartClass = require('../modules/Cart');
-const Product = require('../models/product');
-const ProductInfo = require('../models/productInfo');
-const CatProd = require('../models/catprod');
-const SubCatProd = require('../models/subcatprod');
-const Order = require('../models/order');
-const OrdersCart = require('../models/orderscart');
-const Variant = require('../models/variant');
-const TypedError = require('../modules/ErrorHandler');
+// Initialize controller
+const userController = new UserController();
-const { MyGroup } = require('../models/mygroup');
-const { Circuit } = require('../models/circuit');
-const { Province } = require('../models/province');
-const { City } = require('../models/city');
-const { Account } = require('../models/account');
+// ===== PUBLIC ROUTES =====
-const mongoose = require('mongoose').set('debug', false);
+/**
+ * Register new user
+ * POST /users
+ */
+router.post('/', (req, res) => userController.register(req, res));
-const Subscription = require('../models/subscribers');
-const Macro = require('../modules/Macro');
+/**
+ * Check if username exists
+ * GET /users/:idapp/:username
+ */
+router.get('/:idapp/:username', (req, res) => userController.checkUsername(req, res));
-async function existSubScribe(userId, access, browser) {
+/**
+ * User login
+ * POST /users/login
+ */
+router.post('/login', checkBlocked, (req, res) => userController.login(req, res));
+
+/**
+ * Refresh authentication token
+ * POST /users/newtok
+ */
+router.post('/newtok', (req, res) => userController.refreshToken(req, res));
+
+/**
+ * Get user activities (public profile)
+ * POST /users/activities
+ */
+router.post('/activities', authenticate_noerror, (req, res) =>
+ userController.getProfile(req, res)
+);
+
+// ===== AUTHENTICATED ROUTES =====
+
+/**
+ * Get user profile
+ * POST /users/profile
+ */
+router.post('/profile', authenticate, (req, res) =>
+ userController.getProfile(req, res)
+);
+
+/**
+ * Get user panel info (admin/manager only)
+ * POST /users/panel
+ */
+router.post('/panel', authenticate, (req, res) => {
+ const { User } = require('../models/user');
+ const server_constants = require('../tools/server_constants');
+
+ if (!req.user || (!User.isAdmin(req.user.perm) &&
+ !User.isManager(req.user.perm) &&
+ !User.isFacilitatore(req.user.perm))) {
+ return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({
+ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED,
+ msg: ''
+ });
+ }
+
+ userController.getProfile(req, res);
+});
+
+/**
+ * Update user balance
+ * POST /users/updatesaldo
+ */
+router.post('/updatesaldo', authenticate, (req, res) =>
+ userController.updateSaldo(req, res)
+);
+
+/**
+ * Get user's friends
+ * POST /users/friends
+ */
+router.post('/friends', authenticate, (req, res) =>
+ userController.getFriends(req, res)
+);
+
+/**
+ * Execute friend command
+ * POST /users/friends/cmd
+ */
+router.post('/friends/cmd', authenticate, (req, res) =>
+ userController.executeFriendCommand(req, res)
+);
+
+/**
+ * Send command to user
+ * POST /users/sendcmd
+ */
+router.post('/sendcmd', authenticate, (req, res) => {
+ const usernameLogged = req.user.username;
+ const { idapp, usernameOrig, usernameDest, cmd, value } = req.body;
+
+ userController.userService.sendCommand(
+ req, idapp, usernameOrig, usernameDest, cmd, value
+ ).then(result => res.send(result))
+ .catch(error => res.status(400).send({ error: error.message }));
+});
+
+/**
+ * Get user's groups
+ * POST /users/groups
+ */
+router.post('/groups', authenticate, (req, res) =>
+ userController.getGroups(req, res)
+);
+
+/**
+ * Execute group command
+ * POST /users/groups/cmd
+ */
+router.post('/groups/cmd', authenticate, (req, res) => {
+ const usernameLogged = req.user.username;
+ const { idapp, usernameOrig, groupnameDest, cmd, value } = req.body;
+
+ userController.userService.executeGroupCommand(
+ idapp, usernameOrig, groupnameDest, cmd, value, usernameLogged
+ ).then(result => res.send(result))
+ .catch(error => res.status(400).send({ error: error.message }));
+});
+
+/**
+ * Get user's circuits
+ * POST /users/circuits
+ */
+router.post('/circuits', authenticate_withUser, (req, res) =>
+ userController.getCircuits(req, res)
+);
+
+/**
+ * Execute circuit command
+ * POST /users/circuits/cmd
+ */
+router.post('/circuits/cmd', authenticate, async (req, res) => {
+ const usernameLogged = req.user.username;
+ const { idapp, usernameOrig, circuitname, cmd, value, extrarec } = req.body;
+
try {
- const itemsub = await Subscription.findOne({ userId, access, browser }).lean();
- return itemsub;
- } catch (err) {
- return null;
- }
-}
-
-function getMobileComplete(user) {
- let str = user.profile.intcode_cell + user.profile.cell;
- str = str.replace(/\s+/g, '');
- // str = str.replace(/.+/g, '');
- // str = str.replace(/-+/g, '');
-
- return str;
-}
-
-router.post('/test1', async (req, res) => {
- const user = await User.findOne({
- idapp: 1,
- username: 'paoloar77',
- });
-
- await sendemail.sendEmail_Registration(user.lang, user.email, user, user.idapp, user.linkreg);
-});
-
-// POST /users
-router.post('/', async (req, res) => {
- try {
- tools.mylog('POST /users');
- const body = _.pick(req.body, [
- 'email',
- 'password',
- 'username',
- 'group',
- 'name',
- 'surname',
- 'idapp',
- 'keyappid',
- 'lang',
- 'profile',
- 'aportador_solidario',
- ]);
- body.email = body.email.toLowerCase();
-
- const user = new User(body);
- user.ipaddr = tools.getiPAddressUser(req);
-
- user.email = user.email.trim();
- user.username = user.username.trim();
- user.name = user.name.trim();
- user.surname = user.surname.trim();
-
- if (user.aportador_solidario === 'tuo_username' || user.aportador_solidario === '{username}') {
- user.aportador_solidario = 'surya1977';
- }
-
- // tools.mylog("LANG PASSATO = " + user.lang, "IDAPP", user.idapp);
-
- if (
- !tools.isAlphaNumericAndSpecialCharacter(body.username) ||
- body.email.length < 6 ||
- body.username.length < 4 ||
- body.password.length < 5
- ) {
- await tools.snooze(5000);
- console.log('Username non valido in Registrazione: ' + body.username);
- res.status(400).send({ code: server_constants.RIS_CODE_USERNAME_NOT_VALID, msg: '' });
- return 1;
- }
-
- if (tools.blockwords(body.username) || tools.blockwords(body.name) || tools.blockwords(body.surname)) {
- // tools.writeIPToBan(user.ipaddr + ': [' + user.username + '] ' + user.name + ' ' + user.surname);
- await tools.snooze(5000);
- return res.status(404).send();
- }
-
- user.linkreg = reg.getlinkregByEmail(body.idapp, body.email, body.username);
- user.verified_email = false;
-
- // Se è parte di un invito allora verified_email = true
- const recinvito = await ListaInvitiEmail.findOne({ email: body.email });
- if (recinvito) {
- user.verified_email = true;
-
- recinvito.registered = true;
- recinvito.userIdRegistered = user._id;
- await recinvito.save();
- }
-
- user.lasttimeonline = new Date();
- user.date_reg = new Date();
- user.aportador_iniziale = user.aportador_solidario;
-
- let regexpire = req.body['regexpire'];
- let nonchiedereverifica = false;
- if (regexpire) {
- nonchiedereverifica = await User.getifRegTokenIsValid(body.idapp, regexpire);
- }
-
- if (!nonchiedereverifica) regexpire = '';
-
- if (!tools.getAskToVerifyReg(body.idapp) || nonchiedereverifica) {
- // Se non devo chiedere di verificare all'Invitato, allora lo verifico direttamente
- user.verified_by_aportador = true;
- }
-
- /* if (user.idapp === tools.AYNI) {
- user.profile.paymenttypes = ['paypal'];
- } */
-
- // Controlla se anche l'ultimo record era dallo stesso IP:
- const lastrec = await User.getLastRec(body.idapp);
- if (!!lastrec) {
- if (process.env.LOCALE !== '1') {
- if (lastrec.ipaddr === user.ipaddr) {
- // Se l'ha fatto troppo ravvicinato
- if (lastrec.date_reg) {
- let ris = tools.isdiffSecDateLess(lastrec.date_reg, 3);
- if (ris) {
- const msg = user.ipaddr + ': [' + user.username + '] ' + user.name + ' ' + user.surname;
- tools.writeIPToBan(msg);
-
- await User.findOneAndUpdate({ _id: user._id }, { $set: { banIp: true } });
-
- await telegrambot.sendMsgTelegramToTheAdmin(body.idapp, '‼️ BAN: ' + msg, true);
-
- await tools.snooze(5000);
- res.status(400).send({ code: server_constants.RIS_CODE_BANIP, msg: '' });
- return 1;
- }
- }
- }
- }
- }
-
- // user.perm = 3;
- // if (tools.testing()) {
- // user.verified_email = true;
- // }
-
- // if (user.profile.intcode_cell) {
- // if (user.profile.cell.substring(0, user.profile.intcode_cell.length) === user.profile.intcode_cell) {
- // user.profile.cell = user.profile.cell.substring(user.profile.intcode_cell.length)
- // }
- // }
- let exit;
-
- let utentenonancoraVerificato = false;
-
- const trovarec = await User.findByCredentials(user.idapp, user.username, user.password, true);
-
- // Check if already esist email or username
- exit = await User.findByUsername(user.idapp, user.username).then((useralreadyexist) => {
- if (useralreadyexist) {
- if (tools.getAskToVerifyReg(useralreadyexist.idapp)) {
- if (!useralreadyexist.verified_by_aportador && useralreadyexist.profile.teleg_id > 0) {
- if (trovarec) {
- utentenonancoraVerificato = true;
- }
- }
- }
-
- if (!utentenonancoraVerificato) {
- res.status(400).send({
- code: server_constants.RIS_CODE_USERNAME_ALREADY_EXIST,
- msg: '',
- });
- return 1;
- }
- }
- });
-
- if (!utentenonancoraVerificato) {
- if (exit === 1) return;
-
- exit = await User.findByEmail(user.idapp, user.email).then((useralreadyexist) => {
- if (useralreadyexist) {
- res.status(400).send({
- code: server_constants.RIS_CODE_EMAIL_ALREADY_EXIST,
- msg: '',
- });
- return 1;
- }
- });
-
- if (exit === 1) return;
-
- let recuser = null;
-
- recuser = await User.findByCellAndNameSurname(user.idapp, user.profile.cell, user.name, user.surname);
- if (recuser && user.name !== '' && user.surname !== '' && user.profile.cell !== '') {
- console.log('UTENTE GIA ESISTENTE:\n');
- console.log(user);
- // User already registered!
- res.status(400).send({ code: server_constants.RIS_CODE_USER_ALREADY_EXIST, msg: '' });
- return 1;
- }
- }
-
- let recextra = null;
-
- user.aportador_solidario = user.aportador_solidario.trim();
-
- user.aportador_solidario = user.aportador_solidario.replace('@', '');
-
- let id_aportador = await User.getIdByUsername(user.idapp, user.aportador_solidario);
- if (!id_aportador) {
- // Cerca se esiste l'aportador solidario con l'username Telegram
- const useraportador = await User.getUserByUsernameTelegram(user.idapp, user.aportador_solidario);
- if (useraportador) {
- id_aportador = useraportador._id;
- user.aportador_solidario = useraportador.username;
- }
- }
-
- let idMyGroupSite = tools.getidMyGroupBySite(body.idapp);
- user.idMyGroup = idMyGroupSite ? idMyGroupSite : '';
-
- if (id_aportador) {
- // Ottiene l'username "corretto" (senza maiuscole o minuscole)
- user.aportador_solidario = await User.getRealUsernameByUsername(user.idapp, user.aportador_solidario);
- }
-
- if (!id_aportador && tools.getAskToVerifyReg(body.idapp)) {
- // Si sta tentando di registrare una persona sotto che non corrisponde!
- let msg =
- 'Il link di registrazione non sembra risultare valido.
invitante: ' +
- user.aportador_solidario +
- '
username: ' +
- user.username;
-
- await telegrambot.sendMsgTelegramToTheManagers(user.idapp, msg);
- res.status(400).send({
- code: server_constants.RIS_CODE_USER_APORTADOR_NOT_VALID,
- msg: '',
- });
- return 1;
- }
-
- if (utentenonancoraVerificato) {
- if (id_aportador) {
- // Se mi sono registrato ma l'invitante non mi abilita, allora il posso registrarmi nuovamente, con lo stesso username e password,
- // con un'altro link di un'altro invitante !
- await User.setaportador_solidario(user.idapp, user.username, user.aportador_solidario);
-
- const myuser = await User.findOne({ _id: trovarec._id });
- if (myuser) {
- await telegrambot.askConfirmationUser(myuser.idapp, shared_consts.CallFunz.REGISTRATION, myuser);
-
- const { token, refreshToken } = await myuser.generateAuthToken(req);
- res.header('x-auth', token).header('x-refrtok', refreshToken).send(myuser);
- return true;
- }
- }
- }
-
- // let already_registered = (recextra || user.aportador_solidario === tools.APORTADOR_NONE) && (user.idapp === tools.AYNI);
-
- // Check if is an other people aportador_solidario
-
- /*if (already_registered) {
- // Check in the extraList if is present!
- const msg = 'Utente non trovato: ' + user.name + ' ' + user.surname + ' ' + user.profile.nationality + ' ' + user.profile.cell + ' email: ' + user.email + ' username: ' + user.username;
- console.log('Utente non trovato; ', msg);
- await telegrambot.sendMsgTelegramToTheManagers(user.idapp, msg);
- res.status(400).send({
- code: server_constants.RIS_CODE_USER_EXTRALIST_NOTFOUND,
- msg: 'Controlla se il numero ' + user.profile.cell + ' è corretto.'
- });
- return 1;
- } */
-
- return user
- .save()
- .then(async () => {
- return User.findByUsername(user.idapp, user.username, false)
- .then((usertrovato) => {
- // tools.mylog("TROVATO USERNAME ? ", user.username, usertrovato);
- if (usertrovato !== null) {
- return user.generateAuthToken(req);
- } else {
- res.status(400).send();
- return 0;
- }
- })
- .then(async (ris) => {
- // tools.mylog("passo il TOKEN: ", token);
-
- if (recextra) {
- recextra.registered = true;
- recextra.username = user.username;
- await recextra.save();
-
- // await User.fixUsername(user.idapp, user.ind_order, user.username);
- }
- return ris;
- })
- .then(async (ris) => {
- // tools.mylog("LINKREG = " + user.linkreg);
- // Invia un'email all'utente
- // tools.mylog('process.env.TESTING_ON', process.env.TESTING_ON);
- console.log('res.locale', res.locale);
-
- await telegrambot.askConfirmationUser(
- user.idapp,
- shared_consts.CallFunz.REGISTRATION,
- user,
- '',
- '',
- '',
- '',
- regexpire
- );
-
- // if (!tools.testing()) {
- await sendemail.sendEmail_Registration(user.lang, user.email, user, user.idapp, user.linkreg);
- // }
- res.header('x-auth', ris.token).header('x-refrtok', ris.refreshToken).send(user);
- return true;
- });
- })
- .catch((e) => {
- console.error(e.message);
- res.status(400).send(e);
- });
- } catch (e) {
- console.error('Error: /users REG: ' + e.message);
+ const result = await userController.userService.executeCircuitCommand(
+ idapp, usernameOrig, circuitname, cmd, value, usernameLogged, extrarec
+ );
+ res.send(result);
+ } catch (error) {
+ res.status(400).send({ error: error.message });
}
});
-router.get('/:idapp/:username', async (req, res) => {
- var username = req.params.username;
- const idapp = req.params.idapp;
+/**
+ * Logout user
+ * DELETE /users/me/token
+ */
+router.delete('/me/token', authenticate_withUser, (req, res) =>
+ userController.logout(req, res)
+);
- // if (username === 'pippo') {
- // return res.status(200).send();
- // }
-
- await User.findByUsername(idapp, username, false, true)
- .then(async (user) => {
- if (!user) {
- user = await User.findByUsernameTelegram(idapp, username, false, true);
- if (!user) return res.status(404).send();
- }
- // console.log('TROVATO!')
- return res.status(200).send();
- })
- .catch((e) => {
- return res.status(400).send();
- });
-});
-
-router.patch('/:id', authenticate, (req, res) => {
- const id = req.params.id;
- const body = _.pick(req.body.user, shared_consts.fieldsUserToChange());
-
- tools.mylogshow('PATCH USER: ', id);
-
- if (!User.isAdmin(req.user.perm)) {
- // If without permissions, exit
- return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
- }
-
- User.findByIdAndUpdate(id, { $set: body })
- .then((user) => {
- tools.mylogshow(' USER TO MODIFY: ', user);
- if (!user) {
- return res.status(404).send();
- } else {
- res.send({ code: server_constants.RIS_CODE_OK, msg: '' });
- }
- })
- .catch((e) => {
- tools.mylogserr('Error patch USER: ', e);
- res.status(400).send();
- });
-});
+/**
+ * Set user permissions
+ * POST /users/setperm
+ */
+router.post('/setperm', authenticate, (req, res) =>
+ userController.setPermissions(req, res)
+);
+/**
+ * Get last movements/transactions
+ * POST /users/lastmovs
+ */
router.post('/lastmovs', authenticate, async (req, res) => {
- const nummov = req.body.nummov;
- const nummov_uscita = req.body.nummov_uscita;
- const idapp = req.body.idapp;
-
+ const { nummov, nummov_uscita, idapp } = req.body;
+ const server_constants = require('../tools/server_constants');
+ const tools = require('../tools/general');
+
try {
const { Movement } = require('../models/movement');
-
+
+ let last_transactions = [];
if (nummov) {
last_transactions = await Movement.getLastN_Transactions(idapp, nummov, nummov_uscita);
}
-
+
res.send({ code: server_constants.RIS_CODE_OK, last_transactions });
} catch (e) {
tools.mylogserr('Error lastmovs: ', e);
@@ -457,866 +198,178 @@ router.post('/lastmovs', authenticate, async (req, res) => {
}
});
-router.post('/receiveris', authenticate, (req, res) => {
- const username = req.user ? req.user.username : '';
- const groupname = req.body.groupname;
- const idapp = req.body.idapp;
-
+/**
+ * Set receive RIS flag
+ * POST /users/receiveris
+ */
+router.post('/receiveris', authenticate, async (req, res) => {
+ const username = req.user?.username || '';
+ const { groupname, idapp } = req.body;
+ const { User } = require('../models/user');
+ const { MyGroup } = require('../models/mygroup');
+ const server_constants = require('../tools/server_constants');
+ const tools = require('../tools/general');
+
try {
- if (!username) return res.send({ code: server_constants.RIS_CODE_ERR });
-
- if (groupname) {
- return MyGroup.setReceiveRisGroup(idapp, groupname)
- .then((risult) => {
- res.send({ code: server_constants.RIS_CODE_OK });
- })
- .catch((err) => {
- tools.mylog('ERRORE IN receiveris: ' + err.message);
- res.status(400).send();
- });
- } else if (username) {
- return User.setReceiveRis(idapp, username)
- .then((risult) => {
- res.send({ code: server_constants.RIS_CODE_OK });
- })
- .catch((err) => {
- tools.mylog('ERRORE IN receiveris: ' + err.message);
- res.status(400).send();
- });
+ if (!username) {
+ return res.send({ code: server_constants.RIS_CODE_ERR });
}
- } catch (e) {
- res.status(400).send();
- }
-});
-
-router.post('/listlinkreg', authenticate, (req, res) => {
- const username = req.user ? req.user.username : '';
- const groupname = req.body.groupname;
- const idapp = req.body.idapp;
-
- try {
- if (!username) return res.send({ code: server_constants.RIS_CODE_ERR });
-
- return User.setLinkReg(idapp, username)
- .then((risult) => {
- res.send({ code: server_constants.RIS_CODE_OK });
- })
- .catch((err) => {
- tools.mylog('ERRORE IN listlinkreg: ' + err.message);
- res.status(400).send();
- });
- } catch (e) {
- res.status(400).send();
- }
-});
-
-router.post('/profile', authenticate, (req, res) => {
- const usernameOrig = req.user ? req.user.username : '';
- const perm = req.user ? req.user.perm : tools.Perm.PERM_NONE;
- const username = req.body['username'];
- const idapp = req.body.idapp;
-
- //++Todo: controlla che tipo di dati ha il permesso di leggere
-
- try {
- // Check if ìs a Notif to read
- const idnotif = req.body['idnotif'] ? req.body['idnotif'] : '';
- SendNotif.setNotifAsRead(idapp, usernameOrig, idnotif);
-
- return User.getUserProfileByUsername(idapp, username, usernameOrig, false, perm)
- .then((ris) => {
- return User.getFriendsByUsername(idapp, usernameOrig)
- .then(async (friends) => {
- if (username === usernameOrig) {
- const userprofile = await User.getExtraInfoByUsername(idapp, ris.username);
- ris.profile = userprofile;
- }
-
- return { ris, friends };
- })
- .then((tot) => {
- return res.send({ user: tot.ris, friends: tot.friends });
- });
- })
- .catch((e) => {
- tools.mylog('ERRORE IN Profile: ' + e.message);
- res.status(400).send();
- });
- } catch (e) {
- tools.mylogserr('Error profile: ', e);
- res.status(400).send();
- }
-});
-
-router.post('/activities', authenticate_noerror, (req, res) => {
- const usernameOrig = req.user ? req.user.username : '';
- const perm = req.user ? req.user.perm : tools.Perm.PERM_NONE;
- const username = req.body['username'];
- const idapp = req.body.idapp;
- const locale = req.body.locale;
-
- //++Todo: controlla che tipo di dati ha il permesso di leggere
-
- try {
- // Check if ìs a Notif to read
- const idnotif = req.body['idnotif'] ? req.body['idnotif'] : '';
- SendNotif.setNotifAsRead(idapp, usernameOrig, idnotif);
-
- return User.getUserProfileByUsername(idapp, username, usernameOrig, false, perm)
- .then((ris) => {
- return User.getFriendsByUsername(idapp, usernameOrig)
- .then(async (friends) => {
- let userprofile = null;
- if (req.user) {
- userprofile = await User.getExtraInfoByUsername(idapp, ris.username);
- } else {
- userprofile = await User.getProfilePerActivitiesByUsername(idapp, ris.username);
- ris.aportador_solidario = '';
- ris.date_reg = '';
- ris.email = '';
- }
- ris.profile = userprofile;
-
- return { ris, friends };
- })
- .then((tot) => {
- return res.send({ user: tot.ris, friends: tot.friends });
- });
- })
- .catch((e) => {
- tools.mylog('ERRORE IN Profile: ' + e.message);
- res.status(400).send();
- });
- } catch (e) {
- tools.mylogserr('Error profile: ', e);
- res.status(400).send();
- }
-});
-
-router.post('/panel', authenticate, async (req, res) => {
- const username = req.body['username'];
- idapp = req.body.idapp;
- locale = req.body.locale;
-
- if (!req.user || !User.isAdmin(req.user.perm) && !User.isManager(req.user.perm) && !User.isFacilitatore(req.user.perm)) {
- // If without permissions, exit
- return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
- }
-
- try {
- const myuser = 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 (!!myuser) {
- res.send(myuser);
- } else {
- tools.mylog('ERRORE IN panel: ');
- res.status(400).send();
- }
- } catch (e) {
- tools.mylogserr('Error profile: ', e);
- res.status(400).send();
- }
-});
-
-router.post('/notifs', authenticate, async (req, res) => {
- /* const notifs = req.body['notifs'];
- idapp = req.body.idapp;
- locale = req.body.locale;
-
- const myuser = req.user;
- if (!myuser) {
- return res.status(404).
- send({code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: ''});
- }
-
- try {
- if (!!myuser) {
- if (tools.isArray(notifs) && notifs.length >= 0) {
- myuser.profile.notifs = notifs;
- myuser.save();
- return res.send({code: server_constants.RIS_CODE_OK, msg: ''});
- }
- }
- return res.send({code: server_constants.RIS_CODE_OK, msg: ''});
- } catch (e) {
- tools.mylogserr('Error profile: ', e);
- res.status(400).send();
- }
-
- */
-});
-
-router.post('/newtok', async (req, res) => {
- try {
- const refreshToken = req.body.refreshToken;
-
- // return res.status(403).send({ error: 'Refresh token non valido' });
-
- if (!refreshToken) {
- return res.status(400).send({ error: 'Refresh token mancante' });
- }
-
- const recFound = await User.findByRefreshTokenAnyAccess(refreshToken);
- if (!recFound) {
- return res.status(403).send({ error: 'Refresh token non valido' });
- }
-
- const { token, refreshToken: newRefreshToken } = await recFound.generateAuthToken(req);
-
- return res.status(200).send({
- token,
- refreshToken: newRefreshToken,
- });
- } catch (e) {
- console.error('Errore durante il refresh token:', e);
- return res.status(500).send({ error: 'Errore interno del server' });
- }
-});
-
-// Dizionario per tenere traccia dei tentativi di accesso falliti per ogni utente
-const failedLoginAttempts = {};
-
-// Costante per il numero massimo di tentativi di accesso falliti prima del blocco
-const MAX_FAILED_ATTEMPTS = 30;
-
-// Costante per la durata del blocco in millisecondi (ad esempio 30 minuti)
-const BLOCK_DURATION = 30 * 60 * 1000; // 30 minuti
-
-// Funzione per bloccare un utente per un periodo di tempo dopo un numero specificato di tentativi falliti
-function blockUser(username) {
- failedLoginAttempts[username] = Date.now() + BLOCK_DURATION;
-}
-
-// Middleware per controllare se l'utente è bloccato
-function checkBlocked(req, res, next) {
- const { username } = req.body;
- const now = Date.now();
-
- if (failedLoginAttempts[username] && failedLoginAttempts[username] > now) {
- text = 'Utente bloccato. Riprova più tardi. (username=' + username + ')';
- console.log(text);
- return res.status(403).json({ message: 'Utente bloccato. Riprova più tardi.' });
- }
-
- next();
-}
-
-router.post('/login', checkBlocked, async (req, res) => {
- const body = _.pick(req.body, ['username', 'password', 'idapp', 'keyappid', 'lang']);
- const userpass = new User(body);
- // const subs = _.pick(req.body, ['subs']);
-
- // tools.mylog("LOG: u: " + user.username + " p:" + user.password);
-
- // tools.mylog("user REC:", user);
-
- if (body.keyappid !== process.env.KEY_APP_ID) return res.status(400).send();
-
- let resalreadysent = false;
-
- try {
- const user = await User.findByCredentials(userpass.idapp, userpass.username, userpass.password);
-
- if (!user) {
- const rislogin = await User.tooManyLoginWrong(body.idapp, body.username, true);
-
- if (rislogin.troppilogin) {
- let text =
- 'Troppe richieste di Login ERRATE: ' +
- body.username +
- ' [IP: ' +
- tools.getiPAddressUser(req) +
- '] Tentativi: ' +
- rislogin.retry_pwd;
- telegrambot.sendMsgTelegramToTheManagers(body.idapp, text);
- console.log('/login', text);
- res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: text });
- return;
- }
-
- await tools.snooze(2000);
-
- if (!failedLoginAttempts[body.username]) {
- failedLoginAttempts[body.username] = 1;
- } else {
- failedLoginAttempts[body.username]++;
- }
-
- let numvolteerrati = failedLoginAttempts[body.username];
-
- if (numvolteerrati > 2) {
- const msg =
- 'Tentativo (' +
- numvolteerrati +
- ') di Login ERRATO [' +
- body.username +
- ' , ' +
- ']\n' +
- '[IP: ' +
- tools.getiPAddressUser(req) +
- ']';
- tools.mylogshow(msg);
- await telegrambot.sendMsgTelegramToTheAdmin(req.body.idapp, msg, true);
- tools.writeErrorLog(msg);
- }
-
- if (failedLoginAttempts[body.username] >= MAX_FAILED_ATTEMPTS) {
- blockUser(body.username);
- text =
- 'Troppi tentativi di accesso falliti. Utente bloccato (' +
- body.username +
- ')' +
- ' [IP: ' +
- tools.getiPAddressUser(req) +
- ']';
- tools.mylogshow(text);
- telegrambot.sendMsgTelegramToTheManagers(req.body.idapp, text);
- res.status(403).json({ message: text });
- resalreadysent = true;
- }
-
- return res.status(401).send({ code: server_constants.RIS_CODE_LOGIN_ERR });
+ if (groupname) {
+ await MyGroup.setReceiveRisGroup(idapp, groupname);
} else {
- const myris = await user.generateAuthToken(req);
-
- const usertosend = new User();
-
- shared_consts.fieldsUserToChange().forEach((field) => {
- usertosend[field] = user[field];
- });
-
- const subsExistonDb = await existSubScribe(usertosend._id, 'auth', req.get('User-Agent'));
-
- res.header('x-auth', myris.token).header('x-refrtok', myris.refreshToken).send({
- usertosend,
- code: server_constants.RIS_CODE_OK,
- subsExistonDb,
- });
+ await User.setReceiveRis(idapp, username);
}
- } catch (e) {
- console.error('ERRORE IN LOGIN: ' + e.message);
- if (!resalreadysent) res.status(400).send({ code: server_constants.RIS_CODE_LOGIN_ERR_GENERIC, msgerr: e.message });
- }
-});
-
-router.delete('/me/token', authenticate_withUser, (req, res) => {
- // tools.mylog("TOKENREM = " + req.token);
- try {
- req.user.removeToken(req.token).then(
- () => {
- res.status(200).send();
- },
- () => {
- res.status(400).send();
- }
- );
- } catch (e) {
- console.log('delete(/me/token', e.message);
- }
-});
-
-router.post('/setperm', authenticate, (req, res) => {
- const body = _.pick(req.body, ['idapp', 'username', 'perm']);
- tools.mylog('SETPERM = ' + req.token);
-
- User.setPermissionsById(req.user._id, body).then(
- () => {
- res.status(200).send();
- },
- () => {
- res.status(400).send();
- }
- );
-});
-
-router.post('/import_extralist', async (req, res) => {
- const strdata = req.body.strdata;
- idapp = req.body.idapp;
- locale = req.body.locale;
-
- // const ris = await ExtraList.ImportData(locale, idapp, strdata);
- console.log('ris', ris);
-
- res.send(ris);
-});
-
-router.post('/friends', authenticate, (req, res) => {
- const username = req.user.username;
- idapp = req.body.idapp;
- locale = req.body.locale;
-
- return User.getFriendsByUsername(idapp, username)
- .then((ris) => {
- res.send(ris);
- })
- .catch((e) => {
- tools.mylog('ERRORE IN Profile: ' + e.message);
- res.status(400).send();
- });
-});
-
-router.post('/groups', authenticate, (req, res) => {
- const username = req.user.username;
- idapp = req.body.idapp;
- locale = req.body.locale;
-
- return MyGroup.getGroupsByUsername(idapp, username, req)
- .then((ris) => {
- res.send(ris);
- })
- .catch((e) => {
- tools.mylog('ERRORE IN groups: ' + e.message);
- res.status(400).send();
- });
-});
-
-router.post('/circuits', authenticate_withUser, (req, res) => {
- const username = req.user.username;
- idapp = req.body.idapp;
- locale = req.body.locale;
- nummovTodownload = req.body.nummovTodownload;
-
- return Circuit.getCircuitsByUsername(idapp, username, req.user, nummovTodownload)
- .then((ris) => {
- res.send(ris);
- })
- .catch((e) => {
- tools.mylog('ERRORE IN circuits: ' + e.message);
- res.status(400).send();
- });
-});
-
-router.post('/updatesaldo', authenticate, async (req, res) => {
- const username = req.user.username;
- idapp = req.body.idapp;
- locale = req.body.locale;
- circuitId = req.body.circuitId;
- groupname = req.body.groupname;
- const lastdr = req.body['lastdr'] ? req.body['lastdr'] : '';
-
- try {
- const userprofile = await User.getExtraInfoByUsername(idapp, username);
- let ris = {
- userprofile,
- };
-
- ris.arrrecnotif = await SendNotif.findAllNotifByUsernameIdAndIdApp(
- username,
- lastdr,
- idapp,
- shared_consts.LIMIT_NOTIF_FOR_USER,
- shared_consts.QualiNotifs.OTHERS
- );
- ris.arrrecnotifcoins = await SendNotif.findAllNotifByUsernameIdAndIdApp(
- username,
- lastdr,
- idapp,
- shared_consts.LIMIT_NOTIFCOINS_FOR_USER,
- shared_consts.QualiNotifs.CIRCUITS
- );
-
- return res.send({ ris });
- } catch (e) {
- tools.mylog('ERRORE IN updatesaldo: ' + e);
+
+ res.send({ code: server_constants.RIS_CODE_OK });
+ } catch (err) {
+ tools.mylog('ERRORE IN receiveris: ' + err.message);
res.status(400).send();
}
});
-router.post('/friends/cmd', authenticate, async (req, res) => {
- const usernameLogged = req.user.username;
- const idapp = req.body.idapp;
- const locale = req.body.locale;
- let usernameOrig = req.body.usernameOrig;
- let usernameDest = req.body.usernameDest;
- const cmd = req.body.cmd;
- const value = req.body.value;
-
- if (!User.isAdmin(req.user.perm) || !User.isManager(req.user.perm)) {
- // If without permissions, exit
- if (
- usernameOrig !== usernameLogged &&
- usernameDest !== usernameLogged &&
- (cmd === shared_consts.FRIENDSCMD.SETFRIEND || cmd === shared_consts.FRIENDSCMD.SETHANDSHAKE)
- ) {
- return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
- }
- }
-
- usernameOrig = await User.getRealUsernameByUsername(idapp, usernameOrig);
- usernameDest = await User.getRealUsernameByUsername(idapp, usernameDest);
-
- return User.setFriendsCmd(req, idapp, usernameOrig, usernameDest, cmd, value)
- .then((ris) => {
- res.send(ris);
- })
- .catch((e) => {
- tools.mylog('ERRORE IN Friends/cmd: ' + e.message);
- res.status(400).send();
- });
-});
-
-router.post('/sendcmd', authenticate, async (req, res) => {
- const usernameLogged = req.user.username;
- const idapp = req.body.idapp;
- const locale = req.body.locale;
- let usernameOrig = req.body.usernameOrig;
- let usernameDest = req.body.usernameDest;
- const cmd = req.body.cmd;
- const value = req.body.value;
-
- usernameOrig = await User.getRealUsernameByUsername(idapp, usernameOrig);
- usernameDest = await User.getRealUsernameByUsername(idapp, usernameDest);
-
- return User.sendCmd(req, idapp, usernameOrig, usernameDest, cmd, value)
- .then((ris) => {
- res.send(ris);
- })
- .catch((e) => {
- tools.mylog('ERRORE IN sendcmd: ' + e.message);
- res.status(400).send();
- });
-});
-
-router.post('/groups/cmd', authenticate, (req, res) => {
- const usernameLogged = req.user.username;
- const idapp = req.body.idapp;
- const locale = req.body.locale;
- const usernameOrig = req.body.usernameOrig;
- const groupnameDest = req.body.groupnameDest;
- const cmd = req.body.cmd;
- const value = req.body.value;
-
- /*if (!User.isAdmin(req.user.perm) || !User.isManager(req.user.perm)) {
- // If without permissions, exit
- if (usernameOrig !== usernameLogged) {
- return res.status(404).
- send({code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: ''});
- }
- }*/
-
- return User.setGroupsCmd(idapp, usernameOrig, groupnameDest, cmd, value, usernameLogged)
- .then((ris) => {
- res.send(ris);
- })
- .catch((e) => {
- tools.mylog('ERRORE IN groups/cmd: ' + e.message);
- res.status(400).send();
- });
-});
-
-router.post('/circuits/cmd', authenticate, async (req, res) => {
- const usernameLogged = req.user.username;
- const idapp = req.body.idapp;
- const locale = req.body.locale;
- const usernameOrig = req.body.usernameOrig;
- const circuitname = req.body.circuitname;
- const cmd = req.body.cmd;
- const value = req.body.value;
- const extrarec = req.body.extrarec;
-
- /*if (!User.isAdmin(req.user.perm) || !User.isManager(req.user.perm)) {
- // If without permissions, exit
- if (usernameOrig !== usernameLogged) {
- return res.status(404).
- send({code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: ''});
- }
- }*/
-
- return await User.setCircuitCmd(idapp, usernameOrig, circuitname, cmd, value, usernameLogged, extrarec)
- .then(async (ris) => {
- // Check if ìs a Notif to read
- if (extrarec && extrarec.hasOwnProperty('idnotif')) {
- const idnotif = extrarec['idnotif'] ? extrarec['idnotif'] : '';
- await SendNotif.setNotifAsRead(idapp, usernameOrig, idnotif);
- }
-
- return res.send(ris);
- })
- .catch((e) => {
- tools.mylog('ERRORE IN circuits/cmd: ' + e.message);
- res.status(400).send();
- });
-});
-
-async function ConvertiDaIntAStr(mytable) {
+/**
+ * List registration links
+ * POST /users/listlinkreg
+ */
+router.post('/listlinkreg', authenticate, async (req, res) => {
+ const username = req.user?.username || '';
+ const { idapp } = req.body;
+ const { User } = require('../models/user');
+ const server_constants = require('../tools/server_constants');
+ const tools = require('../tools/general');
+
try {
- console.log('INIZIO - ConvertiDaIntAStr ', mytable.modelName);
-
- return await mytable.find({ _id: { $type: 16 } }).then(async (arr) => {
- console.log('num record ', arr.length);
-
- let ind = 0;
- for (let x of arr) {
- const idnew = x._id;
-
- if (idnew < 10000) {
- const idint = parseInt(x._id, 10) + 10000;
-
- const myrec = new mytable(x._doc);
-
- myrec._doc.date_created = x._doc.date_created;
- myrec._doc.date_updated = x._doc.date_updated;
-
- if (!myrec._doc.date_updated) {
- if (myrec.hasOwnProperty('date_created')) myrec._doc.date_updated = myrec._doc.date_created;
- }
- if (myrec.hasOwnProperty('date_updated') && !myrec._doc.date_created)
- myrec._doc.date_created = myrec._doc.date_updated;
- myrec._doc._id = idint + '';
-
- try {
- const doc = await myrec.save();
- ind++;
- console.log('++Add (', ind, ')', doc._id);
- } catch (err) {
- const myid = parseInt(err.keyValue._id, 10) + 0;
- const canc = await mytable.findOneAndDelete({ _id: myid });
- if (canc) console.log('err', err.message, 'canc', canc._doc._id);
- }
- }
- }
- console.log('FINE - ConvertiDaIntAStr ', mytable.modelName);
- });
+ if (!username) {
+ return res.send({ code: server_constants.RIS_CODE_ERR });
+ }
+
+ await User.setLinkReg(idapp, username);
+ res.send({ code: server_constants.RIS_CODE_OK });
} catch (err) {
- console.error(err);
- }
-}
-async function RimuoviInteri(mytable) {
- try {
- console.log('INIZIO - RimuoviInteri ', mytable.modelName);
-
- const arr = await mytable.find({ _id: { $lte: 10000 } });
- console.log(' search interi...', arr.length);
-
- const ris = await mytable.deleteMany({ _id: { $lte: 10000 } });
-
- console.log('FINE - RimuoviInteri ', mytable.modelName, ris);
- } catch (err) {
- console.error(err);
- }
-}
-
-async function eseguiDbOpUser(idapp, mydata, locale, req, res) {
- let ris = await User.DbOp(idapp, mydata);
-
- const populate = require('../populate/populate');
-
- const globalTables = require('../tools/globalTables');
-
- let mystr = '';
-
- try {
- if (mydata.dbop === 'CreateAccountCircuits') {
- const allcirc = await Circuit.find({ idapp });
-
- for (const mycirc of allcirc) {
- // Il Conto Comunitario prende il nome del circuito !
- await Account.createAccount(idapp, '', mycirc.name, true, '', mycirc.path);
- }
- } else if (mydata.dbop === 'saveStepTut') {
- await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.stepTutorial': mydata.value } });
- } else if (mydata.dbop === 'noNameSurname') {
- await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noNameSurname': mydata.value } });
- } else if (mydata.dbop === 'telegram_verification_skipped') {
- await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.telegram_verification_skipped': mydata.value } });
- } else if (mydata.dbop === 'pwdLikeAdmin') {
- await User.setPwdComeQuellaDellAdmin(mydata);
- } else if (mydata.dbop === 'ripristinaPwdPrec') {
- await User.ripristinaPwdPrec(mydata);
- } else if (mydata.dbop === 'noCircuit') {
- await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noCircuit': mydata.value } });
- } else if (mydata.dbop === 'noCircIta') {
- await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noCircIta': mydata.value } });
- } else if (mydata.dbop === 'insert_circuito_ita') {
- await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.insert_circuito_ita': mydata.value } });
- } else if (mydata.dbop === 'noFoto') {
- await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noFoto': mydata.value } });
- }
- } catch (e) {
- console.log(e.message);
- }
-}
-
-router.post('/dbop', authenticate, async (req, res) => {
- const mydata = req.body.mydata;
- idapp = req.body.idapp;
- locale = req.body.locale;
-
- if (!User.isCollaboratore(req.user.perm)) {
- return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED });
- }
-
- try {
- const cronMod = new CronMod();
- const risOp = await cronMod.eseguiDbOp(idapp, mydata, req, res);
-
- return res.send({ code: server_constants.RIS_CODE_OK, data: risOp });
- } catch (e) {
- console.log(e.message);
- return res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: e.message });
+ tools.mylog('ERRORE IN listlinkreg: ' + err.message);
+ res.status(400).send();
}
});
+// ===== ADMIN ROUTES =====
+
+/**
+ * Update user (admin only)
+ * PATCH /users/:id
+ */
+router.patch('/:id', authenticate, (req, res) => {
+ const { User } = require('../models/user');
+ const _ = require('lodash');
+ const shared_consts = require('../tools/shared_nodejs');
+ const server_constants = require('../tools/server_constants');
+ const tools = require('../tools/general');
+
+ const id = req.params.id;
+ const body = _.pick(req.body.user, shared_consts.fieldsUserToChange());
+
+ tools.mylogshow('PATCH USER: ', id);
+
+ if (!User.isAdmin(req.user.perm)) {
+ return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({
+ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED,
+ msg: ''
+ });
+ }
+
+ User.findByIdAndUpdate(id, { $set: body })
+ .then((user) => {
+ tools.mylogshow(' USER TO MODIFY: ', user);
+ if (!user) {
+ return res.status(404).send();
+ }
+ res.send({ code: server_constants.RIS_CODE_OK, msg: '' });
+ })
+ .catch((e) => {
+ tools.mylogserr('Error patch USER: ', e);
+ res.status(400).send();
+ });
+});
+
+/**
+ * Execute database operation (admin only)
+ * POST /users/dbop
+ */
+router.post('/dbop', authenticate, (req, res) =>
+ userController.executeDbOperation(req, res)
+);
+
+/**
+ * Execute user database operation
+ * POST /users/dbopuser
+ */
router.post('/dbopuser', authenticate, async (req, res) => {
- const mydata = req.body.mydata;
- idapp = req.body.idapp;
- locale = req.body.locale;
-
+ const { mydata, idapp } = req.body;
+ const server_constants = require('../tools/server_constants');
+
try {
- let ris = await eseguiDbOpUser(idapp, mydata, locale, req, res);
-
- if (!ris) {
- ris = {};
- }
-
- ris = await User.updateMyData(ris, idapp, req.user.username);
-
- res.send({ code: server_constants.RIS_CODE_OK, ris });
- } catch (e) {
- res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: e });
-
- console.log(e.message);
- }
-});
-
-router.post('/infomap', authenticate, async (req, res) => {
- const idapp = req.body.idapp;
- const raggruppa = true;
-
- try {
- let myquery = [
- {
- $match: {
- idapp,
- $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
- },
- },
- {
- $lookup: {
- from: 'provinces', // Collezione delle province
- localField: 'profile.resid_province', // Campo nella collezione User che contiene l'ID della provincia
- foreignField: 'prov', // Campo nella collezione Province che identifica l'ID della provincia
- as: 'provinceInfo', // Nome del campo in cui verranno memorizzate le informazioni della provincia
- },
- },
- {
- $addFields: {
- provinceInfo: { $arrayElemAt: ['$provinceInfo', 0] }, // Estrae il primo elemento dell'array provinceInfo
- },
- },
- {
- $project: {
- username: 1,
- name: 1,
- surname: 1,
- email: 1,
- verified_by_aportador: 1,
- aportador_solidario: 1,
- lasttimeonline: 1,
- 'profile.img': 1,
- 'profile.resid_province': 1,
- lat: '$provinceInfo.lat', // Aggiunge il campo lat preso dalla provincia
- long: '$provinceInfo.long', // Aggiunge il campo long preso dalla provincia
- },
- },
- ];
-
- let ris = null;
-
- if (raggruppa) {
- const myquery = [
- {
- $lookup: {
- from: 'users', // Collezione degli utenti
- localField: 'prov', // Campo nella collezione Province che identifica l'ID della provincia
- foreignField: 'profile.resid_province', // Campo nella collezione User che contiene l'ID della provincia
- as: 'users', // Nome del campo in cui verranno memorizzati gli utenti della provincia
- },
- },
- {
- $addFields: {
- userCount: { $size: '$users' }, // Aggiunge il numero di utenti nella provincia
- },
- },
- {
- $lookup: {
- from: 'provinces', // Collezione delle province
- localField: 'prov', // Campo nella collezione Province che identifica l'ID della provincia
- foreignField: 'prov', // Campo nella collezione Province che identifica l'ID della provincia
- as: 'provinceInfo', // Nome del campo in cui verranno memorizzate le informazioni della provincia
- },
- },
- {
- $addFields: {
- provinceDescr: { $arrayElemAt: ['$provinceInfo.descr', 0] }, // Aggiunge il campo descr preso dalla provincia
- },
- },
- {
- $project: {
- _id: 0, // Esclude il campo _id
- province: '$prov', // Rinomina il campo prov come province
- descr: '$provinceDescr',
- userCount: 1,
- lat: 1, // Include il campo lat
- long: 1, // Include il campo long
- },
- },
- ];
-
- ris = await Province.aggregate(myquery);
- } else {
- ris = await User.aggregate(myquery);
- }
-
- if (!ris) {
- ris = {};
- }
-
- res.send({ code: server_constants.RIS_CODE_OK, ris });
- } catch (e) {
- res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: e });
-
- console.log(e.message);
- }
-});
-
-router.post('/mgt', authenticate_withUser, async (req, res) => {
- const mydata = req.body.mydata;
- idapp = req.body.idapp;
- locale = req.body.locale;
-
- try {
- const { nummsgsent, numrec, textsent, text } = await telegrambot.sendMsgFromSiteToBotTelegram(
- idapp,
- req.user,
- mydata
+ const result = await userController.userService.executeUserDbOperation(
+ idapp,
+ mydata,
+ req.user.username
);
-
- return res.send({ numrec, nummsgsent, textsent, text });
+
+ res.send({ code: server_constants.RIS_CODE_OK, ris: result });
} catch (e) {
- res.status(400).send();
- res.send({ code: server_constants.RIS_CODE_ERR, msg: e });
-
- console.log(e.message);
+ res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: e.message });
}
});
-module.exports = router;
+/**
+ * Get map information
+ * POST /users/infomap
+ */
+router.post('/infomap', authenticate, (req, res) =>
+ userController.getMapInfo(req, res)
+);
+
+/**
+ * Management telegram operations
+ * POST /users/mgt
+ */
+router.post('/mgt', authenticate_withUser, async (req, res) => {
+ const { mydata, idapp } = req.body;
+ const telegrambot = require('../telegram/telegrambot');
+
+ try {
+ const { nummsgsent, numrec, textsent, text } =
+ await telegrambot.sendMsgFromSiteToBotTelegram(idapp, req.user, mydata);
+
+ res.send({ numrec, nummsgsent, textsent, text });
+ } catch (e) {
+ res.status(400).send({ error: e.message });
+ }
+});
+
+// ===== TEST ROUTES (Development only) =====
+
+if (process.env.NODE_ENV === 'development' || process.env.LOCALE === '1') {
+ router.post('/test1', async (req, res) => {
+ const { User } = require('../models/user');
+ const sendemail = require('../sendemail');
+
+ const user = await User.findOne({
+ idapp: 1,
+ username: 'paoloar77'
+ });
+
+ if (user) {
+ await sendemail.sendEmail_Registration(
+ user.lang,
+ user.email,
+ user,
+ user.idapp,
+ user.linkreg
+ );
+ }
+
+ res.send({ success: true });
+ });
+}
+
+module.exports = router;
\ No newline at end of file
diff --git a/src/sendemail.js b/src/sendemail.js
index d8a9c8c..fd17f21 100755
--- a/src/sendemail.js
+++ b/src/sendemail.js
@@ -498,8 +498,19 @@ module.exports = {
const strlinkreg = tools.getHostByIdApp(idapp) + `/invitetoreg/${dati.token}`;
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) {
try {
+ const nomecognomeInvitante = await User.getNameSurnameByUsername(idapp, user.aportador_solidario, true);
+
let mylocalsconf = {
idapp,
dataemail: await this.getdataemail(idapp),
@@ -511,17 +522,39 @@ module.exports = {
forgetpwd: tools.getHostByIdApp(idapp) + '/requestresetpwd',
emailto: emailto,
verified_email: user.verified_email,
+ usernameInvitante: user.aportador_solidario,
+ nomeInvitante: nomecognomeInvitante.trim(),
+ nomeInvitato: await User.getNameSurnameEUsernameByUsername(idapp, user.username),
user,
};
mylocalsconf = this.setParamsForTemplate(user, mylocalsconf);
- await this.sendEmail_base(
- tools.getpathregByIdApp(idapp, lang),
- emailto,
- mylocalsconf,
- tools.getreplyToEmailByIdApp(idapp)
- );
+ let quale_email_inviare = '';
+
+ if (user.verified_email) {
+ // se l'utente è già stato verificata la sua email, allora gli mando direttamente la email di invito.
+ 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
const ris = await this.sendEmail_base(
@@ -539,7 +572,7 @@ module.exports = {
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);
+ tools.sendNotifToAdmin(mylocalsconf.idapp, true, '++Registrazione [' + numutenti + '] ' + nometot + aportador);
}
return ris;
@@ -548,6 +581,34 @@ module.exports = {
}
},
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 {
let mylocalsconf = {
idapp,
@@ -560,25 +621,15 @@ module.exports = {
linkRegistrazione: this.getlinkInvitoReg(idapp, dati),
emailto: emailto,
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);
-
- 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);
- }
+ await telegrambot.notifyToTelegram(telegrambot.phase.AMMETTI_UTENTE, mylocalsconf);
return ris;
} catch (e) {
- console.error('Err sendEmail_Registration', e);
+ console.error('Err sendEmail_InvitaAmico', e);
}
},
@@ -1584,7 +1635,7 @@ module.exports = {
smtpTransport,
previewonly
);
- console.log(' ...email inviata?', risult);
+ // console.log(' ...email inviata?', risult);
return risult;
}
diff --git a/src/server/router/index_router_NONUSATO.js b/src/server/router/index_router_NONUSATO.js
index 5d9c1ae..1451bee 100644
--- a/src/server/router/index_router_NONUSATO.js
+++ b/src/server/router/index_router_NONUSATO.js
@@ -362,7 +362,7 @@ router.post('/settable', authenticate, async (req, res) => {
(await !tools.ModificheConsentite(req, params.table, fieldsvalue, mydata ? mydata._id : ''))
) {
// 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)) {
@@ -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 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 {
@@ -1293,7 +1293,7 @@ router.patch('/chval', authenticate, async (req, res) => {
!(mydata.table === 'accounts' && (await Account.canEditAccountAdmins(req.user.username, mydata.id)))
) {
// 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]);
@@ -1576,7 +1576,7 @@ router.patch('/askfunz', authenticate, async (req, res) => {
req.user._id.toString() !== id
) {
// 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
) {
// 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))
) {
// 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;
@@ -1822,7 +1822,7 @@ router.post('/duprec/:table/:id', authenticate, async (req, res) => {
const mytable = globalTables.getTableByTableName(tablename);
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)) {
@@ -1986,8 +1986,8 @@ async function load(req, res, version = '0') {
// Estrazione e validazione degli input
const userId = req.user ? req.user._id.toString() : req.params.userId || '0';
const idapp = req.params.idapp;
- /*const status = req.code === server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED
- ? server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED
+ /*const status = req.code === server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED
+ ? server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED
: 200;*/
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 {
const idapp = req.query.idapp;
diff --git a/src/services/AuthService.js b/src/services/AuthService.js
new file mode 100644
index 0000000..e266775
--- /dev/null
+++ b/src/services/AuthService.js
@@ -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;
\ No newline at end of file
diff --git a/src/services/RegistrationService.js b/src/services/RegistrationService.js
new file mode 100644
index 0000000..6a110d5
--- /dev/null
+++ b/src/services/RegistrationService.js
@@ -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.
invitante: ${user.aportador_solidario}
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;
diff --git a/src/services/UserService.js b/src/services/UserService.js
new file mode 100644
index 0000000..abb9811
--- /dev/null
+++ b/src/services/UserService.js
@@ -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;
\ No newline at end of file
diff --git a/src/telegram/telegrambot.js b/src/telegram/telegrambot.js
index 1f8c438..9a2a7fd 100644
--- a/src/telegram/telegrambot.js
+++ b/src/telegram/telegrambot.js
@@ -652,7 +652,7 @@ const txt_pt = {
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 = {
ADMIN_IDTELEGRAM_SERVER: '12429864', //Paolo
@@ -662,7 +662,8 @@ const MyTelegramBot = {
phase: {
REGISTRATION: 1,
ISCRIZIONE_CONACREIS: 2,
- ISCRIZIONE_ARCADEI: 4,
+ INVITA_AMICO: 5,
+ AMMETTI_UTENTE: 10,
},
getAppTelegram: function () {
@@ -793,6 +794,8 @@ const MyTelegramBot = {
let nome = tools.getNomeCognomeEUserNameByUser(mylocalsconf.user);
text = printf(getstr(langdest, 'MSG_APORTADOR_USER_REGISTERED'), nome, numutenti, aportador);
}
+ } else if (phase === this.phase.INVITA_AMICO) {
+
}
let addtext = '';
@@ -915,6 +918,19 @@ const MyTelegramBot = {
]);
}
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) {
msg_notifpush = printf(getstr(langdest, 'MSG_ACCEPT_NEWENTRY_INGROUP'), name);
domanda = printf(getstr(langdest, 'MSG_ACCEPT_NEWENTRY_INGROUP'), name) + '
' + struserinfomsg;
@@ -1748,10 +1764,15 @@ class Telegram {
if (rec.user) {
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 {
if (!rec.user.profile.username_telegram) {
return this.checkIfUsernameTelegramSet(msg, rec.user);
@@ -1905,8 +1926,8 @@ class Telegram {
risp = 'Siiiii ! Davvero! ' + emo.DREAM;
} else if (MsgBot.PAROLACCE.find((rec) => testo.indexOf(rec) > -1)) {
risp = "Da te non me l'aspettavo proprio !! " + emo.INNOCENT + emo.CROSS_ROSSA;
- } else if (MsgBot.OK.find((rec) => testo.indexOf(rec) > -1)) {
- risp = '👍🏻';
+ // } else if (MsgBot.OK.find((rec) => testo.indexOf(rec) > -1)) {
+ // risp = '👍🏻';
} else if (MsgBot.CUORE.find((rec) => testo.indexOf(rec) > -1)) {
risp = '❤️💚💜';
} 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 !");
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(
this.idapp,
- recuser._id,
+ recuser.user._id,
msg.from.username || '',
msg.from.first_name || '',
msg.from.last_name || ''
@@ -3320,12 +3357,12 @@ class Telegram {
rec.datemenu_updated = null;
rec.menuDb = null;
- if (!!msg.from.username) {
- await MyTelegramBot.askConfirmationUser(this.idapp, shared_consts.CallFunz.REGISTRATION, recuser);
- } else {
- console.log(" ... MA GLI MANCA L'USERNAME TELEGRAM !! ");
- }
+ const user = await User.UserByIdTelegram(this.idapp, msg.chat.id);
+
+ await MyTelegramBot.askConfirmationUser(this.idapp, shared_consts.CallFunz.VERIFICA_TELEGRAM, user);
}
+ } catch (e) {
+ console.error(e);
}
}
@@ -4557,6 +4594,9 @@ if (true) {
`${userDest.username}`
);
+ // Invia una email alla persona che è stata ammessa
+
+
await local_sendMsgTelegram(user.idapp, data.username, msgOrig);
await local_sendMsgTelegram(user.idapp, data.userDest, msgDest);
// Invia questo msg anche all'Admin
diff --git a/src/tools/general.js b/src/tools/general.js
index 2a34c15..c4972d2 100755
--- a/src/tools/general.js
+++ b/src/tools/general.js
@@ -467,6 +467,7 @@ module.exports = {
AYNI: '7',
CNM: '10',
RISO: '13',
+ RISO_STR_PATH: 'RISO',
FIOREDELLAVITA: '15',
PIUCHEBUONO: '17',
MACRO: '18',
diff --git a/src/tools/server_constants.js b/src/tools/server_constants.js
index abc9060..d7a5f14 100755
--- a/src/tools/server_constants.js
+++ b/src/tools/server_constants.js
@@ -30,7 +30,7 @@ module.exports = Object.freeze({
RIS_CODE_HTTP_INVALID_TOKEN: 401,
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,
diff --git a/src/tools/shared_nodejs.js b/src/tools/shared_nodejs.js
index 9cfa9d6..de83c6c 100755
--- a/src/tools/shared_nodejs.js
+++ b/src/tools/shared_nodejs.js
@@ -80,6 +80,7 @@ module.exports = {
UNBLOCK_USER: 156,
REPORT_USER: 158,
FIND_PEOPLE: 166,
+ DELETE_USER: 170,
},
GROUPSCMD: {
@@ -595,6 +596,7 @@ module.exports = {
MS_SHARE_LINK: 2000,
MSG_BENV_REGISTRATO: 2020,
MSG_INVITE_WHATSAPP: 2040,
+ MSG_VERIFICA_TELEGRAM_COMPLETATA: 2050,
},
TypeSend: {
diff --git a/src/validators/UserValidators.js b/src/validators/UserValidators.js
new file mode 100644
index 0000000..c64d6ba
--- /dev/null
+++ b/src/validators/UserValidators.js
@@ -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
+};
\ No newline at end of file
diff --git a/src/version.txt b/src/version.txt
index 3c9b023..8acd4cd 100644
--- a/src/version.txt
+++ b/src/version.txt
@@ -1 +1 @@
-1.2.79
\ No newline at end of file
+1.2.84
\ No newline at end of file