13 Commits

Author SHA1 Message Date
Surya Paolo
9a0cdec7bd - altro aggiornamento restying
- Invio RIS aggiornato
- Eventi
- Home Page restyling
2025-12-18 17:00:43 +01:00
Surya Paolo
3d87c336de - aggiornamento di tante cose...
- generazione Volantini
- pagina RIS
2025-12-17 10:07:51 +01:00
Surya Paolo
037ff6f7f9 - verifica email se non è stata verificata (componente)
- altri aggiornamenti grafica PAGERIS.
- OLLAMA AI
2025-12-12 00:44:12 +01:00
Surya Paolo
b8dcd7f5e0 categorie 2025-12-07 15:17:42 +01:00
Surya Paolo
b35c99c8fb ok 2025-12-07 10:48:08 +01:00
Surya Paolo
086e4ab8ba - aggiornati anche i ContribTypes che appaiono sulla card degli annunci 2025-12-07 10:26:35 +01:00
Surya Paolo
139d3fe241 - Aggiornate tutte le categorie ottimizzandole.
- Migrazione delle vecchie categ. con quelle nuove.
- Create le Categorie e sottocategorie degli Eventi (a parte).
- Aggiornato la card dell'Ospitalità
2025-12-07 02:13:26 +01:00
Surya Paolo
6fe3ed7c8b - aggiornamenti guida RIS, FAQ
- Editor HTML aggiunto CSS e Script
- Statistiche
- CRISBalanceBar
- Inizio Sync... (ma disattivato)
2025-12-02 22:16:29 +01:00
Surya Paolo
81ae2df8ef - aggiornato Card service, e CGridTableRec. 2025-11-28 21:28:38 +01:00
Surya Paolo
997a7b8b98 - email Abilitazione Circuito RISO 2025-11-28 18:53:43 +01:00
Surya Paolo
331a5451b2 - fix zona provinciale
- email abilitazione circuito: invio email ad admin
- admin che abilita la fiducia cliccando sul bottone
2025-11-27 23:51:48 +01:00
Surya Paolo
514c2488cc - comune residenza anche sulla email
- comune non obbligatorio... Skippa
2025-11-27 03:15:01 +01:00
Surya Paolo
33e51bac0e - comune residenza anche sulla email 2025-11-27 01:28:25 +01:00
99 changed files with 12403 additions and 7261 deletions

View File

@@ -39,3 +39,9 @@ AUTH_NEW_SITES=123123123
SCRIPTS_DIR=admin_scripts SCRIPTS_DIR=admin_scripts
CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}] CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}]
DS_API_KEY="sk-222e3addb3d8455d8b0516d93906eec7" DS_API_KEY="sk-222e3addb3d8455d8b0516d93906eec7"
OLLAMA_URL=http://localhost:11434
OLLAMA_DEFAULT_MODEL=llama3.2:3b
GROK_API="xai-PcNM5obgPaETtmnfDWPZk235D75ZgxENU2QmeqPfMQCHh9dwCDVeRRe0oVVA2YOpiUDh1uJieZsMasja"
REPLICATE_API_TOKEN="r8_AVhM6igwvoOnUA65cHVZdhEDfTqBVk94WTB0u"
FAL_KEY="7d251c88-21b5-4b55-8b3e-4bafd910f99f:b81c0a36a25b052f26eb8ac226c7efff"
HF_TOKEN="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"

View File

@@ -30,8 +30,9 @@ SECRK=iUUb38v23jjDFaosWj92axkBOXCQ
TOKEN_LIFE=30d TOKEN_LIFE=30d
REFRESH_TOKEN_LIFE=30d REFRESH_TOKEN_LIFE=30d
AUTH_NEW_SITES=B234HDSAOJ734ndcsdKWNV AUTH_NEW_SITES=B234HDSAOJ734ndcsdKWNV
DOMAINS=[{"hostname":"riso.app","port":"3006"},{"hostname":"freeplanet.app","port":"3000"},{"hostname":"nuovomondo.app","port":"3032"},{"hostname":"germogliamo.app","port":"3042"}] DOMAINS=[{"hostname":"riso.app","port":"3006"},{"hostname":"freeplanet.app","port":"3000"},{"hostname":"nuovomondo.app","port":"3032"}]
DOMAINS_ALLOWED=["riso.app","comunitanuovomondo.app","nuovomondo.app","kolibrilab.it","artenergetica.org","freeplanet.app","www.freeplanet.app","freeplanet.app:3000","freeplanet.app:3001","www.freeplanet.app:3000","www.freeplanet.app:3001"] DOMAINS_NEW=[{"hostname":"riso.app","port":"3006"},{"hostname":"freeplanet.app","port":"3000"},{"hostname":"nuovomondo.app","port":"3032"},{"hostname":"germogliamo.app","port":"3042"}]
DOMAINS_ALLOWED=["riso.app","germogliamo.app","comunitanuovomondo.app","nuovomondo.app","kolibrilab.it","artenergetica.org","freeplanet.app","www.freeplanet.app","freeplanet.app:3000","freeplanet.app:3001","www.freeplanet.app:3000","www.freeplanet.app:3001"]
#DOMAINS=[{"hostname":"abitaregliiblei.it","port":"3021"},{"hostname":"riso.app","port":"3005"}] #DOMAINS=[{"hostname":"abitaregliiblei.it","port":"3021"},{"hostname":"riso.app","port":"3005"}]
SCRIPTS_DIR=admin_scripts SCRIPTS_DIR=admin_scripts
CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}] CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}]

View File

@@ -164,6 +164,7 @@ db.users.insertMany([
"stepTutorial": 0, "stepTutorial": 0,
"noNameSurname": false, "noNameSurname": false,
"noCircuit": false, "noCircuit": false,
"noComune": false,
"noCircIta": false, "noCircIta": false,
"insert_circuito_ita": false, "insert_circuito_ita": false,
"noFoto": false, "noFoto": false,
@@ -282,6 +283,7 @@ db.users.insertMany([
"stepTutorial": 0, "stepTutorial": 0,
"noNameSurname": false, "noNameSurname": false,
"noCircuit": false, "noCircuit": false,
"noComune": false,
"noCircIta": false, "noCircIta": false,
"insert_circuito_ita": false, "insert_circuito_ita": false,
"noFoto": false, "noFoto": false,

View File

@@ -0,0 +1,492 @@
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;
}
.email-body {
padding: 32px 24px;
}
.intro-text {
font-size: 16px;
color: #333;
margin-bottom: 20px;
text-align: center;
line-height: 1.7;
}
.congrats-card {
background: linear-gradient(135deg, #e8f5e9 0%, #f1f8f4 100%);
border: 2px solid #7cb342;
border-radius: 12px;
padding: 24px;
margin: 20px 0;
text-align: center;
}
.congrats-card .congrats-icon {
font-size: 48px;
margin-bottom: 12px;
}
.congrats-card h3 {
font-size: 22px;
color: #558b2f;
margin-bottom: 12px;
font-weight: 700;
}
.congrats-card .territory-name {
font-size: 20px;
color: #7cb342;
font-weight: 600;
margin-top: 8px;
}
.info-section {
background: #ffffff;
border-radius: 8px;
padding: 20px;
margin: 24px 0;
}
.info-section h3 {
font-size: 18px;
color: #1a1a1a;
margin-bottom: 16px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.info-section p {
font-size: 15px;
color: #555;
line-height: 1.7;
margin-bottom: 12px;
}
.info-section ul {
margin: 12px 0;
padding-left: 24px;
}
.info-section li {
font-size: 15px;
color: #555;
line-height: 1.7;
margin-bottom: 8px;
}
.highlight-box {
background: linear-gradient(135deg, #fff8dc 0%, #fef9f3 100%);
border-left: 4px solid #f0ad4e;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.highlight-box h4 {
font-size: 17px;
color: #f0ad4e;
margin-bottom: 12px;
font-weight: 600;
}
.highlight-box p {
font-size: 15px;
color: #555;
line-height: 1.7;
margin-bottom: 8px;
}
.example-box {
background: #e3f2fd;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.example-box h4 {
font-size: 16px;
color: #1976d2;
margin-bottom: 12px;
font-weight: 600;
}
.example-box .transaction {
background: white;
border-radius: 6px;
padding: 12px;
margin: 8px 0;
font-size: 14px;
}
.example-box .benefit {
background: #c8e6c9;
border-radius: 6px;
padding: 12px;
margin-top: 12px;
font-size: 14px;
color: #2e7d32;
}
.steps-box {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.steps-box h4 {
font-size: 17px;
color: #1a1a1a;
margin-bottom: 16px;
font-weight: 600;
text-align: center;
}
.step-item {
display: flex;
align-items: flex-start;
margin-bottom: 16px;
padding: 12px;
background: white;
border-radius: 6px;
}
.step-number {
font-size: 24px;
font-weight: 700;
color: #7cb342;
min-width: 40px;
margin-right: 12px;
}
.step-content h5 {
font-size: 16px;
color: #1a1a1a;
margin-bottom: 6px;
font-weight: 600;
}
.step-content p {
font-size: 14px;
color: #555;
line-height: 1.6;
margin: 0;
}
.cta-section {
text-align: center;
margin: 32px 0;
padding: 24px 0;
border-top: 2px solid #e0e0e0;
border-bottom: 2px solid #e0e0e0;
}
.cta-title {
font-size: 18px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 20px;
}
.cta-button {
display: inline-block;
padding: 18px 56px;
font-size: 20px;
font-weight: 700;
color: white;
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
border-radius: 50px;
text-decoration: none;
box-shadow: 0 6px 20px rgba(124, 179, 66, 0.4);
transition: all 0.3s ease;
}
.button-icon {
font-size: 20px;
margin-right: 10px;
vertical-align: middle;
}
.community-box {
background: linear-gradient(135deg, #e8f5e9 0%, #f1f8f4 100%);
border-radius: 8px;
padding: 20px;
margin: 20px 0;
text-align: center;
}
.community-box h4 {
font-size: 17px;
color: #558b2f;
margin-bottom: 12px;
font-weight: 600;
}
.community-box p {
font-size: 15px;
color: #555;
line-height: 1.7;
margin-bottom: 12px;
}
.community-box a {
color: #2196f3;
text-decoration: none;
font-weight: 600;
}
.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;
}
.email-body {
padding: 20px 16px;
}
.congrats-card .congrats-icon {
font-size: 40px;
}
.congrats-card h3 {
font-size: 20px;
}
.cta-button {
padding: 16px 40px;
font-size: 18px;
width: 100%;
max-width: 300px;
}
.step-item {
flex-direction: column;
}
.step-number {
margin-bottom: 8px;
}
}
body
.email-container
//- Header
.email-header
img.header-logo(src=baseurl+'/images/logo.png' alt=nomeapp)
h1 🎉 Benvenuto nel #{nomeTerritorio}!
p.subtitle Sei stato abilitato con successo
//- Body
.email-body
//- Intro
.intro-text
| Ciao <strong>#{usernameMembro}</strong>,<br>
| complimenti! Sei stato abilitato #{nomeTerritorio} da #{usernameInvitante}.
if linkProfiloAdmin
.divider(style="margin: 16px 0;")
p(style="text-align: center; margin: 16px 0;")
a.profile-button(href=linkProfiloAdmin target="_blank" style="display: inline-block; padding: 10px 24px; font-size: 15px; font-weight: 600; color: #7cb342; background: white; border: 2px solid #7cb342; border-radius: 20px; text-decoration: none; transition: all 0.3s ease;")
span(style="margin-right: 6px;") 👤
| Profilo #{usernameInvitante}
//- Congratulazioni
.congrats-card
.congrats-icon ✅
h3 Abilitazione Completata
p(style="font-size: 15px; color: #555; margin-top: 8px;")
| Ora puoi utilizzare i #{symbol} per i tuoi scambi nella comunità
.territory-name 📍 #{nomeTerritorio}
//- Info comunità
.community-box
h4 💬 Unisciti alla Comunità Territoriale
p
| Entra nel gruppo Telegram di <strong>#{nomeTerritorio}</strong> per interagire con i partecipanti, rimanere aggiornato su eventi, mercatini e opportunità di scambio nella tua zona, e per poter inserire, anche tu, annunci di offro/cerco.
if linkTelegramTerritorio
a.telegram-button(href=linkTelegramTerritorio target="_blank" style="display: inline-block; margin-top: 16px; padding: 14px 32px; font-size: 17px; font-weight: 600; color: white; background: linear-gradient(135deg, #0088cc 0%, #006699 100%); border-radius: 25px; text-decoration: none; box-shadow: 0 4px 12px rgba(0, 136, 204, 0.3); transition: all 0.3s ease;")
span(style="font-size: 20px; margin-right: 8px; vertical-align: middle;") ✈️
| Unisciti al gruppo Telegram
//- Cos'è RIS
.info-section
h3
span 💰
| Cosa sono i RIS?
p
| <strong>RIS</strong> (Rete Italiana Scambio) è un sistema di <strong>credito comunitario</strong> basato sulla fiducia reciproca. Non sono soldi tradizionali, ma un'unità di misura che rappresenta il valore degli scambi all'interno della comunità RISO.
p
| <strong>Parità con l'Euro:</strong> 1 RIS = 1 Euro (solo come riferimento di valore, non come convertibilità)
//- Come funziona la fiducia
.highlight-box
h4 🤝 Come Funziona la "Fiducia Concessa"
p
| <strong>Parti da 0 RIS</strong> - Non devi avere un "saldo positivo" per iniziare a scambiare!
p
| <strong>Quando ricevi</strong> un bene o servizio pagando in RIS → il tuo saldo <strong>diventa positivo</strong>
p
| <strong>Quando offri</strong> un bene o servizio ricevendo RIS → il tuo saldo <strong>diventa negativo</strong>
p(style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #f0ad4e;")
| 💡 <strong>Il saldo negativo non è un debito!</strong> È la fiducia che la comunità ti concede. Significa che hai ricevuto prima di aver dato, e la comunità si fida che restituirai nel tempo.
//- Esempio pratico
.example-box
h4 📖 Esempio Pratico
.transaction
| <strong>Situazione:</strong> Sei un grafico e vuoi comprare 100€ di verdure da un produttore locale
.transaction
| <strong>Transazione mista:</strong>
| <br>• Paghi <strong>80€ in Euro</strong>
| <br>• Paghi <strong>20 RIS</strong> (20% in RIS)
| <br><br>🔻 Il tuo saldo RIS passa da 0 a <strong>-20 RIS</strong>
.benefit
| <strong>✓ Beneficio:</strong> Hai ridotto del 20% l'uso degli Euro, sostenendo il produttore locale e rafforzando la comunità! Puoi iniziare con percentuali basse (5-10%) e aumentare man mano che acquisisci fiducia.
//- Come riequilibrare
.info-section
h3
span ⚖️
| Come Riequilibrare il Saldo
p
| Per riportare il tuo saldo verso lo zero (o in positivo), puoi:
ul
li <strong>Offrire beni o servizi</strong> ricevendo RIS in cambio
li <strong>Vendere prodotti</strong> accettando pagamenti parziali o totali in RIS
li <strong>Mettere annunci</strong> sulla piattaforma specificando che accetti RIS
li <strong>Partecipare ai mercatini</strong> locali della comunità RISO
//- Primi passi
.steps-box
h4 🚀 I Tuoi Primi Passi
.step-item
.step-number 1
.step-content
h5 Esplora la Piattaforma
p Familiarizza con gli annunci, i membri e le funzionalità del #{nomeTerritorio}
.step-item
.step-number 2
.step-content
h5 Crea il Tuo Primo Annuncio
p Pubblica cosa offri o cosa cerchi, specificando se accetti pagamenti in RIS
.step-item
.step-number 3
.step-content
h5 Inizia con Piccole Transazioni
p Comincia con percentuali basse di RIS (5-10%) per prendere confidenza e vedi cosa succede. Più siamo aperti noi e più l'Universo ci aiuta e sostiene e ci dona quello di cui abbiamo bisogno.
.step-item
.step-number 4
.step-content
h5 Partecipa alla Comunità
p Unisciti agli incontri locali e ai mercatini per conoscere altri membri. Se non ci sono membri che propongono incontri, puoi proporti anche tu!
//- CTA
.cta-section
.cta-title Inizia Subito a Usare i RIS!
a.cta-button(href=strlinksito target="_blank")
span.button-icon 🌾
| Vai alla Piattaforma
//- Supporto
.info-section
h3
span ❓
| Hai Domande?
p
| Se hai dubbi sul funzionamento dei RIS o sulla piattaforma, non esitare a:
ul
li Contattare il facilitatore del tuo territorio
li Chiedere nel gruppo Telegram locale
li Partecipare agli incontri di comunità
//- Footer
.email-footer
.divider
p Benvenuto nella Rete Italiana Scambio orizzontale - #{nomeTerritorio}
p(style="margin-top: 12px; font-size: 12px;")
| #{new Date().getFullYear()} #{nomeapp}

View File

@@ -0,0 +1 @@
=`Abilitazione avvenuta su ${nomeTerritorio} in ${nomeapp} - (${usernameMembro})`

View File

@@ -0,0 +1,401 @@
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;
}
.alert-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;
}
.request-card {
background: linear-gradient(135deg, #e3f2fd 0%, #f0f7ff 100%);
border: 2px solid #2196f3;
border-radius: 8px;
padding: 24px;
margin: 20px 0;
text-align: center;
}
.request-card h3 {
font-size: 14px;
text-transform: uppercase;
color: #2196f3;
margin-bottom: 12px;
letter-spacing: 0.5px;
font-weight: 600;
}
.request-card .member-name {
font-size: 28px;
color: #1a1a1a;
font-weight: 700;
margin-bottom: 8px;
}
.request-card .member-detail {
font-size: 15px;
color: #555;
margin: 6px 0;
}
.request-card .member-detail strong {
color: #2196f3;
}
.territory-badge {
background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%);
color: white;
display: inline-block;
padding: 8px 20px;
border-radius: 20px;
font-size: 16px;
font-weight: 600;
margin-top: 12px;
}
.question-box {
background: #e1f5fe;
border-left: 4px solid #2196f3;
border-radius: 8px;
padding: 20px;
margin: 24px 0;
text-align: center;
}
.question-box p {
font-size: 20px;
color: #1a1a1a;
font-weight: 600;
margin: 0;
line-height: 1.4;
}
.cta-section {
text-align: center;
margin: 32px 0;
padding: 24px 0;
border-top: 2px solid #e0e0e0;
border-bottom: 2px solid #e0e0e0;
}
.cta-title {
font-size: 18px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 20px;
}
.cta-button {
display: inline-block;
padding: 18px 56px;
font-size: 20px;
font-weight: 700;
color: white;
background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
border-radius: 50px;
text-decoration: none;
box-shadow: 0 6px 20px rgba(33, 150, 243, 0.4);
transition: all 0.3s ease;
}
.button-icon {
font-size: 20px;
margin-right: 10px;
vertical-align: middle;
}
.info-box {
background: #e8f5e9;
border-radius: 8px;
padding: 16px;
margin: 20px 0;
}
.info-box p {
margin: 0 0 8px 0;
color: #2e7d32;
font-size: 15px;
line-height: 1.6;
}
.info-box p:last-child {
margin-bottom: 0;
}
.responsibility-box {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.responsibility-box h3 {
font-size: 17px;
color: #1a1a1a;
margin-bottom: 12px;
text-align: center;
font-weight: 600;
}
.responsibility-item {
display: flex;
align-items: flex-start;
margin-bottom: 10px;
padding: 6px 0;
}
.responsibility-icon {
font-size: 20px;
margin-right: 10px;
min-width: 24px;
flex-shrink: 0;
}
.responsibility-text {
font-size: 15px;
color: #555;
line-height: 1.5;
}
.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;
}
.alert-icon {
font-size: 48px;
}
.email-body {
padding: 20px 16px;
}
.request-card .member-name {
font-size: 24px;
}
.territory-badge {
font-size: 14px;
padding: 6px 16px;
}
.question-box p {
font-size: 18px;
}
.cta-button {
padding: 16px 40px;
font-size: 18px;
width: 100%;
max-width: 300px;
}
.responsibility-item {
font-size: 14px;
}
}
body
.email-container
//- Header
.email-header
img.header-logo(src=baseurl+'/images/logo.png' alt=nomeapp)
h1 🎯 Richiesta Abilitazione #{nomeTerritorio}
p.subtitle Nuovo membro in attesa di attivazione
//- Body
.email-body
//- Intro
.intro-text
| Ciao <strong>#{nomeFacilitatore}</strong>,<br>
| un nuovo membro richiede l'abilitazione alla fiducia al Circuito del tuo territorio!
//- Card richiesta
.request-card
h3 👤 Richiesta Ingresso Circuito
.member-name #{usernameMembro}
if nomeMembro
if cognomeMembro
.member-detail
strong Nome:
| #{nomeMembro} #{cognomeMembro}
else
.member-detail
strong Nome:
| #{nomeMembro}
if emailMembro
.member-detail
strong Email:
| #{emailMembro}
if telegramMembro
.member-detail
strong Telegram:
a(href=`https://t.me/${telegramMembro}` target="_blank" style="color: #2196f3; text-decoration: none;") @#{telegramMembro}
if comuneResidenza
.member-detail
strong Comune:
| #{comuneResidenza} (#{provinciaResidenza})
.territory-badge 📍 #{nomeTerritorio}
if usernameInvitante
.divider(style="margin: 20px auto; width: 80%;")
h3(style="font-size: 14px; text-transform: uppercase; color: #2196f3; margin-bottom: 12px;") 👥 Invitato da
.member-detail
strong Invitante:
| #{usernameInvitante}
if nomeInvitante
if cognomeInvitante
.member-detail
strong Nome:
| #{nomeInvitante} #{cognomeInvitante}
else
.member-detail
strong Nome:
| #{nomeInvitante}
if telegramInvitante
.member-detail
strong Telegram:
a(href=`https://t.me/${telegramInvitante}` target="_blank" style="color: #2196f3; text-decoration: none;") @#{telegramInvitante}
//- CTA principale
.cta-section
a.cta-button(href=linkAbilitazione target="_blank")
span.button-icon ✓
| Abilita fiducia
.divider(style="margin: 24px 0;")
p(style="font-size: 16px; color: #666; margin-bottom: 16px;") Visualizza i profili
.button-group(style="display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;")
a.secondary-button(href=linkProfiloMembro target="_blank" style="display: inline-block; padding: 12px 24px; font-size: 16px; font-weight: 600; color: #2196f3; background: white; border: 2px solid #2196f3; border-radius: 25px; text-decoration: none; transition: all 0.3s ease;")
span(style="margin-right: 6px;") 👤
| Profilo Membro
if linkProfiloInvitante
a.secondary-button(href=linkProfiloInvitante target="_blank" style="display: inline-block; padding: 12px 24px; font-size: 16px; font-weight: 600; color: #7cb342; background: white; border: 2px solid #7cb342; border-radius: 25px; text-decoration: none; transition: all 0.3s ease;")
span(style="margin-right: 6px;") 👥
| Profilo Invitante
//- Responsabilità
.responsibility-box
h3 📋 Compiti del Facilitatore RISO
.responsibility-item
span.responsibility-icon 🔍
span.responsibility-text
strong Verifica:
| Contatta il membro, se non lo conosci, oppure il suo invitante: #{usernameInvitante}
.responsibility-item
span.responsibility-icon 🌍
span.responsibility-text
strong Territorio:
| Assicurati che il membro appartenga effettivamente al territorio di competenza
.responsibility-item
span.responsibility-icon 👥
span.responsibility-text
strong Integrazione:
| Supporta il nuovo membro nell'attivazione e utilizzo del Circuito locale
//- Info box
.info-box
p
| ✓ Dopo l'abilitazione, #{usernameMembro} potrà accedere al #{nomeTerritorio}
p
| ✓ Il membro riceverà una notifica automatica dell'avvenuta attivazione
//- Footer
.email-footer
.divider
p Hai ricevuto questa email in qualità di Facilitatore RISO per #{nomeTerritorio}
p(style="margin-top: 12px; font-size: 12px;")
| #{new Date().getFullYear()} #{nomeapp}

View File

@@ -0,0 +1 @@
=`Richiesta ingresso di ${usernameMembro} - ${nomeMembro} ${cognomeMembro} su ${nomeTerritorio} in ${nomeapp}`

View File

@@ -294,7 +294,7 @@ html(lang="it")
.email-container .email-container
//- Header //- Header
.email-header .email-header
img.header-logo(src=baseurl+'/images/logo.png' alt='RISO - Rete Italiana Scambio Orizzontale') img.header-logo(src=baseurl+'/images/logo.png' alt=nomeapp)
h1 🔔 Richiesta di Ammissione h1 🔔 Richiesta di Ammissione
p.subtitle Nuovo membro in attesa p.subtitle Nuovo membro in attesa
@@ -317,6 +317,10 @@ html(lang="it")
.member-detail .member-detail
strong Email: strong Email:
| #{emailInvitato} | #{emailInvitato}
if userprofile && userprofile.profile.resid_str_comune && userprofile.profile.resid_province
.member-detail
strong Comune Residenza:
| #{userprofile.profile.resid_str_comune} (#{userprofile.profile.resid_province})
//- Domanda di conferma //- Domanda di conferma
.question-box .question-box
@@ -340,7 +344,7 @@ html(lang="it")
span.responsibility-icon 🛡️ span.responsibility-icon 🛡️
span.responsibility-text span.responsibility-text
strong Fiducia: strong Fiducia:
| L'ammissione si basa sulla fiducia reciproca nella comunità RISO | L'ammissione si basa sulla fiducia reciproca nella comunità #{nomeapp}
.responsibility-item .responsibility-item
span.responsibility-icon 👥 span.responsibility-icon 👥
span.responsibility-text span.responsibility-text
@@ -351,8 +355,6 @@ html(lang="it")
.info-box .info-box
p p
| ✓ Dopo l'ammissione, #{usernameInvitato} potrà completare il profilo | ✓ Dopo l'ammissione, #{usernameInvitato} potrà completare il profilo
p
| ✓ Il facilitatore locale valuterà l'abilitazione all'uso dei RIS
//- Warning box //- Warning box
.warning-box .warning-box
@@ -365,6 +367,4 @@ html(lang="it")
.divider .divider
p Hai ricevuto questa email perché #{usernameInvitato} ha indicato te come invitante su #{nomeapp} p Hai ricevuto questa email perché #{usernameInvitato} ha indicato te come invitante su #{nomeapp}
p(style="margin-top: 12px; font-size: 12px;") p(style="margin-top: 12px; font-size: 12px;")
| #{new Date().getFullYear()} #{nomeapp} - Rete Italiana Scambi Orizzontali | #{new Date().getFullYear()} #{nomeapp}
p(style="margin-top: 12px; font-size: 12px;")
| 🍚 Comunità · Fiducia · Scambi Solidali · Sostenibilità

View File

@@ -448,7 +448,7 @@ html(lang="it")
.step-number 1 .step-number 1
.step-content .step-content
h3 ✅ Completa il tuo profilo h3 ✅ Completa il tuo profilo
p Collega il tuo profilo a Telegram, inserisci la tua Provincia ed accedi ai Circuiti Territoriali per poter iniziare ad usare i RIS. Un profilo completo aiuta gli altri membri a conoscerti meglio! p Collega il tuo profilo a Telegram ed accedi ai Circuiti Territoriali per poter iniziare ad usare i RIS. Un profilo completo aiuta gli altri membri a conoscerti meglio!
.step-item .step-item
.step-number 2 .step-number 2

View File

@@ -1 +1 @@
=`🎉 Il tuo invito è stato accettato su RISO da ${name ? ', ' + name : username} !` =`🎉 Il tuo invito è stato accettato su RISO da ${name ? name : username} !`

View File

@@ -158,6 +158,12 @@ html(lang="it")
.info-value .info-value
| #{user.profile.intcode_cell || ''} #{user.profile.cell || ''} | #{user.profile.intcode_cell || ''} #{user.profile.cell || ''}
if user && user.profile && (user.profile.resid_str_comune && user.profile.resid_province)
.info-row
.info-label Comune di Residenza:
.info-value
| #{user.profile.resid_str_comune || ''} (#{user.profile.resid_province || ''})
if user && user.profile && user.profile.nationality if user && user.profile && user.profile.nationality
.info-row .info-row
.info-label Nazionalità: .info-label Nazionalità:

View File

@@ -317,6 +317,10 @@ html(lang="it")
.member-detail .member-detail
strong Email: strong Email:
| #{emailInvitato} | #{emailInvitato}
if userprofile && userprofile.profile.resid_str_comune && userprofile.profile.resid_province
.member-detail
strong Comune Residenza:
| #{userprofile.profile.resid_str_comune} (#{userprofile.profile.resid_province})
//- Domanda di conferma //- Domanda di conferma
.question-box .question-box

View File

@@ -1 +1 @@
=`🎉 Il tuo invito è stato accettato su ${nomeapp} da ${name ? ', ' + name : username} !` =`🎉 Il tuo invito è stato accettato su ${nomeapp} da ${name ? name : username} !`

View File

@@ -0,0 +1,404 @@
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, #1976D2 0%, #1565C0 100%);
color: white;
padding: 40px 24px;
text-align: center;
}
.email-header h1 {
margin: 0 0 8px 0;
font-size: 26px;
font-weight: 600;
}
.email-header p {
margin: 8px 0 0 0;
font-size: 16px;
opacity: 0.95;
line-height: 1.5;
}
.verification-icon {
font-size: 56px;
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: #1976D2;
}
.info-message {
background: linear-gradient(135deg, #E3F2FD 0%, #BBDEFB 100%);
border-left: 4px solid #1976D2;
border-radius: 8px;
padding: 20px;
margin-bottom: 24px;
text-align: center;
}
.info-message h2 {
color: #1565C0;
font-size: 18px;
margin-bottom: 12px;
font-weight: 600;
}
.info-message p {
color: #1976D2;
font-size: 15px;
line-height: 1.6;
margin: 8px 0;
}
.warning-box {
background: #FFF8E1;
border-left: 4px solid #FFA000;
border-radius: 8px;
padding: 16px;
margin-bottom: 24px;
}
.warning-box p {
color: #E65100;
font-size: 14px;
margin: 0;
line-height: 1.6;
}
.warning-box strong {
color: #BF360C;
}
.email-info-box {
background: #f8f9fa;
border-left: 4px solid #1976D2;
border-radius: 8px;
padding: 16px;
margin-bottom: 24px;
text-align: center;
}
.email-info-title {
font-size: 14px;
font-weight: 600;
color: #555;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.email-address {
font-size: 18px;
font-weight: 600;
color: #1976D2;
word-break: break-word;
}
.cta-section {
margin: 24px 0;
padding: 24px 0;
text-align: center;
}
.cta-title {
font-size: 19px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 8px;
}
.cta-subtitle {
font-size: 14px;
color: #666;
margin-bottom: 20px;
line-height: 1.5;
}
.cta-button {
display: inline-block;
padding: 18px 40px;
font-size: 16px;
font-weight: 600;
color: white;
background: linear-gradient(135deg, #1976D2 0%, #1565C0 100%);
border-radius: 50px;
text-decoration: none;
box-shadow: 0 4px 12px rgba(25, 118, 210, 0.35);
transition: transform 0.2s, box-shadow 0.2s;
}
.cta-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(25, 118, 210, 0.45);
}
.button-icon {
font-size: 20px;
margin-right: 8px;
vertical-align: middle;
}
.link-fallback {
margin-top: 20px;
padding: 16px;
background: #f5f5f5;
border-radius: 8px;
text-align: center;
}
.link-fallback p {
font-size: 13px;
color: #666;
margin-bottom: 8px;
}
.link-fallback a {
font-size: 12px;
color: #1976D2;
word-break: break-all;
}
.expiry-notice {
background: linear-gradient(135deg, #FFEBEE 0%, #FFCDD2 100%);
border-radius: 8px;
padding: 16px;
margin: 24px 0;
text-align: center;
border: 1px solid #EF9A9A;
}
.expiry-notice p {
margin: 0;
color: #C62828;
font-size: 14px;
line-height: 1.6;
}
.expiry-notice strong {
color: #B71C1C;
}
.help-section {
background: #FAFAFA;
border-radius: 8px;
padding: 20px;
margin: 24px 0;
text-align: center;
}
.help-section h3 {
font-size: 16px;
color: #333;
margin-bottom: 12px;
font-weight: 600;
}
.help-section p {
font-size: 14px;
color: #666;
margin: 8px 0;
line-height: 1.6;
}
.help-section a {
color: #1976D2;
text-decoration: none;
}
.help-section a:hover {
text-decoration: underline;
}
.security-note {
background: #E8F5E9;
border-left: 4px solid #4CAF50;
border-radius: 8px;
padding: 14px;
margin: 20px 0;
}
.security-note p {
font-size: 13px;
color: #2E7D32;
margin: 0;
line-height: 1.5;
}
.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: 22px;
}
.email-header p {
font-size: 15px;
}
.email-body {
padding: 20px 14px;
}
.info-message {
padding: 16px;
}
.email-address {
font-size: 16px;
}
.cta-button {
padding: 16px 32px;
font-size: 15px;
display: block;
width: 100%;
}
.link-fallback a {
font-size: 11px;
}
}
body
.email-container
.email-header
- var baseimg = baseurl + '/';
h1 ✉️ Verifica il tuo indirizzo email
p Conferma la tua email per continuare a usare #{nomeapp}
.email-body
.intro-text
| Ciao
strong #{name || username}
| ! 👋
br
| Abbiamo ricevuto una richiesta per verificare nuovamente il tuo indirizzo email.
.info-message
h2 📧 Perché devo verificare di nuovo?
p Potrebbe essere perché hai cambiato email, per motivi di sicurezza, o perché la verifica precedente è scaduta.
p Questa procedura ci aiuta a mantenere sicuro il tuo account.
if emailto
.email-info-box
.email-info-title Email da verificare
.email-address #{emailto}
.cta-section
.cta-title Conferma il tuo indirizzo email
.cta-subtitle Clicca il bottone qui sotto per completare la verifica
if verifyLink
a.cta-button(href=verifyLink target="_blank")
span.button-icon ✓
| Verifica Email
.link-fallback
p Se il bottone non funziona, copia e incolla questo link nel browser:
a(href=verifyLink) #{verifyLink}
.expiry-notice
p ⏰ Questo link scadrà tra
strong 24 ore
| .
p Se non completi la verifica in tempo, dovrai richiedere un nuovo link.
.warning-box
p ⚠️
strong Non hai richiesto questa verifica?
| Ignora questa email. Il tuo account resterà al sicuro.
.security-note
p 🔒 Per la tua sicurezza: non condividere mai questo link con nessuno. Il team di #{nomeapp} non ti chiederà mai la password via email.
.help-section
h3 Hai bisogno di aiuto?
p Non riesci a verificare la tua email?
if supportEmail
p Contattaci a
a(href="mailto:" + supportEmail) #{supportEmail}
if strlinksito
p Oppure visita la nostra
a(href=strlinksito + '/supporto' target="_blank") pagina di supporto
.email-footer
.divider
p Hai ricevuto questa email perché è stata richiesta una verifica per il tuo account su #{nomeapp}
p(style="margin-top: 8px; font-size: 12px; color: #999;")
| Se non hai fatto tu questa richiesta, puoi ignorare questa email.
p(style="margin-top: 12px; font-size: 12px;")
| © #{new Date().getFullYear()} #{nomeapp}

View File

@@ -0,0 +1 @@
Verifica la tua Email - ${nomeapp}`

View File

@@ -515,3 +515,78 @@ Dom 23/11 ORE 01:10: [<b>Circuito RIS Italia</b>]: Inviate Monete da perseo9 a s
Saldi: Saldi:
perseo9: -19.00 RIS] perseo9: -19.00 RIS]
surya1977: 141.95 RIS] surya1977: 141.95 RIS]
Gio 04/12 ORE 18:55: [<b>Circuito RIS Bologna</b>]: Inviate Monete da SurTest a ElenaEspx 0.1 RIS [causale: ]
Saldi:
SurTest: 0.00 RIS]
ElenaEspx: 38.05 RIS]
Mar 09/12 ORE 21:36: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 11 RIS [causale: ssss]
Saldi:
surya1977: 63.00 RIS]
amandadi: 12.00 RIS]
Mer 10/12 ORE 16:18: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 12 RIS [causale: Ciaoo]
Saldi:
surya1977: 51.00 RIS]
amandadi: 24.00 RIS]
Mer 10/12 ORE 17:02: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 52 RIS [causale: Ancora test]
Saldi:
surya1977: -1.00 RIS]
amandadi: 76.00 RIS]
Gio 18/12 ORE 15:41: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 22 RIS [causale: Prova]
Saldi:
surya1977: -23.00 RIS]
amandadi: 98.00 RIS]
Gio 18/12 ORE 15:59: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 2 RIS [causale: aaaa]
Saldi:
surya1977: -3.00 RIS]
amandadi: 78.00 RIS]
Gio 18/12 ORE 16:00: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 2 RIS [causale: aaaa]
Saldi:
surya1977: -5.00 RIS]
amandadi: 80.00 RIS]
Gio 18/12 ORE 16:07: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 1 RIS [causale: aaa]
Saldi:
surya1977: -6.00 RIS]
amandadi: 81.00 RIS]
Gio 18/12 ORE 16:07: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 2 RIS [causale: asdasdas]
Saldi:
surya1977: -8.00 RIS]
amandadi: 83.00 RIS]
Gio 18/12 ORE 16:11: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 3 RIS [causale: Provaa]
Saldi:
surya1977: -11.00 RIS]
amandadi: 86.00 RIS]
Gio 18/12 ORE 16:35: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 2 RIS [causale: aaaa]
Saldi:
surya1977: -13.00 RIS]
amandadi: 88.00 RIS]
Gio 18/12 ORE 16:49: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 5 RIS [causale: Prova da 3. Eccolo 2!]
Saldi:
surya1977: -18.00 RIS]
amandadi: 96.00 RIS]
Gio 18/12 ORE 16:50: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 4 RIS [causale: dasdasdasd]
Saldi:
surya1977: -22.00 RIS]
amandadi: 100.00 RIS]
Gio 18/12 ORE 16:50: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 3 RIS [causale: -25]
Saldi:
surya1977: -25.00 RIS]
amandadi: 103.00 RIS]
Gio 18/12 ORE 16:52: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 1 RIS [causale: asdasdsadas]
Saldi:
surya1977: -27.50 RIS]
amandadi: 105.50 RIS]

View File

@@ -16,11 +16,13 @@
"author": "Surya", "author": "Surya",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fal-ai/client": "^1.7.2",
"axios": "^1.13.0", "axios": "^1.13.0",
"basic-ftp": "^5.0.5", "basic-ftp": "^5.0.5",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"body-parser": "^1.20.3", "body-parser": "^1.20.3",
"canvas": "^3.2.0",
"cheerio": "^1.0.0", "cheerio": "^1.0.0",
"compress-pdf": "^0.5.3", "compress-pdf": "^0.5.3",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
@@ -33,10 +35,12 @@
"email-templates": "^12.0.2", "email-templates": "^12.0.2",
"entities": "^7.0.0", "entities": "^7.0.0",
"express": "^4.21.2", "express": "^4.21.2",
"express-rate-limit": "^7.1.5",
"fast-csv": "^5.0.5", "fast-csv": "^5.0.5",
"formidable": "^3.5.2", "formidable": "^3.5.2",
"fs-extra": "^11.3.2", "fs-extra": "^11.3.2",
"ghostscript4js": "^3.2.3", "ghostscript4js": "^3.2.3",
"groq-sdk": "^0.37.0",
"helmet": "^8.1.0", "helmet": "^8.1.0",
"i18n": "^0.15.1", "i18n": "^0.15.1",
"image-downloader": "^4.3.0", "image-downloader": "^4.3.0",
@@ -58,7 +62,7 @@
"node-telegram-bot-api": "^0.66.0", "node-telegram-bot-api": "^0.66.0",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
"npm-check-updates": "^17.1.15", "npm-check-updates": "^17.1.15",
"openai": "^4.86.2", "openai": "^4.104.0",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",
"pem": "^1.14.8", "pem": "^1.14.8",
@@ -66,6 +70,7 @@
"pug": "^3.0.3", "pug": "^3.0.3",
"puppeteer": "^24.9.0", "puppeteer": "^24.9.0",
"rate-limiter-flexible": "^5.0.5", "rate-limiter-flexible": "^5.0.5",
"replicate": "^1.4.0",
"request": "^2.88", "request": "^2.88",
"sanitize-html": "^2.14.0", "sanitize-html": "^2.14.0",
"save": "^2.9.0", "save": "^2.9.0",

View File

@@ -25,6 +25,7 @@ var file = `.env.${node_env}`;
// GLOBALI (Uguali per TUTTI) // GLOBALI (Uguali per TUTTI)
process.env.LINKVERIF_REG = '/vreg'; process.env.LINKVERIF_REG = '/vreg';
process.env.CHECKREVERIF_EMAIL = '/reverif_email';
process.env.LINK_REQUEST_NEWPASSWORD = '/requestnewpwd'; process.env.LINK_REQUEST_NEWPASSWORD = '/requestnewpwd';
process.env.ADD_NEW_SITE = '/addNewSite'; process.env.ADD_NEW_SITE = '/addNewSite';
process.env.LINK_UPDATE_PASSWORD = '/updatepassword'; process.env.LINK_UPDATE_PASSWORD = '/updatepassword';

View File

@@ -0,0 +1,398 @@
const Asset = require('../models/Asset');
const imageGenerator = require('../services/imageGenerator');
const sharp = require('sharp');
const path = require('path');
const fs = require('fs').promises;
const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads';
const assetController = {
// POST /assets/upload
async upload(req, res) {
try {
if (!req.file) {
return res.status(400).json({
success: false,
error: 'Nessun file caricato'
});
}
const { category = 'other', tags, description, isReusable = true } = req.body;
const file = req.file;
// Ottieni dimensioni immagine
let dimensions = {};
try {
const metadata = await sharp(file.path).metadata();
dimensions = { width: metadata.width, height: metadata.height };
} catch (e) {
console.warn('Cannot read image dimensions');
}
// Genera thumbnail
const thumbDir = path.join(UPLOAD_DIR, 'thumbs');
await fs.mkdir(thumbDir, { recursive: true });
const thumbName = `thumb_${file.filename}`;
const thumbPath = path.join(thumbDir, thumbName);
try {
await sharp(file.path)
.resize(300, 300, { fit: 'cover' })
.jpeg({ quality: 80 })
.toFile(thumbPath);
} catch (e) {
console.warn('Cannot create thumbnail');
}
const asset = new Asset({
type: 'image',
category,
sourceType: 'upload',
file: {
path: file.path,
url: `/uploads/${file.filename}`,
thumbnailPath: thumbPath,
thumbnailUrl: `/uploads/thumbs/${thumbName}`,
originalName: file.originalname,
mimeType: file.mimetype,
size: file.size,
dimensions
},
metadata: {
userId: req.user._id,
tags: tags ? tags.split(',').map(t => t.trim()) : [],
description,
isReusable: isReusable === 'true' || isReusable === true
}
});
await asset.save();
res.status(201).json({
success: true,
data: asset
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// POST /assets/upload-multiple
async uploadMultiple(req, res) {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({
success: false,
error: 'Nessun file caricato'
});
}
const { category = 'other' } = req.body;
const assets = [];
for (const file of req.files) {
let dimensions = {};
try {
const metadata = await sharp(file.path).metadata();
dimensions = { width: metadata.width, height: metadata.height };
} catch (e) {}
const asset = new Asset({
type: 'image',
category,
sourceType: 'upload',
file: {
path: file.path,
url: `/uploads/${file.filename}`,
originalName: file.originalname,
mimeType: file.mimetype,
size: file.size,
dimensions
},
metadata: {
userId: req.user._id
}
});
await asset.save();
assets.push(asset);
}
res.status(201).json({
success: true,
data: assets
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// POST /assets/generate-ai
async generateAi(req, res) {
try {
const {
prompt,
negativePrompt,
provider = 'hf',
category = 'other',
aspectRatio = '9:16',
model,
seed,
steps,
cfg
} = req.body;
if (!prompt) {
return res.status(400).json({
success: false,
error: 'Prompt richiesto'
});
}
const startTime = Date.now();
const imageUrl = await imageGenerator.generate(provider, prompt, {
negativePrompt,
aspectRatio,
model,
seed,
steps,
cfg
});
const generationTime = Date.now() - startTime;
// Salva file
const fileName = `ai_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.jpg`;
const filePath = path.join(UPLOAD_DIR, 'ai-generated', fileName);
await fs.mkdir(path.dirname(filePath), { recursive: true });
let fileSize = 0;
let dimensions = {};
if (imageUrl.startsWith('data:')) {
const base64Data = imageUrl.replace(/^data:image\/\w+;base64,/, '');
const buffer = Buffer.from(base64Data, 'base64');
await fs.writeFile(filePath, buffer);
fileSize = buffer.length;
const metadata = await sharp(buffer).metadata();
dimensions = { width: metadata.width, height: metadata.height };
} else {
const fetch = require('node-fetch');
const response = await fetch(imageUrl);
const buffer = await response.buffer();
await fs.writeFile(filePath, buffer);
fileSize = buffer.length;
const metadata = await sharp(buffer).metadata();
dimensions = { width: metadata.width, height: metadata.height };
}
const asset = new Asset({
type: 'image',
category,
sourceType: 'ai',
file: {
path: filePath,
url: `/uploads/ai-generated/${fileName}`,
mimeType: 'image/jpeg',
size: fileSize,
dimensions
},
aiGeneration: {
prompt,
negativePrompt,
provider,
model,
seed,
steps,
cfg,
requestedSize: aspectRatio,
generationTime
},
metadata: {
userId: req.user._id,
isReusable: true
}
});
await asset.save();
res.status(201).json({
success: true,
data: asset
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// GET /assets
async list(req, res) {
try {
const {
category,
sourceType,
page = 1,
limit = 50
} = req.query;
const query = {
'metadata.userId': req.user._id,
status: 'ready'
};
if (category) query.category = category;
if (sourceType) query.sourceType = sourceType;
const [assets, total] = await Promise.all([
Asset.find(query)
.sort({ createdAt: -1 })
.skip((page - 1) * limit)
.limit(parseInt(limit)),
Asset.countDocuments(query)
]);
res.json({
success: true,
data: assets,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// GET /assets/:id
async getById(req, res) {
try {
const asset = await Asset.findById(req.params.id);
if (!asset) {
return res.status(404).json({
success: false,
error: 'Asset non trovato'
});
}
res.json({
success: true,
data: asset
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// GET /assets/:id/file
async getFile(req, res) {
try {
const asset = await Asset.findById(req.params.id);
if (!asset || !asset.file?.path) {
return res.status(404).json({
success: false,
error: 'File non trovato'
});
}
res.sendFile(path.resolve(asset.file.path));
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// GET /assets/:id/thumbnail
async getThumbnail(req, res) {
try {
const asset = await Asset.findById(req.params.id);
if (!asset) {
return res.status(404).json({
success: false,
error: 'Asset non trovato'
});
}
const thumbPath = asset.file?.thumbnailPath || asset.file?.path;
if (!thumbPath) {
return res.status(404).json({
success: false,
error: 'Thumbnail non disponibile'
});
}
res.sendFile(path.resolve(thumbPath));
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// DELETE /assets/:id
async delete(req, res) {
try {
const asset = await Asset.findById(req.params.id);
if (!asset) {
return res.status(404).json({
success: false,
error: 'Asset non trovato'
});
}
if (asset.metadata.userId.toString() !== req.user._id.toString()) {
return res.status(403).json({
success: false,
error: 'Non autorizzato'
});
}
// Elimina file
try {
if (asset.file?.path) await fs.unlink(asset.file.path);
if (asset.file?.thumbnailPath) await fs.unlink(asset.file.thumbnailPath);
} catch (e) {
console.warn('File deletion warning:', e.message);
}
await Asset.deleteOne({ _id: asset._id });
res.json({
success: true,
message: 'Asset eliminato'
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
}
};
module.exports = assetController;

View File

@@ -0,0 +1,647 @@
const Poster = require('../models/Poster');
const Template = require('../models/Template');
const Asset = require('../models/Asset');
const posterRenderer = require('../services/posterRenderer');
const imageGenerator = require('../services/imageGenerator');
const path = require('path');
const fs = require('fs').promises;
const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads';
const posterController = {
// POST /posters
async create(req, res) {
try {
const {
templateId,
name,
description,
content,
assets,
layerOverrides,
autoRender = false
} = req.body;
// Carica template
const template = await Template.findById(templateId);
if (!template) {
return res.status(404).json({
success: false,
error: 'Template non trovato'
});
}
// Valida contenuti richiesti
const requiredLayers = template.layers.filter(l => l.required);
for (const layer of requiredLayers) {
if (layer.type === 'title' && !content?.title) {
return res.status(400).json({
success: false,
error: `Campo richiesto: ${layer.type}`
});
}
}
const poster = new Poster({
templateId,
templateSnapshot: template.toObject(), // Snapshot per retrocompatibilità
name: name || content?.title || 'Nuova Locandina',
description,
status: 'draft',
content: content || {},
assets: assets || {},
layerOverrides: layerOverrides || {},
renderEngineVersion: '1.0.0',
metadata: {
userId: req.user._id
}
});
await poster.save();
// Incrementa uso template
await template.incrementUsage();
// Auto-render se richiesto
if (autoRender) {
await posterController._renderPoster(poster);
await poster.save();
}
res.status(201).json({
success: true,
data: poster
});
} catch (error) {
console.error('Poster create error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
},
// GET /posters
async list(req, res) {
try {
const {
status,
templateId,
search,
page = 1,
limit = 20,
sortBy = 'createdAt',
sortOrder = 'desc'
} = req.query;
const query = { 'metadata.userId': req.user._id };
if (status) query.status = status;
if (templateId) query.templateId = templateId;
if (search) query.$text = { $search: search };
const sort = { [sortBy]: sortOrder === 'asc' ? 1 : -1 };
const [posters, total] = await Promise.all([
Poster.find(query)
.populate('templateId', 'name templateType thumbnailUrl')
.sort(sort)
.skip((page - 1) * limit)
.limit(parseInt(limit))
.select('-templateSnapshot -history'),
Poster.countDocuments(query)
]);
res.json({
success: true,
data: posters,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// GET /posters/favorites
async listFavorites(req, res) {
try {
const posters = await Poster.findFavorites(req.user._id);
res.json({
success: true,
data: posters
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// GET /posters/recent
async listRecent(req, res) {
try {
const { limit = 10 } = req.query;
const posters = await Poster.findRecent(req.user._id, parseInt(limit));
res.json({
success: true,
data: posters
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// GET /posters/:id
async getById(req, res) {
try {
const poster = await Poster.findById(req.params.id)
.populate('templateId')
.populate('assets.backgroundImage.assetId')
.populate('assets.mainImage.assetId')
.populate('assets.logos.assetId');
if (!poster) {
return res.status(404).json({
success: false,
error: 'Poster non trovato'
});
}
// Check ownership
if (poster.metadata.userId.toString() !== req.user._id.toString()) {
return res.status(403).json({
success: false,
error: 'Accesso negato'
});
}
res.json({
success: true,
data: poster
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// PUT /posters/:id
async update(req, res) {
try {
const poster = await Poster.findById(req.params.id);
if (!poster) {
return res.status(404).json({
success: false,
error: 'Poster non trovato'
});
}
if (poster.metadata.userId.toString() !== req.user._id.toString()) {
return res.status(403).json({
success: false,
error: 'Non autorizzato'
});
}
const updateFields = [
'name', 'description', 'content', 'assets', 'layerOverrides'
];
updateFields.forEach(field => {
if (req.body[field] !== undefined) {
poster[field] = req.body[field];
}
});
// Invalida render precedente se contenuto modificato
if (req.body.content || req.body.assets || req.body.layerOverrides) {
poster.status = 'draft';
poster.addHistory('updated', { fields: Object.keys(req.body) });
}
await poster.save();
res.json({
success: true,
data: poster
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// DELETE /posters/:id
async delete(req, res) {
try {
const poster = await Poster.findById(req.params.id);
if (!poster) {
return res.status(404).json({
success: false,
error: 'Poster non trovato'
});
}
if (poster.metadata.userId.toString() !== req.user._id.toString()) {
return res.status(403).json({
success: false,
error: 'Non autorizzato'
});
}
// Elimina file renderizzati
if (poster.renderOutput) {
const filesToDelete = [
poster.renderOutput.png?.path,
poster.renderOutput.jpg?.path,
poster.renderOutput.webp?.path
].filter(Boolean);
for (const filePath of filesToDelete) {
try {
await fs.unlink(filePath);
} catch (e) {
console.warn('File not found:', filePath);
}
}
}
await Poster.deleteOne({ _id: poster._id });
res.json({
success: true,
message: 'Poster eliminato'
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// POST /posters/:id/render
async render(req, res) {
try {
const poster = await Poster.findById(req.params.id)
.populate('templateId');
if (!poster) {
return res.status(404).json({
success: false,
error: 'Poster non trovato'
});
}
if (poster.metadata.userId.toString() !== req.user._id.toString()) {
return res.status(403).json({
success: false,
error: 'Non autorizzato'
});
}
poster.status = 'processing';
await poster.save();
try {
await posterController._renderPoster(poster);
await poster.save();
res.json({
success: true,
data: {
status: poster.status,
renderOutput: poster.renderOutput
}
});
} catch (renderError) {
poster.setError(renderError.message);
await poster.save();
throw renderError;
}
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// POST /posters/:id/regenerate-ai
async regenerateAi(req, res) {
try {
const { assetType, prompt, provider = 'hf' } = req.body;
const poster = await Poster.findById(req.params.id);
if (!poster) {
return res.status(404).json({
success: false,
error: 'Poster non trovato'
});
}
if (poster.metadata.userId.toString() !== req.user._id.toString()) {
return res.status(403).json({
success: false,
error: 'Non autorizzato'
});
}
// Genera nuova immagine AI
const startTime = Date.now();
const imageUrl = await imageGenerator.generate(provider, prompt);
const generationTime = Date.now() - startTime;
// Salva su filesystem
const fileName = `${poster._id}_${assetType}_${Date.now()}.jpg`;
const filePath = path.join(UPLOAD_DIR, 'ai-generated', fileName);
await fs.mkdir(path.dirname(filePath), { recursive: true });
// Se è base64, converti
let savedPath;
if (imageUrl.startsWith('data:')) {
const base64Data = imageUrl.replace(/^data:image\/\w+;base64,/, '');
await fs.writeFile(filePath, base64Data, 'base64');
savedPath = filePath;
} else {
// Se è URL, scarica
const fetch = require('node-fetch');
const response = await fetch(imageUrl);
const buffer = await response.buffer();
await fs.writeFile(filePath, buffer);
savedPath = filePath;
}
// Aggiorna asset nel poster
const assetData = {
sourceType: 'ai',
url: `/uploads/ai-generated/${fileName}`,
mimeType: 'image/jpeg',
aiParams: {
prompt,
provider,
generatedAt: new Date()
}
};
if (assetType === 'backgroundImage') {
poster.assets.backgroundImage = assetData;
poster.addHistory('ai_background_generated', { provider, duration: generationTime });
} else if (assetType === 'mainImage') {
poster.assets.mainImage = assetData;
poster.addHistory('ai_main_generated', { provider, duration: generationTime });
}
poster.status = 'draft';
await poster.save();
res.json({
success: true,
data: {
assetType,
asset: assetData
}
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// GET /posters/:id/download/:format
async download(req, res) {
try {
const { format } = req.params;
const poster = await Poster.findById(req.params.id);
if (!poster) {
return res.status(404).json({
success: false,
error: 'Poster non trovato'
});
}
const outputFile = poster.renderOutput?.[format];
if (!outputFile?.path) {
return res.status(404).json({
success: false,
error: `Formato ${format} non disponibile`
});
}
// Incrementa download count
await poster.incrementDownload();
const fileName = `${poster.name.replace(/[^a-z0-9]/gi, '_')}_poster.${format}`;
res.download(outputFile.path, fileName);
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// POST /posters/:id/favorite
async toggleFavorite(req, res) {
try {
const poster = await Poster.findById(req.params.id);
if (!poster) {
return res.status(404).json({
success: false,
error: 'Poster non trovato'
});
}
if (poster.metadata.userId.toString() !== req.user._id.toString()) {
return res.status(403).json({
success: false,
error: 'Non autorizzato'
});
}
await poster.toggleFavorite();
res.json({
success: true,
data: {
isFavorite: poster.metadata.isFavorite
}
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// POST /posters/quick-generate (compatibile con la tua bozza)
async quickGenerate(req, res) {
try {
const {
templateId,
titolo,
descrizione,
data,
ora,
luogo,
contatti,
fotoDescrizione,
stile,
provider = 'hf',
aspectRatio = '9:16'
} = req.body;
// Validazione base
if (!titolo || !data || !luogo) {
return res.status(400).json({
success: false,
error: 'Compila titolo, data e luogo'
});
}
// Usa template default o quello specificato
let template;
if (templateId) {
template = await Template.findById(templateId);
} else {
// Template default per quick-generate
template = await Template.findOne({
templateType: 'quick-generate',
isActive: true
});
}
// Genera prompt per AI background
const aiPrompt = `Vertical event poster background, ${stile || 'modern style, vivid colors'}. Subject: ${fotoDescrizione || 'abstract artistic shapes'}. Composition: Central empty space suitable for text overlay. NO TEXT, NO LETTERS, clean illustration, high quality, 4k.`;
// Genera immagine AI
const startTime = Date.now();
const rawImageUrl = await imageGenerator.generate(provider, aiPrompt);
const generationTime = Date.now() - startTime;
// Salva asset generato
const fileName = `quick_${Date.now()}.jpg`;
const filePath = path.join(UPLOAD_DIR, 'ai-generated', fileName);
await fs.mkdir(path.dirname(filePath), { recursive: true });
if (rawImageUrl.startsWith('data:')) {
const base64Data = rawImageUrl.replace(/^data:image\/\w+;base64,/, '');
await fs.writeFile(filePath, base64Data, 'base64');
}
// Crea poster
const poster = new Poster({
templateId: template?._id,
name: titolo,
status: 'processing',
content: {
title: titolo,
subtitle: descrizione,
eventDate: data,
eventTime: ora,
location: luogo,
contacts: contatti
},
assets: {
backgroundImage: {
sourceType: 'ai',
url: `/uploads/ai-generated/${fileName}`,
mimeType: 'image/jpeg',
aiParams: {
prompt: aiPrompt,
provider,
generatedAt: new Date()
}
}
},
originalPrompt: aiPrompt,
styleUsed: stile,
aspectRatio,
provider,
metadata: {
userId: req.user._id
}
});
poster.addHistory('ai_background_generated', { provider, duration: generationTime });
// Render con testi sovrapposti
await posterController._renderPoster(poster, { useQuickRender: true });
await poster.save();
res.json({
success: true,
data: {
posterId: poster._id,
imageUrl: poster.renderOutput?.png?.url || rawImageUrl,
status: poster.status
}
});
} catch (error) {
console.error('Quick generate error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
},
// Helper interno: renderizza poster
async _renderPoster(poster, options = {}) {
const template = poster.templateId || poster.templateSnapshot;
const result = await posterRenderer.render({
template,
content: poster.content,
assets: poster.assets,
layerOverrides: Object.fromEntries(poster.layerOverrides || new Map()),
outputDir: path.join(UPLOAD_DIR, 'posters', 'final'),
posterId: poster._id.toString()
});
poster.setRenderOutput({
png: {
path: result.pngPath,
url: `/uploads/posters/final/${path.basename(result.pngPath)}`,
size: result.pngSize
},
jpg: {
path: result.jpgPath,
url: `/uploads/posters/final/${path.basename(result.jpgPath)}`,
size: result.jpgSize,
quality: 95
},
dimensions: result.dimensions,
duration: result.duration
});
}
};
module.exports = posterController;

View File

@@ -0,0 +1,383 @@
const Template = require('../models/Template');
// Presets formati standard
const FORMAT_PRESETS = {
'A4': { width: 2480, height: 3508, dpi: 300 },
'A4-landscape': { width: 3508, height: 2480, dpi: 300 },
'A3': { width: 3508, height: 4961, dpi: 300 },
'A3-landscape': { width: 4961, height: 3508, dpi: 300 },
'instagram-post': { width: 1080, height: 1080, dpi: 72 },
'instagram-story': { width: 1080, height: 1920, dpi: 72 },
'instagram-portrait': { width: 1080, height: 1350, dpi: 72 },
'facebook-post': { width: 1200, height: 630, dpi: 72 },
'facebook-event': { width: 1920, height: 1080, dpi: 72 },
'twitter-post': { width: 1200, height: 675, dpi: 72 },
'poster-24x36': { width: 7200, height: 10800, dpi: 300 },
'flyer-5x7': { width: 1500, height: 2100, dpi: 300 }
};
const templateController = {
// POST /templates
async create(req, res) {
try {
const {
name,
templateType,
description,
format,
safeArea,
backgroundColor,
layers,
logoSlots,
palette,
typography,
defaultAiPromptHints,
metadata
} = req.body;
// Applica preset se specificato
let finalFormat = format;
if (format?.preset && FORMAT_PRESETS[format.preset]) {
finalFormat = {
...FORMAT_PRESETS[format.preset],
preset: format.preset,
unit: 'px'
};
}
// Valida layers
if (!layers || !Array.isArray(layers) || layers.length === 0) {
return res.status(400).json({
success: false,
error: 'Almeno un layer è richiesto'
});
}
// Assicura ID unici per layer
const layersWithIds = layers.map((layer, idx) => ({
...layer,
id: layer.id || `layer_${layer.type}_${idx}`
}));
const template = new Template({
name,
templateType,
description,
format: finalFormat,
safeArea: safeArea || {},
backgroundColor: backgroundColor || '#1a1a2e',
layers: layersWithIds,
logoSlots: logoSlots || { enabled: false, slots: [] },
palette: palette || {},
typography: typography || {},
defaultAiPromptHints: defaultAiPromptHints || {},
metadata: {
...metadata,
author: req.user?.name || 'System'
},
userId: req.user?._id
});
await template.save();
res.status(201).json({
success: true,
data: template
});
} catch (error) {
console.error('Template create error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
},
// GET /templates
async list(req, res) {
try {
const {
type,
search,
tags,
page = 1,
limit = 20,
sortBy = 'createdAt',
sortOrder = 'desc'
} = req.query;
const query = { isActive: true };
if (type) {
query.templateType = type;
}
if (tags) {
const tagArray = tags.split(',').map(t => t.trim());
query['metadata.tags'] = { $in: tagArray };
}
if (search) {
query.$text = { $search: search };
}
// Se utente autenticato, mostra anche i suoi privati
if (req.user) {
query.$or = [
{ 'metadata.isPublic': true },
{ userId: req.user._id }
];
} else {
query['metadata.isPublic'] = true;
}
const sort = { [sortBy]: sortOrder === 'asc' ? 1 : -1 };
const [templates, total] = await Promise.all([
Template.find(query)
.sort(sort)
.skip((page - 1) * limit)
.limit(parseInt(limit))
.select('-layers -logoSlots'), // Escludi dati pesanti per list
Template.countDocuments(query)
]);
res.json({
success: true,
data: templates,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
});
} catch (error) {
console.error('Template list error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
},
// GET /templates/types
async getTypes(req, res) {
try {
const types = await Template.distinct('templateType', { isActive: true });
res.json({
success: true,
data: types.sort()
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// GET /templates/presets
async getFormatPresets(req, res) {
res.json({
success: true,
data: FORMAT_PRESETS
});
},
// GET /templates/:id
async getById(req, res) {
try {
const template = await Template.findById(req.params.id);
if (!template) {
return res.status(404).json({
success: false,
error: 'Template non trovato'
});
}
// Check accesso
if (!template.metadata.isPublic &&
(!req.user || template.userId?.toString() !== req.user._id.toString())) {
return res.status(403).json({
success: false,
error: 'Accesso negato'
});
}
res.json({
success: true,
data: template
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// PUT /templates/:id
async update(req, res) {
try {
const template = await Template.findById(req.params.id);
if (!template) {
return res.status(404).json({
success: false,
error: 'Template non trovato'
});
}
// Check ownership
if (template.userId?.toString() !== req.user._id.toString()) {
return res.status(403).json({
success: false,
error: 'Non autorizzato a modificare questo template'
});
}
const updateFields = [
'name', 'description', 'templateType', 'format', 'safeArea',
'backgroundColor', 'layers', 'logoSlots', 'palette',
'typography', 'defaultAiPromptHints', 'metadata', 'isActive'
];
updateFields.forEach(field => {
if (req.body[field] !== undefined) {
template[field] = req.body[field];
}
});
// Incrementa versione
if (template.metadata) {
const version = template.metadata.version || '1.0.0';
const parts = version.split('.').map(Number);
parts[2]++;
template.metadata.version = parts.join('.');
}
await template.save();
res.json({
success: true,
data: template
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// DELETE /templates/:id
async delete(req, res) {
try {
const template = await Template.findById(req.params.id);
if (!template) {
return res.status(404).json({
success: false,
error: 'Template non trovato'
});
}
if (template.userId?.toString() !== req.user._id.toString()) {
return res.status(403).json({
success: false,
error: 'Non autorizzato'
});
}
// Soft delete
template.isActive = false;
await template.save();
res.json({
success: true,
message: 'Template eliminato'
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// POST /templates/:id/duplicate
async duplicate(req, res) {
try {
const original = await Template.findById(req.params.id);
if (!original) {
return res.status(404).json({
success: false,
error: 'Template non trovato'
});
}
const duplicateData = original.toObject();
delete duplicateData._id;
delete duplicateData.createdAt;
delete duplicateData.updatedAt;
duplicateData.name = `${original.name} (copia)`;
duplicateData.userId = req.user._id;
duplicateData.metadata = {
...duplicateData.metadata,
isPublic: false,
usageCount: 0,
author: req.user?.name || 'System',
version: '1.0.0'
};
const duplicate = new Template(duplicateData);
await duplicate.save();
res.status(201).json({
success: true,
data: duplicate
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
},
// GET /templates/:id/preview
async getPreview(req, res) {
try {
const template = await Template.findById(req.params.id)
.select('previewUrl thumbnailUrl name');
if (!template) {
return res.status(404).json({
success: false,
error: 'Template non trovato'
});
}
res.json({
success: true,
data: {
previewUrl: template.previewUrl,
thumbnailUrl: template.thumbnailUrl,
name: template.name
}
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
}
};
module.exports = templateController;

45
src/data/asset.json Normal file
View File

@@ -0,0 +1,45 @@
{
"_id": "asset_bg_001",
"type": "image",
"category": "background",
"sourceType": "ai",
"file": {
"path": "/uploads/assets/backgrounds/forest_autumn_001.jpg",
"url": "/api/assets/asset_bg_001/file",
"thumbnailPath": "/uploads/assets/backgrounds/thumbs/forest_autumn_001_thumb.jpg",
"thumbnailUrl": "/api/assets/asset_bg_001/thumbnail",
"originalName": null,
"mimeType": "image/jpeg",
"size": 2458000,
"dimensions": { "width": 2480, "height": 3508 }
},
"aiGeneration": {
"prompt": "Mystical autumn forest at golden hour...",
"negativePrompt": "text, letters, words...",
"provider": "hf",
"model": "FLUX.1-dev",
"seed": 8847291,
"steps": 35,
"cfg": 7.5,
"requestedSize": "1024x1536",
"actualSize": "1024x1536",
"generationTime": 12500,
"cost": 0
},
"usage": {
"usedInPosters": ["poster_sagra_funghi_2025_001"],
"usedInTemplates": [],
"usageCount": 1
},
"metadata": {
"userId": "user_001",
"tags": ["forest", "autumn", "background", "nature"],
"isReusable": true
},
"createdAt": "2025-01-15T10:25:00.000Z"
}

150
src/data/poster.json Normal file
View File

@@ -0,0 +1,150 @@
{
"_id": "poster_sagra_funghi_2025_001",
"templateId": "template_raccolta_funghi_001",
"name": "Sagra del Fungo Porcino 2025",
"status": "completed",
"content": {
"title": "SAGRA DEL FUNGO PORCINO",
"subtitle": "XXV Edizione - Tradizione e Sapori del Bosco",
"eventDate": "15-16-17 Ottobre 2025",
"eventTime": "10:00 - 23:00",
"location": "Parco delle Querce, Borgo Montano (PG)",
"contacts": "Tel: 0742 123456 | info@sagrafungoporcino.it | www.sagrafungoporcino.it",
"extraText": [
"Ingresso Libero",
"Stand Gastronomici • Musica dal Vivo • Mercatino Artigianale"
]
},
"assets": {
"backgroundImage": {
"id": "asset_bg_001",
"sourceType": "ai",
"url": "/uploads/posters/poster_sagra_2025_bg.jpg",
"thumbnailUrl": "/uploads/posters/thumbs/poster_sagra_2025_bg_thumb.jpg",
"mimeType": "image/jpeg",
"size": 2458000,
"dimensions": { "width": 2480, "height": 3508 },
"aiParams": {
"prompt": "Mystical autumn forest at golden hour, morning mist between ancient oak trees, forest floor covered with porcini mushrooms, warm orange and golden light filtering through leaves, photorealistic, cinematic composition, National Geographic style, 8k quality",
"negativePrompt": "text, letters, words, watermark, signature, blurry, low quality, cartoon, anime",
"provider": "hf",
"model": "FLUX.1-dev",
"seed": 8847291,
"steps": 35,
"cfg": 7.5,
"size": "1024x1536",
"generatedAt": "2025-01-15T10:25:00.000Z"
}
},
"mainImage": {
"id": "asset_main_001",
"sourceType": "upload",
"url": "/uploads/assets/porcini_basket_hero.jpg",
"thumbnailUrl": "/uploads/assets/thumbs/porcini_basket_hero_thumb.jpg",
"originalName": "IMG_20241015_porcini.jpg",
"mimeType": "image/jpeg",
"size": 1845000,
"dimensions": { "width": 1920, "height": 1280 },
"uploadedAt": "2025-01-15T10:20:00.000Z"
},
"logos": [
{
"id": "asset_logo_001",
"slotId": "logo_slot_1",
"sourceType": "upload",
"url": "/uploads/logos/comune_borgomontano.png",
"originalName": "logo_comune.png",
"mimeType": "image/png",
"size": 45000
},
{
"id": "asset_logo_002",
"slotId": "logo_slot_2",
"sourceType": "upload",
"url": "/uploads/logos/proloco_borgomontano.png",
"originalName": "logo_proloco.png",
"mimeType": "image/png",
"size": 38000
},
{
"id": "asset_logo_003",
"slotId": "logo_slot_3",
"sourceType": "ai",
"url": "/uploads/logos/ai_generated_mushroom_logo.png",
"mimeType": "image/png",
"size": 52000,
"aiParams": {
"prompt": "Minimal vector logo of a porcini mushroom, flat design, golden brown color, white background, simple elegant icon",
"provider": "ideogram",
"model": "ideogram-v2"
}
}
]
},
"layerOverrides": {
"layer_title": {
"style": {
"fontSize": 78,
"color": "#fff8e7"
}
},
"layer_event_date": {
"style": {
"color": "#ffa502"
}
}
},
"renderOutput": {
"png": {
"path": "/uploads/posters/final/poster_sagra_2025_final.png",
"size": 8945000,
"url": "/api/posters/poster_sagra_funghi_2025_001/download/png"
},
"jpg": {
"path": "/uploads/posters/final/poster_sagra_2025_final.jpg",
"quality": 95,
"size": 2145000,
"url": "/api/posters/poster_sagra_funghi_2025_001/download/jpg"
},
"dimensions": {
"width": 2480,
"height": 3508
},
"renderedAt": "2025-01-15T10:30:00.000Z"
},
"renderEngineVersion": "1.0.0",
"history": [
{
"action": "created",
"timestamp": "2025-01-15T10:15:00.000Z",
"userId": "user_001"
},
{
"action": "ai_background_generated",
"timestamp": "2025-01-15T10:25:00.000Z",
"details": { "provider": "hf", "duration": 12500 }
},
{
"action": "rendered",
"timestamp": "2025-01-15T10:30:00.000Z",
"details": { "duration": 3200 }
}
],
"metadata": {
"userId": "user_001",
"projectId": "project_eventi_2025",
"tags": ["sagra", "fungo", "autunno", "2025"],
"isPublic": false,
"isFavorite": true
},
"createdAt": "2025-01-15T10:15:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
}

272
src/data/template.json Normal file
View File

@@ -0,0 +1,272 @@
{
"_id": "template_raccolta_funghi_001",
"name": "Raccolta Funghi Autunnale",
"templateType": "outdoor-event",
"description": "Template per eventi all'aperto legati alla natura",
"format": {
"preset": "A4",
"width": 2480,
"height": 3508,
"unit": "px",
"dpi": 300
},
"safeArea": {
"top": 0.04,
"right": 0.04,
"bottom": 0.04,
"left": 0.04
},
"backgroundColor": "#1a1a2e",
"layers": [
{
"id": "layer_bg",
"type": "backgroundImage",
"zIndex": 0,
"position": { "x": 0, "y": 0, "w": 1, "h": 1 },
"anchor": "top-left",
"required": false,
"fallback": {
"type": "gradient",
"direction": "to-bottom",
"colors": ["#2d3436", "#636e72"]
},
"style": {
"opacity": 1,
"blur": 0,
"objectFit": "cover",
"overlay": {
"enabled": true,
"type": "gradient",
"direction": "to-bottom",
"stops": [
{ "position": 0, "color": "rgba(0,0,0,0)" },
{ "position": 0.5, "color": "rgba(0,0,0,0.3)" },
{ "position": 1, "color": "rgba(0,0,0,0.85)" }
]
}
}
},
{
"id": "layer_main_image",
"type": "mainImage",
"zIndex": 1,
"position": { "x": 0.5, "y": 0.28, "w": 0.85, "h": 0.38 },
"anchor": "center",
"required": false,
"style": {
"borderRadius": 24,
"objectFit": "cover",
"shadow": {
"enabled": true,
"blur": 40,
"spread": 0,
"offsetX": 0,
"offsetY": 20,
"color": "rgba(0,0,0,0.6)"
},
"border": {
"enabled": false,
"width": 4,
"color": "#ffffff"
}
}
},
{
"id": "layer_title",
"type": "title",
"zIndex": 10,
"position": { "x": 0.5, "y": 0.54, "w": 0.92, "h": 0.12 },
"anchor": "center",
"required": true,
"maxLines": 2,
"style": {
"fontFamily": "Montserrat",
"fontWeight": 900,
"fontSize": 82,
"fontSizeMin": 48,
"fontSizeMax": 120,
"autoFit": true,
"color": "#ffffff",
"textAlign": "center",
"textTransform": "uppercase",
"letterSpacing": 6,
"lineHeight": 1.05,
"shadow": {
"enabled": true,
"blur": 15,
"offsetX": 3,
"offsetY": 3,
"color": "rgba(0,0,0,0.9)"
},
"stroke": {
"enabled": true,
"width": 3,
"color": "rgba(0,0,0,0.5)"
}
}
},
{
"id": "layer_subtitle",
"type": "subtitle",
"zIndex": 10,
"position": { "x": 0.5, "y": 0.635, "w": 0.85, "h": 0.05 },
"anchor": "center",
"required": false,
"style": {
"fontFamily": "Open Sans",
"fontWeight": 400,
"fontSize": 32,
"color": "#f0f0f0",
"textAlign": "center",
"letterSpacing": 2,
"lineHeight": 1.3,
"shadow": {
"enabled": true,
"blur": 8,
"offsetX": 1,
"offsetY": 1,
"color": "rgba(0,0,0,0.7)"
}
}
},
{
"id": "layer_event_date",
"type": "eventDate",
"zIndex": 10,
"position": { "x": 0.5, "y": 0.72, "w": 0.9, "h": 0.06 },
"anchor": "center",
"required": true,
"style": {
"fontFamily": "Bebas Neue",
"fontWeight": 400,
"fontSize": 56,
"color": "#ffd700",
"textAlign": "center",
"letterSpacing": 4,
"textTransform": "uppercase",
"shadow": {
"enabled": true,
"blur": 10,
"offsetX": 2,
"offsetY": 2,
"color": "rgba(0,0,0,0.8)"
}
}
},
{
"id": "layer_location",
"type": "location",
"zIndex": 10,
"position": { "x": 0.5, "y": 0.79, "w": 0.85, "h": 0.05 },
"anchor": "center",
"required": true,
"icon": {
"enabled": true,
"name": "location_on",
"size": 28,
"color": "#e74c3c"
},
"style": {
"fontFamily": "Open Sans",
"fontWeight": 600,
"fontSize": 28,
"color": "#ffffff",
"textAlign": "center",
"letterSpacing": 1
}
},
{
"id": "layer_contacts",
"type": "contacts",
"zIndex": 10,
"position": { "x": 0.5, "y": 0.86, "w": 0.9, "h": 0.04 },
"anchor": "center",
"required": false,
"style": {
"fontFamily": "Open Sans",
"fontWeight": 400,
"fontSize": 22,
"color": "#cccccc",
"textAlign": "center",
"letterSpacing": 0.5
}
},
{
"id": "layer_extra_text",
"type": "extraText",
"zIndex": 10,
"position": { "x": 0.5, "y": 0.91, "w": 0.85, "h": 0.03 },
"anchor": "center",
"required": false,
"style": {
"fontFamily": "Open Sans",
"fontWeight": 300,
"fontSize": 18,
"fontStyle": "italic",
"color": "#aaaaaa",
"textAlign": "center"
}
}
],
"logoSlots": {
"enabled": true,
"maxCount": 3,
"collapseIfEmpty": true,
"slots": [
{
"id": "logo_slot_1",
"position": { "x": 0.12, "y": 0.96, "w": 0.12, "h": 0.05 },
"anchor": "bottom-left",
"style": { "objectFit": "contain", "opacity": 0.9 }
},
{
"id": "logo_slot_2",
"position": { "x": 0.5, "y": 0.96, "w": 0.12, "h": 0.05 },
"anchor": "bottom-center",
"style": { "objectFit": "contain", "opacity": 0.9 }
},
{
"id": "logo_slot_3",
"position": { "x": 0.88, "y": 0.96, "w": 0.12, "h": 0.05 },
"anchor": "bottom-right",
"style": { "objectFit": "contain", "opacity": 0.9 }
}
]
},
"palette": {
"primary": "#e94560",
"secondary": "#0f3460",
"accent": "#ffd700",
"background": "#1a1a2e",
"text": "#ffffff",
"textSecondary": "#cccccc",
"textMuted": "#888888"
},
"typography": {
"titleFont": "Montserrat",
"headingFont": "Bebas Neue",
"bodyFont": "Open Sans",
"accentFont": "Playfair Display"
},
"defaultAiPromptHints": {
"backgroundImage": "atmospheric outdoor scene, nature, forest, autumn colors, cinematic lighting, no text, no letters",
"mainImage": "detailed illustration, high quality, vibrant colors, no text"
},
"metadata": {
"author": "System",
"version": "1.0.0",
"tags": ["natura", "outdoor", "autunno", "sagra"]
},
"createdAt": "2025-01-15T10:00:00.000Z",
"updatedAt": "2025-01-15T10:00:00.000Z"
}

View File

@@ -71,12 +71,12 @@
"CIRCUIT_REMOVED_ADMIN_YOU": "%s ti è stato rimosso l'incarico di Amministratore del %s da parte di %s", "CIRCUIT_REMOVED_ADMIN_YOU": "%s ti è stato rimosso l'incarico di Amministratore del %s da parte di %s",
"RICHIESTA_BLOCCO_CIRCUIT": "Richiesta di bloccare il %s da parte di %s", "RICHIESTA_BLOCCO_CIRCUIT": "Richiesta di bloccare il %s da parte di %s",
"CIRCUIT_ELIMINATO": "Il %s è stato eliminato da parte di %s", "CIRCUIT_ELIMINATO": "Il %s è stato eliminato da parte di %s",
"FIDO_IMPOSTATO_ADMINS_CIRCUIT": "✅ l'utente %s è stato abilitato alla Fiducia (%s RIS) sul '%s' (da parte di %s)", "FIDO_IMPOSTATO_ADMINS_CIRCUIT": "✅ Nuovo membro abilitato al Circuito RIS\n\n👤 Membro: %s\n🤝 Fiducia Concessa: %s RIS\n📍 Circuito: %s\n👨💼 Abilitato da: %s\n\nIl nuovo membro può ora utilizzare i RIS per gli scambi nel territorio.",
"FIDO_IMPOSTATO_ADMINS_CIRCUIT_MYGROUP": "✅ il Conto di Gruppo %s è stato abilitato alla Fiducia fino a -%s sul '%s' (da parte di %s)", "FIDO_IMPOSTATO_ADMINS_CIRCUIT_MYGROUP": "✅ il Conto di Gruppo %s è stato abilitato alla Fiducia fino a -%s sul '%s' (da parte di %s)",
"ACCETTATO_NOTIFICA_ADMINS_CIRCUIT": "✅ l'utente %s è stato accettato a far parte del '%s' (da parte di %s)", "ACCETTATO_NOTIFICA_ADMINS_CIRCUIT": "✅ l'utente %s è stato accettato a far parte del '%s' (da parte di %s)",
"ACCETTATO_NOTIFICA_ADMINS_CIRCUIT_MYGROUP": "✅ il Conto di Gruppo %s è stato accettato a far parte del '%s' (da parte di %s)", "ACCETTATO_NOTIFICA_ADMINS_CIRCUIT_MYGROUP": "✅ il Conto di Gruppo %s è stato accettato a far parte del '%s' (da parte di %s)",
"CIRCUIT_ACCEPTED": "✅ Sei stato accettato da %s a far parte del %s.\nApri la APP e clicca in alto a destra sull'icona delle monete, oppure clicca qui: %s", "CIRCUIT_ACCEPTED": "✅ Sei stato accettato da %s a far parte del %s.\nApri la APP e clicca in alto a destra sull'icona delle monete, oppure clicca qui: %s",
"FIDO_IMPOSTATO": "✅ Ti è stata attivata la possibilità di utilizzare la Fiducia Concessa fino a %s RIS da %s sul '%s'.", "FIDO_IMPOSTATO": "🎉 Complimenti! Sei stato abilitato al Circuito RIS di %s\n\n✅ Abilitazione completata da %s\n\n💰 Cosa significa?\nOra puoi utilizzare i RIS (credito comunitario) per i tuoi scambi nel territorio.\n\n🤝 Fiducia Concessa: %s RIS\nPuoi ricevere beni/servizi pagando in RIS anche partendo da saldo zero. Il saldo negativo non è un debito, ma la fiducia che la comunità ti concede!\n\n📱 Prossimi passi:\n• Esplora la piattaforma e crea annunci\n• Inizia con piccole transazioni (5-10%% in RIS)\n• Partecipa agli incontri e mercatini locali\n• Unisciti al gruppo Telegram territoriale\n\n💡 Ricorda: 1 RIS = 1 Euro (come riferimento di valore)\n\nBenvenuto nella comunità RISO! 🍚💚",
"CIRCUIT_ACCEPTED_YOU": "✅ Hai accettato %s a far parte del '%s'", "CIRCUIT_ACCEPTED_YOU": "✅ Hai accettato %s a far parte del '%s'",
"CIRCUIT_REFUSED": "❌ Ti è stato rifiutato l'accesso da %s a far parte del '%s'. Se pensi sia un'errore, contatta l'amministratore del Circuito.", "CIRCUIT_REFUSED": "❌ Ti è stato rifiutato l'accesso da %s a far parte del '%s'. Se pensi sia un'errore, contatta l'amministratore del Circuito.",
"CIRCUIT_REMOVED": "❌ l'utente %s è stato rimosso del %s (da parte di %s)", "CIRCUIT_REMOVED": "❌ l'utente %s è stato rimosso del %s (da parte di %s)",

42
src/middleware/upload.js Normal file
View File

@@ -0,0 +1,42 @@
const multer = require('multer');
const path = require('path');
const crypto = require('crypto');
const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads';
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, UPLOAD_DIR);
},
filename: (req, file, cb) => {
const uniqueId = crypto.randomBytes(8).toString('hex');
const ext = path.extname(file.originalname);
cb(null, `${Date.now()}_${uniqueId}${ext}`);
}
});
const fileFilter = (req, file, cb) => {
const allowedTypes = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'image/svg+xml'
];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Tipo file non supportato'), false);
}
};
const upload = multer({
storage,
fileFilter,
limits: {
fileSize: 20 * 1024 * 1024 // 20MB max
}
});
module.exports = upload;

137
src/models/Asset.js Normal file
View File

@@ -0,0 +1,137 @@
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Sub-schema: File Info
const FileInfoSchema = new Schema({
path: { type: String, required: true },
url: { type: String },
thumbnailPath: { type: String },
thumbnailUrl: { type: String },
originalName: { type: String },
mimeType: { type: String, required: true },
size: { type: Number }, // bytes
dimensions: {
width: { type: Number },
height: { type: Number }
}
}, { _id: false });
// Sub-schema: AI Generation Params
const AiGenerationSchema = new Schema({
prompt: { type: String, required: true },
negativePrompt: { type: String },
provider: {
type: String,
required: true,
enum: ['hf', 'fal', 'ideogram', 'openai', 'stability', 'midjourney']
},
model: { type: String },
seed: { type: Number },
steps: { type: Number },
cfg: { type: Number },
requestedSize: { type: String },
actualSize: { type: String },
aspectRatio: { type: String },
styleType: { type: String },
generationTime: { type: Number }, // ms
cost: { type: Number, default: 0 },
rawResponse: { type: Schema.Types.Mixed }
}, { _id: false });
// Sub-schema: Usage Tracking
const UsageTrackingSchema = new Schema({
usedInPosters: [{ type: Schema.Types.ObjectId, ref: 'Poster' }],
usedInTemplates: [{ type: Schema.Types.ObjectId, ref: 'Template' }],
usageCount: { type: Number, default: 0 }
}, { _id: false });
// Sub-schema: Asset Metadata
const AssetMetadataSchema = new Schema({
userId: { type: Schema.Types.ObjectId, ref: 'User', index: true },
projectId: { type: Schema.Types.ObjectId, ref: 'Project' },
tags: [{ type: String }],
description: { type: String },
isReusable: { type: Boolean, default: true },
isPublic: { type: Boolean, default: false }
}, { _id: false });
// MAIN SCHEMA: Asset
const AssetSchema = new Schema({
type: {
type: String,
required: true,
enum: ['image', 'logo', 'icon', 'font']
},
category: {
type: String,
required: true,
enum: ['background', 'main', 'logo', 'decoration', 'overlay', 'other'],
index: true
},
sourceType: {
type: String,
required: true,
enum: ['upload', 'ai', 'library', 'url'],
index: true
},
file: { type: FileInfoSchema, required: true },
aiGeneration: { type: AiGenerationSchema },
usage: { type: UsageTrackingSchema, default: () => ({}) },
metadata: { type: AssetMetadataSchema, default: () => ({}) },
status: {
type: String,
enum: ['processing', 'ready', 'error', 'deleted'],
default: 'ready'
},
errorMessage: { type: String }
}, {
timestamps: true,
toJSON: { virtuals: true }
});
// Indexes
AssetSchema.index({ 'metadata.userId': 1, category: 1 });
AssetSchema.index({ 'metadata.tags': 1 });
AssetSchema.index({ sourceType: 1, status: 1 });
// Virtual: isAiGenerated
AssetSchema.virtual('isAiGenerated').get(function() {
return this.sourceType === 'ai';
});
// Methods
AssetSchema.methods.addUsage = async function(posterId, type = 'poster') {
if (type === 'poster' && !this.usage.usedInPosters.includes(posterId)) {
this.usage.usedInPosters.push(posterId);
} else if (type === 'template' && !this.usage.usedInTemplates.includes(posterId)) {
this.usage.usedInTemplates.push(posterId);
}
this.usage.usageCount = this.usage.usedInPosters.length + this.usage.usedInTemplates.length;
return this.save();
};
AssetSchema.methods.getPublicUrl = function() {
return this.file.url || `/api/assets/${this._id}/file`;
};
// Statics
AssetSchema.statics.findByUser = function(userId, category = null) {
const query = { 'metadata.userId': userId, status: 'ready' };
if (category) query.category = category;
return this.find(query).sort({ createdAt: -1 });
};
AssetSchema.statics.findReusable = function(userId, category = null) {
const query = {
'metadata.userId': userId,
'metadata.isReusable': true,
status: 'ready'
};
if (category) query.category = category;
return this.find(query).sort({ 'usage.usageCount': -1 });
};
module.exports = mongoose.model('Asset', AssetSchema);

262
src/models/Poster.js Normal file
View File

@@ -0,0 +1,262 @@
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Sub-schema: Content
const PosterContentSchema = new Schema({
title: { type: String, maxlength: 500 },
subtitle: { type: String, maxlength: 500 },
eventDate: { type: String, maxlength: 200 },
eventTime: { type: String, maxlength: 100 },
location: { type: String, maxlength: 500 },
contacts: { type: String, maxlength: 1000 },
extraText: [{ type: String }],
customFields: { type: Map, of: String }
}, { _id: false });
// Sub-schema: Asset AI Params (embedded)
const EmbeddedAiParamsSchema = new Schema({
prompt: { type: String },
negativePrompt: { type: String },
provider: { type: String },
model: { type: String },
seed: { type: Number },
steps: { type: Number },
cfg: { type: Number },
size: { type: String },
generatedAt: { type: Date }
}, { _id: false });
// Sub-schema: Poster Asset Reference
const PosterAssetSchema = new Schema({
id: { type: String },
assetId: { type: Schema.Types.ObjectId, ref: 'Asset' },
slotId: { type: String }, // per loghi
sourceType: { type: String, enum: ['upload', 'ai', 'library', 'url'] },
url: { type: String },
thumbnailUrl: { type: String },
originalName: { type: String },
mimeType: { type: String },
size: { type: Number },
dimensions: {
width: { type: Number },
height: { type: Number }
},
aiParams: EmbeddedAiParamsSchema
}, { _id: false });
// Sub-schema: Assets Container
const PosterAssetsSchema = new Schema({
backgroundImage: PosterAssetSchema,
mainImage: PosterAssetSchema,
logos: [PosterAssetSchema]
}, { _id: false });
// Sub-schema: Layer Override Style
const LayerOverrideStyleSchema = new Schema({
fontSize: { type: Number },
color: { type: String },
fontWeight: { type: Number },
opacity: { type: Number },
// altri override possibili
}, { _id: false });
// Sub-schema: Layer Override
const LayerOverrideSchema = new Schema({
position: {
x: { type: Number },
y: { type: Number },
w: { type: Number },
h: { type: Number }
},
visible: { type: Boolean },
style: LayerOverrideStyleSchema
}, { _id: false });
// Sub-schema: Render Output File
const RenderOutputFileSchema = new Schema({
path: { type: String, required: true },
url: { type: String },
size: { type: Number },
quality: { type: Number }
}, { _id: false });
// Sub-schema: Render Output
const RenderOutputSchema = new Schema({
png: RenderOutputFileSchema,
jpg: RenderOutputFileSchema,
webp: RenderOutputFileSchema,
pdf: RenderOutputFileSchema,
dimensions: {
width: { type: Number },
height: { type: Number }
},
renderedAt: { type: Date }
}, { _id: false });
// Sub-schema: History Entry
const HistoryEntrySchema = new Schema({
action: {
type: String,
required: true,
enum: ['created', 'updated', 'ai_background_generated', 'ai_main_generated', 'rendered', 'downloaded', 'shared', 'deleted']
},
timestamp: { type: Date, default: Date.now },
userId: { type: Schema.Types.ObjectId, ref: 'User' },
details: { type: Schema.Types.Mixed }
}, { _id: false });
// Sub-schema: Poster Metadata
const PosterMetadataSchema = new Schema({
userId: { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
projectId: { type: Schema.Types.ObjectId, ref: 'Project' },
tags: [{ type: String }],
isPublic: { type: Boolean, default: false },
isFavorite: { type: Boolean, default: false },
viewCount: { type: Number, default: 0 },
downloadCount: { type: Number, default: 0 }
}, { _id: false });
// MAIN SCHEMA: Poster
const PosterSchema = new Schema({
templateId: {
type: Schema.Types.ObjectId,
ref: 'Template',
required: true,
index: true
},
templateSnapshot: { type: Schema.Types.Mixed }, // copia del template al momento della creazione
name: { type: String, required: true, trim: true, maxlength: 300 },
description: { type: String, maxlength: 1000 },
status: {
type: String,
enum: ['draft', 'processing', 'completed', 'error'],
default: 'draft',
index: true
},
content: { type: PosterContentSchema, required: true },
assets: { type: PosterAssetsSchema, default: () => ({}) },
layerOverrides: { type: Map, of: LayerOverrideSchema, default: () => new Map() },
renderOutput: RenderOutputSchema,
renderEngineVersion: { type: String, default: '1.0.0' },
history: [HistoryEntrySchema],
metadata: { type: PosterMetadataSchema, required: true },
errorMessage: { type: String },
// Campi dalla tua bozza originale
originalPrompt: { type: String }, // prompt completo usato
styleUsed: { type: String },
aspectRatio: { type: String },
provider: { type: String }
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
// Indexes
PosterSchema.index({ 'metadata.userId': 1, status: 1 });
PosterSchema.index({ 'metadata.tags': 1 });
PosterSchema.index({ 'metadata.isFavorite': 1, 'metadata.userId': 1 });
PosterSchema.index({ createdAt: -1 });
PosterSchema.index({ name: 'text', description: 'text' });
// Virtual: isCompleted
PosterSchema.virtual('isCompleted').get(function() {
return this.status === 'completed' && this.renderOutput?.png?.path;
});
// Virtual: downloadUrl
PosterSchema.virtual('downloadUrl').get(function() {
if (this.renderOutput?.png?.path) {
return `/api/posters/${this._id}/download/png`;
}
return null;
});
// Pre-save: aggiorna history
PosterSchema.pre('save', function(next) {
if (this.isNew) {
this.history = this.history || [];
this.history.push({
action: 'created',
timestamp: new Date(),
userId: this.metadata.userId
});
}
next();
});
// Methods
PosterSchema.methods.addHistory = function(action, details = {}) {
this.history.push({
action,
timestamp: new Date(),
userId: this.metadata.userId,
details
});
return this;
};
PosterSchema.methods.setRenderOutput = function(outputData) {
this.renderOutput = {
...outputData,
renderedAt: new Date()
};
this.status = 'completed';
this.addHistory('rendered', { duration: outputData.duration });
return this;
};
PosterSchema.methods.setError = function(errorMessage) {
this.status = 'error';
this.errorMessage = errorMessage;
return this;
};
PosterSchema.methods.incrementDownload = async function() {
this.metadata.downloadCount = (this.metadata.downloadCount || 0) + 1;
this.addHistory('downloaded');
return this.save();
};
PosterSchema.methods.toggleFavorite = async function() {
this.metadata.isFavorite = !this.metadata.isFavorite;
return this.save();
};
// Statics
PosterSchema.statics.findByUser = function(userId, options = {}) {
const query = { 'metadata.userId': userId };
if (options.status) query.status = options.status;
if (options.isFavorite) query['metadata.isFavorite'] = true;
return this.find(query)
.populate('templateId', 'name templateType thumbnailUrl')
.sort({ createdAt: -1 })
.limit(options.limit || 50);
};
PosterSchema.statics.findFavorites = function(userId) {
return this.find({
'metadata.userId': userId,
'metadata.isFavorite': true
}).sort({ updatedAt: -1 });
};
PosterSchema.statics.findRecent = function(userId, limit = 10) {
return this.find({
'metadata.userId': userId,
status: 'completed'
})
.sort({ createdAt: -1 })
.limit(limit)
.select('name renderOutput.png.url thumbnailUrl createdAt');
};
module.exports = mongoose.model('Poster', PosterSchema);

253
src/models/Template.js Normal file
View File

@@ -0,0 +1,253 @@
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Sub-schema: Posizione layer
const PositionSchema = new Schema({
x: { type: Number, required: true, min: 0, max: 1 },
y: { type: Number, required: true, min: 0, max: 1 },
w: { type: Number, required: true, min: 0, max: 1 },
h: { type: Number, required: true, min: 0, max: 1 }
}, { _id: false });
// Sub-schema: Ombra
const ShadowSchema = new Schema({
enabled: { type: Boolean, default: false },
blur: { type: Number, default: 10 },
spread: { type: Number, default: 0 },
offsetX: { type: Number, default: 0 },
offsetY: { type: Number, default: 4 },
color: { type: String, default: 'rgba(0,0,0,0.5)' }
}, { _id: false });
// Sub-schema: Stroke
const StrokeSchema = new Schema({
enabled: { type: Boolean, default: false },
width: { type: Number, default: 2 },
color: { type: String, default: '#000000' }
}, { _id: false });
// Sub-schema: Border
const BorderSchema = new Schema({
enabled: { type: Boolean, default: false },
width: { type: Number, default: 2 },
color: { type: String, default: '#ffffff' },
style: { type: String, enum: ['solid', 'dashed', 'dotted'], default: 'solid' }
}, { _id: false });
// Sub-schema: Gradient Stop
const GradientStopSchema = new Schema({
position: { type: Number, required: true, min: 0, max: 1 },
color: { type: String, required: true }
}, { _id: false });
// Sub-schema: Overlay
const OverlaySchema = new Schema({
enabled: { type: Boolean, default: false },
type: { type: String, enum: ['solid', 'gradient'], default: 'gradient' },
color: { type: String },
direction: { type: String, default: 'to-bottom' },
stops: [GradientStopSchema]
}, { _id: false });
// Sub-schema: Fallback
const FallbackSchema = new Schema({
type: { type: String, enum: ['solid', 'gradient'], default: 'solid' },
color: { type: String },
direction: { type: String },
colors: [{ type: String }]
}, { _id: false });
// Sub-schema: Icon
const IconSchema = new Schema({
enabled: { type: Boolean, default: false },
name: { type: String },
size: { type: Number, default: 24 },
color: { type: String, default: '#ffffff' }
}, { _id: false });
// Sub-schema: Stile Layer (unificato per immagini e testi)
const LayerStyleSchema = new Schema({
// Comuni
opacity: { type: Number, default: 1, min: 0, max: 1 },
// Per immagini
objectFit: { type: String, enum: ['cover', 'contain', 'fill', 'none'], default: 'cover' },
blur: { type: Number, default: 0 },
borderRadius: { type: Number, default: 0 },
overlay: OverlaySchema,
border: BorderSchema,
// Per testi
fontFamily: { type: String },
fontWeight: { type: Number, default: 400 },
fontSize: { type: Number },
fontSizeMin: { type: Number },
fontSizeMax: { type: Number },
autoFit: { type: Boolean, default: false },
fontStyle: { type: String, enum: ['normal', 'italic'], default: 'normal' },
color: { type: String },
textAlign: { type: String, enum: ['left', 'center', 'right'], default: 'center' },
textTransform: { type: String, enum: ['none', 'uppercase', 'lowercase', 'capitalize'], default: 'none' },
letterSpacing: { type: Number, default: 0 },
lineHeight: { type: Number, default: 1.2 },
// Effetti
shadow: ShadowSchema,
stroke: StrokeSchema
}, { _id: false });
// Sub-schema: Layer
const LayerSchema = new Schema({
id: { type: String, required: true },
type: {
type: String,
required: true,
enum: ['backgroundImage', 'mainImage', 'logo', 'title', 'subtitle', 'eventDate', 'eventTime', 'location', 'contacts', 'extraText', 'customText', 'customImage', 'shape', 'divider']
},
zIndex: { type: Number, default: 0 },
position: { type: PositionSchema, required: true },
anchor: {
type: String,
enum: ['top-left', 'top-center', 'top-right', 'center-left', 'center', 'center-right', 'bottom-left', 'bottom-center', 'bottom-right'],
default: 'center'
},
required: { type: Boolean, default: false },
visible: { type: Boolean, default: true },
locked: { type: Boolean, default: false },
maxLines: { type: Number },
fallback: FallbackSchema,
icon: IconSchema,
style: { type: LayerStyleSchema, default: () => ({}) }
}, { _id: false });
// Sub-schema: Logo Slot
const LogoSlotSchema = new Schema({
id: { type: String, required: true },
position: { type: PositionSchema, required: true },
anchor: { type: String, default: 'center' },
style: { type: LayerStyleSchema, default: () => ({}) }
}, { _id: false });
// Sub-schema: Logo Slots Config
const LogoSlotsConfigSchema = new Schema({
enabled: { type: Boolean, default: true },
maxCount: { type: Number, default: 3, min: 1, max: 10 },
collapseIfEmpty: { type: Boolean, default: true },
slots: [LogoSlotSchema]
}, { _id: false });
// Sub-schema: Format
const FormatSchema = new Schema({
preset: { type: String, default: 'custom' }, // A4, A3, Instagram, Facebook, custom
width: { type: Number, required: true },
height: { type: Number, required: true },
unit: { type: String, enum: ['px', 'mm', 'in'], default: 'px' },
dpi: { type: Number, default: 300 }
}, { _id: false });
// Sub-schema: Safe Area
const SafeAreaSchema = new Schema({
top: { type: Number, default: 0, min: 0, max: 0.5 },
right: { type: Number, default: 0, min: 0, max: 0.5 },
bottom: { type: Number, default: 0, min: 0, max: 0.5 },
left: { type: Number, default: 0, min: 0, max: 0.5 }
}, { _id: false });
// Sub-schema: Palette
const PaletteSchema = new Schema({
primary: { type: String, default: '#e94560' },
secondary: { type: String, default: '#0f3460' },
accent: { type: String, default: '#ffd700' },
background: { type: String, default: '#1a1a2e' },
text: { type: String, default: '#ffffff' },
textSecondary: { type: String, default: '#cccccc' },
textMuted: { type: String, default: '#888888' }
}, { _id: false });
// Sub-schema: Typography
const TypographySchema = new Schema({
titleFont: { type: String, default: 'Montserrat' },
headingFont: { type: String, default: 'Bebas Neue' },
bodyFont: { type: String, default: 'Open Sans' },
accentFont: { type: String, default: 'Playfair Display' }
}, { _id: false });
// Sub-schema: AI Prompt Hints
const AiPromptHintsSchema = new Schema({
backgroundImage: { type: String },
mainImage: { type: String }
}, { _id: false });
// Sub-schema: Metadata
const TemplateMetadataSchema = new Schema({
author: { type: String, default: 'System' },
version: { type: String, default: '1.0.0' },
tags: [{ type: String }],
isPublic: { type: Boolean, default: false },
usageCount: { type: Number, default: 0 }
}, { _id: false });
// MAIN SCHEMA: Template
const TemplateSchema = new Schema({
name: { type: String, required: true, trim: true, maxlength: 200 },
templateType: { type: String, required: true, trim: true, index: true },
description: { type: String, maxlength: 1000 },
format: { type: FormatSchema, required: true },
safeArea: { type: SafeAreaSchema, default: () => ({}) },
backgroundColor: { type: String, default: '#1a1a2e' },
layers: { type: [LayerSchema], required: true, validate: [arr => arr.length > 0, 'Almeno un layer richiesto'] },
logoSlots: { type: LogoSlotsConfigSchema, default: () => ({}) },
palette: { type: PaletteSchema, default: () => ({}) },
typography: { type: TypographySchema, default: () => ({}) },
defaultAiPromptHints: { type: AiPromptHintsSchema, default: () => ({}) },
previewUrl: { type: String },
thumbnailUrl: { type: String },
metadata: { type: TemplateMetadataSchema, default: () => ({}) },
isActive: { type: Boolean, default: true },
userId: { type: Schema.Types.ObjectId, ref: 'User', index: true }
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
// Indexes
TemplateSchema.index({ templateType: 1, isActive: 1 });
TemplateSchema.index({ 'metadata.tags': 1 });
TemplateSchema.index({ name: 'text', description: 'text', templateType: 'text' });
// Virtual: layer count
TemplateSchema.virtual('layerCount').get(function() {
return this.layers ? this.layers.length : 0;
});
// Methods
TemplateSchema.methods.getLayerById = function(layerId) {
return this.layers.find(l => l.id === layerId);
};
TemplateSchema.methods.getLayersByType = function(type) {
return this.layers.filter(l => l.type === type);
};
TemplateSchema.methods.incrementUsage = async function() {
this.metadata.usageCount = (this.metadata.usageCount || 0) + 1;
return this.save();
};
// Statics
TemplateSchema.statics.findByType = function(templateType) {
return this.find({ templateType, isActive: true }).sort({ 'metadata.usageCount': -1 });
};
TemplateSchema.statics.findPublic = function() {
return this.find({ 'metadata.isPublic': true, isActive: true });
};
module.exports = mongoose.model('Template', TemplateSchema);

View File

@@ -32,6 +32,12 @@ const AccountSchema = new Schema({
numtransactions: { numtransactions: {
type: Number, type: Number,
}, },
sent: {
type: Number,
},
received: {
type: Number,
},
username: { username: {
type: String, type: String,
}, },
@@ -56,6 +62,9 @@ const AccountSchema = new Schema({
fidoConcesso: { fidoConcesso: {
type: Number, type: Number,
}, },
username_admin_abilitante: {
type: String,
},
qta_maxConcessa: { qta_maxConcessa: {
type: Number, type: Number,
}, },
@@ -237,8 +246,22 @@ AccountSchema.statics.addtoSaldo = async function (myaccount, amount, mitt) {
myaccount.date_updated = new Date(); myaccount.date_updated = new Date();
myaccountupdate.saldo = myaccount.saldo; myaccountupdate.saldo = myaccount.saldo;
myaccountupdate.sent = myaccount.sent;
myaccountupdate.received = myaccount.received;
myaccountupdate.totTransato = myaccount.totTransato; myaccountupdate.totTransato = myaccount.totTransato;
myaccountupdate.numtransactions = myaccount.numtransactions; myaccountupdate.numtransactions = myaccount.numtransactions;
if (amount > 0) {
if (myaccountupdate.received === undefined) {
myaccountupdate.received = 0;
}
myaccountupdate.received += 1;
} else {
if (myaccountupdate.sent === undefined) {
myaccountupdate.sent = 0;
}
myaccountupdate.sent += 1;
}
myaccountupdate.date_updated = myaccount.date_updated; myaccountupdate.date_updated = myaccount.date_updated;
const ris = await Account.updateOne( const ris = await Account.updateOne(
@@ -318,8 +341,11 @@ AccountSchema.statics.getAccountByUsernameAndCircuitId = async function (
saldo: 0, saldo: 0,
saldo_pend: 0, saldo_pend: 0,
fidoConcesso: 0, fidoConcesso: 0,
username_admin_abilitante: '',
qta_maxConcessa: 0, qta_maxConcessa: 0,
totTransato: 0, totTransato: 0,
sent: 0,
received: 0,
numtransactions: 0, numtransactions: 0,
totTransato_pend: 0, totTransato_pend: 0,
}); });
@@ -643,9 +669,10 @@ AccountSchema.statics.SetMinMaxPersonali = async function (idapp, fidoConcesso,
} }
}; };
AccountSchema.statics.updateFido = async function (idapp, username, groupname, circuitId, fido) { AccountSchema.statics.updateFido = async function (idapp, username, groupname, circuitId, fido, username_action) {
let paramstoupdate = { let paramstoupdate = {
fidoConcesso: fido, fidoConcesso: fido,
username_admin_abilitante: username_action,
}; };
let risult = null; let risult = null;
if (groupname) risult = await Account.updateOne({ idapp, circuitId, groupname }, { $set: paramstoupdate }); if (groupname) risult = await Account.updateOne({ idapp, circuitId, groupname }, { $set: paramstoupdate });

88
src/models/bacheca.js Executable file
View File

@@ -0,0 +1,88 @@
const mongoose = require('mongoose').set('debug', false)
const Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.level = "F";
const tools = require('../tools/general');
const { ObjectId } = require('mongodb');
// Resolving error Unknown modifier: $pushAll
mongoose.plugin(schema => {
schema.options.usePushEach = true
});
const BachecaSchema = new Schema({
_id: {
type: Number,
},
descr: {
type: String,
},
idSectorBacheca: [{
type: Number
}],
icon: {
type: String,
},
img: {
type: String,
},
});
BachecaSchema.statics.findAllIdApp = async function (idapp) {
const Bacheca = this;
const query = [
{ $sort: { descr: 1 } }
];
const res = await Bacheca
.aggregate(query)
.then((arrrec) => {
return arrrec
})
return res;
};
BachecaSchema.pre('save', async function (next) {
if (this.isNew) {
const myrec = await Bacheca.findOne().limit(1).sort({_id:-1});
if (!!myrec) {
if (myrec._doc._id === 0)
this._id = 1;
else
this._id = myrec._doc._id + 1;
} else {
this._id = 1;
}
}
next();
});
BachecaSchema.statics.getFieldsForSearch = function () {
return [{ field: 'label', type: tools.FieldType.string },
{ field: 'descr', type: tools.FieldType.string }]
};
BachecaSchema.statics.executeQueryTable = function (idapp, params) {
params.fieldsearch = this.getFieldsForSearch();
return tools.executeQueryTable(this, 0, params);
};
const Bacheca = mongoose.model('Bacheca', BachecaSchema);
Bacheca.createIndexes()
.then(() => { })
.catch((err) => { throw err; });
module.exports = { Bacheca };

View File

@@ -87,6 +87,9 @@ const CircuitSchema = new Schema({
totTransato: { totTransato: {
type: Number, type: Number,
}, },
numTransazioni: {
type: Number,
},
nome_valuta: { nome_valuta: {
type: String, type: String,
maxlength: 20, maxlength: 20,
@@ -166,6 +169,7 @@ const CircuitSchema = new Schema({
}, },
date_created: { date_created: {
type: Date, type: Date,
default: Date.now,
}, },
date_updated: { date_updated: {
type: Date, type: Date,
@@ -174,6 +178,7 @@ const CircuitSchema = new Schema({
{ {
username: { type: String }, username: { type: String },
date: { type: Date }, date: { type: Date },
enable_to_receive_email: { type: Boolean },
}, },
], ],
photos: [ photos: [
@@ -325,6 +330,7 @@ CircuitSchema.statics.getWhatToShow = function (idapp, username) {
numMembers: 1, numMembers: 1,
totCircolante: 1, totCircolante: 1,
totTransato: 1, totTransato: 1,
numTransazioni: 1,
systemUserId: 1, systemUserId: 1,
createdBy: 1, createdBy: 1,
date_created: 1, date_created: 1,
@@ -410,6 +416,7 @@ CircuitSchema.statics.getWhatToShow_Unknown = function (idapp, username) {
nome_valuta: 1, nome_valuta: 1,
totCircolante: 1, totCircolante: 1,
totTransato: 1, totTransato: 1,
numTransazioni: 1,
fido_scoperto_default: 1, fido_scoperto_default: 1,
fido_scoperto_default_grp: 1, fido_scoperto_default_grp: 1,
qta_max_default_grp: 1, qta_max_default_grp: 1,
@@ -823,6 +830,7 @@ CircuitSchema.statics.sendCoins = async function (onlycheck, idapp, usernameOrig
const circolanteAtt = this.getCircolanteSingolaTransaz(accountorigTable, accountdestTable); const circolanteAtt = this.getCircolanteSingolaTransaz(accountorigTable, accountdestTable);
// Somma di tutte le transazioni // Somma di tutte le transazioni
circuittable.numTransazioni += 1;
circuittable.totTransato += myqty; circuittable.totTransato += myqty;
// circuittable.totCircolante = circuittable.totCircolante + (circolanteAtt - circolantePrec); // circuittable.totCircolante = circuittable.totCircolante + (circolanteAtt - circolantePrec);
circuittable.totCircolante = await Account.calcTotCircolante(idapp, circuittable._id); circuittable.totCircolante = await Account.calcTotCircolante(idapp, circuittable._id);
@@ -830,6 +838,7 @@ CircuitSchema.statics.sendCoins = async function (onlycheck, idapp, usernameOrig
paramstoupdate = { paramstoupdate = {
totTransato: circuittable.totTransato, totTransato: circuittable.totTransato,
totCircolante: circuittable.totCircolante, totCircolante: circuittable.totCircolante,
numTransazioni: circuittable.numTransazioni,
}; };
await Circuit.updateOne({ _id: circuittable }, { $set: paramstoupdate }); await Circuit.updateOne({ _id: circuittable }, { $set: paramstoupdate });
@@ -899,7 +908,14 @@ CircuitSchema.statics.sendCoins = async function (onlycheck, idapp, usernameOrig
let myuserDest = await User.getUserByUsername(idapp, extrarec.dest); let myuserDest = await User.getUserByUsername(idapp, extrarec.dest);
// Invia una email al destinatario ! // Invia una email al destinatario !
await sendemail.sendEmail_RisRicevuti(myuserDest.lang, myuserDest, myuserDest.email, idapp, paramsrec, extrarec); await sendemail.sendEmail_RisRicevuti(
myuserDest.lang,
myuserDest,
myuserDest.email,
idapp,
paramsrec,
extrarec
);
} else if (extrarec.groupdest || extrarec.contoComDest) { } else if (extrarec.groupdest || extrarec.contoComDest) {
const groupDestoContoCom = extrarec.groupdest const groupDestoContoCom = extrarec.groupdest
? extrarec.groupdest ? extrarec.groupdest
@@ -1030,8 +1046,10 @@ CircuitSchema.statics.getCircuitByCircuitId = async function (circuitId) {
return await Circuit.findOne({ _id: circuitId }); return await Circuit.findOne({ _id: circuitId });
}; };
// getListAdminsByCircuitPath - ritorna oggetti interi + USER_ADMIN_CIRCUITS
// getListAdminsByCircuitPath - ritorna oggetti interi senza duplicati
CircuitSchema.statics.getListAdminsByCircuitPath = async function (idapp, circuitPath) { CircuitSchema.statics.getListAdminsByCircuitPath = async function (idapp, circuitPath) {
let arr = await Circuit.findOne( let circuit = await Circuit.findOne(
{ {
idapp, idapp,
path: circuitPath, path: circuitPath,
@@ -1040,9 +1058,22 @@ CircuitSchema.statics.getListAdminsByCircuitPath = async function (idapp, circui
{ admins: 1 } { admins: 1 }
).lean(); ).lean();
let myarr = arr && arr.admins ? arr.admins : []; let adminObjects = circuit && circuit.admins ? circuit.admins : [];
return [...myarr, ...shared_consts.USER_ADMIN_CIRCUITS]; // Aggiungi USER_ADMIN_CIRCUITS come oggetti
let systemAdmins = shared_consts.USER_ADMIN_CIRCUITS.map((username) => ({
username,
date: null,
_id: null,
}));
// Unisci e rimuovi duplicati per username
let allAdmins = [...adminObjects, ...systemAdmins];
let uniqueAdmins = allAdmins.filter(
(admin, index, self) => index === self.findIndex((a) => a.username === admin.username)
);
return uniqueAdmins;
}; };
// Imposta a tutti i Conti Collettivi, i seguenti minimi e massimi // Imposta a tutti i Conti Collettivi, i seguenti minimi e massimi
@@ -1173,11 +1204,12 @@ CircuitSchema.statics.createCircuitIfNotExist = async function (req, idapp, prov
qta_max_default_grp: shared_consts.CIRCUIT_PARAMS.SCOPERTO_MAX_GRP, qta_max_default_grp: shared_consts.CIRCUIT_PARAMS.SCOPERTO_MAX_GRP,
valuta_per_euro: 1, valuta_per_euro: 1,
totTransato: 0, totTransato: 0,
numTransazioni: 0,
totCircolante: 0, totCircolante: 0,
date_created: new Date(), date_created: new Date(),
admins: admins.map((username) => ({ username })), admins: admins.map((username) => ({ username })),
askManagerToEnter: false, askManagerToEnter: false,
sendEmailAfterAskingToEnter: false, sendEmailAfterAskingToEnter: true,
circuitoIndipendente: false, circuitoIndipendente: false,
}); });
@@ -1316,7 +1348,7 @@ CircuitSchema.statics.SetDefMinMaxCollettivi = async function (idapp, valmin, va
} }
}; };
CircuitSchema.statics.setFido = async function (idapp, username, circuitName, groupname) { CircuitSchema.statics.setFido = async function (idapp, username, circuitName, groupname, username_action) {
try { try {
mycircuit = await Circuit.findOne({ idapp, name: circuitName }).lean(); mycircuit = await Circuit.findOne({ idapp, name: circuitName }).lean();
if (mycircuit) { if (mycircuit) {
@@ -1369,9 +1401,14 @@ CircuitSchema.statics.setFido = async function (idapp, username, circuitName, gr
variato = await Account.updateQtaMax(idapp, username, groupname, circuitId, qtamax); variato = await Account.updateQtaMax(idapp, username, groupname, circuitId, qtamax);
} }
const ris = await Account.updateFido(idapp, username, groupname, circuitId, fido); const ris = await Account.updateFido(idapp, username, groupname, circuitId, fido, username_action);
if (ris) { if (ris) {
return { qta_maxConcessa: qtamax, fidoConcesso: fido, changed: variato || (ris && ris.modifiedCount > 0) }; return {
qta_maxConcessa: qtamax,
fidoConcesso: fido,
username_admin_abilitante: username_action,
changed: variato || (ris && ris.modifiedCount > 0),
};
} }
} }
} }
@@ -1424,7 +1461,7 @@ CircuitSchema.statics.getFido = async function (idapp, username, circuitName, gr
return null; return null;
}; };
CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi) { CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi, options) {
const { User } = require('../models/user'); const { User } = require('../models/user');
const { MyGroup } = require('../models/mygroup'); const { MyGroup } = require('../models/mygroup');
const { SendNotif } = require('../models/sendnotif'); const { SendNotif } = require('../models/sendnotif');
@@ -1523,7 +1560,7 @@ CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi) {
let numtransazionitot = 0; let numtransazionitot = 0;
const arrcircuits = await Circuit.find({ idapp }).lean(); const arrcircuits = await Circuit.find({ idapp });
for (const circuit of arrcircuits) { for (const circuit of arrcircuits) {
let strusersnotinaCircuit = ''; let strusersnotinaCircuit = '';
let strusersnotExist = ''; let strusersnotExist = '';
@@ -1603,6 +1640,16 @@ CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi) {
_id: null, _id: null,
numtransactions: { $sum: 1 }, numtransactions: { $sum: 1 },
totTransato: { $sum: { $abs: '$amount' } }, totTransato: { $sum: { $abs: '$amount' } },
sentCount: {
$sum: {
$cond: [{ $eq: ['$accountFromId', account._id] }, 1, 0],
},
},
receivedCount: {
$sum: {
$cond: [{ $eq: ['$accountToId', account._id] }, 1, 0],
},
},
saldo: { saldo: {
$sum: { $sum: {
$cond: [ $cond: [
@@ -1619,6 +1666,8 @@ CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi) {
]); ]);
let numtransactions = result && result.length > 0 ? result[0].numtransactions : 0; let numtransactions = result && result.length > 0 ? result[0].numtransactions : 0;
let sentCount = result && result.length > 0 ? result[0].sentCount : 0;
let receivedCount = result && result.length > 0 ? result[0].receivedCount : 0;
let totTransato = result && result.length > 0 ? result[0].totTransato : 0; let totTransato = result && result.length > 0 ? result[0].totTransato : 0;
let saldo = result && result.length > 0 ? result[0].saldo : 0; let saldo = result && result.length > 0 ? result[0].saldo : 0;
@@ -1662,6 +1711,8 @@ CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi) {
if (correggi) await Account.findOneAndUpdate({ _id: account._id }, { $set: { totTransato } }); if (correggi) await Account.findOneAndUpdate({ _id: account._id }, { $set: { totTransato } });
} }
await Account.findOneAndUpdate({ _id: account._id }, { $set: { sent: sentCount, received: receivedCount } });
saldotot += account.saldo; saldotot += account.saldo;
// if (account.totTransato === NaN || account.totTransato === undefined) // if (account.totTransato === NaN || account.totTransato === undefined)
@@ -1676,6 +1727,11 @@ CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi) {
// await account.calcPending(); // await account.calcPending();
ind++; ind++;
} // FINE ACCOUNT
if (options?.setnumtransaction) {
circuit.numTransazioni = numtransazionitot;
await circuit.save(); // salva su db
} }
let numaccounts = accounts.length; let numaccounts = accounts.length;
@@ -1859,6 +1915,11 @@ CircuitSchema.statics.getCircuitiExtraProvinciali = async function (idapp) {
return circuits; return circuits;
}; };
CircuitSchema.statics.ricalcolaNumTransazioni = async function (circuitId) {
const Circuit = this;
// +TODO: Ricalcola il numero delle transazioni avvenute
};
CircuitSchema.statics.getCircuitoItalia = async function (idapp) { CircuitSchema.statics.getCircuitoItalia = async function (idapp) {
const Circuit = this; const Circuit = this;
@@ -1867,6 +1928,22 @@ CircuitSchema.statics.getCircuitoItalia = async function (idapp) {
return circuit; return circuit;
}; };
CircuitSchema.statics.getSymbolByCircuitId = async function (circuitId) {
const Circuit = this;
const circuit = await Circuit.findOne({ _id: circuitId }, { symbol: 1});
return circuit?.symbol || '';
};
CircuitSchema.statics.isEnableToReceiveEmailByExtraRec = async function (idapp, recnotif) {
let ricevo = true;
if (recnotif.tag === 'setfido') {
// Controllo se l'utente ha scelto di ricevere l'email
}
return ricevo;
};
const Circuit = mongoose.model('Circuit', CircuitSchema); const Circuit = mongoose.model('Circuit', CircuitSchema);
Circuit.createIndexes() Circuit.createIndexes()

View File

@@ -19,6 +19,12 @@ const ContribtypeSchema = new Schema({
label: { label: {
type: String, type: String,
}, },
icon: {
type: String,
},
color: {
type: String,
},
showprice: { showprice: {
type: Boolean, type: Boolean,
} }

View File

@@ -106,6 +106,8 @@ MovementSchema.statics.addMov = async function (
idOrdersCart idOrdersCart
) { ) {
try { try {
const { Circuit } = require('./circuit');
// Only positive values // Only positive values
amount = Math.abs(amount); amount = Math.abs(amount);
@@ -342,8 +344,12 @@ MovementSchema.statics.getQueryMovsByCircuitId = async function (idapp, username
'circuitfrom.symbol': 1, 'circuitfrom.symbol': 1,
'circuitto.symbol': 1, 'circuitto.symbol': 1,
'userfrom.verified_by_aportador': 1, 'userfrom.verified_by_aportador': 1,
'userfrom.name': 1,
'userfrom.surname': 1,
'userfrom.username': 1, 'userfrom.username': 1,
'userfrom.profile.img': 1, 'userfrom.profile.img': 1,
'userto.name': 1,
'userto.surname': 1,
'userto.username': 1, 'userto.username': 1,
'userto.profile.img': 1, 'userto.profile.img': 1,
'userto.verified_by_aportador': 1, 'userto.verified_by_aportador': 1,
@@ -579,7 +585,11 @@ MovementSchema.statics.getQueryAllUsersMovsByCircuitId = async function (idapp,
'circuitfrom.symbol': 1, 'circuitfrom.symbol': 1,
'circuitto.symbol': 1, 'circuitto.symbol': 1,
'userfrom.username': 1, 'userfrom.username': 1,
'userfrom.name': 1,
'userfrom.surname': 1,
'userfrom.profile.img': 1, 'userfrom.profile.img': 1,
'userto.name': 1,
'userto.surname': 1,
'userto.username': 1, 'userto.username': 1,
'userto.profile.img': 1, 'userto.profile.img': 1,
'groupfrom.groupname': 1, 'groupfrom.groupname': 1,
@@ -1013,7 +1023,11 @@ MovementSchema.statics.getLastN_Transactions = async function (
'circuitto.name': 1, 'circuitto.name': 1,
'userfrom.verified_by_aportador': 1, 'userfrom.verified_by_aportador': 1,
'userfrom.username': 1, 'userfrom.username': 1,
'userfrom.name': 1,
'userfrom.surname': 1,
'userfrom.profile.img': 1, 'userfrom.profile.img': 1,
'userto.name': 1,
'userto.surname': 1,
'userto.username': 1, 'userto.username': 1,
'userto.profile.img': 1, 'userto.profile.img': 1,
'userto.verified_by_aportador': 1, 'userto.verified_by_aportador': 1,

View File

@@ -33,13 +33,24 @@ const MyBachecaSchema = new Schema({
}, },
userId: { type: Schema.Types.ObjectId, ref: 'User' }, userId: { type: Schema.Types.ObjectId, ref: 'User' },
groupname: { type: String }, groupname: { type: String },
idSector: { idSectorBacheca: {
type: Number, type: Number,
}, },
idSkill: { idBacheca: {
type: Number, type: Number,
default: 0, default: 0,
}, },
// ------------------
idSector: { // VECCHIO
type: Number,
},
idSkill: { // VECCHIO
type: Number,
default: 0,
},
// ------------------
idStatusSkill: [ idStatusSkill: [
{ {
type: Number, type: Number,
@@ -123,6 +134,7 @@ const MyBachecaSchema = new Schema({
}, },
date_created: { date_created: {
type: Date, type: Date,
default: Date.now,
}, },
date_updated: { date_updated: {
type: Date, type: Date,
@@ -233,8 +245,8 @@ MyBachecaSchema.statics.getMyRecById = function (idapp, id) {
}, },
{ {
$lookup: { $lookup: {
from: 'skills', from: 'bachecas',
localField: 'idSkill', localField: 'idBacheca',
foreignField: '_id', foreignField: '_id',
as: 'recSkill', as: 'recSkill',
}, },
@@ -256,10 +268,10 @@ MyBachecaSchema.statics.getMyRecById = function (idapp, id) {
}, },
{ {
$lookup: { $lookup: {
from: 'sectors', from: 'sectorbachecas',
localField: 'idSector', localField: 'idSectorBacheca',
foreignField: '_id', foreignField: '_id',
as: 'sector', as: 'sectorBacheca',
}, },
}, },
{ {
@@ -267,7 +279,7 @@ MyBachecaSchema.statics.getMyRecById = function (idapp, id) {
newRoot: { newRoot: {
$mergeObjects: [ $mergeObjects: [
{ {
$arrayElemAt: ['$sector', 0], $arrayElemAt: ['$sectorBacheca', 0],
}, },
'$$ROOT', '$$ROOT',
], ],

View File

@@ -150,6 +150,9 @@ const MySingleElemSchema = {
parambool4: { parambool4: {
type: Boolean, type: Boolean,
}, },
parambool5: {
type: Boolean,
},
number: { number: {
type: Number, type: Number,
}, },
@@ -236,6 +239,12 @@ const MySingleElemSchema = {
class4: { class4: {
type: String, type: String,
}, },
stiletit_str: {
type: String,
},
stiletit_icon: {
type: String,
},
styleadd: { styleadd: {
type: String, type: String,
}, },

View File

@@ -16,7 +16,7 @@ const { ObjectId } = require('mongodb');
const tableModel = shared_consts.TABLES_MYGOODS; const tableModel = shared_consts.TABLES_MYGOODS;
// Resolving error Unknown modifier: $pushAll // Resolving error Unknown modifier: $pushAll
mongoose.plugin(schema => { mongoose.plugin((schema) => {
schema.options.usePushEach = true; schema.options.usePushEach = true;
}); });
@@ -41,16 +41,19 @@ const MyGoodSchema = new Schema({
idShipping: [ idShipping: [
{ {
type: Number, type: Number,
}], },
],
idContribType: [ idContribType: [
{ {
type: String, type: String,
}], },
],
idCity: [ idCity: [
{ {
type: Number, type: Number,
}], },
],
pub_to_share: { pub_to_share: {
type: Number, // PUB_TO_SHARE_ALL, PUB_TO_SHARE_ONLY_TABLE_FOLLOW type: Number, // PUB_TO_SHARE_ALL, PUB_TO_SHARE_ONLY_TABLE_FOLLOW
}, },
@@ -61,9 +64,11 @@ const MyGoodSchema = new Schema({
adType: { adType: {
type: Number, type: Number,
}, },
otherfilters: [{ otherfilters: [
{
type: Number, type: Number,
}], },
],
photos: [ photos: [
{ {
imagefile: { imagefile: {
@@ -75,7 +80,8 @@ const MyGoodSchema = new Schema({
description: { description: {
type: String, type: String,
}, },
}], },
],
note: { note: {
type: String, type: String,
default: '', default: '',
@@ -89,19 +95,19 @@ const MyGoodSchema = new Schema({
}, },
date_created: { date_created: {
type: Date, type: Date,
default: Date.now,
}, },
date_updated: { date_updated: {
type: Date, type: Date,
}, },
}, },
...Reaction.getFieldsForReactions(), ...Reaction.getFieldsForReactions(),
...tools.getFieldsForAnnunci() ...tools.getFieldsForAnnunci(),
}); });
MyGoodSchema.pre('save', async function (next) { MyGoodSchema.pre('save', async function (next) {
if (this.isNew) { if (this.isNew) {
if (!this.date_created) if (!this.date_created) this.date_created = new Date();
this.date_created = new Date();
} }
next(); next();
@@ -110,15 +116,11 @@ MyGoodSchema.pre('save', async function (next) {
MyGoodSchema.statics.findAllIdApp = async function (idapp) { MyGoodSchema.statics.findAllIdApp = async function (idapp) {
const MyGood = this; const MyGood = this;
const query = [ const query = [{ $match: { idapp } }, { $sort: { descr: 1 } }];
{ $match: { idapp } },
{ $sort: { descr: 1 } },
];
return await MyGood.aggregate(query).then((arrrec) => { return await MyGood.aggregate(query).then((arrrec) => {
return arrrec; return arrrec;
}); });
}; };
MyGoodSchema.statics.getFieldsForSearch = function () { MyGoodSchema.statics.getFieldsForSearch = function () {
@@ -134,7 +136,6 @@ MyGoodSchema.statics.getFieldsLastForSearch = function () {
]; ];
}; };
MyGoodSchema.statics.executeQueryTable = function (idapp, params, user) { MyGoodSchema.statics.executeQueryTable = function (idapp, params, user) {
params.fieldsearch = this.getFieldsForSearch(); params.fieldsearch = this.getFieldsForSearch();
params.fieldsearch_last = this.getFieldsLastForSearch(); params.fieldsearch_last = this.getFieldsLastForSearch();
@@ -159,44 +160,40 @@ MyGoodSchema.statics.getMyRecById = function (idapp, idGood) {
const MyGood = this; const MyGood = this;
let myparsid = { let myparsid = {
'_id': idGood, _id: idGood,
idapp, idapp,
}; };
let query = [ let query = [
{ {
'$match': $match: myparsid,
myparsid,
}, },
{ {
'$sort': { $sort: {
'desc': 1, desc: 1,
}, },
}, },
{ {
'$addFields': { $addFields: {
'myId1': { myId1: {
'$toObjectId': '$userId', $toObjectId: '$userId',
}, },
}, },
}, },
{ {
'$lookup': { $lookup: {
'from': 'users', from: 'users',
'localField': 'myId1', localField: 'myId1',
'foreignField': '_id', foreignField: '_id',
'as': 'user', as: 'user',
}, },
}, },
{ {
'$replaceRoot': { $replaceRoot: {
'newRoot': { newRoot: {
'$mergeObjects': [ $mergeObjects: [
{ {
'$arrayElemAt': [ $arrayElemAt: ['$user', 0],
'$user',
0,
],
}, },
'$$ROOT', '$$ROOT',
], ],
@@ -207,22 +204,19 @@ MyGoodSchema.statics.getMyRecById = function (idapp, idGood) {
$project: shared_consts.getProjectForAll({}, tableModel), $project: shared_consts.getProjectForAll({}, tableModel),
}, },
{ {
'$lookup': { $lookup: {
'from': 'goods', from: 'goods',
'localField': 'idGood', localField: 'idGood',
'foreignField': '_id', foreignField: '_id',
'as': 'recGood', as: 'recGood',
}, },
}, },
{ {
'$replaceRoot': { $replaceRoot: {
'newRoot': { newRoot: {
'$mergeObjects': [ $mergeObjects: [
{ {
'$arrayElemAt': [ $arrayElemAt: ['$recGood', 0],
'$recGood',
0,
],
}, },
'$$ROOT', '$$ROOT',
], ],
@@ -233,23 +227,19 @@ MyGoodSchema.statics.getMyRecById = function (idapp, idGood) {
$project: shared_consts.getProjectForAll({}, tableModel), $project: shared_consts.getProjectForAll({}, tableModel),
}, },
{ {
'$lookup': { $lookup: {
'from': 'sectorgoods', from: 'sectorgoods',
'localField': 'idSectorGood', localField: 'idSectorGood',
// 'localField': 'recGood.idSectorGood', foreignField: '_id',
'foreignField': '_id', as: 'sectorGood',
'as': 'sectorGood',
}, },
}, },
{ {
'$replaceRoot': { $replaceRoot: {
'newRoot': { newRoot: {
'$mergeObjects': [ $mergeObjects: [
{ {
'$arrayElemAt': [ $arrayElemAt: ['$sectorgood', 0],
'$sectorgood',
0,
],
}, },
'$$ROOT', '$$ROOT',
], ],
@@ -258,10 +248,10 @@ MyGoodSchema.statics.getMyRecById = function (idapp, idGood) {
}, },
{ {
$lookup: { $lookup: {
'from': 'mygroups', from: 'mygroups',
'localField': 'groupname', localField: 'groupname',
'foreignField': 'groupname', foreignField: 'groupname',
'as': 'mygrp', as: 'mygrp',
}, },
}, },
{ {
@@ -274,22 +264,19 @@ MyGoodSchema.statics.getMyRecById = function (idapp, idGood) {
$project: shared_consts.getProjectForAll({}, tableModel), $project: shared_consts.getProjectForAll({}, tableModel),
}, },
{ {
'$lookup': { $lookup: {
'from': 'subgoods', from: 'subgoods',
'localField': 'idShipping', localField: 'idShipping',
'foreignField': '_id', foreignField: '_id',
'as': 'MyGood', as: 'MyGood',
}, },
}, },
{ {
'$replaceRoot': { $replaceRoot: {
'newRoot': { newRoot: {
'$mergeObjects': [ $mergeObjects: [
{ {
'$arrayElemAt': [ $arrayElemAt: ['$MyGood', 0],
'$MyGood',
0,
],
}, },
'$$ROOT', '$$ROOT',
], ],
@@ -300,22 +287,19 @@ MyGoodSchema.statics.getMyRecById = function (idapp, idGood) {
$project: shared_consts.getProjectForAll({}, tableModel), $project: shared_consts.getProjectForAll({}, tableModel),
}, },
{ {
'$lookup': { $lookup: {
'from': 'cities', from: 'cities',
'localField': 'idCity', localField: 'idCity',
'foreignField': '_id', foreignField: '_id',
'as': 'mycities', as: 'mycities',
}, },
}, },
{ {
'$replaceRoot': { $replaceRoot: {
'newRoot': { newRoot: {
'$mergeObjects': [ $mergeObjects: [
{ {
'$arrayElemAt': [ $arrayElemAt: ['$mycities', 0],
'$mycities',
0,
],
}, },
'$$ROOT', '$$ROOT',
], ],
@@ -344,32 +328,30 @@ MyGoodSchema.statics.getCompleteRecord = function (idapp, id) {
const MyGood = this; const MyGood = this;
return MyGood.getMyRecById(idapp, id); return MyGood.getMyRecById(idapp, id);
}; };
MyGoodSchema.statics.getProject = function () { MyGoodSchema.statics.getProject = function () {
let proj = { let proj = {
'recGood': 1, recGood: 1,
'sectorGood': 1, sectorGood: 1,
'idSectorGood': 1, idSectorGood: 1,
'idGood': 1, idGood: 1,
'idShipping': 1, idShipping: 1,
'idStatusGood': 1, idStatusGood: 1,
//**ADDFIELD_MYGOOD //**ADDFIELD_MYGOOD
}; };
const proj_add = shared_consts.getProjectForAll() const proj_add = shared_consts.getProjectForAll();
return Object.assign({}, proj, proj_add); return Object.assign({}, proj, proj_add);
};
}
const MyGood = mongoose.model('MyGood', MyGoodSchema); const MyGood = mongoose.model('MyGood', MyGoodSchema);
MyGood.createIndexes() MyGood.createIndexes()
.then(() => { }) .then(() => {})
.catch((err) => { throw err; }); .catch((err) => {
throw err;
});
module.exports = { MyGood }; module.exports = { MyGood };

View File

@@ -96,6 +96,7 @@ const MyGroupSchema = new Schema({
}, },
date_created: { date_created: {
type: Date, type: Date,
default: Date.now,
}, },
date_updated: { date_updated: {
type: Date, type: Date,

View File

@@ -96,6 +96,7 @@ const MyHospSchema = new Schema({
}, },
date_created: { date_created: {
type: Date, type: Date,
default: Date.now,
}, },
date_updated: { date_updated: {
type: Date, type: Date,
@@ -359,7 +360,7 @@ MyHospSchema.statics.SettaAdTypeOffro_In_Hosps = async function () {
} }
}; };
MyHospSchema.statics.getProject = function () { /*MyHospSchema.statics.getProject = function () {
let proj = { let proj = {
visibile: 1, visibile: 1,
typeHosp: 1, typeHosp: 1,
@@ -378,7 +379,7 @@ MyHospSchema.statics.getProject = function () {
return Object.assign({}, proj, proj_add); return Object.assign({}, proj, proj_add);
} }
*/
const MyHosp = mongoose.model('MyHosp', MyHospSchema); const MyHosp = mongoose.model('MyHosp', MyHospSchema);

View File

@@ -96,6 +96,7 @@ const MySkillSchema = new Schema(
}, },
date_created: { date_created: {
type: Date, type: Date,
default: Date.now,
}, },
date_updated: { date_updated: {
type: Date, type: Date,
@@ -347,7 +348,7 @@ MySkillSchema.statics.getMyRecById = function (idapp, idSkill) {
}); });
}; };
MySkillSchema.statics.getProject = function (proj_add2) { /*MySkillSchema.statics.getProject = function (proj_add2) {
let proj = { let proj = {
recSkill: 1, recSkill: 1,
sector: 1, sector: 1,
@@ -364,6 +365,7 @@ MySkillSchema.statics.getProject = function (proj_add2) {
return Object.assign({}, proj, proj_add); return Object.assign({}, proj, proj_add);
} }
*/
MySkillSchema.statics.getCompleteRecord = function (idapp, id) { MySkillSchema.statics.getCompleteRecord = function (idapp, id) {
const MySkill = this; const MySkill = this;

88
src/models/sectorbacheca.js Executable file
View File

@@ -0,0 +1,88 @@
const mongoose = require('mongoose').set('debug', false)
const Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.level = "F";
const tools = require('../tools/general');
const { ObjectId } = require('mongodb');
// Resolving error Unknown modifier: $pushAll
mongoose.plugin(schema => {
schema.options.usePushEach = true
});
const SectorBachecaSchema = new Schema({
_id: {
type: Number,
},
descr: {
type: String,
},
idSectorBacheca: {
type: Number
},
icon: {
type: String,
},
img: {
type: String,
},
color: {
type: String,
},
theme: {
type: String,
},
});
SectorBachecaSchema.pre('save', async function (next) {
if (this.isNew) {
const myrec = await SectorBacheca.findOne().limit(1).sort({_id:-1});
if (!!myrec) {
if (myrec._doc._id === 0)
this._id = 1;
else
this._id = myrec._doc._id + 1;
} else {
this._id = 1;
}
}
next();
});
SectorBachecaSchema.statics.findAllIdApp = async function (idapp) {
const SectorBacheca = this;
const query = [
{ $sort: { descr: 1 } }
];
return await SectorBacheca
.aggregate(query)
.then((arrrec) => {
return arrrec
})
};
SectorBachecaSchema.statics.getFieldsForSearch = function () {
return [{ field: 'descr', type: tools.FieldType.string }]
};
SectorBachecaSchema.statics.executeQueryTable = function (idapp, params) {
params.fieldsearch = this.getFieldsForSearch();
return tools.executeQueryTable(this, 0, params);
};
const SectorBacheca = mongoose.model('SectorBacheca', SectorBachecaSchema);
SectorBacheca.createIndexes()
.then(() => { })
.catch((err) => { throw err; });
module.exports = { SectorBacheca };

View File

@@ -505,6 +505,7 @@ sendNotifSchema.statics.getDescrAndLinkByRecNotif = async function (recnotif, us
recnotif.paramsObj.circuitnameDest, recnotif.paramsObj.circuitnameDest,
username_action username_action
); );
tag = 'setfido_admin_group';
} else { } else {
newdescr = i18n.__( newdescr = i18n.__(
'FIDO_IMPOSTATO_ADMINS_CIRCUIT', 'FIDO_IMPOSTATO_ADMINS_CIRCUIT',
@@ -513,18 +514,20 @@ sendNotifSchema.statics.getDescrAndLinkByRecNotif = async function (recnotif, us
recnotif.paramsObj.circuitnameDest, recnotif.paramsObj.circuitnameDest,
username_action username_action
); );
tag = 'setfido_admin';
} }
recnotif.openUrl = '/my/' + sender; recnotif.openUrl = '/my/' + sender;
} else { } else {
newdescr = i18n.__( newdescr = i18n.__(
'FIDO_IMPOSTATO', 'FIDO_IMPOSTATO',
recnotif.paramsObj.circuitnameDest,
recnotif.paramsObj.extrarec.username_admin_abilitante,
-recnotif.paramsObj.extrarec.fidoConcesso, -recnotif.paramsObj.extrarec.fidoConcesso,
username_action,
recnotif.paramsObj.circuitnameDest
); );
}
tag = 'setfido'; tag = 'setfido';
}
} else if (recnotif.typeid === shared_consts.TypeNotifs.ID_CIRCUIT_ACCEPTED) { } else if (recnotif.typeid === shared_consts.TypeNotifs.ID_CIRCUIT_ACCEPTED) {
if (recnotif.paramsObj.isAdmin) { if (recnotif.paramsObj.isAdmin) {
if (recnotif.extrarec.groupname) { if (recnotif.extrarec.groupname) {
@@ -971,7 +974,7 @@ sendNotifSchema.statics.findAllNotifCoinsAllIdAndIdApp = function (idapp) {
}); });
}; };
sendNotifSchema.statics.saveAndSendNotif = async function (myrecnotif, req, res, user) { sendNotifSchema.statics.saveAndSendNotif = async function (myrecnotif, req, res, user, paramsObj) {
const SendNotif = this; const SendNotif = this;
let idapp = req.body.idapp; let idapp = req.body.idapp;
@@ -1003,7 +1006,8 @@ sendNotifSchema.statics.saveAndSendNotif = async function (myrecnotif, req, res,
res, res,
idapp, idapp,
user ? user : req.user, user ? user : req.user,
myrecread myrecread,
paramsObj
); );
else return false; else return false;
} }
@@ -1018,7 +1022,8 @@ sendNotifSchema.statics.saveAndSendNotif = async function (myrecnotif, req, res,
res, res,
idapp, idapp,
user ? user : req.user, user ? user : req.user,
myrecout myrecout,
paramsObj
); );
} }
@@ -1312,7 +1317,7 @@ sendNotifSchema.statics.createNewNotifToSingleUser = async function (req, res, p
myrecnotif = this.getExtraParam(myrecnotif, paramsObj); myrecnotif = this.getExtraParam(myrecnotif, paramsObj);
return await SendNotif.sendToSingleUserDest(myrecnotif, req, res, onlysave); return await SendNotif.sendToSingleUserDest(myrecnotif, req, res, onlysave, paramsObj);
} catch (e) { } catch (e) {
console.error('createNewNotification', e); console.error('createNewNotification', e);
return null; return null;
@@ -1378,6 +1383,8 @@ sendNotifSchema.statics.getNotificationRecipients = async function (myrecnotifpa
if (myrecnotifpass.tablerec === shared_consts.TABLES_MYGOODS) { if (myrecnotifpass.tablerec === shared_consts.TABLES_MYGOODS) {
idSector = myrectableorig.idSectorGood; idSector = myrectableorig.idSectorGood;
} else if (myrecnotifpass.tablerec === shared_consts.TABLES_MYBACHECAS) {
idSector = myrectableorig.idSectorBacheca;
} else { } else {
idSector = myrectableorig.idSector; idSector = myrectableorig.idSector;
} }
@@ -1547,7 +1554,7 @@ sendNotifSchema.statics.sendToTheDestinations = async function (myrecnotifpass,
} }
}; };
sendNotifSchema.statics.sendToSingleUserDest = async function (myrecnotif, req, res, onlysave) { sendNotifSchema.statics.sendToSingleUserDest = async function (myrecnotif, req, res, onlysave, paramsObj) {
const SendNotif = this; const SendNotif = this;
try { try {
@@ -1559,9 +1566,9 @@ sendNotifSchema.statics.sendToSingleUserDest = async function (myrecnotif, req,
: myrecnotif.dest; : myrecnotif.dest;
if (onlysave) { if (onlysave) {
return await SendNotif.saveNotif(myrecnotif, req); return await SendNotif.saveNotif(myrecnotif, req, paramsObj);
} else { } else {
return await SendNotif.saveAndSendNotif(myrecnotif, req, res, null); return await SendNotif.saveAndSendNotif(myrecnotif, req, res, null, paramsObj);
} }
} catch (e) { } catch (e) {
console.error('sendToSingleUserDest', e); console.error('sendToSingleUserDest', e);

View File

@@ -174,6 +174,7 @@ const SiteSchema = new Schema({
bookingEvents: { type: Boolean, default: false }, bookingEvents: { type: Boolean, default: false },
enableEcommerce: { type: Boolean, default: false }, enableEcommerce: { type: Boolean, default: false },
enableAI: { type: Boolean, default: false }, enableAI: { type: Boolean, default: false },
enablePoster: { type: Boolean, default: false },
enableGroups: { type: Boolean, default: false }, enableGroups: { type: Boolean, default: false },
enableCircuits: { type: Boolean, default: false }, enableCircuits: { type: Boolean, default: false },
enableGoods: { type: Boolean, default: false }, enableGoods: { type: Boolean, default: false },

View File

@@ -40,7 +40,8 @@ mongoose.plugin((schema) => {
mongoose.set('debug', false); mongoose.set('debug', false);
const UserSchema = new mongoose.Schema({ const UserSchema = new mongoose.Schema(
{
userId: { userId: {
type: String, type: String,
}, },
@@ -55,6 +56,9 @@ const UserSchema = new mongoose.Schema({
message: '{VALUE} is not a valid email' message: '{VALUE} is not a valid email'
}*/ }*/
}, },
link_verif_email: {
type: String,
},
hash: { hash: {
type: String, type: String,
}, },
@@ -231,6 +235,7 @@ const UserSchema = new mongoose.Schema({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
deletedAt: Date,
sospeso: { sospeso: {
type: Boolean, type: Boolean,
}, },
@@ -465,6 +470,7 @@ const UserSchema = new mongoose.Schema({
_id: false, _id: false,
circuitname: { type: String }, circuitname: { type: String },
date: { type: Date }, date: { type: Date },
token: { type: String },
}, },
], ],
last_circuitpath: { last_circuitpath: {
@@ -538,6 +544,9 @@ const UserSchema = new mongoose.Schema({
noCircuit: { noCircuit: {
type: Boolean, type: Boolean,
}, },
noComune: {
type: Boolean,
},
noCircIta: { noCircIta: {
type: Boolean, type: Boolean,
}, },
@@ -576,7 +585,12 @@ const UserSchema = new mongoose.Schema({
version: { type: Number }, version: { type: Number },
insert_circuito_ita: { type: Boolean }, insert_circuito_ita: { type: Boolean },
}, },
}); updatedAt: { type: Date, default: Date.now },
},
{
timestamps: true, // Mongoose aggiorna automaticamente updatedAt
}
);
UserSchema.methods.toJSON = function () { UserSchema.methods.toJSON = function () {
const user = this; const user = this;
@@ -938,7 +952,6 @@ UserSchema.statics.findByToken = async function (
project project
); );
// Verifica scadenza token per idapp specifici // Verifica scadenza token per idapp specifici
if (user) { if (user) {
const checkExpiry = tools.getEnableTokenExpiredByIdApp(user.idapp); const checkExpiry = tools.getEnableTokenExpiredByIdApp(user.idapp);
@@ -2487,24 +2500,37 @@ UserSchema.statics.addCircuitToUser = async function (idapp, usernameOrig, circu
} else { } else {
// prima di aggiungerlo controlla se esiste già ! // prima di aggiungerlo controlla se esiste già !
let update = { const token = tools.getTokenRandom();
$addToSet: {
// Utilizziamo $addToSet invece di $push per garantire che l'elemento venga aggiunto solo se non esiste già let update = null;
const updateparams = {
$set: {
'profile.mycircuits.$[elem].date': new Date(),
'profile.mycircuits.$[elem].token': token,
},
};
let ris = await User.updateOne(
{ idapp, username: usernameOrig, 'profile.mycircuits.circuitname': circuitname },
updateparams,
{ arrayFilters: [{ 'elem.circuitname': circuitname }] }
);
// Se non ha modificato nulla (nModified === 0), significa che non esiste, quindi lo aggiungiamo
if (ris.modifiedCount === 0) {
const insertUpdate = {
$push: {
'profile.mycircuits': { 'profile.mycircuits': {
$each: [
{
circuitname, circuitname,
date: new Date(), date: new Date(),
}, token,
],
}, },
}, },
}; };
ris = await User.updateOne( ris = await User.updateOne({ idapp, username: usernameOrig }, insertUpdate);
{ idapp, username: usernameOrig, 'profile.mycircuits': { $not: { $elemMatch: { circuitname } } } }, }
update
);
if (confido) { if (confido) {
// Elimina la richiesta: // Elimina la richiesta:
@@ -2523,6 +2549,24 @@ UserSchema.statics.addCircuitToUser = async function (idapp, usernameOrig, circu
}; };
// Rimuovo il Gruppo per Tutti gli Utenti // Rimuovo il Gruppo per Tutti gli Utenti
UserSchema.statics.getCircuitByTokenAndUsername = async function (idapp, username, token) {
try {
const user = await User.findOne({
idapp,
username,
'profile.mycircuits.token': token,
});
const foundCircuit = user?.profile?.mycircuits?.find((c) => c.token === token);
if (foundCircuit) {
return { circuitname: foundCircuit.circuitname, user };
}
} catch (e) {
return { circuitname: '', user: null };
}
return { circuitname: '', user: null };
};
UserSchema.statics.removeAllUsersFromMyGroups = async function (idapp, groupnameDest) { UserSchema.statics.removeAllUsersFromMyGroups = async function (idapp, groupnameDest) {
return await User.updateMany({ idapp }, { $pull: { 'profile.mygroups': { groupname: { $in: [groupnameDest] } } } }); return await User.updateMany({ idapp }, { $pull: { 'profile.mygroups': { groupname: { $in: [groupnameDest] } } } });
}; };
@@ -2573,6 +2617,12 @@ UserSchema.statics.removeBookmark = async function (idapp, username, id, tab) {
UserSchema.statics.addBookmark = async function (idapp, username, id, tab) { UserSchema.statics.addBookmark = async function (idapp, username, id, tab) {
return await User.updateOne({ idapp, username }, { $push: { 'profile.bookmark': { id, tab } } }); return await User.updateOne({ idapp, username }, { $push: { 'profile.bookmark': { id, tab } } });
}; };
UserSchema.statics.setLinkToVerifiedEmail = async function (idapp, username, link_verif_email) {
return await User.updateOne({ idapp, username }, { $set: { link_verif_email } });
};
UserSchema.statics.findByLinkVerifEmail = async function (idapp, link_verif_email) {
return await User.findOne({ idapp, link_verif_email });
};
// Rimuovo il Partecipa // Rimuovo il Partecipa
UserSchema.statics.removeAttend = async function (idapp, username, id, tab) { UserSchema.statics.removeAttend = async function (idapp, username, id, tab) {
return await User.updateOne({ idapp, username }, { $pull: { 'profile.attend': { id: { $in: [id] }, tab } } }); return await User.updateOne({ idapp, username }, { $pull: { 'profile.attend': { id: { $in: [id] }, tab } } });
@@ -2628,7 +2678,7 @@ UserSchema.statics.setFriendsCmd = async function (req, idapp, usernameOrig, use
); );
msgDest = i18n.__({ phrase: '✅ Sei stato Ammesso correttamente da %s!', locale: lang }, usernameOrig); msgDest = i18n.__({ phrase: '✅ Sei stato Ammesso correttamente da %s!', locale: lang }, usernameOrig);
msgAdmin = i18n.__( msgAdmin = i18n.__(
{ phrase: '✅ %s è stato Ammesso correttamente (da %s)!', locale: userDest.lang }, { phrase: '✅ %s è stato Ammesso/a correttamente (da %s) tramite Email!', locale: userDest.lang },
userDest.username, userDest.username,
usernameOrig usernameOrig
); );
@@ -3424,11 +3474,12 @@ UserSchema.statics.setCircuitCmd = async function (
} }
} }
} else if (cmd === shared_consts.CIRCUITCMD.SETFIDO) { } else if (cmd === shared_consts.CIRCUITCMD.SETFIDO) {
ris = await Circuit.setFido(idapp, usernameOrig, circuitname, groupname); ris = await Circuit.setFido(idapp, usernameOrig, circuitname, groupname, username_action);
if (ris && ris.fidoConcesso && ris.changed) { if (ris && ris.fidoConcesso && ris.changed) {
if (extrarec) { if (extrarec) {
extrarec.fidoConcesso = ris.fidoConcesso; extrarec.fidoConcesso = ris.fidoConcesso;
extrarec.qta_maxConcessa = ris.qta_maxConcessa; extrarec.qta_maxConcessa = ris.qta_maxConcessa;
extrarec.username_admin_abilitante = ris.username_admin_abilitante;
} }
// Elimina la richiesta: // Elimina la richiesta:
@@ -3957,6 +4008,15 @@ UserSchema.statics.getInfoAskFriendByUsername = async function (idapp, username)
.then((rec) => (!!rec ? rec : null)); .then((rec) => (!!rec ? rec : null));
}; };
UserSchema.statics.isFidoConcesso = async function (idapp, username, nomeCircuito) {
try {
const myfido = await Circuit.getFido(idapp, username, nomeCircuito, '');
return !!myfido && myfido > 0 ? true : false;
} catch (e) {
return false;
}
};
UserSchema.statics.getAskedFriendsByUsername = async function (idapp, username) { UserSchema.statics.getAskedFriendsByUsername = async function (idapp, username) {
const whatToShow_Unknown = getWhatToShow_Unknown(idapp, username); const whatToShow_Unknown = getWhatToShow_Unknown(idapp, username);
@@ -4443,6 +4503,20 @@ UserSchema.statics.getNameSurnameEUsernameByUsername = async function (idapp, us
console.error('getNameSurnameByUsername', e); console.error('getNameSurnameByUsername', e);
}); });
}; };
UserSchema.statics.getProfileByUsername = async function (idapp, username, reale = false) {
const User = this;
return await User.findOne(
{
idapp,
username,
$or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
},
{ profile: 1 }
).catch((e) => {
console.error('getNameSurnameByUsername', e);
});
};
UserSchema.statics.getIdByUsername = async function (idapp, username) { UserSchema.statics.getIdByUsername = async function (idapp, username) {
const User = this; const User = this;
@@ -6849,11 +6923,7 @@ UserSchema.statics.updateProvinceUserByComune = async function (idapp, userId, i
'profile.resid_province': recCity ? recCity.prov : '', 'profile.resid_province': recCity ? recCity.prov : '',
}; };
await User.findOneAndUpdate( await User.findOneAndUpdate({ _id: userId }, { $set: updateData }, { new: true });
{ _id: userId },
{ $set: updateData },
{ new: true }
);
// Ritorna i dati aggiornati nel formato che ti serve // Ritorna i dati aggiornati nel formato che ti serve
return { return {
@@ -6869,8 +6939,8 @@ UserSchema.statics.updateProvinceUserByComune = async function (idapp, userId, i
_id: userId, _id: userId,
profile: { profile: {
resid_str_comune: '', resid_str_comune: '',
resid_province: '' resid_province: '',
} },
}; };
} }
}; };
@@ -6900,6 +6970,52 @@ UserSchema.statics.createNewSubRecord = async function (idapp, req) {
return rec; return rec;
}; };
// Nel tuo User schema, aggiungi questo metodo statico:
UserSchema.statics.isEnableToReceiveEmailByUsernameECmd = async function (idapp, username, cmd) {
let ricevo = true;
if (cmd === shared_consts.CIRCUITCMD.SETFIDO) {
// Controllo se l'utente ha scelto di ricevere l'email
}
return ricevo;
};
UserSchema.statics.getTokenByUsernameAndCircuitName = async function (idapp, username, circuitname) {
const User = this;
const user = await User.findOne(
{
idapp,
username,
'profile.mycircuits.circuitname': circuitname,
},
{
'profile.mycircuits.$': 1,
}
);
return user?.profile?.mycircuits?.[0]?.token || null;
};
UserSchema.statics.softDelete = async function (id) {
return this.findByIdAndUpdate(
id,
{
deleted: true,
deletedAt: new Date(),
},
{ new: true }
);
};
UserSchema.statics.getUsersList = function (idapp) {
return this.find({
idapp: idapp,
$or: [{ deleted: { $exists: false } }, { deleted: false }],
}).lean();
};
const User = mongoose.model('User', UserSchema); const User = mongoose.model('User', UserSchema);
class Hero { class Hero {
@@ -6956,7 +7072,7 @@ const FuncUsers = {
}, },
}; };
UserSchema.index({ 'tokens.token': 1, 'tokens.access': 1 }); UserSchema.index({ 'tokens.token': 1, 'tokens.access': 1, idapp: 1, deleted: 1, updatedAt: 1 });
module.exports = { module.exports = {
User, User,

163
src/models/version.js Executable file
View File

@@ -0,0 +1,163 @@
const mongoose = require('mongoose').set('debug', false);
const Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.level = 'F';
const tools = require('../tools/general');
const { ObjectId } = require('mongodb');
const LASTVERSION = 'lastversion';
// Resolving error Unknown modifier: $pushAll
mongoose.plugin((schema) => {
schema.options.usePushEach = true;
});
const VersionSchema = new Schema({
descr: {
type: String,
},
idapp: {
type: String,
},
executed: {
type: Boolean,
},
table: {
type: String,
},
version: {
type: Number,
},
date_created: {
type: Date,
},
});
VersionSchema.statics.isJobExecuted = async function (idapp, descr) {
const Version = this;
try {
const myrec = await Version.findOne({ idapp, descr });
return myrec.executed;
} catch (e) {
return false;
}
};
VersionSchema.statics.setJobExecuted = async function (idapp, descr) {
const Version = this;
try {
const myrec = await Version.findOneAndUpdate(
{ idapp, descr },
{
$setOnInsert: { idapp, descr },
$set: { executed: true },
},
{ new: true, upsert: true }
);
if (!myrec) {
throw new Error(`Error setting job as executed for idapp ${idapp} and descr ${descr}`);
}
return myrec.executed;
} catch (e) {
console.error(`Error in setJobExecuted: ${e.message}`);
return false;
}
};
VersionSchema.statics.findAllIdApp = async function (idapp) {
const Version = this;
const query = [{ $sort: { descr: 1 } }];
const res = await Version.aggregate(query).then((arrrec) => {
return arrrec;
});
return res;
};
VersionSchema.statics.getLastVersionRun = async function (idapp) {
const Version = this;
try {
const myrec = await Version.findOne({ idapp, table: '', descr: LASTVERSION });
return myrec ? myrec.version : 0;
} catch (e) {
return 0;
}
};
VersionSchema.statics.setLastVersionRun = async function (idapp, version) {
const Version = this;
try {
const myrec = await Version.findOneAndUpdate(
{ idapp, table: '', descr: LASTVERSION },
{
$setOnInsert: { idapp, table: '', descr: LASTVERSION },
$set: { version },
},
{ new: true, upsert: true }
);
return !!myrec;
} catch (e) {
console.error(`Error in setLastVersionTable: ${e.message}`);
return false;
}
};
VersionSchema.statics.isTableVersionUpdated = async function (idapp, table) {
const Version = this;
const lastversion = await Version.getLastVersionRun(idapp);
try {
const myrec = await Version.findOne({ idapp, table });
return myrec.version === lastversion;
} catch (e) {
return false;
}
};
VersionSchema.statics.updateTableVersion = async function (idapp, table, version) {
const Version = this;
try {
const myrec = await Version.findOneAndUpdate(
{ idapp, table },
{
$setOnInsert: { idapp, table },
$set: { version },
},
{ new: true, upsert: true }
);
return !!myrec;
} catch (e) {
console.error(`Error in updateTableVersion: ${e.message}`);
return false;
}
};
const Version = mongoose.model('Version', VersionSchema);
Version.createIndexes()
.then(() => {})
.catch((err) => {
throw err;
});
module.exports = { Version };

View File

@@ -199,6 +199,10 @@ class CronMod {
ris = await User.setVerifiedByAportadorToALL(); ris = await User.setVerifiedByAportadorToALL();
} else if (mydata.dbop === 'RewriteContribType') { } else if (mydata.dbop === 'RewriteContribType') {
ris = populate.rewriteTable('contribtypes'); ris = populate.rewriteTable('contribtypes');
} else if (mydata.dbop === 'RewriteCategESubCateg') {
const migration = require('../populate/migration-categories');
ris = await migration.aggiornaCategorieESottoCategorie();
} else if (mydata.dbop === 'ReplaceUsername') { } else if (mydata.dbop === 'ReplaceUsername') {
if (User.isAdmin(req.user.perm)) { if (User.isAdmin(req.user.perm)) {
ris = globalTables.replaceUsername(req.body.idapp, mydata.search_username, mydata.replace_username); ris = globalTables.replaceUsername(req.body.idapp, mydata.search_username, mydata.replace_username);
@@ -266,6 +270,8 @@ class CronMod {
await Order.RemoveDeletedOrdersInOrderscart(); await Order.RemoveDeletedOrdersInOrderscart();
} else if (mydata.dbop === 'CheckTransazioniCircuiti') { } else if (mydata.dbop === 'CheckTransazioniCircuiti') {
await Circuit.CheckTransazioniCircuiti(false); await Circuit.CheckTransazioniCircuiti(false);
} else if (mydata.dbop === 'CalcNumTransCircuiti') {
await Circuit.CheckTransazioniCircuiti(false, { setnumtransaction: true });
} else if (mydata.dbop === 'CorreggiTransazioniCircuiti') { } else if (mydata.dbop === 'CorreggiTransazioniCircuiti') {
await Circuit.CheckTransazioniCircuiti(true); await Circuit.CheckTransazioniCircuiti(true);
} else if (mydata.dbop === 'RemovePendentTransactions') { } else if (mydata.dbop === 'RemovePendentTransactions') {

View File

@@ -1,575 +0,0 @@
/**
* Classe per gestire l'invio di notifiche via Email e Telegram
* per gli eventi della piattaforma RISO
*/
class InvioNotifiche {
/**
* @param {Object} config - Configurazione per l'invio notifiche
* @param {Object} config.emailService - Servizio per invio email (es. nodemailer)
* @param {Object} config.telegramBot - Istanza del bot Telegram
* @param {String} config.adminTelegramId - ID Telegram dell'amministratore
* @param {String} config.adminEmail - Email dell'amministratore
* @param {String} config.baseUrl - URL base dell'applicazione
* @param {String} config.nomeApp - Nome dell'applicazione
* @param {Object} config.emailTemplates - Path ai template email PUG
*/
constructor(config) {
this.emailService = config.emailService;
this.telegramBot = config.telegramBot;
this.adminTelegramId = config.adminTelegramId;
this.adminEmail = config.adminEmail;
this.baseUrl = config.baseUrl;
this.nomeApp = config.nomeApp || 'RISO';
this.emailTemplates = config.emailTemplates || {};
// Logger (puoi sostituirlo con Winston o altro)
this.logger = config.logger || console;
}
// ============================================
// METODI PRIVATI - Invio Base
// ============================================
/**
* Invia una email
* @private
*/
async _inviaEmail(destinatario, oggetto, htmlContent, templatePath = null, templateData = null) {
try {
let html = htmlContent;
// Se è specificato un template PUG, renderizzalo
if (templatePath && templateData) {
const pug = require('pug');
html = pug.renderFile(templatePath, templateData);
}
const emailOptions = {
from: `${this.nomeApp} <noreply@riso.app>`,
to: destinatario,
subject: oggetto,
html: html,
};
const result = await this.emailService.sendMail(emailOptions);
this.logger.info(`Email inviata a ${destinatario}: ${oggetto}`);
return { success: true, messageId: result.messageId };
} catch (error) {
this.logger.error(`Errore invio email a ${destinatario}:`, error);
return { success: false, error: error.message };
}
}
/**
* Invia un messaggio Telegram
* @private
*/
async _inviaTelegram(telegramId, messaggio, opzioni = {}) {
try {
if (!telegramId || telegramId === 0) {
this.logger.warn(`Telegram ID non valido: ${telegramId}`);
return { success: false, error: 'Telegram ID non valido' };
}
const defaultOptions = {
parse_mode: 'HTML',
disable_web_page_preview: false,
...opzioni,
};
const result = await this.telegramBot.sendMessage(telegramId, messaggio, defaultOptions);
this.logger.info(`Messaggio Telegram inviato a ${telegramId}`);
return { success: true, messageId: result.message_id };
} catch (error) {
this.logger.error(`Errore invio Telegram a ${telegramId}:`, error);
return { success: false, error: error.message };
}
}
/**
* Invia notifica sia via email che Telegram
* @private
*/
async _inviaNotificaDoppia(destinatario, opzioniEmail, opzioniTelegram) {
const risultati = {
email: null,
telegram: null,
};
// Invia email
if (destinatario.email) {
risultati.email = await this._inviaEmail(
destinatario.email,
opzioniEmail.oggetto,
opzioniEmail.html,
opzioniEmail.templatePath,
opzioniEmail.templateData
);
}
// Invia Telegram
if (destinatario.telegramId && destinatario.telegramId !== 0) {
risultati.telegram = await this._inviaTelegram(
destinatario.telegramId,
opzioniTelegram.messaggio,
opzioniTelegram.opzioni
);
}
return risultati;
}
/**
* Invia copia all'amministratore
* @private
*/
async _inviaCopiaCopiaAdmin(oggetto, messaggio) {
const risultati = {
email: null,
telegram: null,
};
// Email admin
if (this.adminEmail) {
risultati.email = await this._inviaEmail(this.adminEmail, `[ADMIN] ${oggetto}`, messaggio);
}
// Telegram admin
if (this.adminTelegramId) {
risultati.telegram = await this._inviaTelegram(this.adminTelegramId, `🔔 <b>NOTIFICA ADMIN</b>\n\n${messaggio}`);
}
return risultati;
}
// ============================================
// EVENTO 1: REGISTRAZIONE UTENTE
// ============================================
/**
* Gestisce le notifiche dopo la registrazione
* @param {Object} utente - Dati dell'utente registrato
* @param {String} tokenVerifica - Token per verifica email
*/
async notificaRegistrazione(utente, tokenVerifica) {
try {
this.logger.info(`Notifica registrazione per utente: ${utente.username}`);
// Se l'email è già verificata, salta la verifica e vai direttamente alla richiesta ammissione
if (utente.verified_email === true) {
this.logger.info(`Email già verificata per ${utente.username}, salto verifica email`);
// Invia direttamente la richiesta di ammissione all'invitante
await this.notificaRichiestaAmmissione(utente);
return {
success: true,
message: 'Email già verificata, richiesta ammissione inviata',
emailVerificaInviata: false,
};
}
// Email non verificata: invia email di verifica
const linkVerifica = `${this.baseUrl}/verifica-email/${tokenVerifica}`;
const templateData = {
name: utente.name,
username: utente.username,
emailto: utente.email,
nomeapp: this.nomeApp,
baseurl: this.baseUrl,
linkVerifica: linkVerifica,
};
const risultato = await this._inviaEmail(
utente.email,
`Verifica il tuo indirizzo email - ${this.nomeApp}`,
null,
this.emailTemplates.verificaEmail,
templateData
);
// Notifica admin della nuova registrazione
const messaggioAdmin = `
📝 <b>Nuova Registrazione</b>
👤 <b>Username:</b> ${utente.username}
📧 <b>Email:</b> ${utente.email}
${utente.name ? `🏷️ <b>Nome:</b> ${utente.name}` : ''}
✅ <b>Email verificata:</b> ${utente.verified_email ? 'Sì' : 'No'}
📅 <b>Data:</b> ${new Date().toLocaleString('it-IT')}
${utente.verified_email ? "✓ Richiesta ammissione inviata all'invitante" : '⏳ In attesa verifica email'}
`.trim();
await this._inviaCopiaCopiaAdmin('Nuova Registrazione', messaggioAdmin);
return {
success: true,
message: 'Email di verifica inviata',
emailVerificaInviata: true,
risultato,
};
} catch (error) {
this.logger.error('Errore in notificaRegistrazione:', error);
throw error;
}
}
// ============================================
// EVENTO 2: EMAIL VERIFICATA
// ============================================
/**
* Notifica l'invitante che l'utente ha verificato l'email
* @param {Object} utente - Dati dell'utente che ha verificato l'email
*/
async notificaRichiestaAmmissione(utente) {
try {
this.logger.info(`Notifica richiesta ammissione per utente: ${utente.username}`);
// Recupera dati invitante (assumendo che tu abbia un metodo per recuperarlo)
const invitante = await this._getInvitante(utente.invitante_id);
if (!invitante) {
throw new Error(`Invitante non trovato per utente ${utente.username}`);
}
// Dati per email invitante
const templateDataInvitante = {
nomeInvitante: invitante.name || invitante.username,
nomeUtente: utente.name || utente.username,
usernameUtente: utente.username,
emailUtente: utente.email,
nomeapp: this.nomeApp,
baseurl: this.baseUrl,
linkAmmetti: `${this.baseUrl}/admin/ammetti-utente/${utente.id}`,
dataRegistrazione: new Date(utente.created_at).toLocaleDateString('it-IT'),
};
// Messaggio Telegram per invitante
const messaggioTelegramInvitante = `
🎉 <b>Nuovo Utente da Ammettere!</b>
Ciao ${invitante.name || invitante.username}!
L'utente che hai invitato ha completato la registrazione:
👤 <b>Nome:</b> ${utente.name || 'Non specificato'}
🔑 <b>Username:</b> ${utente.username}
📧 <b>Email:</b> ${utente.email}
📅 <b>Registrato il:</b> ${new Date(utente.created_at).toLocaleDateString('it-IT')}
✅ <b>Azione richiesta:</b> Accedi alla piattaforma per ammettere questo utente alla comunità RISO.
<a href="${this.baseUrl}/admin/ammetti-utente/${utente.id}">👉 Clicca qui per ammettere</a>
`.trim();
// Invia notifica all'invitante
const risultatiInvitante = await this._inviaNotificaDoppia(
{
email: invitante.email,
telegramId: invitante.teleg_id,
},
{
oggetto: `Nuovo utente da ammettere: ${utente.username}`,
templatePath: this.emailTemplates.richiestaAmmissione,
templateData: templateDataInvitante,
},
{
messaggio: messaggioTelegramInvitante,
opzioni: {
reply_markup: {
inline_keyboard: [
[{ text: '✅ Ammetti Utente', url: `${this.baseUrl}/admin/ammetti-utente/${utente.id}` }],
],
},
},
}
);
// Notifica admin
const messaggioAdmin = `
✅ <b>Email Verificata - Richiesta Ammissione</b>
👤 <b>Utente:</b> ${utente.username} (${utente.email})
👥 <b>Invitante:</b> ${invitante.username} (${invitante.email})
📅 <b>Data verifica:</b> ${new Date().toLocaleString('it-IT')}
📧 Notifica inviata all'invitante
`.trim();
await this._inviaCopiaCopiaAdmin('Richiesta Ammissione', messaggioAdmin);
return {
success: true,
message: 'Notifica richiesta ammissione inviata',
risultatiInvitante,
};
} catch (error) {
this.logger.error('Errore in notificaRichiestaAmmissione:', error);
throw error;
}
}
// ============================================
// EVENTO 3: UTENTE AMMESSO
// ============================================
/**
* Invia email di benvenuto all'utente ammesso
* @param {Object} utente - Dati dell'utente ammesso
*/
async notificaUtenteAmmesso(utente) {
try {
this.logger.info(`Notifica utente ammesso: ${utente.username}`);
// Dati per email benvenuto
const templateData = {
name: utente.name,
username: utente.username,
emailto: utente.email,
nomeapp: this.nomeApp,
baseurl: this.baseUrl,
strlinksito: this.baseUrl,
forgetpwd: `${this.baseUrl}/reset-password`,
verified_email: true,
};
// Invia email di benvenuto (template già esistente)
const risultatoEmail = await this._inviaEmail(
utente.email,
`💚 Benvenuto nella comunità ${this.nomeApp}!`,
null,
this.emailTemplates.benvenuto,
templateData
);
// Messaggio Telegram all'utente (se ha già Telegram collegato)
if (utente.teleg_id && utente.teleg_id !== 0) {
const messaggioTelegramUtente = `
🎉 <b>Benvenuto nella comunità RISO!</b>
Ciao ${utente.name || utente.username}!
Sei stato ammesso nella rete RISO! 🌱
Ora puoi:
✅ Completare il tuo profilo
📢 Pubblicare i tuoi primi annunci
🔍 Esplorare beni e servizi nella tua comunità
💬 Unirti al gruppo territoriale
<a href="${this.baseUrl}">👉 Accedi ora alla piattaforma</a>
Costruiamo insieme un'economia più umana e solidale! 💚
`.trim();
await this._inviaTelegram(utente.teleg_id, messaggioTelegramUtente, {
reply_markup: {
inline_keyboard: [[{ text: '🚀 Vai alla Piattaforma', url: this.baseUrl }]],
},
});
}
// Notifica admin
const messaggioAdmin = `
✅ <b>Utente Ammesso</b>
👤 <b>Username:</b> ${utente.username}
📧 <b>Email:</b> ${utente.email}
${utente.name ? `🏷️ <b>Nome:</b> ${utente.name}` : ''}
📅 <b>Ammesso il:</b> ${new Date().toLocaleString('it-IT')}
📧 Email di benvenuto inviata
${utente.teleg_id && utente.teleg_id !== 0 ? '📱 Notifica Telegram inviata' : '⏳ Telegram non ancora collegato'}
`.trim();
await this._inviaCopiaCopiaAdmin('Utente Ammesso', messaggioAdmin);
return {
success: true,
message: 'Notifica utente ammesso inviata',
risultatoEmail,
};
} catch (error) {
this.logger.error('Errore in notificaUtenteAmmesso:', error);
throw error;
}
}
// ============================================
// EVENTO 4: PROFILO COMPLETATO + TELEGRAM VERIFICATO
// ============================================
/**
* Notifica l'invitante che l'utente ha completato il profilo e verificato Telegram
* @param {Object} utente - Dati dell'utente che ha completato il profilo
*/
async notificaProfiloCompletato(utente) {
try {
this.logger.info(`Notifica profilo completato per utente: ${utente.username}`);
// Verifica che il profilo sia effettivamente completo e Telegram verificato
if (!utente.teleg_id || utente.teleg_id === 0) {
this.logger.warn(`Telegram non verificato per ${utente.username}`);
return {
success: false,
message: 'Telegram non ancora verificato',
};
}
// Recupera invitante
const invitante = await this._getInvitante(utente.invitante_id);
if (!invitante) {
throw new Error(`Invitante non trovato per utente ${utente.username}`);
}
// Dati per email invitante
const templateDataInvitante = {
nomeInvitante: invitante.name || invitante.username,
nomeUtente: utente.name || utente.username,
usernameUtente: utente.username,
nomeapp: this.nomeApp,
baseurl: this.baseUrl,
linkProfilo: `${this.baseUrl}/utenti/${utente.username}`,
};
// Messaggio Telegram per invitante
const messaggioTelegramInvitante = `
🎊 <b>Profilo Completato!</b>
Ciao ${invitante.name || invitante.username}!
L'utente che hai invitato ha completato il suo profilo ed è ora attivo su RISO:
👤 <b>Nome:</b> ${utente.name || utente.username}
🔑 <b>Username:</b> @${utente.username}
✅ <b>Telegram:</b> Verificato
📱 <b>Profilo:</b> Completato
L'utente è ora un membro attivo della comunità e può iniziare a pubblicare annunci!
<a href="${this.baseUrl}/utenti/${utente.username}">👉 Visualizza profilo</a>
`.trim();
// Invia notifica all'invitante
const risultatiInvitante = await this._inviaNotificaDoppia(
{
email: invitante.email,
telegramId: invitante.teleg_id,
},
{
oggetto: `${utente.username} ha completato il profilo su ${this.nomeApp}!`,
templatePath: this.emailTemplates.profiloCompletato,
templateData: templateDataInvitante,
},
{
messaggio: messaggioTelegramInvitante,
opzioni: {
reply_markup: {
inline_keyboard: [[{ text: '👤 Visualizza Profilo', url: `${this.baseUrl}/utenti/${utente.username}` }]],
},
},
}
);
// Notifica admin
const messaggioAdmin = `
✅ <b>Profilo Completato</b>
👤 <b>Utente:</b> ${utente.username}
📧 <b>Email:</b> ${utente.email}
📱 <b>Telegram:</b> Verificato (ID: ${utente.teleg_id})
👥 <b>Invitante:</b> ${invitante.username}
📅 <b>Data:</b> ${new Date().toLocaleString('it-IT')}
✅ L'utente è ora completamente attivo sulla piattaforma
`.trim();
await this._inviaCopiaCopiaAdmin('Profilo Completato', messaggioAdmin);
return {
success: true,
message: 'Notifica profilo completato inviata',
risultatiInvitante,
};
} catch (error) {
this.logger.error('Errore in notificaProfiloCompletato:', error);
throw error;
}
}
// ============================================
// METODI HELPER
// ============================================
/**
* Recupera i dati dell'invitante (da implementare con il tuo DB)
* @private
*/
async _getInvitante(invitanteId) {
// TODO: Implementa il recupero dell'invitante dal database
// Esempio con MongoDB:
// return await User.findById(invitanteId);
// Esempio con MySQL/PostgreSQL:
// return await db.query('SELECT * FROM users WHERE id = ?', [invitanteId]);
throw new Error('Metodo _getInvitante non implementato. Implementalo con il tuo database.');
}
/**
* Verifica se il profilo utente è completo
* @param {Object} utente
* @returns {Boolean}
*/
isProfiloCompleto(utente) {
// Definisci i criteri per considerare un profilo completo
return !!(
(utente.name && utente.email && utente.teleg_id && utente.teleg_id !== 0)
// Aggiungi altri campi necessari
);
}
/**
* Metodo principale per orchestrare le notifiche basate sugli eventi
* @param {String} evento - Tipo di evento
* @param {Object} dati - Dati dell'evento
*/
async gestisciEvento(evento, dati) {
try {
switch (evento) {
case 'REGISTRAZIONE':
return await this.notificaRegistrazione(dati.utente, dati.tokenVerifica);
case 'EMAIL_VERIFICATA':
return await this.notificaRichiestaAmmissione(dati.utente);
case 'UTENTE_AMMESSO':
return await this.notificaUtenteAmmesso(dati.utente);
case 'PROFILO_COMPLETATO':
// Verifica che Telegram sia effettivamente verificato
if (dati.utente.teleg_id && dati.utente.teleg_id !== 0) {
return await this.notificaProfiloCompletato(dati.utente);
} else {
this.logger.warn(`Profilo completato ma Telegram non verificato per ${dati.utente.username}`);
return { success: false, message: 'Telegram non verificato' };
}
default:
throw new Error(`Evento non riconosciuto: ${evento}`);
}
} catch (error) {
this.logger.error(`Errore gestione evento ${evento}:`, error);
throw error;
}
}
}
module.exports = InvioNotifiche;

View File

@@ -1,541 +0,0 @@
/**
* TEST UNITARI PER LA CLASSE InvioNotifiche
*
* Framework: Jest
* Installazione: npm install --save-dev jest
* Esecuzione: npm test
*/
const InvioNotifiche = require('./InvioNotifiche');
// Mock dei servizi esterni
const mockEmailService = {
sendMail: jest.fn().mockResolvedValue({ messageId: 'mock-email-id' })
};
const mockTelegramBot = {
sendMessage: jest.fn().mockResolvedValue({ message_id: 123 })
};
const mockLogger = {
info: jest.fn(),
warn: jest.fn(),
error: jest.fn()
};
describe('InvioNotifiche', () => {
let notifiche;
beforeEach(() => {
// Reset dei mock prima di ogni test
jest.clearAllMocks();
// Inizializza classe con mock
notifiche = new InvioNotifiche({
emailService: mockEmailService,
telegramBot: mockTelegramBot,
adminTelegramId: '999999999',
adminEmail: 'paolo@riso.app',
baseUrl: 'https://riso.app',
nomeApp: 'RISO',
emailTemplates: {
verificaEmail: './templates/verifica.pug',
richiestaAmmissione: './templates/ammissione.pug',
benvenuto: './templates/benvenuto.pug',
profiloCompletato: './templates/profilo.pug'
},
logger: mockLogger
});
// Mock del metodo _getInvitante
notifiche._getInvitante = jest.fn().mockResolvedValue({
id: 5,
username: 'invitante',
email: 'invitante@example.com',
name: 'Marco Invitante',
teleg_id: 111111111
});
});
// ============================================
// TEST: notificaRegistrazione
// ============================================
describe('notificaRegistrazione', () => {
test('Invia email di verifica quando verified_email = false', async () => {
const utente = {
id: 1,
username: 'mario.rossi',
email: 'mario@example.com',
name: 'Mario Rossi',
verified_email: false,
invitante_id: 5
};
const result = await notifiche.notificaRegistrazione(utente, 'token123');
// Verifica che l'email sia stata inviata
expect(mockEmailService.sendMail).toHaveBeenCalledTimes(2); // utente + admin
expect(result.emailVerificaInviata).toBe(true);
expect(result.success).toBe(true);
// Verifica logging
expect(mockLogger.info).toHaveBeenCalledWith(
expect.stringContaining('Notifica registrazione')
);
});
test('Salta verifica email quando verified_email = true', async () => {
const utente = {
id: 1,
username: 'mario.rossi',
email: 'mario@example.com',
name: 'Mario Rossi',
verified_email: true,
invitante_id: 5
};
const result = await notifiche.notificaRegistrazione(utente, null);
// Verifica che NON sia stata inviata email di verifica
expect(result.emailVerificaInviata).toBe(false);
expect(result.success).toBe(true);
// Verifica che sia stata chiamata notificaRichiestaAmmissione
expect(notifiche._getInvitante).toHaveBeenCalledWith(5);
});
test('Gestisce errori correttamente', async () => {
mockEmailService.sendMail.mockRejectedValueOnce(new Error('SMTP Error'));
const utente = {
id: 1,
username: 'mario.rossi',
email: 'mario@example.com',
verified_email: false,
invitante_id: 5
};
await expect(
notifiche.notificaRegistrazione(utente, 'token123')
).rejects.toThrow();
expect(mockLogger.error).toHaveBeenCalled();
});
});
// ============================================
// TEST: notificaRichiestaAmmissione
// ============================================
describe('notificaRichiestaAmmissione', () => {
test('Invia notifica email e telegram all\'invitante', async () => {
const utente = {
id: 1,
username: 'mario.rossi',
email: 'mario@example.com',
name: 'Mario Rossi',
invitante_id: 5,
created_at: new Date()
};
const result = await notifiche.notificaRichiestaAmmissione(utente);
// Verifica chiamate
expect(notifiche._getInvitante).toHaveBeenCalledWith(5);
expect(mockEmailService.sendMail).toHaveBeenCalled();
expect(mockTelegramBot.sendMessage).toHaveBeenCalled();
expect(result.success).toBe(true);
});
test('Gestisce invitante non trovato', async () => {
notifiche._getInvitante.mockResolvedValueOnce(null);
const utente = {
id: 1,
username: 'mario.rossi',
invitante_id: 999
};
await expect(
notifiche.notificaRichiestaAmmissione(utente)
).rejects.toThrow('Invitante non trovato');
});
test('Invia notifica anche se Telegram fallisce', async () => {
mockTelegramBot.sendMessage.mockRejectedValueOnce(new Error('Telegram Error'));
const utente = {
id: 1,
username: 'mario.rossi',
email: 'mario@example.com',
invitante_id: 5,
created_at: new Date()
};
const result = await notifiche.notificaRichiestaAmmissione(utente);
// Email dovrebbe essere comunque inviata
expect(mockEmailService.sendMail).toHaveBeenCalled();
expect(mockLogger.error).toHaveBeenCalledWith(
expect.stringContaining('Errore invio Telegram'),
expect.any(Error)
);
});
});
// ============================================
// TEST: notificaUtenteAmmesso
// ============================================
describe('notificaUtenteAmmesso', () => {
test('Invia email di benvenuto all\'utente', async () => {
const utente = {
id: 1,
username: 'mario.rossi',
email: 'mario@example.com',
name: 'Mario Rossi',
teleg_id: 0
};
const result = await notifiche.notificaUtenteAmmesso(utente);
expect(mockEmailService.sendMail).toHaveBeenCalled();
expect(result.success).toBe(true);
// Verifica oggetto email
const emailCall = mockEmailService.sendMail.mock.calls[0][0];
expect(emailCall.subject).toContain('Benvenuto');
});
test('Invia anche Telegram se utente ha teleg_id', async () => {
const utente = {
id: 1,
username: 'mario.rossi',
email: 'mario@example.com',
name: 'Mario Rossi',
teleg_id: 123456789
};
await notifiche.notificaUtenteAmmesso(utente);
expect(mockTelegramBot.sendMessage).toHaveBeenCalledWith(
123456789,
expect.stringContaining('Benvenuto'),
expect.any(Object)
);
});
test('Salta Telegram se teleg_id = 0', async () => {
const utente = {
id: 1,
username: 'mario.rossi',
email: 'mario@example.com',
teleg_id: 0
};
await notifiche.notificaUtenteAmmesso(utente);
// Solo email, no Telegram
expect(mockTelegramBot.sendMessage).not.toHaveBeenCalled();
});
});
// ============================================
// TEST: notificaProfiloCompletato
// ============================================
describe('notificaProfiloCompletato', () => {
test('Invia notifica se Telegram è verificato', async () => {
const utente = {
id: 1,
username: 'mario.rossi',
email: 'mario@example.com',
name: 'Mario Rossi',
teleg_id: 123456789,
invitante_id: 5
};
const result = await notifiche.notificaProfiloCompletato(utente);
expect(notifiche._getInvitante).toHaveBeenCalledWith(5);
expect(mockEmailService.sendMail).toHaveBeenCalled();
expect(mockTelegramBot.sendMessage).toHaveBeenCalled();
expect(result.success).toBe(true);
});
test('Non invia se Telegram non verificato', async () => {
const utente = {
id: 1,
username: 'mario.rossi',
email: 'mario@example.com',
teleg_id: 0,
invitante_id: 5
};
const result = await notifiche.notificaProfiloCompletato(utente);
expect(result.success).toBe(false);
expect(result.message).toContain('Telegram non ancora verificato');
expect(mockEmailService.sendMail).not.toHaveBeenCalled();
});
});
// ============================================
// TEST: gestisciEvento
// ============================================
describe('gestisciEvento', () => {
test('Gestisce evento REGISTRAZIONE', async () => {
const spy = jest.spyOn(notifiche, 'notificaRegistrazione');
await notifiche.gestisciEvento('REGISTRAZIONE', {
utente: { username: 'test' },
tokenVerifica: 'token123'
});
expect(spy).toHaveBeenCalled();
});
test('Gestisce evento EMAIL_VERIFICATA', async () => {
const spy = jest.spyOn(notifiche, 'notificaRichiestaAmmissione');
await notifiche.gestisciEvento('EMAIL_VERIFICATA', {
utente: { username: 'test', invitante_id: 5 }
});
expect(spy).toHaveBeenCalled();
});
test('Gestisce evento UTENTE_AMMESSO', async () => {
const spy = jest.spyOn(notifiche, 'notificaUtenteAmmesso');
await notifiche.gestisciEvento('UTENTE_AMMESSO', {
utente: { username: 'test' }
});
expect(spy).toHaveBeenCalled();
});
test('Gestisce evento PROFILO_COMPLETATO solo se Telegram verificato', async () => {
const spy = jest.spyOn(notifiche, 'notificaProfiloCompletato');
// Con Telegram verificato
await notifiche.gestisciEvento('PROFILO_COMPLETATO', {
utente: { username: 'test', teleg_id: 123, invitante_id: 5 }
});
expect(spy).toHaveBeenCalled();
spy.mockClear();
// Senza Telegram
const result = await notifiche.gestisciEvento('PROFILO_COMPLETATO', {
utente: { username: 'test', teleg_id: 0 }
});
expect(spy).not.toHaveBeenCalled();
expect(result.success).toBe(false);
});
test('Lancia errore per evento non riconosciuto', async () => {
await expect(
notifiche.gestisciEvento('EVENTO_INESISTENTE', {})
).rejects.toThrow('Evento non riconosciuto');
});
});
// ============================================
// TEST: isProfiloCompleto
// ============================================
describe('isProfiloCompleto', () => {
test('Ritorna true se profilo completo', () => {
const utente = {
name: 'Mario Rossi',
email: 'mario@example.com',
teleg_id: 123456789
};
expect(notifiche.isProfiloCompleto(utente)).toBe(true);
});
test('Ritorna false se manca name', () => {
const utente = {
email: 'mario@example.com',
teleg_id: 123456789
};
expect(notifiche.isProfiloCompleto(utente)).toBe(false);
});
test('Ritorna false se manca email', () => {
const utente = {
name: 'Mario Rossi',
teleg_id: 123456789
};
expect(notifiche.isProfiloCompleto(utente)).toBe(false);
});
test('Ritorna false se Telegram non verificato', () => {
const utente = {
name: 'Mario Rossi',
email: 'mario@example.com',
teleg_id: 0
};
expect(notifiche.isProfiloCompleto(utente)).toBe(false);
});
});
// ============================================
// TEST: Metodi Privati
// ============================================
describe('Metodi privati', () => {
test('_inviaEmail invia email correttamente', async () => {
const result = await notifiche._inviaEmail(
'test@example.com',
'Test Subject',
'<p>Test HTML</p>'
);
expect(result.success).toBe(true);
expect(mockEmailService.sendMail).toHaveBeenCalledWith(
expect.objectContaining({
to: 'test@example.com',
subject: 'Test Subject',
html: '<p>Test HTML</p>'
})
);
});
test('_inviaTelegram invia messaggio correttamente', async () => {
const result = await notifiche._inviaTelegram(
123456789,
'Test message'
);
expect(result.success).toBe(true);
expect(mockTelegramBot.sendMessage).toHaveBeenCalledWith(
123456789,
'Test message',
expect.any(Object)
);
});
test('_inviaTelegram gestisce ID non valido', async () => {
const result = await notifiche._inviaTelegram(0, 'Test');
expect(result.success).toBe(false);
expect(result.error).toContain('non valido');
expect(mockLogger.warn).toHaveBeenCalled();
});
test('_inviaCopiaCopiaAdmin invia a email e telegram admin', async () => {
await notifiche._inviaCopiaCopiaAdmin('Test', 'Message');
expect(mockEmailService.sendMail).toHaveBeenCalledWith(
expect.objectContaining({
to: 'admin@riso.app',
subject: '[ADMIN] Test'
})
);
expect(mockTelegramBot.sendMessage).toHaveBeenCalledWith(
'999999999',
expect.stringContaining('NOTIFICA ADMIN'),
expect.any(Object)
);
});
});
});
// ============================================
// TEST: Integrazione
// ============================================
describe('Test di Integrazione', () => {
let notifiche;
beforeEach(() => {
notifiche = new InvioNotifiche({
emailService: mockEmailService,
telegramBot: mockTelegramBot,
adminTelegramId: '999999999',
adminEmail: 'admin@riso.app',
baseUrl: 'https://riso.app',
nomeApp: 'RISO',
emailTemplates: {},
logger: mockLogger
});
notifiche._getInvitante = jest.fn().mockResolvedValue({
id: 5,
username: 'invitante',
email: 'invitante@example.com',
teleg_id: 111111111
});
});
test('Flusso completo: Registrazione → Verifica → Ammissione → Profilo', async () => {
const utente = {
id: 1,
username: 'mario.rossi',
email: 'mario@example.com',
name: 'Mario Rossi',
verified_email: false,
invitante_id: 5,
teleg_id: 0,
created_at: new Date()
};
// 1. Registrazione
await notifiche.notificaRegistrazione(utente, 'token123');
expect(mockEmailService.sendMail).toHaveBeenCalled();
// 2. Verifica email
utente.verified_email = true;
await notifiche.notificaRichiestaAmmissione(utente);
expect(notifiche._getInvitante).toHaveBeenCalled();
// 3. Ammissione
await notifiche.notificaUtenteAmmesso(utente);
expect(mockEmailService.sendMail).toHaveBeenCalled();
// 4. Profilo completato
utente.teleg_id = 123456789;
await notifiche.notificaProfiloCompletato(utente);
expect(mockTelegramBot.sendMessage).toHaveBeenCalled();
// Verifica che tutte le notifiche admin siano state inviate
const adminCalls = mockEmailService.sendMail.mock.calls.filter(
call => call[0].subject.includes('[ADMIN]')
);
expect(adminCalls.length).toBeGreaterThan(0);
});
});
// Configurazione package.json per Jest
/*
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"jest": {
"testEnvironment": "node",
"coveragePathIgnorePatterns": ["/node_modules/"],
"testMatch": ["**/__tests__/**/*.js", "**/?(*.)+(spec|test).js"]
}
}
*/

View File

@@ -1,82 +1,82 @@
module.exports = { module.exports = {
list: [ list: [
{ _id: 1, idSectorGood: [1], descr: 'Abbigliamento donna', icon: 'fas fa-tshirt', color: 'pink' }, { _id: 1, idSectorGood: [1], descr: 'Abbigliamento donna', icon: 'fas fa-tshirt' },
{ _id: 2, idSectorGood: [1], descr: 'Abbigliamento uomo', icon: 'fas fa-tshirt', color: 'blue' }, { _id: 2, idSectorGood: [1], descr: 'Abbigliamento uomo', icon: 'fas fa-tshirt' },
{ _id: 3, idSectorGood: [1], descr: 'Accessori', icon: 'fas fa-glasses', color: 'purple' }, { _id: 3, idSectorGood: [1], descr: 'Accessori', icon: 'fas fa-glasses' },
{ _id: 4, idSectorGood: [1], descr: 'Scarpe donna', icon: 'fas fa-shoe-prints', color: 'pink' }, { _id: 4, idSectorGood: [1], descr: 'Scarpe donna', icon: 'fas fa-shoe-prints' },
{ _id: 5, idSectorGood: [1], descr: 'Scarpe uomo', icon: 'fas fa-shoe-prints', color: 'blue' }, { _id: 5, idSectorGood: [1], descr: 'Scarpe uomo', icon: 'fas fa-shoe-prints' },
{ _id: 6, idSectorGood: [2], descr: 'Bagno', icon: 'fas fa-bath', color: 'teal' }, { _id: 6, idSectorGood: [2], descr: 'Bagno', icon: 'fas fa-bath' },
{ _id: 7, idSectorGood: [2], descr: 'Camera', icon: 'fas fa-bed', color: 'indigo' }, { _id: 7, idSectorGood: [2], descr: 'Camera', icon: 'fas fa-bed' },
{ _id: 8, idSectorGood: [2], descr: 'Complementi d\'arredo', icon: 'fas fa-couch', color: 'brown' }, { _id: 8, idSectorGood: [2], descr: "Complementi d'arredo", icon: 'fas fa-couch' },
{ _id: 9, idSectorGood: [2], descr: 'Cucina', icon: 'fas fa-utensils', color: 'orange' }, { _id: 9, idSectorGood: [2], descr: 'Cucina', icon: 'fas fa-utensils' },
{ _id: 10, idSectorGood: [2], descr: 'Esterno', icon: 'fas fa-tree', color: 'green' }, { _id: 10, idSectorGood: [2], descr: 'Esterno', icon: 'fas fa-tree' },
{ _id: 11, idSectorGood: [2], descr: 'Soggiorno', icon: 'fas fa-tv', color: 'grey' }, { _id: 11, idSectorGood: [2], descr: 'Soggiorno', icon: 'fas fa-tv' },
{ _id: 12, idSectorGood: [3], descr: 'Altri veicoli', icon: 'fas fa-car', color: 'black' }, { _id: 12, idSectorGood: [3], descr: 'Altri veicoli', icon: 'fas fa-car' },
{ _id: 13, idSectorGood: [3], descr: 'Auto', icon: 'fas fa-car', color: 'red' }, { _id: 13, idSectorGood: [3], descr: 'Auto', icon: 'fas fa-car' },
{ _id: 14, idSectorGood: [3], descr: 'Moto', icon: 'fas fa-motorcycle', color: 'black' }, { _id: 14, idSectorGood: [3], descr: 'Moto', icon: 'fas fa-motorcycle' },
{ _id: 15, idSectorGood: [3], descr: 'Camper', icon: 'fas fa-caravan', color: 'orange' }, { _id: 15, idSectorGood: [3], descr: 'Camper', icon: 'fas fa-caravan' },
{ _id: 16, idSectorGood: [3], descr: 'Van (furgoni camperizzati)', icon: 'fas fa-truck', color: 'blue' }, { _id: 16, idSectorGood: [3], descr: 'Van (furgoni camperizzati)', icon: 'fas fa-truck' },
{ _id: 17, idSectorGood: [4], descr: 'Bigiotteria', icon: 'fas fa-gem', color: 'gold' }, { _id: 17, idSectorGood: [4], descr: 'Bigiotteria', icon: 'fas fa-gem' },
{ _id: 18, idSectorGood: [4], descr: 'Lavoretti', icon: 'fas fa-paint-brush', color: 'yellow' }, { _id: 18, idSectorGood: [4], descr: 'Lavoretti', icon: 'fas fa-paint-brush' },
{ _id: 19, idSectorGood: [4], descr: 'Altro', icon: 'fas fa-question', color: 'grey' }, { _id: 19, idSectorGood: [4], descr: 'Altro', icon: 'fas fa-question' },
{ _id: 20, idSectorGood: [5], descr: 'Accessori bellezza', icon: 'fas fa-spa', color: 'pink' }, { _id: 20, idSectorGood: [5], descr: 'Accessori bellezza', icon: 'fas fa-spa' },
{ _id: 21, idSectorGood: [5], descr: 'Creme e detergenti', icon: 'fas fa-pump-soap', color: 'teal' }, { _id: 21, idSectorGood: [5], descr: 'Creme e detergenti', icon: 'fas fa-pump-soap' },
{ _id: 22, idSectorGood: [5], descr: 'Trucchi e profumi', icon: 'fas fa-palette', color: 'purple' }, { _id: 22, idSectorGood: [5], descr: 'Trucchi e profumi', icon: 'fas fa-palette' },
{ _id: 23, idSectorGood: [6], descr: 'Giocattoli e giochi di società', icon: 'fas fa-dice', color: 'yellow' }, { _id: 23, idSectorGood: [6], descr: 'Giocattoli e giochi di società', icon: 'fas fa-dice' },
{ _id: 24, idSectorGood: [6], descr: 'Igiene e pannolini', icon: 'fas fa-baby', color: 'pink' }, { _id: 24, idSectorGood: [6], descr: 'Igiene e pannolini', icon: 'fas fa-baby' },
{ _id: 25, idSectorGood: [6], descr: 'Lettini e culle', icon: 'fas fa-baby-carriage', color: 'blue' }, { _id: 25, idSectorGood: [6], descr: 'Lettini e culle', icon: 'fas fa-baby-carriage' },
{ _id: 26, idSectorGood: [6], descr: 'Passeggini & co', icon: 'fas fa-baby-carriage', color: 'green' }, { _id: 26, idSectorGood: [6], descr: 'Passeggini & co', icon: 'fas fa-baby-carriage' },
{ _id: 27, idSectorGood: [6], descr: 'Vestiti e scarpe', icon: 'fas fa-socks', color: 'purple' }, { _id: 27, idSectorGood: [6], descr: 'Vestiti e scarpe', icon: 'fas fa-socks' },
{ _id: 28, idSectorGood: [7], descr: 'Bere', icon: 'fas fa-glass-cheers', color: 'red' }, { _id: 28, idSectorGood: [7], descr: 'Bere', icon: 'fas fa-glass-cheers' },
{ _id: 29, idSectorGood: [7], descr: 'Mangiare', icon: 'fas fa-utensils', color: 'orange' }, { _id: 29, idSectorGood: [7], descr: 'Mangiare', icon: 'fas fa-utensils' },
{ _id: 30, idSectorGood: [8], descr: 'Antiquariato', icon: 'fas fa-history', color: 'brown' }, { _id: 30, idSectorGood: [8], descr: 'Antiquariato', icon: 'fas fa-history' },
{ _id: 31, idSectorGood: [8], descr: 'Collezionismo', icon: 'fas fa-coins', color: 'gold' }, { _id: 31, idSectorGood: [8], descr: 'Collezionismo', icon: 'fas fa-coins' },
{ _id: 32, idSectorGood: [9], descr: 'Cellulari e accessori', icon: 'fas fa-mobile-alt', color: 'blue' }, { _id: 32, idSectorGood: [9], descr: 'Cellulari e accessori', icon: 'fas fa-mobile-alt' },
{ _id: 33, idSectorGood: [9], descr: 'Computer e software', icon: 'fas fa-laptop', color: 'grey' }, { _id: 33, idSectorGood: [9], descr: 'Computer e software', icon: 'fas fa-laptop' },
{ _id: 34, idSectorGood: [9], descr: 'Elettrodomestici', icon: 'fas fa-blender', color: 'green' }, { _id: 34, idSectorGood: [9], descr: 'Elettrodomestici', icon: 'fas fa-blender' },
{ _id: 35, idSectorGood: [9], descr: 'Fotografia', icon: 'fas fa-camera', color: 'black' }, { _id: 35, idSectorGood: [9], descr: 'Fotografia', icon: 'fas fa-camera' },
{ _id: 36, idSectorGood: [9], descr: 'Videogiochi e console', icon: 'fas fa-gamepad', color: 'purple' }, { _id: 36, idSectorGood: [9], descr: 'Videogiochi e console', icon: 'fas fa-gamepad' },
{ _id: 37, idSectorGood: [10], descr: 'Console', icon: 'fas fa-gamepad', color: 'black' }, { _id: 37, idSectorGood: [10], descr: 'Console', icon: 'fas fa-gamepad' },
{ _id: 38, idSectorGood: [10], descr: 'Giochi di società', icon: 'fas fa-dice', color: 'yellow' }, { _id: 38, idSectorGood: [10], descr: 'Giochi di società', icon: 'fas fa-dice' },
{ _id: 39, idSectorGood: [10], descr: 'PC games', icon: 'fas fa-desktop', color: 'blue' }, { _id: 39, idSectorGood: [10], descr: 'PC games', icon: 'fas fa-desktop' },
{ _id: 40, idSectorGood: [11], descr: 'Attrezzatura', icon: 'fas fa-tools', color: 'grey' }, { _id: 40, idSectorGood: [11], descr: 'Attrezzatura', icon: 'fas fa-tools' },
{ _id: 41, idSectorGood: [11], descr: 'Materiali', icon: 'fas fa-box-open', color: 'brown' }, { _id: 41, idSectorGood: [11], descr: 'Materiali', icon: 'fas fa-box-open' },
{ _id: 42, idSectorGood: [11], descr: 'Prodotti', icon: 'fas fa-box', color: 'green' }, { _id: 42, idSectorGood: [11], descr: 'Prodotti', icon: 'fas fa-box' },
{ _id: 43, idSectorGood: [11], descr: 'Strumentazione', icon: 'fas fa-toolbox', color: 'blue' }, { _id: 43, idSectorGood: [11], descr: 'Strumentazione', icon: 'fas fa-toolbox' },
{ _id: 44, idSectorGood: [12], descr: ' riviste e fumetti', icon: 'fas fa-book-open', color: 'red' }, { _id: 44, idSectorGood: [12], descr: ' riviste e fumetti', icon: 'fas fa-book-open' },
{ _id: 45, idSectorGood: [13], descr: 'CD e vinili', icon: 'fas fa-compact-disc', color: 'black' }, { _id: 45, idSectorGood: [13], descr: 'CD e vinili', icon: 'fas fa-compact-disc' },
{ _id: 46, idSectorGood: [13], descr: 'Film e DVD', icon: 'fas fa-film', color: 'blue' }, { _id: 46, idSectorGood: [13], descr: 'Film e DVD', icon: 'fas fa-film' },
{ _id: 47, idSectorGood: [13], descr: 'Strumenti musicali', icon: 'fas fa-guitar', color: 'brown' }, { _id: 47, idSectorGood: [13], descr: 'Strumenti musicali', icon: 'fas fa-guitar' },
{ _id: 48, idSectorGood: [14], descr: 'Arredamento', icon: 'fas fa-couch', color: 'brown' }, { _id: 48, idSectorGood: [14], descr: 'Arredamento', icon: 'fas fa-couch' },
{ _id: 49, idSectorGood: [14], descr: 'Attrezzature e accessori', icon: 'fas fa-tools', color: 'grey' }, { _id: 49, idSectorGood: [14], descr: 'Attrezzature e accessori', icon: 'fas fa-tools' },
{ _id: 50, idSectorGood: [14], descr: 'Cancelleria e cartucce', icon: 'fas fa-print', color: 'blue' }, { _id: 50, idSectorGood: [14], descr: 'Cancelleria e cartucce', icon: 'fas fa-print' },
{ _id: 51, idSectorGood: [15], descr: 'Abbigliamento', icon: 'fas fa-tshirt', color: 'purple' }, { _id: 51, idSectorGood: [15], descr: 'Abbigliamento', icon: 'fas fa-tshirt' },
{ _id: 52, idSectorGood: [15], descr: 'Attrezzature e accessori Sport', icon: 'fas fa-football-ball', color: 'green' }, { _id: 52, idSectorGood: [15], descr: 'Attrezzature e accessori Sport', icon: 'fas fa-football-ball' },
{ _id: 53, idSectorGood: [15], descr: 'Bici e accessori', icon: 'fas fa-bicycle', color: 'blue' }, { _id: 53, idSectorGood: [15], descr: 'Bici e accessori', icon: 'fas fa-bicycle' },
{ _id: 54, idSectorGood: [17], descr: 'Edilizia', icon: 'fas fa-hard-hat', color: 'orange' }, { _id: 54, idSectorGood: [17], descr: 'Edilizia', icon: 'fas fa-hard-hat' },
{ _id: 55, idSectorGood: [17], descr: 'Modellismo', icon: 'fas fa-puzzle-piece', color: 'yellow' }, { _id: 55, idSectorGood: [17], descr: 'Modellismo', icon: 'fas fa-puzzle-piece' },
{ _id: 56, idSectorGood: [17], descr: 'Cucito', icon: 'fas fa-cut', color: 'pink' }, { _id: 56, idSectorGood: [17], descr: 'Cucito', icon: 'fas fa-cut' },
{ _id: 57, idSectorGood: [17], descr: 'Pulizia', icon: 'fas fa-broom', color: 'green' }, { _id: 57, idSectorGood: [17], descr: 'Pulizia', icon: 'fas fa-broom' },
{ _id: 58, idSectorGood: [17], descr: 'Per Imbiancare', icon: 'fas fa-paint-roller', color: 'white' }, { _id: 58, idSectorGood: [17], descr: 'Per Imbiancare', icon: 'fas fa-paint-roller' },
{ _id: 59, idSectorGood: [17], descr: 'Giardinaggio', icon: 'fas fa-seedling', color: 'green' }, { _id: 59, idSectorGood: [17], descr: 'Giardinaggio', icon: 'fas fa-seedling' },
{ _id: 60, idSectorGood: [17], descr: 'Falegnameria', icon: 'fas fa-hammer', color: 'brown' }, { _id: 60, idSectorGood: [17], descr: 'Falegnameria', icon: 'fas fa-hammer' },
{ _id: 61, idSectorGood: [7], descr: 'Pane', icon: 'fas fa-bread-slice', color: 'brown' }, { _id: 61, idSectorGood: [7], descr: 'Pane', icon: 'fas fa-bread-slice' },
{ _id: 62, idSectorGood: [7], descr: 'Pasta', icon: 'fas fa-utensils', color: 'yellow' }, { _id: 62, idSectorGood: [7], descr: 'Pasta', icon: 'fas fa-utensils' },
{ _id: 63, idSectorGood: [7], descr: 'Formaggi', icon: 'fas fa-cheese', color: 'yellow' }, { _id: 63, idSectorGood: [7], descr: 'Formaggi', icon: 'fas fa-cheese' },
{ _id: 64, idSectorGood: [7], descr: 'Olio', icon: 'fas fa-oil-can', color: 'green' }, { _id: 64, idSectorGood: [7], descr: 'Olio', icon: 'fas fa-oil-can' },
{ _id: 65, idSectorGood: [7], descr: 'Fervida', icon: 'fas fa-fire', color: 'red' }, { _id: 65, idSectorGood: [7], descr: 'Fervida', icon: 'fas fa-fire' },
{ _id: 66, idSectorGood: [7], descr: 'Fermentati', icon: 'fas fa-beer', color: 'brown' }, { _id: 66, idSectorGood: [7], descr: 'Fermentati', icon: 'fas fa-beer' },
{ _id: 67, idSectorGood: [7], descr: 'Marmellate', icon: 'fas fa-jar', color: 'orange' }, { _id: 67, idSectorGood: [7], descr: 'Marmellate', icon: 'fas fa-jar' },
{ _id: 68, idSectorGood: [7], descr: 'Salse', icon: 'fas fa-mortar-pestle', color: 'red' }, { _id: 68, idSectorGood: [7], descr: 'Salse', icon: 'fas fa-mortar-pestle' },
{ _id: 69, idSectorGood: [20], descr: 'Cereali', icon: 'fas fa-wheat', color: 'yellow' }, { _id: 69, idSectorGood: [20], descr: 'Cereali', icon: 'fas fa-wheat' },
{ _id: 70, idSectorGood: [20], descr: 'Frutta', icon: 'fas fa-apple-alt', color: 'red' }, { _id: 70, idSectorGood: [20], descr: 'Frutta', icon: 'fas fa-apple-alt' },
{ _id: 71, idSectorGood: [20], descr: 'Ortaggi', icon: 'fas fa-carrot', color: 'orange' }, { _id: 71, idSectorGood: [20], descr: 'Ortaggi', icon: 'fas fa-carrot' },
{ _id: 72, idSectorGood: [20], descr: 'Zootecnia', icon: 'fas fa-paw', color: 'brown' }, { _id: 72, idSectorGood: [20], descr: 'Zootecnia', icon: 'fas fa-paw' },
{ _id: 73, idSectorGood: [20], descr: 'Biologica', icon: 'fas fa-leaf', color: 'green' }, { _id: 73, idSectorGood: [20], descr: 'Biologica', icon: 'fas fa-leaf' },
{ _id: 74, idSectorGood: [20], descr: 'Permacultura', icon: 'fas fa-recycle', color: 'green' }, { _id: 74, idSectorGood: [20], descr: 'Permacultura', icon: 'fas fa-recycle' },
{ _id: 75, idSectorGood: [20], descr: 'Sinergico', icon: 'fas fa-handshake', color: 'green' }, { _id: 75, idSectorGood: [20], descr: 'Sinergico', icon: 'fas fa-handshake' },
{ _id: 76, idSectorGood: [20], descr: 'Tradizionale', icon: 'fas fa-tractor', color: 'brown' }, { _id: 76, idSectorGood: [20], descr: 'Tradizionale', icon: 'fas fa-tractor' },
{ _id: 77, idSectorGood: [20], descr: 'Viticoltura', icon: 'fas fa-wine-glass-alt', color: 'purple' }, { _id: 77, idSectorGood: [20], descr: 'Viticoltura', icon: 'fas fa-wine-glass-alt' },
{ _id: 78, idSectorGood: [20], descr: 'Acquacoltura', icon: 'fas fa-fish', color: 'blue' }, { _id: 78, idSectorGood: [20], descr: 'Acquacoltura', icon: 'fas fa-fish' },
], ],
}; };

View File

@@ -0,0 +1,25 @@
module.exports = {
list: [
{_id: 1, descr: 'Abbigliamento', icon: 'fas fa-tshirt', color: 'blue-7'},
{_id: 2, descr: 'Arredamento', icon: 'fas fa-couch', color: 'brown-7'},
{_id: 3, descr: 'Auto e Moto', icon: 'fas fa-car', color: 'grey-9'},
{_id: 4, descr: 'Artigianato', icon: 'fas fa-gem', color: 'amber-7'},
{_id: 5, descr: 'Bellezza e Igiene', icon: 'fas fa-spa', color: 'pink-7'},
{_id: 6, descr: 'Bimbi', icon: 'fas fa-baby', color: 'cyan-7'},
{_id: 7, descr: 'Cibo', icon: 'fas fa-utensils', color: 'orange-7'},
{_id: 8, descr: 'Collezionismo e Antiquariato', icon: 'fas fa-coins', color: 'brown-9'},
{_id: 9, descr: 'Elettronica', icon: 'fas fa-laptop', color: 'blue-grey-8'},
{_id: 10, descr: 'Giochi', icon: 'fas fa-gamepad', color: 'purple-7'},
{_id: 11, descr: 'Hobby e Fai da Te', icon: 'fas fa-palette', color: 'deep-orange-6'},
{_id: 12, descr: 'Libri', icon: 'fas fa-book-open', color: 'indigo-7'},
{_id: 13, descr: 'Musica e Film', icon: 'fas fa-film', color: 'deep-purple-7'},
{_id: 14, descr: 'Scuola e Ufficio', icon: 'fas fa-pen', color: 'blue-8'},
{_id: 15, descr: 'Sport', icon: 'fas fa-bicycle', color: 'green-8'},
{_id: 16, descr: 'Varie', icon: 'fas fa-box', color: 'grey-7'},
{_id: 17, descr: 'Attrezzature', icon: 'fas fa-tools', color: 'grey-8'},
{_id: 18, descr: 'Animali', icon: 'fas fa-paw', color: 'brown-6'},
{_id: 19, descr: 'Arte e Decorazioni', icon: 'fas fa-palette', color: 'purple-6'},
{_id: 20, descr: 'Agricoltura', icon: 'fas fa-seedling', color: 'green-7'},
{_id: 21, descr: 'Elettrodomestici', icon: 'fas fa-plug', color: 'blue-grey-7'},
],
};

View File

@@ -0,0 +1,17 @@
module.exports = {
list: [
{_id: 1, descr: 'Abitare'},
{_id: 2, descr: 'Agricoltura'},
{_id: 3, descr: 'Alimentazione'},
{_id: 4, descr: 'Animali'},
{_id: 5, descr: 'Auto e Veicoli'},
{_id: 6, descr: 'Benessere'},-
{_id: 7, descr: 'Per la Casa'},
{_id: 8, descr: 'Intrattenimento'},
{_id: 10, descr: 'Per la Persona'},
{_id: 11, descr: 'Progetti di Gruppo'},
{_id: 12, descr: 'Salute'},
{_id: 13, descr: 'Tecnologie'},
{_id: 14, descr: 'Servizi'},
],
};

View File

@@ -1,26 +0,0 @@
const { ObjectId } = require('mongodb');
module.exports = {
list: [
/*{ _id: new ObjectId('605c72e2f9b1a019c1e4f4a1'), idapp: '18', title: 'Alimentazione Sana' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4a2'), idapp: '18', title: 'Attualità e Informazione Libera' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4a3'), idapp: '18', title: 'Psicologia e Crescita Personale' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4a4'), idapp: '18', title: 'Educazione e Formazione' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4a5'), idapp: '18', title: 'Bambini e Ragazzi Felici' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4a6'), idapp: '18', title: 'Salute e Benessere Naturali' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4a7'), idapp: '18', title: 'Nuove Scienze' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4a8'), idapp: '18', title: 'Spiritualità e Sciamanesimo' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4a9'), idapp: '18', title: 'Storia e Archeologia Segreta' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4aa'), idapp: '18', title: 'Autosufficienza, Autoproduzione e Vita Naturale' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4ab'), idapp: '18', title: 'Yoga' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4ac'), idapp: '18', title: 'Amici Animali' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4ad'), idapp: '18', title: 'Corpi Energetici' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4ae'), idapp: '18', title: 'Erbe, Alberi e Natura' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4af'), idapp: '18', title: 'Astrologia, Esoterismi e Numerologia' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4b0'), idapp: '18', title: 'Universo Femminile' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4b1'), idapp: '18', title: 'Sessualità e Relazione di coppia' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4b2'), idapp: '18', title: 'Tarocchi, Oracoli e Carte' },
{ _id: new ObjectId('605c72e2f9b1a019c1e4f4b3'), idapp: '18', title: 'Techiche per il corpo' },
*/
],
};

View File

@@ -3,187 +3,206 @@ const { ObjectId } = require('mongodb');
module.exports = { module.exports = {
list: [ list: [
{ {
"_id": new ObjectId("615a353c002c8298f4495be7"), _id: new ObjectId('615a353c002c8298f4495be7'),
"idapp": "1", idapp: '1',
"label": "Dono", label: 'Dono',
"__v": 0 __v: 0,
}, },
{ {
"_id": new ObjectId("61bc466567de9a1f54b25494"), _id: new ObjectId('61bc466567de9a1f54b25494'),
"idapp": "1", idapp: '1',
"label": "Offerta Libera", label: 'Offerta Libera',
"__v": 0 __v: 0,
}, },
{ {
"_id": new ObjectId("61bc454867de9a1f54b25462"), _id: new ObjectId('61bc454867de9a1f54b25462'),
"idapp": "1", idapp: '1',
"label": "Baratto (scambio Beni o Servizi)", label: 'Baratto (scambio Beni o Servizi)',
"__v": 0 __v: 0,
}, },
{ {
"_id": new ObjectId("61bc482667de9a1f54b2549c"), _id: new ObjectId('61bc482667de9a1f54b2549c'),
"idapp": "1", idapp: '1',
"label": "Scambio Lavoro", label: 'Scambio Lavoro',
"__v": 0 __v: 0,
}, },
{ {
"_id": new ObjectId("61bc482667de9a1f54b2649c"), _id: new ObjectId('61bc482667de9a1f54b2649c'),
"idapp": "1", idapp: '1',
"label": "Monete Alternative", label: 'Monete Alternative',
"__v": 0 __v: 0,
}, },
{ {
"_id": new ObjectId("61bc482667de9a1f54b3549e"), _id: new ObjectId('61bc482667de9a1f54b3549e'),
"idapp": "1", idapp: '1',
"label": "Euro", label: 'Euro',
"__v": 0 __v: 0,
}, },
{ {
"_id": new ObjectId("615a353c002c8298f4495bf7"), _id: new ObjectId('615a353c002c8298f4495bf7'),
"idapp": "12", idapp: '12',
"label": "Dono", label: 'Dono',
"__v": 0 __v: 0,
}, },
{ {
"_id": new ObjectId("61bc466567de9a1f54b254f4"), _id: new ObjectId('61bc466567de9a1f54b254f4'),
"idapp": "12", idapp: '12',
"label": "Offerta Libera", label: 'Offerta Libera',
"__v": 0 __v: 0,
}, },
{ {
"_id": new ObjectId("61bc454867de9a1f54b254f2"), _id: new ObjectId('61bc454867de9a1f54b254f2'),
"idapp": "12", idapp: '12',
"label": "Baratto", label: 'Baratto',
"__v": 0 __v: 0,
}, },
{ {
"_id": new ObjectId("61bc482667de9a1f54b25412"), _id: new ObjectId('61bc482667de9a1f54b25412'),
"idapp": "12", idapp: '12',
"label": "Scambio Lavoro", label: 'Scambio Lavoro',
"__v": 0 __v: 0,
}, },
{ {
"_id": new ObjectId("61bc482667de9a1f64b254ab"), _id: new ObjectId('61bc482667de9a1f64b254ab'),
"idapp": "12", idapp: '12',
"label": "Monete Alternative", label: 'Monete Alternative',
"__v": 0 __v: 0,
}, },
{ {
"_id": new ObjectId("61bc482667de9a1f64b254fb"), _id: new ObjectId('61bc482667de9a1f64b254fb'),
"idapp": "12", idapp: '12',
"label": "Euro", label: 'Euro',
"__v": 0 __v: 0,
}, },
{ {
"_id": new ObjectId("5dbc6b0801234f629f75e98d"), _id: new ObjectId('5dbc6b0801234f629f75e98d'),
"idapp": "2", idapp: '2',
"__v": 0, __v: 0,
"label": "Offerta Libera" label: 'Offerta Libera',
}, },
/* 2 */ /* 2 */
{ {
"_id": new ObjectId("5dbc6b1001234f629f75e98e"), _id: new ObjectId('5dbc6b1001234f629f75e98e'),
"idapp": "2", idapp: '2',
"__v": 0, __v: 0,
"label": "Ingresso Gratuito" label: 'Ingresso Gratuito',
}, },
/* 3 */ /* 3 */
{ {
"_id": new ObjectId("5dbc6b1801234f629f75e98f"), _id: new ObjectId('5dbc6b1801234f629f75e98f'),
"idapp": "2", idapp: '2',
"__v": 0, __v: 0,
"label": "Contributo", label: 'Contributo',
"showprice": true showprice: true,
}, },
/* 4 */ /* 4 */
{ {
"_id": new ObjectId("5dbc6b3001234f629f75e990"), _id: new ObjectId('5dbc6b3001234f629f75e990'),
"idapp": "2", idapp: '2',
"__v": 0 __v: 0,
}, },
{ {
"_id": new ObjectId("602c315137d9f0738ded312f"), _id: new ObjectId('602c315137d9f0738ded312f'),
"idapp": "10", idapp: '10',
"__v": 0, __v: 0,
"label": "Contributo", label: 'Contributo',
"showprice": true showprice: true,
}, },
/* 7 */ /* 7 */
{ {
"_id": new ObjectId("602c316037d9f0738ded3132"), _id: new ObjectId('602c316037d9f0738ded3132'),
"idapp": "10", idapp: '10',
"__v": 0, __v: 0,
"label": "Gratuito" label: 'Gratuito',
}, },
/* 8 */ /* 8 */
{ {
"_id": new ObjectId("60514b3f733ce468d09366f2"), _id: new ObjectId('60514b3f733ce468d09366f2'),
"idapp": "10", idapp: '10',
"__v": 0, __v: 0,
"label": "Evento ONLINE Gratuito" label: 'Evento ONLINE Gratuito',
}, },
{ {
"_id": new ObjectId("515a353c002c8298f4495bf7"), _id: new ObjectId('515a353c002c8298f4495bf7'),
"idapp": "13", idapp: '13',
"label": "🎁 Dono", label: 'Dono',
"__v": 0 icon: '🎁',
color: '#e91e63',
__v: 0,
}, },
{ {
"_id": new ObjectId("51bc466567de9a1f54b254f4"), _id: new ObjectId('51bc466567de9a1f54b254f4'),
"idapp": "13", idapp: '13',
"label": "💸 Offerta Libera", label: 'Offerta Libera',
"__v": 0 icon: '💸',
color: '#4caf50',
__v: 0,
}, },
{ {
"_id": new ObjectId("51bc454867de9a1f54b254f2"), _id: new ObjectId('51bc454867de9a1f54b254f2'),
"idapp": "13", idapp: '13',
"label": "🤝 Baratto", label: 'Baratto',
"__v": 0 icon: '🤝',
color: '#ff9800',
__v: 0,
}, },
{ {
"_id": new ObjectId("51bc482667de9a1f54b25412"), _id: new ObjectId('51bc482667de9a1f54b25412'),
"idapp": "13", idapp: '13',
"label": "💪 Scambio Lavoro", label: 'Scambio Lavoro',
"__v": 0 icon: '💪',
color: '#9c27b0',
__v: 0,
}, },
{ {
"_id": new ObjectId("51bc482667de9a1f64b254ab"), _id: new ObjectId('51bc482667de9a1f64b254ab'),
"idapp": "13", idapp: '13',
"label": "🪙 Monete Alternative", label: 'Monete Alternative',
"__v": 0 icon: '🪙',
color: '#795548',
__v: 0,
}, },
{ {
"_id": new ObjectId("51bc482667de9a1f64b254ac"), _id: new ObjectId('51bc482667de9a1f64b254ac'),
"idapp": "13", idapp: '13',
"label": "🍚 RIS", label: 'RIS',
"__v": 0 icon: '🍚',
color: '#ffc107',
__v: 0,
}, },
{ {
"_id": new ObjectId("51bc482667de9a1f64b254fb"), _id: new ObjectId('51bc482667de9a1f64b254fb'),
"idapp": "13", idapp: '13',
"label": "💶 Euro", label: 'Euro',
"__v": 0 icon: '💶',
color: '#0d47a1',
__v: 0,
}, },
{ {
"_id": new ObjectId("51bc482667de9a1f64b254ff"), _id: new ObjectId('51bc482667de9a1f64b254ff'),
"idapp": "13", idapp: '13',
"label": "₿ Bitcoin", label: 'Bitcoin',
"__v": 0 icon: '₿',
color: '#ff9800',
__v: 0,
}, },
{ {
"_id": new ObjectId("51bc482667de9a1f64b255ff"), _id: new ObjectId('51bc482667de9a1f64b255ff'),
"idapp": "13", idapp: '13',
"label": "⏳ Banca del Tempo", label: 'Banca del Tempo',
"__v": 0 icon: '⏳',
} color: '#00bcd4',
] __v: 0,
} },
],
};

View File

@@ -0,0 +1,539 @@
// migration-categories.js
const mongoose = require('mongoose');
const shared_consts = require('../tools/shared_nodejs');
const tools = require('../tools/general');
const { Sector } = require('../models/sector');
const { SectorGood } = require('../models/sectorgood');
const { SectorBacheca } = require('../models/sectorbacheca');
const { Skill } = require('../models/skill');
const { Good } = require('../models/good');
const { Bacheca } = require('../models/bacheca');
const { Version } = require('../models/version');
const { MySkill } = require('../models/myskill');
const { MyGood } = require('../models/mygood');
const { MyBacheca } = require('../models/mybacheca');
// ============================================================================
// MAPPING DEFINITIONS
// ============================================================================
// Mapping vecchi ID Skills -> nuovi ID Skills
const skillSectorMapping = {
1: 1, // Abitare -> Abitare e Costruire
2: 2, // Agricoltura -> Agricoltura e Orticoltura
3: 3, // Alimentazione -> Alimentazione e Trasformazione
4: 4, // Animali -> Animali e Allevamento
5: 10, // Auto e Veicoli -> Mobilità e Trasporti
6: 6, // Benessere -> Benessere e Cura Naturale
7: 9, // Per la Casa -> Manutenzione e Riparazione
8: 15, // Intrattenimento -> Tempo Libero e Cultura
10: 7, // Per la Persona -> Educazione e Formazione (parte)
11: 11, // Progetti di Gruppo -> Progetti Comunitari
12: 12, // Salute -> Salute e Terapie
13: 13, // Tecnologie -> Tecnologie Appropriate
14: 5, // Servizi -> Artigianato e Creazione
};
// Mapping vecchi ID SubSkills -> nuovi ID SubSkills
const subSkillMapping = {
1: 1, // Autocostruzione
2: 4, // Ecovillaggi / Comunità
3: 3, // Cohousing
4: 13, // Orto sinergico
5: 15, // Pacciamatura
6: 14, // Orto tradizionale
7: 16, // Permacultura
8: 12, // Cultura idroponica
9: 11, // Elettrocultura
10: 10, // Aratura + semina
11: 17, // Potatura
12: 18, // Raccolta
13: 20, // Preparazione cibi
14: 21, // Preparazione bevande
15: 20, // Autoproduzione alimenti e bevande
16: 32, // Servizi per Cani
17: 32, // Servizi per Gatti
18: 30, // Servizi per Animali da allevamento
19: 33, // Veterinario
20: 114, // Riparazioni Auto
21: 115, // Riparazioni Moto
22: 111, // Riparazioni Camper / Van
23: 111, // Creazione di Van Camperizzati
24: 112, // Noleggio veicoli
25: 113, // Lavaggio auto
26: 50, // Alimentazione Naturale
27: 57, // Ginnastica
28: 68, // Yoga
29: 66, // Trattamenti Olistici
30: 60, // Meditazione e mindfulness
31: 65, // Trattamenti Energetici
32: 67, // Trattamenti Sonori
33: 51, // Arteterapia
34: 63, // Teatroterapia
35: 52, // Cantoterapia
36: 65, // Trattamenti Luminosi
37: 55, // Fitoterapia
38: 58, // Kinesiologia
39: 66, // Terapie Naturali
40: 96, // Muratore
41: 95, // Imbianchino
42: 91, // Elettricista - TV
43: 41, // Falegname
44: 92, // Fabbro
45: 90, // Arredamento
46: 94, // Idraulico
47: 93, // Giardiniere
48: 86, // Canne fumarie e camini e stufe
49: 82, // Pannelli solari
50: 101, // Riparazioni varie
51: 101, // Tuttofare
52: 100, // Traslochi
53: 97, // Piastrellista
54: 98, // Pulizie
55: 171, // Ballo
56: 172, // Canto
57: 176, // Musica
58: 175, // Letteratura e poesia
59: 178, // Teatro
60: 174, // Fotografia
61: 173, // Film making
62: 177, // Sport
63: 170, // Arte
70: 163, // Parrucchiere -> Sartoria su Misura
71: 66, // Estetista -> Trattamenti Olistici
72: 136, // Omeopatia
73: 137, // Assistenza anziani -> OSS
76: 125, // Baby sitter -> Volontariato
77: 163, // Sarto
78: 160, // Autoproduzione prodotti persona
79: 70, // Corsi e Formazione
80: 125, // Supporto spesa
81: 125, // Volontariato
82: 123, // Gruppi di acquisto
83: 120, // Banca del tempo
84: 121, // Collabora con noi
85: 122, // Eventi
86: 124, // Laboratori
87: 121, // Idee e suggerimenti
88: 133, // Medico di base
89: 142, // Specialista
90: 139, // Pediatra
91: 131, // Dentista
92: 140, // Psicologo
93: 141, // Psicoterapeuta
94: 138, // Ostetrica
95: 135, // Nutrizionista
97: 130, // Counseling
98: 151, // Assistenza PC / software
99: 150, // Assistenza Cellulari
100: 154, // Realizzazione Siti web
101: 153, // Realizzazione App / Piattaforme
102: 152, // Corsi d'Informatica
103: 99, // Riparazione Elettrodomestici
104: 132, // Infermiera
105: 137, // OSS
106: 125, // Badante
107: 59, // Massaggi
109: 130, // Supporto
110: 130, // Consulenza
111: 56, // Floriterapia
112: 54, // Costellazioni Familiari
113: 53, // Coach Motivazionale
114: 64, // Tecniche Essene
115: 62, // Riflessologia
116: 61, // Naturopatia
120: 170, // Grafica
121: 154, // Web
122: 2, // Progettazione -> Bioedilizia
123: 134, // Medicina Naturale
124: 5, // Casa in Vendita
125: 6, // Casa in Affitto
126: 8, // Terreno
127: 7, // Stanza in affitto
128: 7, // Stanza in condivisione
129: 23, // Home Restaurant
130: 82, // Pannelli Solari
131: 84, // Pompe di calore
132: 82, // Impianti Fotovoltaici
133: 43, // Restauro
134: 101, // Altro
135: 82, // Pannelli Solari
136: 152, // Corso di Podcasting : Corsi d'Informatica
137: 69, // Biodanza
74: 75, // Contabile/commercialista -> Consulenze Professionali
75: 75, // Avvocato -> Consulenze Professionali
108: 126, // Affrancamento -> Servizi alla Comunità
117: 75, // Organizzazione Aziendale -> Consulenze Professionali
118: 75, // Project Manager -> Consulenze Professionali
119: 75, // Consulenze per Startup -> Consulenze Professionali
};
// Mapping vecchi ID SectorGoods -> nuovi ID SectorGoods
const sectorGoodMapping = {
1: 1, // Abbigliamento -> Abbigliamento e Accessori
2: 2, // Arredamento -> Arredamento e Casa
3: 3, // Auto e Moto -> Auto, Moto e Veicoli
4: 4, // Artigianato -> Artigianato e Creazioni
5: 5, // Bellezza e Igiene -> Bellezza e Cura Persona
6: 6, // Bimbi -> Infanzia e Bambini
7: 7, // Cibo -> Cibi e Bevande
8: 8, // Collezionismo e Antiquariato (invariato)
9: 9, // Elettronica -> Elettronica e Tecnologia
10: 10, // Giochi -> Gaming e Console
11: 11, // Hobby e Fai da Te -> Giardinaggio e Fai da Te
12: 12, // Libri -> Libri e Riviste
13: 13, // Musica e Film (invariato)
14: 14, // Scuola e Ufficio (invariato)
15: 15, // Sport -> Sport e Fitness
16: null, // Varie -> DISTRIBUITO (rimuovere, troppo generico)
17: 17, // Attrezzature -> Attrezzature e Strumenti
18: 16, // Animali
19: 20, // Arte e Decorazioni
20: 19, // Agricoltura -> Agricoltura e Orticoltura
21: 18, // Elettrodomestici
};
// Mapping vecchi ID SectorBacheca -> nuovi ID SectorBacheca
const sectorBachecaMapping = {
1: 1, // Abitare -> Abitare Alternativo
2: 2, // Agricoltura -> Agricoltura e Permacultura
3: 3, // Alimentazione -> Alimentazione Consapevole
4: 4, // Animali -> Animali e Natura
5: 11, // Auto e Veicoli -> Mobilità Sostenibile
6: 5, // Benessere -> Benessere Olistico
7: 10, // Per la Casa -> Lavoro e Autoproduzione
8: 6, // Intrattenimento -> Cultura e Arte
10: 8, // Per la Persona -> Educazione Alternativa
11: 12, // Progetti di Gruppo -> Progetti Comunitari
12: 13, // Salute -> Salute Naturale
13: 14, // Tecnologie -> Tecnologie Appropriate
14: 7, // Servizi -> Economia Solidale
};
// Mapping vecchi idSkill -> nuovi idBacheca
const bachecaMapping = {
1: 1, // Autocostruzione
2: 4, // Ecovillaggi / Comunità -> Ecovillaggi
3: 3, // Cohousing
4: 12, // Orto sinergico -> Permacultura
7: 12, // Permacultura
26: 40, // Alimentazione Naturale -> Cerchi di Condivisione
27: 44, // Ginnastica -> Yoga e Movimento
28: 44, // Yoga -> Yoga e Movimento
29: 43, // Trattamenti Olistici -> Pratiche Energetiche
30: 42, // Meditazione e mindfulness -> Meditazione
55: 50, // Ballo
56: 51, // Canto -> Cerchi di Canto
57: 53, // Musica -> Concerti
59: 55, // Teatro
79: 73, // Corsi e Formazione -> Workshop e Corsi
82: 24, // Gruppi di acquisto -> Gruppi Acquisto
83: 60, // Banca del tempo -> Banca del Tempo
84: 114, // Collabora con noi -> Nuove Iniziative
85: 113, // Eventi -> Eventi Comunitari
86: 71, // Laboratori -> Laboratori Bambini
87: 111, // Idee e suggerimenti -> Brainstorming
// Aggiungi altri mapping secondo necessità
// Per gli altri usa categoria generica o 0
};
// ============================================================================
// MIGRATION FUNCTIONS
// ============================================================================
/**
* Aggiorna i settori Skills con nuove categorie
*/
async function updateSkillSectors(Sector) {
console.log('🔄 Aggiornamento Skill Sectors...');
const newSectors = require('./new_sectors');
await Sector.deleteMany({});
const result = await Sector.insertMany(newSectors.list);
console.log(`${result.length} Skill Sectors aggiornati`);
}
/**
* Aggiorna le Skills con nuove sottocategorie
*/
async function updateSkills(Skill) {
console.log('🔄 Aggiornamento Sub-Skills...');
const newSkills = require('./new_subskills'); // Il file che hai creato sopra
await Skill.deleteMany({});
const result = await Skill.insertMany(newSkills.list);
console.log(`${result.length} Sub-Skills aggiornate`);
}
async function updateSectorGoods(Good) {
console.log('🔄 Aggiornamento SectorGoods...');
const newSectorGoods = require('./new_sectorgoods');
await SectorGood.deleteMany({});
const result = await SectorGood.insertMany(newSectorGoods.list);
console.log(`${result.length} SectorGood aggiornati`);
}
async function updateSectorBachecas(Bacheca) {
console.log('🔄 Aggiornamento SectorBachecas...');
const newSectorBachecas = require('./new_sectorbachecas');
await SectorBacheca.deleteMany({});
const result = await SectorBacheca.insertMany(newSectorBachecas.list);
console.log(`${result.length} SectorBacheca aggiornati`);
}
/**
* Aggiorna i Goods (rimozione campo color)
*/
async function updateGoods(Good) {
console.log('🔄 Aggiornamento Goods...');
const newGoods = require('./new_goods');
await Good.deleteMany({});
const result = await Good.insertMany(newGoods.list);
console.log(`${result.length} Goods aggiornati`);
}
async function updateBachecas(Good) {
console.log('🔄 Aggiornamento Bachecas...');
const newBachecas = require('./new_bachecas');
await Bacheca.deleteMany({});
const result = await Bacheca.insertMany(newBachecas.list);
console.log(`${result.length} Bachecas aggiornati`);
}
/**
* Migra i record MySkill
*/
async function migrateMySkills(MySkill) {
console.log('🔄 Migrazione MySkill records...');
const records = await MySkill.find({}).lean();
let updated = 0;
let unchanged = 0;
for (const record of records) {
const updates = {};
// Mappa idSector
if (record.idSector && skillSectorMapping[record.idSector]) {
if (skillSectorMapping[record.idSector] !== record.idSector) {
updates.idSector = skillSectorMapping[record.idSector];
}
}
// Mappa idSkill (tutte hanno destinazione ora)
if (record.idSkill && subSkillMapping[record.idSkill] !== undefined) {
const newId = subSkillMapping[record.idSkill];
if (newId !== record.idSkill) {
updates.idSkill = newId;
}
}
if (Object.keys(updates).length > 0) {
await MySkill.updateOne({ _id: record._id }, { $set: updates });
updated++;
} else {
unchanged++;
}
}
console.log(`✅ MySkill: ${updated} aggiornati, ${unchanged} invariati`);
}
/**
* Migra i record MyGood
*/
async function migrateMyGoods(MyGood) {
console.log('🔄 Migrazione MyGood records...');
const records = await MyGood.find({}).lean();
let updated = 0;
let unchanged = 0;
for (const record of records) {
const updates = {};
// Mappa idSectorGood
if (record.idSectorGood && sectorGoodMapping[record.idSectorGood]) {
if (sectorGoodMapping[record.idSectorGood] !== record.idSectorGood) {
updates.idSectorGood = sectorGoodMapping[record.idSectorGood];
}
}
// idGood rimane invariato (stessi ID)
if (Object.keys(updates).length > 0) {
await MyGood.updateOne({ _id: record._id }, { $set: updates });
updated++;
} else {
unchanged++;
}
}
console.log(`✅ MyGood: ${updated} aggiornati, ${unchanged} invariati`);
}
/**
* Migra i record MyBacheca
*/
async function migrateMyBacheca(MyBacheca) {
console.log('🔄 Migrazione MyBacheca records...');
const records = await MyBacheca.find({}).lean();
let updated = 0;
let unchanged = 0;
for (const record of records) {
const updates = {};
// Mappa idSector -> idSectorBacheca (vecchio campo -> nuovo campo)
if (record.idSector && sectorBachecaMapping[record.idSector]) {
updates.idSectorBacheca = sectorBachecaMapping[record.idSector];
}
// Mappa idSkill -> idBacheca (vecchio campo -> nuovo campo)
if (record.idSkill && bachecaMapping[record.idSkill] !== undefined) {
updates.idBacheca = bachecaMapping[record.idSkill];
} else if (record.idSkill) {
// Se non c'è mapping specifico, metti 0 (generico)
updates.idBacheca = 0;
}
// Rimuovi i campi vecchi
const unsetFields = {};
if (record.idSector !== undefined) unsetFields.idSector = '';
if (record.idSkill !== undefined) unsetFields.idSkill = '';
if (Object.keys(updates).length > 0 || Object.keys(unsetFields).length > 0) {
const updateQuery = {};
if (Object.keys(updates).length > 0) {
updateQuery.$set = updates;
}
if (Object.keys(unsetFields).length > 0) {
updateQuery.$unset = unsetFields;
}
await MyBacheca.updateOne({ _id: record._id }, updateQuery);
updated++;
} else {
unchanged++;
}
}
console.log(`✅ MyBacheca: ${updated} aggiornati, ${unchanged} invariati`);
}
async function aggiornaCategorieESottoCategorie() {
console.log('\n Migrazione DIC 2025 da ESEGUIRE ----------- ');
await updateSkillSectors(Sector);
await updateSkills(Skill);
await updateSectorGoods(SectorGood);
await updateSectorBachecas(SectorBacheca);
await updateGoods(Good);
await updateBachecas(Bacheca);
// const populate = require('./populate');
// ris = populate.rewriteTable('contribtypes');
return ris;
}
// ============================================================================
// MAIN MIGRATION SCRIPT
// ============================================================================
async function runMigration() {
try {
const idapp = 0; // TUTTI
console.log('🚀 Controllo Versioni Tabelle (runMigration)');
const isMigratioDic2025Executed = await Version.isJobExecuted(
idapp,
shared_consts.JOB_TO_EXECUTE.MIGRATION_SECTORS_DIC25
);
const vers_server_str = await tools.getVersServer();
const version_server = tools.versionToNumber(vers_server_str);
// Step 1: Aggiorna categorie solo 1 volta sola !
if (!isMigratioDic2025Executed) {
await aggiornaCategorieESottoCategorie();
}
const myskill_updated = await Version.isTableVersionUpdated(idapp, 'myskill');
if (!myskill_updated) {
// MYSKILL
if (!isMigratioDic2025Executed) await migrateMySkills(MySkill);
// ++ Altri aggiornamenti da fare
// FINE - Aggiorna Versione Tabella
await Version.updateTableVersion(idapp, 'myskill', version_server);
}
const mygood_updated = await Version.isTableVersionUpdated(idapp, 'mygood');
if (!mygood_updated) {
// MYGOOD
if (!isMigratioDic2025Executed) await migrateMyGoods(MyGood);
// ++ Altri aggiornamenti da fare
// FINE - Aggiorna Versione Tabella
await Version.updateTableVersion(idapp, 'mygood', version_server);
}
const mybacheca_updated = await Version.isTableVersionUpdated(idapp, 'mybacheca');
if (!mybacheca_updated) {
// MYBACHECA
if (!isMigratioDic2025Executed) await migrateMyBacheca(MyBacheca);
// ++ Altri aggiornamenti da fare
// FINE - Aggiorna Versione Tabella
await Version.updateTableVersion(idapp, 'mybacheca', version_server);
}
// FINE -----------------------
if (!isMigratioDic2025Executed) {
await Version.setJobExecuted(idapp, shared_consts.JOB_TO_EXECUTE.MIGRATION_SECTORS_DIC25);
console.log('\n✅ Migrazione DIC 2025 completata con successo!');
}
await Version.setLastVersionRun(idapp, version_server);
} catch (error) {
console.error('❌ Errore durante la migrazione:', error);
} finally {
}
}
module.exports = {
runMigration,
skillSectorMapping,
subSkillMapping,
sectorGoodMapping,
sectorBachecaMapping,
aggiornaCategorieESottoCategorie,
};

View File

@@ -0,0 +1,107 @@
// new_bachecas.js
module.exports = {
list: [
// Abitare Alternativo (1)
{ _id: 1, idSectorBacheca: [1], descr: 'Autocostruzione', icon: 'fas fa-hammer' },
{ _id: 2, idSectorBacheca: [1], descr: 'Bioedilizia', icon: 'fas fa-leaf' },
{ _id: 3, idSectorBacheca: [1], descr: 'Cohousing', icon: 'home_work' },
{ _id: 4, idSectorBacheca: [1], descr: 'Ecovillaggi', icon: 'fas fa-users' },
{ _id: 5, idSectorBacheca: [1], descr: 'Offerte Immobili', icon: 'fas fa-key' },
{ _id: 6, idSectorBacheca: [1], descr: 'Stanze Condivise', icon: 'fas fa-door-open' },
// Agricoltura e Permacultura (2)
{ _id: 10, idSectorBacheca: [2], descr: 'Corsi Agricoltura', icon: 'fas fa-chalkboard-teacher' },
{ _id: 11, idSectorBacheca: [2], descr: 'Orti Condivisi', icon: 'fas fa-seedling' },
{ _id: 12, idSectorBacheca: [2], descr: 'Permacultura', icon: 'fas fa-recycle' },
{ _id: 13, idSectorBacheca: [2], descr: 'Scambio Sementi', icon: 'fas fa-exchange-alt' },
{ _id: 14, idSectorBacheca: [2], descr: 'Visite in Azienda', icon: 'fas fa-tractor' },
{ _id: 15, idSectorBacheca: [2], descr: 'Wwoof e Volontariato', icon: 'fas fa-hands-helping' },
// Alimentazione Consapevole (3)
{ _id: 20, idSectorBacheca: [3], descr: 'Autoproduzione', icon: 'kitchen' },
{ _id: 21, idSectorBacheca: [3], descr: 'Cene Condivise', icon: 'fas fa-utensils' },
{ _id: 22, idSectorBacheca: [3], descr: 'Corsi Cucina', icon: 'restaurant' },
{ _id: 23, idSectorBacheca: [3], descr: 'Fermentazione', icon: 'fas fa-flask' },
{ _id: 24, idSectorBacheca: [3], descr: 'Gruppi Acquisto', icon: 'fas fa-shopping-basket' },
{ _id: 25, idSectorBacheca: [3], descr: 'Panificazione', icon: 'fas fa-bread-slice' },
// Animali e Natura (4)
{ _id: 30, idSectorBacheca: [4], descr: 'Apicoltura', icon: 'hive' },
{ _id: 31, idSectorBacheca: [4], descr: 'Escursioni Natura', icon: 'fas fa-hiking' },
{ _id: 32, idSectorBacheca: [4], descr: 'Pet Sitting Condiviso', icon: 'fas fa-paw' },
{ _id: 33, idSectorBacheca: [4], descr: 'Riconoscimento Piante', icon: 'fas fa-leaf' },
// Benessere Olistico (5)
{ _id: 40, idSectorBacheca: [5], descr: 'Cerchi di Condivisione', icon: 'fas fa-circle' },
{ _id: 41, idSectorBacheca: [5], descr: 'Costellazioni Familiari', icon: 'fas fa-users' },
{ _id: 42, idSectorBacheca: [5], descr: 'Meditazione', icon: 'fas fa-om' },
{ _id: 43, idSectorBacheca: [5], descr: 'Pratiche Energetiche', icon: 'fas fa-hand-sparkles' },
{ _id: 44, idSectorBacheca: [5], descr: 'Yoga e Movimento', icon: 'self_improvement' },
// Cultura e Arte (6)
{ _id: 50, idSectorBacheca: [6], descr: 'Ballo e Danza', icon: 'fas fa-music' },
{ _id: 51, idSectorBacheca: [6], descr: 'Cerchi di Canto', icon: 'fas fa-microphone' },
{ _id: 52, idSectorBacheca: [6], descr: 'Cinema e Film', icon: 'fas fa-film' },
{ _id: 53, idSectorBacheca: [6], descr: 'Concerti', icon: 'fas fa-guitar' },
{ _id: 54, idSectorBacheca: [6], descr: 'Mostre e Arte', icon: 'fas fa-palette' },
{ _id: 55, idSectorBacheca: [6], descr: 'Teatro', icon: 'fas fa-theater-masks' },
// Economia Solidale (7)
{ _id: 60, idSectorBacheca: [7], descr: 'Banca del Tempo', icon: 'fas fa-clock' },
{ _id: 61, idSectorBacheca: [7], descr: 'Finanza Etica', icon: 'savings' },
{ _id: 62, idSectorBacheca: [7], descr: 'Mercatini Scambio', icon: 'fas fa-store' },
{ _id: 63, idSectorBacheca: [7], descr: 'Monete Locali', icon: 'fas fa-coins' },
// Educazione Alternativa (8)
{ _id: 70, idSectorBacheca: [8], descr: 'Educazione Parentale', icon: 'fas fa-child' },
{ _id: 71, idSectorBacheca: [8], descr: 'Laboratori Bambini', icon: 'fas fa-baby' },
{ _id: 72, idSectorBacheca: [8], descr: 'Scuola Libertaria', icon: 'fas fa-book-reader' },
{ _id: 73, idSectorBacheca: [8], descr: 'Workshop e Corsi', icon: 'fas fa-chalkboard-teacher' },
// Energie Rinnovabili (9)
{ _id: 80, idSectorBacheca: [9], descr: 'Autocostruzione Solare', icon: 'fas fa-solar-panel' },
{ _id: 81, idSectorBacheca: [9], descr: 'Compostaggio', icon: 'fas fa-recycle' },
{ _id: 82, idSectorBacheca: [9], descr: 'Comunità Energetiche', icon: 'fas fa-plug' },
{ _id: 83, idSectorBacheca: [9], descr: 'Riciclo Creativo', icon: 'restore_from_trash' },
// Lavoro e Autoproduzione (10)
{ _id: 90, idSectorBacheca: [10], descr: 'Artigianato', icon: 'fas fa-hammer' },
{ _id: 91, idSectorBacheca: [10], descr: 'Autocostruzione', icon: 'fas fa-tools' },
{ _id: 92, idSectorBacheca: [10], descr: 'Falegnameria', icon: 'handyman' },
{ _id: 93, idSectorBacheca: [10], descr: 'Riparazione', icon: 'fas fa-wrench' },
{ _id: 94, idSectorBacheca: [10], descr: 'Sartoria', icon: 'fas fa-cut' },
// Mobilità Sostenibile (11)
{ _id: 100, idSectorBacheca: [11], descr: 'Biciclette', icon: 'fas fa-bicycle' },
{ _id: 101, idSectorBacheca: [11], descr: 'Camperisti', icon: 'fas fa-caravan' },
{ _id: 102, idSectorBacheca: [11], descr: 'Car Pooling', icon: 'fas fa-car' },
{ _id: 103, idSectorBacheca: [11], descr: 'Ciclofficine', icon: 'fas fa-tools' },
// Progetti Comunitari (12)
{ _id: 110, idSectorBacheca: [12], descr: 'Assemblee', icon: 'fas fa-users' },
{ _id: 111, idSectorBacheca: [12], descr: 'Brainstorming', icon: 'fas fa-lightbulb' },
{ _id: 112, idSectorBacheca: [12], descr: 'Co-progettazione', icon: 'account_tree' },
{ _id: 113, idSectorBacheca: [12], descr: 'Eventi Comunitari', icon: 'fas fa-calendar-alt' },
{ _id: 114, idSectorBacheca: [12], descr: 'Nuove Iniziative', icon: 'fas fa-rocket' },
// Salute Naturale (13)
{ _id: 120, idSectorBacheca: [13], descr: 'Erboristeria', icon: 'fas fa-leaf' },
{ _id: 121, idSectorBacheca: [13], descr: 'Fitoterapia', icon: 'fas fa-mortar-pestle' },
{ _id: 122, idSectorBacheca: [13], descr: 'Medicine Tradizionali', icon: 'healing' },
{ _id: 123, idSectorBacheca: [13], descr: 'Primo Soccorso', icon: 'fas fa-first-aid' },
// Tecnologie Appropriate (14)
{ _id: 130, idSectorBacheca: [14], descr: 'Linux e Open Source', icon: 'computer' },
{ _id: 131, idSectorBacheca: [14], descr: 'Privacy Digitale', icon: 'fas fa-shield-alt' },
{ _id: 132, idSectorBacheca: [14], descr: 'Riparazione Devices', icon: 'fas fa-mobile-alt' },
{ _id: 133, idSectorBacheca: [14], descr: 'Self Hosting', icon: 'fas fa-server' },
],
};

87
src/populate/new_goods.js Normal file
View File

@@ -0,0 +1,87 @@
module.exports = {
list: [
{ _id: 1, idSectorGood: [1], descr: 'Abbigliamento donna', icon: 'fas fa-tshirt' },
{ _id: 2, idSectorGood: [1], descr: 'Abbigliamento uomo', icon: 'fas fa-tshirt' },
{ _id: 3, idSectorGood: [1], descr: 'Accessori', icon: 'fas fa-glasses' },
{ _id: 4, idSectorGood: [1], descr: 'Scarpe donna', icon: 'fas fa-shoe-prints' },
{ _id: 5, idSectorGood: [1], descr: 'Scarpe uomo', icon: 'fas fa-shoe-prints' },
{ _id: 6, idSectorGood: [2], descr: 'Bagno', icon: 'fas fa-bath' },
{ _id: 7, idSectorGood: [2], descr: 'Camera', icon: 'fas fa-bed' },
{ _id: 8, idSectorGood: [2], descr: "Complementi d'arredo", icon: 'fas fa-couch' },
{ _id: 9, idSectorGood: [2], descr: 'Cucina', icon: 'fas fa-utensils' },
{ _id: 10, idSectorGood: [2], descr: 'Esterno', icon: 'fas fa-tree' },
{ _id: 11, idSectorGood: [2], descr: 'Soggiorno', icon: 'fas fa-tv' },
{ _id: 12, idSectorGood: [3], descr: 'Altri veicoli', icon: 'fas fa-car' },
{ _id: 13, idSectorGood: [3], descr: 'Auto', icon: 'fas fa-car' },
{ _id: 14, idSectorGood: [3], descr: 'Moto', icon: 'fas fa-motorcycle' },
{ _id: 15, idSectorGood: [3], descr: 'Camper', icon: 'fas fa-caravan' },
{ _id: 16, idSectorGood: [3], descr: 'Van (furgoni camperizzati)', icon: 'fas fa-truck' },
{ _id: 17, idSectorGood: [4], descr: 'Bigiotteria', icon: 'fas fa-gem' },
{ _id: 18, idSectorGood: [4], descr: 'Lavoretti', icon: 'fas fa-paint-brush' },
{ _id: 19, idSectorGood: [4], descr: 'Altro', icon: 'fas fa-question' },
{ _id: 20, idSectorGood: [5], descr: 'Accessori bellezza', icon: 'fas fa-spa' },
{ _id: 21, idSectorGood: [5], descr: 'Creme e detergenti', icon: 'soap' },
{ _id: 22, idSectorGood: [5], descr: 'Trucchi e profumi', icon: 'fas fa-palette' },
{ _id: 23, idSectorGood: [6], descr: 'Giocattoli e giochi di società', icon: 'fas fa-dice' },
{ _id: 24, idSectorGood: [6], descr: 'Igiene e pannolini', icon: 'fas fa-baby' },
{ _id: 25, idSectorGood: [6], descr: 'Lettini e culle', icon: 'fas fa-baby-carriage' },
{ _id: 26, idSectorGood: [6], descr: 'Passeggini & co', icon: 'fas fa-baby-carriage' },
{ _id: 27, idSectorGood: [6], descr: 'Vestiti e scarpe', icon: 'fas fa-socks' },
{ _id: 28, idSectorGood: [7], descr: 'Bere', icon: 'fas fa-glass-cheers' },
{ _id: 29, idSectorGood: [7], descr: 'Mangiare', icon: 'fas fa-utensils' },
{ _id: 30, idSectorGood: [8], descr: 'Antiquariato', icon: 'fas fa-history' },
{ _id: 31, idSectorGood: [8], descr: 'Collezionismo', icon: 'fas fa-coins' },
{ _id: 32, idSectorGood: [9], descr: 'Cellulari e accessori', icon: 'fas fa-mobile-alt' },
{ _id: 33, idSectorGood: [9], descr: 'Computer e software', icon: 'fas fa-laptop' },
{ _id: 34, idSectorGood: [9], descr: 'Elettrodomestici', icon: 'fas fa-blender' },
{ _id: 35, idSectorGood: [9], descr: 'Fotografia', icon: 'fas fa-camera' },
{ _id: 36, idSectorGood: [9], descr: 'Videogiochi e console', icon: 'fas fa-gamepad' },
{ _id: 37, idSectorGood: [10], descr: 'Console', icon: 'fas fa-gamepad' },
{ _id: 38, idSectorGood: [10], descr: 'Giochi di società', icon: 'fas fa-dice' },
{ _id: 39, idSectorGood: [10], descr: 'PC games', icon: 'fas fa-desktop' },
{ _id: 40, idSectorGood: [11], descr: 'Attrezzatura', icon: 'fas fa-tools' },
{ _id: 41, idSectorGood: [11], descr: 'Materiali', icon: 'fas fa-box-open' },
{ _id: 42, idSectorGood: [11], descr: 'Prodotti', icon: 'fas fa-box' },
{ _id: 43, idSectorGood: [11], descr: 'Strumentazione', icon: 'fas fa-toolbox' },
{ _id: 44, idSectorGood: [12], descr: ' riviste e fumetti', icon: 'fas fa-book-open' },
{ _id: 45, idSectorGood: [13], descr: 'CD e vinili', icon: 'fas fa-compact-disc' },
{ _id: 46, idSectorGood: [13], descr: 'Film e DVD', icon: 'fas fa-film' },
{ _id: 47, idSectorGood: [13], descr: 'Strumenti musicali', icon: 'fas fa-guitar' },
{ _id: 48, idSectorGood: [14], descr: 'Arredamento', icon: 'fas fa-couch' },
{ _id: 49, idSectorGood: [14], descr: 'Attrezzature e accessori', icon: 'fas fa-tools' },
{ _id: 50, idSectorGood: [14], descr: 'Cancelleria e cartucce', icon: 'fas fa-print' },
{ _id: 51, idSectorGood: [15], descr: 'Abbigliamento', icon: 'fas fa-tshirt' },
{ _id: 52, idSectorGood: [15], descr: 'Attrezzature e accessori Sport', icon: 'fas fa-football-ball' },
{ _id: 53, idSectorGood: [15], descr: 'Bici e accessori', icon: 'fas fa-bicycle' },
{ _id: 54, idSectorGood: [17], descr: 'Edilizia', icon: 'fas fa-hard-hat' },
{ _id: 55, idSectorGood: [17], descr: 'Modellismo', icon: 'fas fa-puzzle-piece' },
{ _id: 56, idSectorGood: [17], descr: 'Cucito', icon: 'fas fa-cut' },
{ _id: 57, idSectorGood: [17], descr: 'Pulizia', icon: 'fas fa-broom' },
{ _id: 58, idSectorGood: [17], descr: 'Per Imbiancare', icon: 'fas fa-paint-roller' },
{ _id: 59, idSectorGood: [17], descr: 'Giardinaggio', icon: 'fas fa-seedling' },
{ _id: 60, idSectorGood: [17], descr: 'Falegnameria', icon: 'fas fa-hammer' },
{ _id: 61, idSectorGood: [7], descr: 'Pane', icon: 'fas fa-bread-slice' },
{ _id: 62, idSectorGood: [7], descr: 'Pasta', icon: 'fas fa-utensils' },
{ _id: 63, idSectorGood: [7], descr: 'Formaggi', icon: 'cake' },
{ _id: 64, idSectorGood: [7], descr: 'Olio', icon: 'water_drop' },
{ _id: 65, idSectorGood: [7], descr: 'Fervida', icon: 'fas fa-fire' },
{ _id: 66, idSectorGood: [7], descr: 'Fermentati', icon: 'fas fa-beer' },
{ _id: 67, idSectorGood: [7], descr: 'Marmellate', icon: 'kitchen' },
{ _id: 68, idSectorGood: [7], descr: 'Salse', icon: 'fas fa-mortar-pestle' },
{ _id: 69, idSectorGood: [20], descr: 'Cereali', icon: 'grain' },
{ _id: 70, idSectorGood: [20], descr: 'Frutta', icon: 'fas fa-apple-alt' },
{ _id: 71, idSectorGood: [20], descr: 'Ortaggi', icon: 'fas fa-carrot' },
{ _id: 72, idSectorGood: [20], descr: 'Zootecnia', icon: 'fas fa-paw' },
{ _id: 73, idSectorGood: [20], descr: 'Biologica', icon: 'fas fa-leaf' },
{ _id: 74, idSectorGood: [20], descr: 'Permacultura', icon: 'fas fa-recycle' },
{ _id: 75, idSectorGood: [20], descr: 'Sinergico', icon: 'fas fa-handshake' },
{ _id: 76, idSectorGood: [20], descr: 'Tradizionale', icon: 'fas fa-tractor' },
{ _id: 77, idSectorGood: [20], descr: 'Viticoltura', icon: 'wine_bar' },
{ _id: 78, idSectorGood: [20], descr: 'Acquacoltura', icon: 'fas fa-fish' },
{ _id: 79, idSectorGood: [2], descr: 'Casalinghi', icon: 'home_repair_service' },
],
};

View File

@@ -0,0 +1,19 @@
// new_sectorbachecas.js
module.exports = {
list: [
{ _id: 1, descr: 'Abitare Alternativo', icon: 'fas fa-home', color: 'brown-7' },
{ _id: 2, descr: 'Agricoltura e Permacultura', icon: 'fas fa-seedling', color: 'green-7' },
{ _id: 3, descr: 'Alimentazione Consapevole', icon: 'fas fa-apple-alt', color: 'orange-7' },
{ _id: 4, descr: 'Animali e Natura', icon: 'fas fa-paw', color: 'brown-6' },
{ _id: 5, descr: 'Benessere Olistico', icon: 'fas fa-spa', color: 'teal-6' },
{ _id: 6, descr: 'Cultura e Arte', icon: 'fas fa-palette', color: 'deep-purple-6' },
{ _id: 7, descr: 'Economia Solidale', icon: 'fas fa-handshake', color: 'amber-7' },
{ _id: 8, descr: 'Educazione Alternativa', icon: 'fas fa-book-reader', color: 'indigo-7' },
{ _id: 9, descr: 'Energie Rinnovabili', icon: 'fas fa-solar-panel', color: 'yellow-8' },
{ _id: 10, descr: 'Lavoro e Autoproduzione', icon: 'fas fa-hammer', color: 'grey-8' },
{ _id: 11, descr: 'Mobilità Sostenibile', icon: 'fas fa-bicycle', color: 'blue-7' },
{ _id: 12, descr: 'Progetti Comunitari', icon: 'fas fa-users', color: 'purple-7' },
{ _id: 13, descr: 'Salute Naturale', icon: 'fas fa-heart', color: 'red-6' },
{ _id: 14, descr: 'Tecnologie Appropriate', icon: 'fas fa-laptop-code', color: 'blue-grey-7' },
],
};

View File

@@ -0,0 +1,24 @@
module.exports = {
list: [
{ _id: 1, descr: 'Abbigliamento e Accessori', icon: 'fas fa-tshirt', color: 'blue-7' },
{ _id: 2, descr: 'Arredamento e Casa', icon: 'fas fa-couch', color: 'brown-7' },
{ _id: 3, descr: 'Auto, Moto e Veicoli', icon: 'fas fa-car', color: 'grey-9' },
{ _id: 4, descr: 'Artigianato e Creazioni', icon: 'fas fa-gem', color: 'amber-7' },
{ _id: 5, descr: 'Bellezza e Cura Persona', icon: 'fas fa-spa', color: 'pink-7' },
{ _id: 6, descr: 'Infanzia e Bambini', icon: 'fas fa-baby', color: 'cyan-7' },
{ _id: 7, descr: 'Cibi e Bevande', icon: 'fas fa-apple-alt', color: 'orange-7' },
{ _id: 8, descr: 'Collezionismo e Antiquariato', icon: 'fas fa-coins', color: 'brown-9' },
{ _id: 9, descr: 'Elettronica e Tecnologia', icon: 'fas fa-laptop', color: 'blue-grey-8' },
{ _id: 10, descr: 'Gaming e Console', icon: 'fas fa-gamepad', color: 'purple-7' },
{ _id: 11, descr: 'Giardinaggio e Fai da Te', icon: 'fas fa-seedling', color: 'green-7' },
{ _id: 12, descr: 'Libri e Riviste', icon: 'fas fa-book-open', color: 'indigo-7' },
{ _id: 13, descr: 'Musica e Film', icon: 'fas fa-film', color: 'deep-purple-7' },
{ _id: 14, descr: 'Scuola e Ufficio', icon: 'fas fa-pen', color: 'blue-8' },
{ _id: 15, descr: 'Sport e Fitness', icon: 'fas fa-bicycle', color: 'green-8' },
{ _id: 16, descr: 'Animali', icon: 'fas fa-paw', color: 'brown-6' },
{ _id: 17, descr: 'Attrezzature e Strumenti', icon: 'fas fa-tools', color: 'grey-8' },
{ _id: 18, descr: 'Elettrodomestici', icon: 'fas fa-plug', color: 'teal-7' },
{ _id: 19, descr: 'Agricoltura e Orticoltura', icon: 'fas fa-tractor', color: 'green-9' },
{ _id: 20, descr: 'Arte e Decorazioni', icon: 'fas fa-palette', color: 'purple-6' },
],
};

View File

@@ -0,0 +1,19 @@
module.exports = {
list: [
{ _id: 1, descr: 'Abitare e Costruire', icon: 'fas fa-home', color: 'brown-7' },
{ _id: 2, descr: 'Agricoltura e Orticoltura', icon: 'fas fa-seedling', color: 'green-7' },
{ _id: 3, descr: 'Alimentazione e Trasformazione', icon: 'fas fa-apple-alt', color: 'orange-7' },
{ _id: 4, descr: 'Animali e Allevamento', icon: 'fas fa-paw', color: 'brown-6' },
{ _id: 5, descr: 'Artigianato e Creazione', icon: 'fas fa-hammer', color: 'amber-7' },
{ _id: 6, descr: 'Benessere e Cura Naturale', icon: 'fas fa-spa', color: 'teal-6' },
{ _id: 7, descr: 'Educazione e Formazione', icon: 'fas fa-book-reader', color: 'indigo-7' },
{ _id: 8, descr: 'Energia e Sostenibilità', icon: 'fas fa-solar-panel', color: 'yellow-8' },
{ _id: 9, descr: 'Manutenzione e Riparazione', icon: 'fas fa-tools', color: 'grey-8' },
{ _id: 10, descr: 'Mobilità e Trasporti', icon: 'fas fa-bicycle', color: 'blue-7' },
{ _id: 11, descr: 'Progetti Comunitari', icon: 'fas fa-users', color: 'purple-7' },
{ _id: 12, descr: 'Salute e Terapie', icon: 'fas fa-heart', color: 'red-6' },
{ _id: 13, descr: 'Tecnologie Appropriate', icon: 'fas fa-laptop-code', color: 'blue-grey-7' },
{ _id: 14, descr: 'Tessile e Sartoria', icon: 'fas fa-cut', color: 'pink-7' },
{ _id: 15, descr: 'Tempo Libero e Cultura', icon: 'fas fa-palette', color: 'deep-purple-6' },
],
};

View File

@@ -0,0 +1,163 @@
module.exports = {
list: [
// Abitare e Costruire
{ _id: 1, idSector: [1], descr: 'Autocostruzione', icon: 'fas fa-hammer' },
{ _id: 2, idSector: [1], descr: 'Bioedilizia', icon: 'fas fa-leaf' },
{ _id: 3, idSector: [1], descr: 'Cohousing', icon: 'home_work' },
{ _id: 4, idSector: [1], descr: 'Ecovillaggi e Comunità', icon: 'fas fa-users' },
{ _id: 5, idSector: [1], descr: 'Immobili in Vendita', icon: 'fas fa-home' },
{ _id: 6, idSector: [1], descr: 'Immobili in Affitto', icon: 'fas fa-key' },
{ _id: 7, idSector: [1], descr: 'Stanze Condivise', icon: 'fas fa-door-open' },
{ _id: 8, idSector: [1], descr: 'Terreni', icon: 'fas fa-map' },
// Agricoltura e Orticoltura
{ _id: 10, idSector: [2], descr: 'Aratura e Semina', icon: 'fas fa-tractor' },
{ _id: 11, idSector: [2], descr: 'Elettrocultura', icon: 'fas fa-bolt' },
{ _id: 12, idSector: [2], descr: 'Idroponica', icon: 'fas fa-flask' },
{ _id: 13, idSector: [2], descr: 'Orto Sinergico', icon: 'fas fa-seedling' },
{ _id: 14, idSector: [2], descr: 'Orto Tradizionale', icon: 'fas fa-carrot' },
{ _id: 15, idSector: [2], descr: 'Pacciamatura', icon: 'fas fa-layer-group' },
{ _id: 16, idSector: [2], descr: 'Permacultura', icon: 'fas fa-recycle' },
{ _id: 17, idSector: [2], descr: 'Potatura', icon: 'fas fa-cut' },
{ _id: 18, idSector: [2], descr: 'Raccolta', icon: 'fas fa-apple-alt' },
// Alimentazione e Trasformazione
{ _id: 20, idSector: [3], descr: 'Autoproduzione Alimenti', icon: 'kitchen' },
{ _id: 21, idSector: [3], descr: 'Autoproduzione Bevande', icon: 'fas fa-wine-bottle' },
{ _id: 22, idSector: [3], descr: 'Conservazione e Fermentazione', icon: 'fas fa-box' },
{ _id: 23, idSector: [3], descr: 'Home Restaurant', icon: 'fas fa-utensils' },
{ _id: 24, idSector: [3], descr: 'Panificazione', icon: 'fas fa-bread-slice' },
// Animali e Allevamento
{ _id: 30, idSector: [4], descr: 'Allevamento Etico', icon: 'fas fa-horse' },
{ _id: 31, idSector: [4], descr: 'Apicoltura', icon: 'hive' },
{ _id: 32, idSector: [4], descr: 'Cura Animali da Compagnia', icon: 'fas fa-paw' },
{ _id: 33, idSector: [4], descr: 'Veterinaria Olistica', icon: 'fas fa-stethoscope' },
// Artigianato e Creazione
{ _id: 40, idSector: [5], descr: 'Ceramica e Terracotta', icon: 'water_drop' },
{ _id: 41, idSector: [5], descr: 'Falegnameria', icon: 'fas fa-hammer' },
{ _id: 42, idSector: [5], descr: 'Lavorazione Metalli', icon: 'construction' },
{ _id: 43, idSector: [5], descr: 'Restauro', icon: 'fas fa-paint-brush' },
{ _id: 44, idSector: [5], descr: 'Sartoria e Ricamo', icon: 'fas fa-cut' },
// Benessere e Cura Naturale
{ _id: 50, idSector: [6], descr: 'Alimentazione Naturale', icon: 'fas fa-leaf' },
{ _id: 51, idSector: [6], descr: 'Arteterapia', icon: 'fas fa-palette' },
{ _id: 52, idSector: [6], descr: 'Cantoterapia', icon: 'fas fa-music' },
{ _id: 53, idSector: [6], descr: 'Coach Motivazionale', icon: 'fas fa-bullhorn' },
{ _id: 54, idSector: [6], descr: 'Costellazioni Familiari', icon: 'fas fa-users' },
{ _id: 55, idSector: [6], descr: 'Fitoterapia', icon: 'fas fa-mortar-pestle' },
{ _id: 56, idSector: [6], descr: 'Floriterapia', icon: 'local_florist' },
{ _id: 57, idSector: [6], descr: 'Ginnastica e Movimento', icon: 'fas fa-dumbbell' },
{ _id: 58, idSector: [6], descr: 'Kinesiologia', icon: 'fas fa-running' },
{ _id: 59, idSector: [6], descr: 'Massaggi', icon: 'fas fa-hands' },
{ _id: 60, idSector: [6], descr: 'Meditazione e Mindfulness', icon: 'fas fa-om' },
{ _id: 61, idSector: [6], descr: 'Naturopatia', icon: 'fas fa-leaf' },
{ _id: 62, idSector: [6], descr: 'Riflessologia', icon: 'footprint' },
{ _id: 63, idSector: [6], descr: 'Teatroterapia', icon: 'fas fa-theater-masks' },
{ _id: 64, idSector: [6], descr: 'Tecniche Essene', icon: 'fas fa-praying-hands' },
{ _id: 65, idSector: [6], descr: 'Trattamenti Energetici', icon: 'fas fa-hand-sparkles' },
{ _id: 66, idSector: [6], descr: 'Trattamenti Olistici', icon: 'fas fa-spa' },
{ _id: 67, idSector: [6], descr: 'Trattamenti Sonori', icon: 'fas fa-bell' },
{ _id: 68, idSector: [6], descr: 'Yoga', icon: 'self_improvement' },
{ _id: 69, idSector: [6], descr: 'Biodanza', icon: 'fas fa-seedling' },
// Educazione e Formazione
{ _id: 70, idSector: [7], descr: 'Corsi e Workshop', icon: 'fas fa-chalkboard-teacher' },
{ _id: 71, idSector: [7], descr: 'Educazione Parentale', icon: 'fas fa-child' },
{ _id: 72, idSector: [7], descr: 'Formazione Tecnica', icon: 'fas fa-tools' },
{ _id: 73, idSector: [7], descr: 'Lingue', icon: 'fas fa-language' },
{ _id: 74, idSector: [7], descr: 'Scuola Libertaria', icon: 'fas fa-book-reader' },
{ _id: 75, idSector: [7], descr: 'Consulenze Professionali', icon: 'fas fa-briefcase' },
{ _id: 76, idSector: [7], descr: 'Preparazione Esami', icon: 'school' },
// Energia e Sostenibilità
{ _id: 80, idSector: [8], descr: 'Biogas e Biomasse', icon: 'fas fa-fire' },
{ _id: 81, idSector: [8], descr: 'Compostaggio', icon: 'fas fa-recycle' },
{ _id: 82, idSector: [8], descr: 'Fotovoltaico', icon: 'fas fa-solar-panel' },
{ _id: 83, idSector: [8], descr: 'Gestione Rifiuti', icon: 'fas fa-trash-alt' },
{ _id: 84, idSector: [8], descr: 'Pompe di Calore', icon: 'fas fa-temperature-high' },
{ _id: 85, idSector: [8], descr: 'Solare Termico', icon: 'fas fa-sun' },
{ _id: 86, idSector: [8], descr: 'Stufe e Caminetti', icon: 'fas fa-fire-alt' },
// Manutenzione e Riparazione
{ _id: 90, idSector: [9], descr: 'Arredamento', icon: 'fas fa-couch' },
{ _id: 91, idSector: [9], descr: 'Elettricista', icon: 'fas fa-plug' },
{ _id: 92, idSector: [9], descr: 'Fabbro', icon: 'fas fa-wrench' },
{ _id: 93, idSector: [9], descr: 'Giardiniere', icon: 'fas fa-leaf' },
{ _id: 94, idSector: [9], descr: 'Idraulico', icon: 'fas fa-faucet' },
{ _id: 95, idSector: [9], descr: 'Imbianchino', icon: 'fas fa-paint-roller' },
{ _id: 96, idSector: [9], descr: 'Muratore', icon: 'fas fa-hard-hat' },
{ _id: 97, idSector: [9], descr: 'Piastrellista', icon: 'fas fa-th' },
{ _id: 98, idSector: [9], descr: 'Pulizie', icon: 'fas fa-broom' },
{ _id: 99, idSector: [9], descr: 'Riparazione Elettrodomestici', icon: 'fas fa-blender' },
{ _id: 100, idSector: [9], descr: 'Traslochi', icon: 'fas fa-truck' },
{ _id: 101, idSector: [9], descr: 'Tuttofare', icon: 'fas fa-toolbox' },
// Mobilità e Trasporti
{ _id: 110, idSector: [10], descr: 'Biciclette', icon: 'fas fa-bicycle' },
{ _id: 111, idSector: [10], descr: 'Camperizzazione', icon: 'fas fa-caravan' },
{ _id: 112, idSector: [10], descr: 'Car Sharing', icon: 'fas fa-car' },
{ _id: 113, idSector: [10], descr: 'Lavaggio Auto', icon: 'fas fa-water' },
{ _id: 114, idSector: [10], descr: 'Meccanica Auto', icon: 'fas fa-car-crash' },
{ _id: 115, idSector: [10], descr: 'Meccanica Moto', icon: 'fas fa-motorcycle' },
// Progetti Comunitari
{ _id: 120, idSector: [11], descr: 'Banca del Tempo', icon: 'fas fa-clock' },
{ _id: 121, idSector: [11], descr: 'Collaborazioni', icon: 'fas fa-handshake' },
{ _id: 122, idSector: [11], descr: 'Eventi e Incontri', icon: 'fas fa-calendar-alt' },
{ _id: 123, idSector: [11], descr: 'Gruppi di Acquisto', icon: 'fas fa-shopping-basket' },
{ _id: 124, idSector: [11], descr: 'Laboratori', icon: 'fas fa-flask' },
{ _id: 125, idSector: [11], descr: 'Volontariato', icon: 'fas fa-hand-holding-heart' },
{ _id: 126, idSector: [11], descr: 'Servizi alla Comunità', icon: 'fas fa-hands-helping' },
// Salute e Terapie
{ _id: 130, idSector: [12], descr: 'Counseling', icon: 'fas fa-comments' },
{ _id: 131, idSector: [12], descr: 'Dentista', icon: 'fas fa-tooth' },
{ _id: 132, idSector: [12], descr: 'Infermieristica', icon: 'fas fa-user-nurse' },
{ _id: 133, idSector: [12], descr: 'Medicina di Base', icon: 'fas fa-user-md' },
{ _id: 134, idSector: [12], descr: 'Medicina Naturale', icon: 'fas fa-leaf' },
{ _id: 135, idSector: [12], descr: 'Nutrizionista', icon: 'fas fa-apple-alt' },
{ _id: 136, idSector: [12], descr: 'Omeopatia', icon: 'fas fa-vial' },
{ _id: 137, idSector: [12], descr: 'OSS', icon: 'fas fa-briefcase-medical' },
{ _id: 138, idSector: [12], descr: 'Ostetrica', icon: 'fas fa-baby' },
{ _id: 139, idSector: [12], descr: 'Pediatra', icon: 'fas fa-child' },
{ _id: 140, idSector: [12], descr: 'Psicologo', icon: 'fas fa-brain' },
{ _id: 141, idSector: [12], descr: 'Psicoterapeuta', icon: 'fas fa-couch' },
{ _id: 142, idSector: [12], descr: 'Specialisti', icon: 'fas fa-stethoscope' },
// Tecnologie Appropriate
{ _id: 150, idSector: [13], descr: 'Assistenza Cellulari', icon: 'fas fa-mobile-alt' },
{ _id: 151, idSector: [13], descr: 'Assistenza PC', icon: 'fas fa-laptop' },
{ _id: 152, idSector: [13], descr: 'Corsi Informatica', icon: 'fas fa-graduation-cap' },
{ _id: 153, idSector: [13], descr: 'Realizzazione App', icon: 'fas fa-mobile' },
{ _id: 154, idSector: [13], descr: 'Realizzazione Siti Web', icon: 'fas fa-globe' },
{ _id: 155, idSector: [13], descr: 'Software Libero', icon: 'fas fa-code' },
// Tessile e Sartoria
{ _id: 160, idSector: [14], descr: 'Autoproduzione Abbigliamento', icon: 'fas fa-tshirt' },
{ _id: 161, idSector: [14], descr: 'Filatura', icon: 'fas fa-spinner' },
{ _id: 162, idSector: [14], descr: 'Riparazioni Tessili', icon: 'checkroom' },
{ _id: 163, idSector: [14], descr: 'Sartoria su Misura', icon: 'fas fa-ruler' },
{ _id: 164, idSector: [14], descr: 'Tessitura', icon: 'fas fa-grip-lines' },
{ _id: 165, idSector: [14], descr: 'Tingere Naturale', icon: 'fas fa-tint' },
// Tempo Libero e Cultura
{ _id: 170, idSector: [15], descr: 'Arte e Pittura', icon: 'fas fa-palette' },
{ _id: 171, idSector: [15], descr: 'Ballo', icon: 'groups' },
{ _id: 172, idSector: [15], descr: 'Canto', icon: 'fas fa-microphone' },
{ _id: 173, idSector: [15], descr: 'Film Making', icon: 'fas fa-video' },
{ _id: 174, idSector: [15], descr: 'Fotografia', icon: 'fas fa-camera' },
{ _id: 175, idSector: [15], descr: 'Letteratura', icon: 'fas fa-book' },
{ _id: 176, idSector: [15], descr: 'Musica', icon: 'fas fa-music' },
{ _id: 177, idSector: [15], descr: 'Sport', icon: 'fas fa-running' },
{ _id: 178, idSector: [15], descr: 'Teatro', icon: 'fas fa-theater-masks' },
{ _id: 179, idSector: [15], descr: 'Altre Attività Culturali', icon: 'fas fa-question' },
{ _id: 180, idSector: [15], descr: 'Giochi da Tavolo', icon: 'extension' },
],
};

View File

@@ -5,6 +5,7 @@ const Path = require('path');
const mongoose = require('mongoose').set('debug', false) const mongoose = require('mongoose').set('debug', false)
const shared_consts = require('../tools/shared_nodejs'); const shared_consts = require('../tools/shared_nodejs');
const migration = require('./migration-categories');
module.exports = { module.exports = {
@@ -24,6 +25,7 @@ module.exports = {
}); });
} }
} }
} catch (e) { } catch (e) {
console.log('error insertIntoDb', e); console.log('error insertIntoDb', e);
} }
@@ -190,6 +192,9 @@ module.exports = {
} }
} }
// FAI LA MIGRAZIONE
migration.runMigration();
console.log('FINE - popolaTabelleNuove'); console.log('FINE - popolaTabelleNuove');
return true; return true;

View File

@@ -1,25 +0,0 @@
module.exports = {
list: [
{_id: 1, descr: 'Abbigliamento', icon: 'fas fa-tshirt', color: 'blue-7'},
{_id: 2, descr: 'Arredamento', icon: 'fas fa-chair', color: 'green-7'},
{_id: 3, descr: 'Auto e Moto', icon: 'fas fa-car', color: 'orange-7'},
{_id: 4, descr: 'Artigianato', icon: 'fas fa-screwdriver', color: 'red-7'},
{_id: 5, descr: 'Bellezza e Igiene', icon: 'fas fa-spa', color: 'purple-7'},
{_id: 6, descr: 'Bimbi', icon: 'fas fa-child', color: 'cyan-7'},
{_id: 7, descr: 'Cibo'},
{_id: 8, descr: 'Collezionismo e Antiquariato'},
{_id: 9, descr: 'Elettronica di Consumo'},
{_id: 10, descr: 'Giochi', icon: 'fas fa-gamepad', color: 'purple-7'},
{_id: 11, descr: 'Hobby', icon: 'fas fa-guitar', color: 'cyan-7'},
{_id: 12, descr: 'Libri', icon: 'fas fa-book', color: 'indigo-7'},
{_id: 13, descr: 'Musica e Film', icon: 'fas fa-music', color: 'brown-7'},
{_id: 14, descr: 'Scuola e Ufficio', icon: 'fas fa-graduation-cap', color: 'pink-7'},
{_id: 15, descr: 'Sport', icon: 'fas fa-futbol', color: 'orange-7'},
{_id: 16, descr: 'Un po\' di Tutto', icon: 'fas fa-globe-europe', color: 'red-7'},
{_id: 17, descr: 'Attrezzature', icon: 'fas fa-tools', color: 'blue-7'},
{_id: 18, descr: 'Animali', icon: 'fas fa-paw', color: 'green-7'},
{_id: 19, descr: 'Arte / Decorazioni', icon: 'fas fa-palette', color: 'purple-7'},
{_id: 20, descr: 'Agricoltura', icon: 'fas fa-seedling', color: 'green-7'},
],
};

View File

@@ -1,41 +0,0 @@
/*
module.exports = {
list: [
{ _id: 2, descr: 'Agricoltura', icon: 'fas fa-tractor', color: '#4CAF50' },
{ _id: 3, descr: 'Cibo e Ristorazione', icon: 'fas fa-utensils', color: '#FF9800' },
{ _id: 4, descr: 'Animali', icon: 'fas fa-paw', color: '#795548' },
{ _id: 5, descr: 'Auto e Veicoli', icon: 'fas fa-car', color: '#607D8B' },
{ _id: 6, descr: 'Salute e Benessere', icon: 'fas fa-heartbeat', color: '#E91E63' },
{ _id: 7, descr: 'Casa e Arredamento', icon: 'fas fa-home', color: '#9C27B0' },
{ _id: 8, descr: 'Attività Ricreative e di Intrattenim.', icon: 'fas fa-gamepad', color: '#FF5722' },
{ _id: 13, descr: 'Tecnologie', icon: 'fas fa-microchip', color: '#2196F3' },
{ _id: 15, descr: 'Artigianato', icon: 'fas fa-hammer', color: '#FFC107' },
{ _id: 16, descr: 'Arte e Cultura', icon: 'fas fa-palette', color: '#3F51B5' },
{ _id: 17, descr: 'Assistenza e Integrazione', icon: 'fas fa-hands-helping', color: '#00BCD4' },
{ _id: 18, descr: 'Attività fisica e sportiva', icon: 'fas fa-running', color: '#8BC34A' },
{ _id: 20, descr: 'Bambini', icon: 'fas fa-baby', color: '#FFEB3B' },
{ _id: 21, descr: 'Consulenza e Supporto Professionale', icon: 'fas fa-briefcase', color: '#9E9E9E' },
{ _id: 22, descr: 'Formazione e crescita personale', icon: 'fas fa-graduation-cap', color: '#673AB7' },
{ _id: 23, descr: 'Manutenzione e riparazione', icon: 'fas fa-wrench', color: '#F44336' },
{ _id: 24, descr: 'Mobilità e Trasporti', icon: 'fas fa-bus', color: '#009688' },
],
};
*/
module.exports = {
list: [
{_id: 1, descr: 'Abitare'},
{_id: 2, descr: 'Agricoltura'},
{_id: 3, descr: 'Alimentazione'},
{_id: 4, descr: 'Animali'},
{_id: 5, descr: 'Auto e Veicoli'},
{_id: 6, descr: 'Benessere'},
{_id: 7, descr: 'Per la Casa'},
{_id: 8, descr: 'Intrattenimento'},
{_id: 10, descr: 'Per la Persona'},
{_id: 11, descr: 'Progetti di Gruppo'},
{_id: 12, descr: 'Salute'},
{_id: 13, descr: 'Tecnologie'},
{_id: 14, descr: 'Servizi'},
],
};

913
src/router/api2_router.js Normal file
View File

@@ -0,0 +1,913 @@
/**
* API2 Router - Ollama Integration
*/
const express = require('express');
const router = express.Router();
// Configurazione Ollama
const OLLAMA_URL = process.env.OLLAMA_URL || 'http://localhost:11434';
const DEFAULT_MODEL = process.env.OLLAMA_DEFAULT_MODEL || 'llama3.2';
// ============================================
// ROUTES PRINCIPALI
// ============================================
/**
* Health check Ollama
* GET /api2/health
*/
router.get('/health', async (req, res) => {
try {
const response = await fetch(`${OLLAMA_URL}/api/tags`);
if (response.ok) {
const data = await response.json();
res.json({
status: 'ok',
ollama: 'connected',
modelsCount: data.models?.length || 0
});
} else {
res.status(503).json({ status: 'error', ollama: 'disconnected' });
}
} catch (error) {
res.status(503).json({
status: 'error',
ollama: 'unreachable',
message: error.message
});
}
});
/**
* Lista modelli disponibili
* GET /api2/models
*/
router.get('/models', async (req, res) => {
try {
const response = await fetch(`${OLLAMA_URL}/api/tags`);
if (!response.ok) {
console.error('[api2/models] Error:', response.status);
throw new Error(`Ollama error: ${response.status}`);
}
const data = await response.json();
res.json({ models: data.models || [] });
} catch (error) {
console.error('[api2/models] Error:', error);
res.status(500).json({ error: error.message });
}
});
/**
* Info modello specifico
* POST /api2/models/info
*/
router.post('/models/info', async (req, res) => {
try {
const { model } = req.body;
if (!model) {
return res.status(400).json({ error: 'Model name richiesto' });
}
const response = await fetch(`${OLLAMA_URL}/api/show`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: model }),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status}`);
}
const data = await response.json();
res.json(data);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ============================================
// GENERAZIONE TESTO
// ============================================
/**
* Generazione testo (non-streaming)
* POST /api2/generate
*/
router.post('/generate', async (req, res) => {
try {
const {
model = DEFAULT_MODEL,
prompt,
temperature = 0.7,
maxTokens,
system,
topP,
topK,
} = req.body;
if (!prompt) {
return res.status(400).json({ error: 'Prompt richiesto' });
}
const payload = {
model,
prompt,
stream: false,
options: {
temperature,
...(maxTokens && { num_predict: maxTokens }),
...(topP && { top_p: topP }),
...(topK && { top_k: topK }),
},
};
if (system) payload.system = system;
const response = await fetch(`${OLLAMA_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status}`);
}
const data = await response.json();
res.json({
response: data.response,
model: data.model,
totalDuration: data.total_duration,
loadDuration: data.load_duration,
promptEvalCount: data.prompt_eval_count,
evalCount: data.eval_count,
});
} catch (error) {
console.error('[api2/generate] Error:', error);
res.status(500).json({ error: error.message });
}
});
/**
* Generazione testo con streaming (SSE)
* POST /api2/generate/stream
*/
router.post('/generate/stream', async (req, res) => {
try {
const {
model = DEFAULT_MODEL,
prompt,
temperature = 0.7,
system,
maxTokens,
} = req.body;
if (!prompt) {
return res.status(400).json({ error: 'Prompt richiesto' });
}
const payload = {
model,
prompt,
stream: true,
options: {
temperature,
...(maxTokens && { num_predict: maxTokens }),
},
};
if (system) payload.system = system;
const response = await fetch(`${OLLAMA_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status}`);
}
// Setup SSE
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('X-Accel-Buffering', 'no');
const reader = response.body.getReader();
const decoder = new TextDecoder();
const pump = async () => {
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
res.write('data: [DONE]\n\n');
res.end();
break;
}
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(line => line.trim());
for (const line of lines) {
try {
const json = JSON.parse(line);
res.write(`data: ${JSON.stringify(json)}\n\n`);
} catch (e) {
// Ignora linee non JSON
}
}
}
} catch (error) {
console.error('[api2/generate/stream] Stream error:', error);
res.end();
}
};
// Handle client disconnect
req.on('close', () => {
reader.cancel();
});
pump();
} catch (error) {
console.error('[api2/generate/stream] Error:', error);
res.status(500).json({ error: error.message });
}
});
// ============================================
// CHAT
// ============================================
/**
* Chat (non-streaming)
* POST /api2/chat
*/
router.post('/chat', async (req, res) => {
try {
const {
model = DEFAULT_MODEL,
messages,
temperature = 0.7,
system,
maxTokens,
} = req.body;
if (!messages || !Array.isArray(messages)) {
return res.status(400).json({ error: 'Messages array richiesto' });
}
const payload = {
model,
messages,
stream: false,
options: {
temperature,
...(maxTokens && { num_predict: maxTokens }),
},
};
if (system) payload.system = system;
// console.log('payload', payload);
const response = await fetch(`${OLLAMA_URL}/api/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status} - ${response.statusText}`);
}
const data = await response.json();
res.json({
message: data.message,
model: data.model,
totalDuration: data.total_duration,
});
} catch (error) {
console.error('[api2/chat] Error:', error);
res.status(500).json({ error: error.message });
}
});
/**
* Chat con streaming (SSE)
* POST /api2/chat/stream
*/
router.post('/chat/stream', async (req, res) => {
try {
const {
model = DEFAULT_MODEL,
messages,
temperature = 0.7,
system,
maxTokens,
} = req.body;
if (!messages || !Array.isArray(messages)) {
return res.status(400).json({ error: 'Messages array richiesto' });
}
const payload = {
model,
messages,
stream: true,
options: {
temperature,
...(maxTokens && { num_predict: maxTokens }),
},
};
if (system) payload.system = system;
const response = await fetch(`${OLLAMA_URL}/api/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status}`);
}
// Setup SSE
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('X-Accel-Buffering', 'no');
const reader = response.body.getReader();
const decoder = new TextDecoder();
const pump = async () => {
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
res.write('data: [DONE]\n\n');
res.end();
break;
}
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(line => line.trim());
for (const line of lines) {
try {
const json = JSON.parse(line);
res.write(`data: ${JSON.stringify(json)}\n\n`);
} catch (e) {
// Ignora linee non JSON
}
}
}
} catch (error) {
console.error('[api2/chat/stream] Stream error:', error);
res.end();
}
};
// Handle client disconnect
req.on('close', () => {
reader.cancel();
});
pump();
} catch (error) {
console.error('[api2/chat/stream] Error:', error);
res.status(500).json({ error: error.message });
}
});
// ============================================
// ENDPOINTS HELPER
// ============================================
/**
* Genera codice
* POST /api2/code
*/
router.post('/code', async (req, res) => {
try {
const {
prompt,
language = 'javascript',
model = DEFAULT_MODEL,
temperature = 0.3,
} = req.body;
if (!prompt) {
return res.status(400).json({ error: 'Prompt richiesto' });
}
const systemPrompt = `Sei un esperto programmatore. Rispondi SOLO con codice ${language} valido e funzionante, senza spiegazioni prima o dopo. Usa commenti nel codice se necessario per spiegare.`;
const response = await fetch(`${OLLAMA_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt: `Scrivi codice ${language} per: ${prompt}`,
system: systemPrompt,
stream: false,
options: { temperature },
}),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status}`);
}
const data = await response.json();
res.json({
code: data.response,
language,
model: data.model,
});
} catch (error) {
console.error('[api2/code] Error:', error);
res.status(500).json({ error: error.message });
}
});
/**
* Traduci testo
* POST /api/translate
*/
router.post('/translate', async (req, res) => {
try {
const {
text,
targetLang = 'english',
sourceLang,
model = DEFAULT_MODEL,
} = req.body;
if (!text) {
return res.status(400).json({ error: 'Text richiesto' });
}
const sourceInfo = sourceLang ? ` da ${sourceLang}` : '';
const prompt = `Traduci il seguente testo${sourceInfo} in ${targetLang}. Rispondi SOLO con la traduzione, senza spiegazioni:\n\n${text}`;
const response = await fetch(`${OLLAMA_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt,
stream: false,
options: { temperature: 0.3 },
}),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status}`);
}
const data = await response.json();
res.json({
translation: data.response.trim(),
targetLang,
sourceLang,
});
} catch (error) {
console.error('[api2/translate] Error:', error);
res.status(500).json({ error: error.message });
}
});
/**
* Riassumi testo
* POST /api/summarize
*/
router.post('/summarize', async (req, res) => {
try {
const {
text,
maxLength,
style = 'conciso', // conciso, dettagliato, bullet
model = DEFAULT_MODEL,
} = req.body;
if (!text) {
return res.status(400).json({ error: 'Text richiesto' });
}
let styleInstruction = '';
switch (style) {
case 'bullet':
styleInstruction = 'Usa un elenco puntato.';
break;
case 'dettagliato':
styleInstruction = 'Fornisci un riassunto dettagliato.';
break;
default:
styleInstruction = 'Sii conciso e chiaro.';
}
const lengthInstruction = maxLength ? ` Massimo ${maxLength} parole.` : '';
const response = await fetch(`${OLLAMA_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt: `Riassumi il seguente testo. ${styleInstruction}${lengthInstruction}\n\n${text}`,
stream: false,
options: { temperature: 0.5 },
}),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status}`);
}
const data = await response.json();
res.json({
summary: data.response.trim(),
style,
});
} catch (error) {
console.error('[api2/summarize] Error:', error);
res.status(500).json({ error: error.message });
}
});
/**
* Analisi sentiment
* POST /api/sentiment
*/
router.post('/sentiment', async (req, res) => {
try {
const { text, model = DEFAULT_MODEL } = req.body;
if (!text) {
return res.status(400).json({ error: 'Text richiesto' });
}
const response = await fetch(`${OLLAMA_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt: `Analizza il sentiment del seguente testo e rispondi SOLO con un JSON valido nel formato:
{"sentiment": "positive" | "negative" | "neutral" | "mixed", "confidence": 0.0-1.0, "emotions": ["emotion1", "emotion2"], "explanation": "breve spiegazione"}
Testo da analizzare:
${text}`,
stream: false,
options: { temperature: 0.1 },
}),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status}`);
}
const data = await response.json();
try {
const jsonMatch = data.response.match(/\{[\s\S]*\}/);
if (jsonMatch) {
const parsed = JSON.parse(jsonMatch[0]);
res.json(parsed);
} else {
res.json({ sentiment: 'unknown', raw: data.response });
}
} catch (e) {
res.json({ sentiment: 'unknown', raw: data.response });
}
} catch (error) {
console.error('[api2/sentiment] Error:', error);
res.status(500).json({ error: error.message });
}
});
/**
* Estrai JSON strutturato da testo
* POST /api/extract
*/
router.post('/extract', async (req, res) => {
try {
const { text, schema, model = DEFAULT_MODEL } = req.body;
if (!text) {
return res.status(400).json({ error: 'Text richiesto' });
}
if (!schema) {
return res.status(400).json({ error: 'Schema richiesto' });
}
const response = await fetch(`${OLLAMA_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt: `Estrai le informazioni dal seguente testo e restituisci SOLO un JSON valido con questa struttura:
${JSON.stringify(schema, null, 2)}
Testo da analizzare:
${text}
Rispondi SOLO con il JSON, senza altro testo.`,
stream: false,
options: { temperature: 0.1 },
}),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status}`);
}
const data = await response.json();
try {
const jsonMatch = data.response.match(/\{[\s\S]*\}/);
if (jsonMatch) {
res.json(JSON.parse(jsonMatch[0]));
} else {
res.status(422).json({ error: 'Could not parse JSON', raw: data.response });
}
} catch (e) {
res.status(422).json({ error: 'Invalid JSON response', raw: data.response });
}
} catch (error) {
console.error('[api2/extract] Error:', error);
res.status(500).json({ error: error.message });
}
});
/**
* Correggi grammatica
* POST /api/grammar
*/
router.post('/grammar', async (req, res) => {
try {
const {
text,
language = 'italiano',
model = DEFAULT_MODEL,
} = req.body;
if (!text) {
return res.status(400).json({ error: 'Text richiesto' });
}
const response = await fetch(`${OLLAMA_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt: `Correggi gli errori grammaticali e di ortografia nel seguente testo in ${language}. Rispondi SOLO con il testo corretto, senza spiegazioni:\n\n${text}`,
stream: false,
options: { temperature: 0.2 },
}),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status}`);
}
const data = await response.json();
res.json({
corrected: data.response.trim(),
original: text,
language,
});
} catch (error) {
console.error('[api2/grammar] Error:', error);
res.status(500).json({ error: error.message });
}
});
/**
* Rispondi a domanda con contesto
* POST /api/qa
*/
router.post('/qa', async (req, res) => {
try {
const {
question,
context,
model = DEFAULT_MODEL,
} = req.body;
if (!question) {
return res.status(400).json({ error: 'Question richiesta' });
}
if (!context) {
return res.status(400).json({ error: 'Context richiesto' });
}
const response = await fetch(`${OLLAMA_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt: `Basandoti SOLO sul seguente contesto, rispondi alla domanda. Se la risposta non è nel contesto, dì che non hai abbastanza informazioni.
Contesto:
${context}
Domanda: ${question}
Risposta:`,
stream: false,
options: { temperature: 0.3 },
}),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status}`);
}
const data = await response.json();
res.json({
answer: data.response.trim(),
question,
});
} catch (error) {
console.error('[api2/qa] Error:', error);
res.status(500).json({ error: error.message });
}
});
/**
* Genera embeddings
* POST /api/embeddings
*/
router.post('/embeddings', async (req, res) => {
try {
const {
text,
model = 'nomic-embed-text',
} = req.body;
if (!text) {
return res.status(400).json({ error: 'Text richiesto' });
}
const response = await fetch(`${OLLAMA_URL}/api/embeddings`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt: text,
}),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status}`);
}
const data = await response.json();
res.json({
embedding: data.embedding,
dimensions: data.embedding?.length,
model,
});
} catch (error) {
console.error('[api2/embeddings] Error:', error);
res.status(500).json({ error: error.message });
}
});
/**
* Classifica testo in categorie
* POST /api/classify
*/
router.post('/classify', async (req, res) => {
try {
const {
text,
categories,
model = DEFAULT_MODEL,
} = req.body;
if (!text) {
return res.status(400).json({ error: 'Text richiesto' });
}
if (!categories || !Array.isArray(categories)) {
return res.status(400).json({ error: 'Categories array richiesto' });
}
const response = await fetch(`${OLLAMA_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt: `Classifica il seguente testo in UNA delle seguenti categorie: ${categories.join(', ')}
Rispondi SOLO con un JSON nel formato:
{"category": "categoria_scelta", "confidence": 0.0-1.0, "reason": "breve motivazione"}
Testo: ${text}`,
stream: false,
options: { temperature: 0.1 },
}),
});
if (!response.ok) {
throw new Error(`Ollama error: ${response.status}`);
}
const data = await response.json();
try {
const jsonMatch = data.response.match(/\{[\s\S]*\}/);
if (jsonMatch) {
res.json(JSON.parse(jsonMatch[0]));
} else {
res.json({ category: 'unknown', raw: data.response });
}
} catch (e) {
res.json({ category: 'unknown', raw: data.response });
}
} catch (error) {
console.error('[api2/classify] Error:', error);
res.status(500).json({ error: error.message });
}
});
/**
* Pull/scarica un nuovo modello
* POST /api2/models/pull
*/
router.post('/models/pull', async (req, res) => {
try {
const { model } = req.body;
if (!model) {
return res.status(400).json({ error: 'Model name richiesto' });
}
// Streaming del progresso
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const response = await fetch(`${OLLAMA_URL}/api/pull`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: model, stream: true }),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) {
res.write('data: [DONE]\n\n');
res.end();
break;
}
const chunk = decoder.decode(value);
res.write(`data: ${chunk}\n\n`);
}
} catch (error) {
res.status(500).json({ error: error.message });
}
});
/**
* Elimina un modello
* DELETE /api2/models/:name
*/
router.delete('/models/:name', async (req, res) => {
try {
const { name } = req.params;
const response = await fetch(`${OLLAMA_URL}/api/delete`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
});
if (response.ok) {
res.json({ success: true, message: `Modello ${name} eliminato` });
} else {
throw new Error(`Errore eliminazione: ${response.status}`);
}
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ============================================
// EXPORT
// ============================================
module.exports = router;

View File

@@ -2,8 +2,22 @@ const express = require('express');
const { authenticate, authenticate_noerror } = require('../middleware/authenticate'); const { authenticate, authenticate_noerror } = require('../middleware/authenticate');
const router = express.Router(); const router = express.Router();
const templatesRouter = require('../routes/templates');
const postersRouter = require('../routes/posters');
const assetsRouter = require('../routes/assets');
const PageView = require('../models/PageView'); const PageView = require('../models/PageView');
// const { Groq } = require('groq-sdk');
const fal = require('@fal-ai/client');
const imageGenerator = require('../services/imageGenerator'); // Assicurati che il percorso sia corretto
const posterEditor = require('../services/PosterEditor'); // <--- Importa la nuova classe
const multer = require('multer'); const multer = require('multer');
const XLSX = require('xlsx'); const XLSX = require('xlsx');
@@ -19,6 +33,10 @@ const { MyElem } = require('../models/myelem');
const axios = require('axios'); const axios = require('axios');
router.use('/templates', authenticate, templatesRouter);
router.use('/posters', authenticate, postersRouter);
router.use('/assets', authenticate, assetsRouter);
router.post('/test-lungo', authenticate, (req, res) => { router.post('/test-lungo', authenticate, (req, res) => {
const timeout = req.body.timeout; const timeout = req.body.timeout;
@@ -389,7 +407,6 @@ router.post('/search-books', authenticate, async (req, res) => {
let productfind = null; let productfind = null;
for (let field of book) { for (let field of book) {
field = field.trim(); field = field.trim();
let valido = typeof field === 'string' && field.length > 4 && field.length < 50; let valido = typeof field === 'string' && field.length > 4 && field.length < 50;
if (valido) { if (valido) {
@@ -494,4 +511,46 @@ router.post('/chatbot', authenticate, async (req, res) => {
} }
}); });
router.post('/generateposter', async (req, res) => {
const {
titolo, data, ora, luogo, descrizione, contatti, fotoDescrizione, stile,
provider = 'hf' // Default a HF (Gratis)
} = req.body;
// 1. Prompt per l'AI: Chiediamo SOLO lo sfondo, VIETIAMO il testo.
// Questo garantisce che Flux si concentri sulla bellezza dell'immagine.
const promptAI = `Vertical event poster background, ${stile || 'modern style, vivid colors'}.
Subject: ${fotoDescrizione || 'abstract artistic shapes'}.
Composition: Central empty space or clean layout suitable for overlaying text later.
NO TEXT, NO LETTERS, clean illustration, high quality, 4k.`;
try {
console.log('1. Generazione Sfondo AI...');
// Genera solo l'immagine base
const rawImageUrl = await imageGenerator.generate(provider, promptAI);
console.log('2. Composizione Grafica Testi...');
// Sovrapponi i testi con Canvas
const finalPosterBase64 = await posterEditor.createPoster(rawImageUrl, {
titolo,
data,
ora,
luogo,
contatti
});
res.json({
success: true,
imageUrl: finalPosterBase64, // Restituisce l'immagine completa in base64
step: 'AI + Canvas Composition'
});
} catch (err) {
console.error('Errore:', err.message);
res.status(500).json({ error: err.message });
}
});
module.exports = router; module.exports = router;

View File

@@ -54,11 +54,13 @@ const { MyElem } = require('../models/myelem');
const { Cron } = require('../models/cron'); const { Cron } = require('../models/cron');
const { Skill } = require('../models/skill'); const { Skill } = require('../models/skill');
const { Good } = require('../models/good'); const { Good } = require('../models/good');
const { Bacheca } = require('../models/bacheca');
const { StatusSkill } = require('../models/statusSkill'); const { StatusSkill } = require('../models/statusSkill');
const { Province } = require('../models/province'); const { Province } = require('../models/province');
const { City } = require('../models/city'); const { City } = require('../models/city');
const { Sector } = require('../models/sector'); const { Sector } = require('../models/sector');
const { SectorGood } = require('../models/sectorgood'); const { SectorGood } = require('../models/sectorgood');
const { SectorBacheca } = require('../models/sectorbacheca');
const { CatGrp } = require('../models/catgrp'); const { CatGrp } = require('../models/catgrp');
const Site = require('../models/site'); const Site = require('../models/site');
const { Level } = require('../models/level'); const { Level } = require('../models/level');
@@ -143,9 +145,9 @@ router.post('/ammetti', (req, res) => {
} else { } else {
const lang = user.lang; const lang = user.lang;
console.log('user', user); console.log('user', user);
user.verified_by_aportador = false; // user.verified_by_aportador = false;
if (user.verified_by_aportador) { if (user.verified_by_aportador) {
res.send({ return res.send({
code: server_constants.RIS_CODE_GIA_AMMESSO, code: server_constants.RIS_CODE_GIA_AMMESSO,
msg: 'Il membro ' + user.username + ' è stato già Ammesso!', msg: 'Il membro ' + user.username + ' è stato già Ammesso!',
}); });
@@ -180,7 +182,7 @@ router.post('/ammetti', (req, res) => {
// user.token_da_ammettere = 'OK'; // user.token_da_ammettere = 'OK';
user.save().then(() => { user.save().then(() => {
res.send({ return res.send({
code: server_constants.RIS_CODE_AMMESSO, code: server_constants.RIS_CODE_AMMESSO,
msg: 'Ottimo! Hai ammesso ' + user.username + '!', msg: 'Ottimo! Hai ammesso ' + user.username + '!',
//msg: tools.getres__('Ottimo! Hai ammesso', res) + ' ' + user.username + '!', //msg: tools.getres__('Ottimo! Hai ammesso', res) + ' ' + user.username + '!',
@@ -199,6 +201,67 @@ router.post('/ammetti', (req, res) => {
} }
}); });
router.post('/abcirc', async (req, res) => {
const body = _.pick(req.body, ['idapp', 'token', 'username', 'username_action', 'cmd', 'groupname']);
const idapp = body.idapp;
const token = body.token;
const username = body.username;
const groupname = body.groupname;
const username_action = body.username_action;
const cmd = parseInt(body.cmd);
try {
// Cerco il token se è ancora da ammettere
await User.getCircuitByTokenAndUsername(idapp, username, token)
.then(async (ris) => {
const { circuitname, user } = ris;
if (!user) {
return res.send({
code: server_constants.RIS_CODE_ERRORE,
msg: 'La richiesta di Abilitazione non è più presente su questo circuito !',
});
} else {
const lang = user.lang;
// console.log('user', user);
let nomeCircuito = circuitname;
let giaabilitato = await User.isFidoConcesso(idapp, username, nomeCircuito);
let ret = null;
// user.verified_by_aportador = false;
if (giaabilitato) {
return res.send({
code: server_constants.RIS_CODE_GIA_AMMESSO,
msg: 'Il membro <strong>' + user.username + '</strong> è già stato abilitato al circuito ' + nomeCircuito + '!',
circuitName: nomeCircuito,
});
} else {
ret = await User.setCircuitCmd(user.idapp, username, nomeCircuito, cmd, 0, username_action, {
groupname,
});
return res.send({
code: server_constants.RIS_CODE_AMMESSO,
msg: 'Ottimo! Hai abilitato ' + user.username + ' al circuito ' + nomeCircuito + '!',
circuitName: nomeCircuito,
//msg: tools.getres__('Ottimo! Hai ammesso', res) + ' ' + user.username + '!',
});
}
}
})
.catch((e) => {
console.log('Errore Abilitazione Circuito:', e.message);
res.status(400).send();
});
} catch (e) {
console.error('Errore: ', e);
res.status(400).send();
}
});
router.post(process.env.LINKVERIF_REG, (req, res) => { router.post(process.env.LINKVERIF_REG, (req, res) => {
const body = _.pick(req.body, ['idapp', 'idlink']); const body = _.pick(req.body, ['idapp', 'idlink']);
const idapp = body.idapp; const idapp = body.idapp;
@@ -237,6 +300,44 @@ router.post(process.env.LINKVERIF_REG, (req, res) => {
}); });
}); });
router.post(process.env.CHECKREVERIF_EMAIL, (req, res) => {
const body = _.pick(req.body, ['idapp', 'idlink']);
const idapp = body.idapp;
const idlink = body.idlink;
// Cerco l'idlink se è ancora da Verificare
User.findByLinkVerifEmail(idapp, idlink)
.then((user) => {
if (!user) {
//console.log("NON TROVATO!");
return res.status(404).send({code: RIS_CODE_ERRORE, msg: 'Verifica email non andata a buon fine. Ripetere.'});
} else {
console.log('user', user);
if (user.verified_email) {
res.send({
code: server_constants.RIS_CODE_EMAIL_ALREADY_VERIFIED,
msg: tools.getres__("L'Email è già stata Verificata", res),
});
} else {
user.verified_email = true;
user.lasttimeonline = new Date();
user.save().then(() => {
//console.log("TROVATOOOOOO!");
res.send({
code: server_constants.RIS_CODE_EMAIL_VERIFIED,
msg: tools.getres__('EMAIL', res) + ' ' + tools.getres__('VERIF', res),
});
});
}
}
})
.catch((e) => {
console.log(process.env.LINKVERIF_REG, e.message);
res.status(400).send();
});
});
router.post(process.env.ADD_NEW_SITE, async (req, res) => { router.post(process.env.ADD_NEW_SITE, async (req, res) => {
try { try {
const body = req.body; const body = req.body;
@@ -1597,7 +1698,9 @@ router.patch('/chval', authenticate, async (req, res) => {
profileData = await User.updateProvinceUserByComune(idapp, id, idcomune); profileData = await User.updateProvinceUserByComune(idapp, id, idcomune);
provincia = profileData['profile.resid_province']; provincia = profileData.profile.resid_province;
} else {
provincia = rec.profile.resid_province;
} }
if (provincia) { if (provincia) {
@@ -2199,7 +2302,9 @@ async function load(req, res, version = '0') {
adtypes: version >= 91 ? AdType.findAllIdApp(idapp) : Promise.resolve([]), adtypes: version >= 91 ? AdType.findAllIdApp(idapp) : Promise.resolve([]),
adtypegoods: version >= 91 ? AdTypeGood.findAllIdApp(idapp) : Promise.resolve([]), adtypegoods: version >= 91 ? AdTypeGood.findAllIdApp(idapp) : Promise.resolve([]),
sectorgoods: version >= 91 ? SectorGood.findAllIdApp(idapp) : Promise.resolve([]), sectorgoods: version >= 91 ? SectorGood.findAllIdApp(idapp) : Promise.resolve([]),
sectorbachecas: version >= 91 ? SectorBacheca.findAllIdApp(idapp) : Promise.resolve([]),
goods: version >= 91 ? Good.findAllIdApp(idapp) : Promise.resolve([]), goods: version >= 91 ? Good.findAllIdApp(idapp) : Promise.resolve([]),
bachecas: version >= 91 ? Bacheca.findAllIdApp(idapp) : Promise.resolve([]),
site: version >= 91 ? Site.findAllIdApp(idapp) : Promise.resolve([]), site: version >= 91 ? Site.findAllIdApp(idapp) : Promise.resolve([]),
mygroups: version >= 91 ? MyGroup.findAllGroups(idapp) : Promise.resolve([]), mygroups: version >= 91 ? MyGroup.findAllGroups(idapp) : Promise.resolve([]),
listcircuits: version >= 91 ? Circuit.findAllIdApp(idapp) : Promise.resolve([]), listcircuits: version >= 91 ? Circuit.findAllIdApp(idapp) : Promise.resolve([]),
@@ -2344,6 +2449,8 @@ async function load(req, res, version = '0') {
crons: data.crons, crons: data.crons,
raccoltacataloghis: data.raccoltacataloghis, raccoltacataloghis: data.raccoltacataloghis,
statuscode2: data.statuscode2, statuscode2: data.statuscode2,
sectorbachecas: data.sectorbachecas,
bachecas: data.bachecas,
}; };
} }

View File

@@ -134,7 +134,7 @@ router.post('/send', authenticate, async (req, res) => {
params.usernameDest = req.user.username; params.usernameDest = req.user.username;
} }
const ris = await globalTables.SendMsgToParam(idapp, params); const ris = await globalTables.SendMsgToParam(idapp, params, null);
return res.send({ return res.send({
code: server_constants.RIS_CODE_OK, code: server_constants.RIS_CODE_OK,

View File

@@ -64,7 +64,7 @@ router.post('/', authenticate, (req, res) => {
myrecsend.msg = recmsg.message; myrecsend.msg = recmsg.message;
let myid = recmsg._id; let myid = recmsg._id;
// ##Todo !! DA SISTEMARE !!! // ##Todo !! DA SISTEMARE !!!
return await SendNotif.saveAndSendNotif(myrecsend, req, res).then((out) => { return await SendNotif.saveAndSendNotif(myrecsend, req, res, null).then((out) => {
if (out) if (out)
return res.send({ code: server_constants.RIS_CODE_OK, msg: '', id: myid }); return res.send({ code: server_constants.RIS_CODE_OK, msg: '', id: myid });
else else

View File

@@ -21,7 +21,7 @@ router.post('/', authenticate, async (req, res) => {
tools.mylog('crea SendNotif'); tools.mylog('crea SendNotif');
let myrecnotif = new SendNotif(body); let myrecnotif = new SendNotif(body);
const recout = await SendNotif.saveAndSendNotif(myrecnotif, req, res); const recout = await SendNotif.saveAndSendNotif(myrecnotif, req, res, null);
if (recout) { if (recout) {
return res.send({ code: server_constants.RIS_CODE_OK, notif: '', record: recout }); return res.send({ code: server_constants.RIS_CODE_OK, notif: '', record: recout });
} else { } else {

View File

@@ -506,13 +506,15 @@ router.post('/profile', authenticate, (req, res) => {
const perm = req.user ? req.user.perm : tools.Perm.PERM_NONE; const perm = req.user ? req.user.perm : tools.Perm.PERM_NONE;
const username = req.body['username']; const username = req.body['username'];
const idapp = req.body.idapp; const idapp = req.body.idapp;
const idnotif = req.body['idnotif'] || '';
//++Todo: controlla che tipo di dati ha il permesso di leggere //++Todo: controlla che tipo di dati ha il permesso di leggere
try { try {
// Check if ìs a Notif to read // Check if ìs a Notif to read
const idnotif = req.body['idnotif'] ? req.body['idnotif'] : ''; if (idnotif) {
SendNotif.setNotifAsRead(idapp, usernameOrig, idnotif); SendNotif.setNotifAsRead(idapp, usernameOrig, idnotif);
}
return User.getUserProfileByUsername(idapp, username, usernameOrig, false, perm) return User.getUserProfileByUsername(idapp, username, usernameOrig, false, perm)
.then((ris) => { .then((ris) => {
@@ -601,6 +603,7 @@ router.post('/panel', authenticate, async (req, res) => {
username: 1, username: 1,
name: 1, name: 1,
surname: 1, surname: 1,
verified_email: 1,
email: 1, email: 1,
verified_by_aportador: 1, verified_by_aportador: 1,
aportador_solidario: 1, aportador_solidario: 1,
@@ -1122,6 +1125,13 @@ async function eseguiDbOpUser(idapp, mydata, locale, req, res) {
await User.ripristinaPwdPrec(mydata); await User.ripristinaPwdPrec(mydata);
} else if (mydata.dbop === 'noCircuit') { } else if (mydata.dbop === 'noCircuit') {
await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noCircuit': mydata.value } }); await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noCircuit': mydata.value } });
} else if (mydata.dbop === 'noComune') {
await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noComune': mydata.value } });
} else if (mydata.dbop === 'verifiedemail') {
await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'verified_email': mydata.value } });
} else if (mydata.dbop === 'resendVerificationEmail') {
// Invia la email di Verifica email
const ris = await sendemail.sendEmail_ReVerifyingEmail(mydata, idapp);
} else if (mydata.dbop === 'noCircIta') { } else if (mydata.dbop === 'noCircIta') {
await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noCircIta': mydata.value } }); await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noCircIta': mydata.value } });
} else if (mydata.dbop === 'insert_circuito_ita') { } else if (mydata.dbop === 'insert_circuito_ita') {

21
src/routes/assets.js Normal file
View File

@@ -0,0 +1,21 @@
const express = require('express');
const router = express.Router();
const assetController = require('../controllers/assetController');
const { authenticate } = require('../middleware/authenticate');
const upload = require('../middleware/upload');
// Upload
router.post('/upload', authenticate, upload.single('file'), assetController.upload);
router.post('/upload-multiple', authenticate, upload.array('files', 10), assetController.uploadMultiple);
// AI Generation
router.post('/generate-ai', authenticate, assetController.generateAi);
// CRUD
router.get('/', authenticate, assetController.list);
router.get('/:id', assetController.getById);
router.get('/:id/file', assetController.getFile);
router.get('/:id/thumbnail', assetController.getThumbnail);
router.delete('/:id', authenticate, assetController.delete);
module.exports = router;

25
src/routes/posters.js Normal file
View File

@@ -0,0 +1,25 @@
const express = require('express');
const router = express.Router();
const posterController = require('../controllers/posterController');
const { authenticate } = require('../middleware/authenticate');
const upload = require('../middleware/upload');
// CRUD Posters
router.post('/', authenticate, posterController.create);
router.get('/', authenticate, posterController.list);
router.get('/favorites', authenticate, posterController.listFavorites);
router.get('/recent', authenticate, posterController.listRecent);
router.get('/:id', authenticate, posterController.getById);
router.put('/:id', authenticate, posterController.update);
router.delete('/:id', authenticate, posterController.delete);
// Azioni speciali
router.post('/:id/render', authenticate, posterController.render);
router.post('/:id/regenerate-ai', authenticate, posterController.regenerateAi);
router.get('/:id/download/:format', posterController.download);
router.post('/:id/favorite', authenticate, posterController.toggleFavorite);
// Quick generate (come nella tua bozza)
router.post('/quick-generate', authenticate, posterController.quickGenerate);
module.exports = router;

29
src/routes/sync.js Normal file
View File

@@ -0,0 +1,29 @@
// server/routes/sync.js
const express = require('express');
const router = express.Router();
const SyncManager = require('../services/SyncManager');
const { authenticate_noerror_WithUserLean } = require('../middleware/authenticate');
router.post('/sync/:idapp', authenticate_noerror_WithUserLean, async (req, res) => {
try {
const { idapp } = req.params;
const { tables } = req.body; // { users: { lastSync: 0 }, groups: { lastSync: 123456 } }
const results = await SyncManager.sync(tables, idapp, req.user);
res.json({
success: true,
data: results,
serverTime: Date.now()
});
} catch (error) {
console.error('Sync error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
module.exports = router;

17
src/routes/templates.js Normal file
View File

@@ -0,0 +1,17 @@
const express = require('express');
const router = express.Router();
const templateController = require('../controllers/templateController');
const { authenticate } = require('../middleware/authenticate');
// CRUD Templates
router.post('/', authenticate, templateController.create);
router.get('/', templateController.list);
router.get('/types', templateController.getTypes);
router.get('/presets', templateController.getFormatPresets);
router.get('/:id', templateController.getById);
router.put('/:id', authenticate, templateController.update);
router.delete('/:id', authenticate, templateController.delete);
router.post('/:id/duplicate', authenticate, templateController.duplicate);
router.get('/:id/preview', templateController.getPreview);
module.exports = router;

View File

@@ -0,0 +1,33 @@
const Template = require('../models/Template');
const templateSeeds = require('../templates/template-seeds');
const MONGODB_URI = process.env.MONGODB_URI || '';
async function seedTemplates() {
try {
// await mongoose.connect(MONGODB_URI);
// Opzionale: rimuovi template esistenti con stessi templateType
const existingTypes = templateSeeds.map(t => t.templateType);
await Template.deleteMany({ templateType: { $in: existingTypes } });
console.log('✓ Template esistenti rimossi');
// Inserisci nuovi template
const result = await Template.insertMany(templateSeeds);
console.log(`${result.length} template inseriti con successo`);
// Log dei template creati
result.forEach(t => {
console.log(` - ${t.name} (${t.templateType})`);
});
// await mongoose.disconnect();
console.log('✓ Disconnesso da MongoDB');
process.exit(0);
} catch (error) {
console.error('✗ Errore seeding:', error);
process.exit(1);
}
}
seedTemplates();

View File

@@ -14,4 +14,3 @@ const seedTemplates = async () => {
}; };
seedTemplates(); seedTemplates();
s

View File

@@ -368,17 +368,17 @@ function checkifSendEmail() {
module.exports = { module.exports = {
sendEmail_base_e_manager: async function (idapp, template, to, mylocalsconf, replyTo, transport, previewonly) { sendEmail_base_e_manager: async function (idapp, template, to, mylocalsconf, replyTo, transport, previewonly) {
await this.sendEmail_base(template, to, mylocalsconf, replyTo, transport, previewonly); await this.sendEmail_base(idapp, template, to, mylocalsconf, replyTo, transport, previewonly);
await this.sendEmail_base(template, tools.getAdminEmailByIdApp(idapp), mylocalsconf, '', transport, previewonly); await this.sendEmail_base(idapp, template, tools.getAdminEmailByIdApp(idapp), mylocalsconf, '', transport, previewonly);
if (tools.isManagAndAdminDifferent(idapp)) { if (tools.isManagAndAdminDifferent(idapp)) {
const email = tools.getManagerEmailByIdApp(idapp); const email = tools.getManagerEmailByIdApp(idapp);
await this.sendEmail_base(template, email, mylocalsconf, '', transport, previewonly); await this.sendEmail_base(idapp, template, email, mylocalsconf, '', transport, previewonly);
} }
}, },
sendEmail_base: async function (template, to, mylocalsconf, replyTo, transport, previewonly) { sendEmail_base: async function (idapp, template, to, mylocalsconf, replyTo, transport, previewonly) {
if (to === '') return false; if (to === '') return false;
// console.log('mylocalsconf', mylocalsconf); // console.log('mylocalsconf', mylocalsconf);
@@ -389,9 +389,17 @@ module.exports = {
if (!replyTo) replyTo = ''; if (!replyTo) replyTo = '';
const emailSender = mylocalsconf.dataemail.from;
let senderName = '';
if (idapp) {
senderName = tools.getNomeAppByIdApp(mylocalsconf.idapp);
}
const emailcompleta = senderName ? `"${senderName}" <${emailSender}>` : emailSender;
const paramemail = { const paramemail = {
message: { message: {
from: mylocalsconf.dataemail.from, // sender address from: emailcompleta,
headers: { headers: {
'Reply-To': replyTo, 'Reply-To': replyTo,
}, },
@@ -457,9 +465,12 @@ module.exports = {
sendEmail_Normale: async function (mylocalsconf, to, subject, html, replyTo) { sendEmail_Normale: async function (mylocalsconf, to, subject, html, replyTo) {
try { try {
const emailSender = tools.getEmailByIdApp(mylocalsconf.idapp);
const senderName = tools.getNomeAppByIdApp(mylocalsconf.idapp);
// setup e-mail data with unicode symbols // setup e-mail data with unicode symbols
var mailOptions = { var mailOptions = {
from: tools.getEmailByIdApp(mylocalsconf.idapp), // sender address from: `"${senderName}" <${emailSender}>`,
dataemail: await this.getdataemail(mylocalsconf.idapp), dataemail: await this.getdataemail(mylocalsconf.idapp),
to: to, to: to,
generateTextFromHTML: true, generateTextFromHTML: true,
@@ -494,6 +505,21 @@ module.exports = {
const strlinkreg = tools.getHostByIdApp(idapp) + process.env.LINKVERIF_REG + `/?idapp=${idapp}&idlink=${idreg}`; const strlinkreg = tools.getHostByIdApp(idapp) + process.env.LINKVERIF_REG + `/?idapp=${idapp}&idlink=${idreg}`;
return strlinkreg; return strlinkreg;
}, },
getlinkVerifyEmail: async function (idapp, email, username) {
try {
const reg = require('./reg/registration');
const idverif = reg.getlinkregByEmail(idapp, email, username);
await User.setLinkToVerifiedEmail(idapp, username, idverif);
const strlinkreg =
tools.getHostByIdApp(idapp) + process.env.CHECKREVERIF_EMAIL + `/?idapp=${idapp}&idlink=${idverif}`;
return strlinkreg;
} catch (e) {
console.error('ERROR getlinkVerifyEmail');
}
},
getlinkInvitoReg: function (idapp, dati) { getlinkInvitoReg: function (idapp, dati) {
const strlinkreg = tools.getHostByIdApp(idapp) + `/invitetoreg/${dati.token}`; const strlinkreg = tools.getHostByIdApp(idapp) + `/invitetoreg/${dati.token}`;
return strlinkreg; return strlinkreg;
@@ -505,9 +531,24 @@ module.exports = {
} }
return ''; return '';
}, },
getLinkAbilitaCircuito: function (idapp, user, data) {
if (data.token_circuito_da_ammettere) {
const strlink =
tools.getHostByIdApp(idapp) +
`/abcirc/${data.cmd}/${data.token_circuito_da_ammettere}/${user.username}/${data.myusername}`;
return strlink;
}
return '';
},
getPathEmail(idapp, email_template) { getPathEmail(idapp, email_template) {
const RISO_TEMPLATES = ['reg_notifica_all_invitante', 'reg_email_benvenuto_ammesso', 'reg_chiedi_ammettere_all_invitante']; const RISO_TEMPLATES = [
'reg_notifica_all_invitante',
'reg_email_benvenuto_ammesso',
'reg_chiedi_ammettere_all_invitante',
'circuit_chiedi_facilitatori_di_entrare',
'circuit_abilitato_al_fido_membro',
];
if (idapp === '13') { if (idapp === '13') {
if (RISO_TEMPLATES.includes(email_template)) { if (RISO_TEMPLATES.includes(email_template)) {
@@ -543,6 +584,7 @@ module.exports = {
usernameInvitante: user.aportador_solidario, usernameInvitante: user.aportador_solidario,
nomeInvitante: nomecognomeInvitante.trim(), nomeInvitante: nomecognomeInvitante.trim(),
nomeInvitato: await User.getNameSurnameEUsernameByUsername(idapp, user.username), nomeInvitato: await User.getNameSurnameEUsernameByUsername(idapp, user.username),
userprofile: await User.getProfileByUsername(idapp, user.username),
usernameInvitato: user.username, usernameInvitato: user.username,
emailInvitato: user.email, emailInvitato: user.email,
user, user,
@@ -561,34 +603,24 @@ module.exports = {
} }
//Invia una email al nuovo utente //Invia una email al nuovo utente
await this.sendEmail_base(quale_email_inviare, emailto, mylocalsconf, tools.getreplyToEmailByIdApp(idapp)); await this.sendEmail_base(idapp, quale_email_inviare, emailto, mylocalsconf, tools.getreplyToEmailByIdApp(idapp));
if (user.verified_email && user.aportador_solidario && user.verified_by_aportador) { if (user.verified_email && user.aportador_solidario && user.verified_by_aportador) {
const pathemail = this.getPathEmail(idapp, 'reg_notifica_all_invitante'); const pathemail = this.getPathEmail(idapp, 'reg_notifica_all_invitante');
// Manda anche una email al suo Invitante // Manda anche una email al suo Invitante
const recaportador = await User.getUserShortDataByUsername(idapp, user.aportador_solidario); const recaportador = await User.getUserShortDataByUsername(idapp, user.aportador_solidario);
const ris = await this.sendEmail_base( const ris = await this.sendEmail_base(idapp, pathemail + '/' + tools.LANGADMIN, recaportador.email, mylocalsconf, '');
pathemail + '/' + tools.LANGADMIN,
recaportador.email,
mylocalsconf,
''
);
} else if (user.aportador_solidario && !user.verified_by_aportador) { } else if (user.aportador_solidario && !user.verified_by_aportador) {
const pathemail = this.getPathEmail(idapp, 'reg_chiedi_ammettere_all_invitante'); const pathemail = this.getPathEmail(idapp, 'reg_chiedi_ammettere_all_invitante');
// Manda una email al suo Invitante per chiedere di essere ammesso // Manda una email al suo Invitante per chiedere di essere ammesso
const recaportador = await User.getUserShortDataByUsername(idapp, user.aportador_solidario); const recaportador = await User.getUserShortDataByUsername(idapp, user.aportador_solidario);
const ris = await this.sendEmail_base( const ris = await this.sendEmail_base(idapp, pathemail + '/' + tools.LANGADMIN, recaportador.email, mylocalsconf, '');
pathemail + '/' + tools.LANGADMIN,
recaportador.email,
mylocalsconf,
''
);
} }
// Send to the Admin an Email // Send to the Admin an Email
const ris = await this.sendEmail_base( const ris = await this.sendEmail_base(idapp,
'admin/registration/' + tools.LANGADMIN, 'admin/registration/' + tools.LANGADMIN,
tools.getAdminEmailByIdApp(idapp), tools.getAdminEmailByIdApp(idapp),
mylocalsconf, mylocalsconf,
@@ -630,7 +662,7 @@ module.exports = {
messaggioPersonalizzato: dati.messaggioPersonalizzato, messaggioPersonalizzato: dati.messaggioPersonalizzato,
}; };
const ris = await this.sendEmail_base('invitaamico/' + lang, emailto, mylocalsconf, ''); const ris = await this.sendEmail_base(idapp, 'invitaamico/' + lang, emailto, mylocalsconf, '');
await telegrambot.notifyToTelegram(telegrambot.phase.INVITA_AMICO, mylocalsconf); await telegrambot.notifyToTelegram(telegrambot.phase.INVITA_AMICO, mylocalsconf);
@@ -657,7 +689,7 @@ module.exports = {
const quale_email_inviare = this.getPathEmail(idapp, 'reg_email_benvenuto_ammesso') + '/' + lang; const quale_email_inviare = this.getPathEmail(idapp, 'reg_email_benvenuto_ammesso') + '/' + lang;
const ris = await this.sendEmail_base(quale_email_inviare, emailto, mylocalsconf, ''); const ris = await this.sendEmail_base(idapp, quale_email_inviare, emailto, mylocalsconf, '');
await telegrambot.notifyToTelegram(telegrambot.phase.AMMETTI_UTENTE, mylocalsconf); await telegrambot.notifyToTelegram(telegrambot.phase.AMMETTI_UTENTE, mylocalsconf);
@@ -666,6 +698,127 @@ module.exports = {
console.error('Err sendEmail_Utente_Ammesso', e); console.error('Err sendEmail_Utente_Ammesso', e);
} }
}, },
sendEmail_ReVerifyingEmail: async function (dati, idapp) {
try {
const user = await User.getUserById(idapp, dati._id);
const lang = user.lang;
const username = user.username;
const email = user.email;
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),
emailto: email,
name: user.name,
username: user.username,
verifyLink: await this.getlinkVerifyEmail(idapp, email, username),
user,
supportEmail: tools.getContactEmailSupportBydApp(idapp),
};
const quale_email_inviare = this.getPathEmail(idapp, 'reg_resend_email_to_verifiyng') + '/' + lang;
const ris = await this.sendEmail_base(idapp, quale_email_inviare, email, mylocalsconf, '');
return ris;
} catch (e) {
console.error('Err sendEmail_ReVerifyingEmail', e);
}
},
sendEmail_Utente_Abilitato_Circuito_FidoConcesso: async function (lang, emailto, user, idapp, dati) {
try {
const { Circuit } = require('../models/circuit');
let mylocalsconf = {
idapp,
dataemail: await this.getdataemail(idapp),
baseurl: tools.getHostByIdApp(idapp),
locale: lang,
nomeapp: tools.getNomeAppByIdApp(idapp),
strlinksito: tools.getHostByIdApp(idapp),
emailto: emailto,
usernameInvitante: dati.usernameInvitante,
linkProfiloAdmin: tools.getLinkUserProfile(idapp, dati.usernameInvitante),
user,
symbol: await Circuit.getSymbolByCircuitId(dati.circuitId),
usernameMembro: user.username,
nomeTerritorio: dati.nomeTerritorio,
linkTelegramTerritorio: dati.link_group,
};
const quale_email_inviare = this.getPathEmail(idapp, 'circuit_abilitato_al_fido_membro') + '/' + lang;
const ris = await this.sendEmail_base(idapp, quale_email_inviare, emailto, mylocalsconf, '');
await telegrambot.notifyToTelegram(telegrambot.phase.AMMETTI_UTENTE, mylocalsconf);
return ris;
} catch (e) {
console.error('Err sendEmail_Utente_Ammesso', e);
}
},
sendEmail_Richiesta_Al_Facilitatore_Di_FarEntrare_AlCircuito: async function (
lang,
emailto,
user,
userInvitante,
idapp,
dati
) {
try {
// dati.circuitId
// dati.groupname
dati.cmd = shared_consts.CIRCUITCMD.SETFIDO;
const linkAbilitazione = this.getLinkAbilitaCircuito(idapp, user, dati);
const { Circuit } = require('../models/circuit');
let mylocalsconf = {
idapp,
dataemail: await this.getdataemail(idapp),
baseurl: tools.getHostByIdApp(idapp),
locale: lang,
usernameInvitante: user.aportador_solidario,
nomeInvitante: userInvitante.name,
cognomeInvitante: userInvitante.surname,
nomeapp: tools.getNomeAppByIdApp(idapp),
strlinksito: tools.getHostByIdApp(idapp),
//strlinkreg: this.getlinkReg(idapp, idreg),
emailto: emailto,
usernameMembro: user.username,
nomeMembro: user.name,
cognomeMembro: user.surname,
emailMembro: user.email,
nomeFacilitatore: dati.nomeFacilitatore,
nomeTerritorio: dati.nomeTerritorio,
comuneResidenza: user.profile.resid_str_comune,
provinciaResidenza: user.profile.resid_province,
user,
symbol: await Circuit.getSymbolByCircuitId(dati.circuitId),
linkAbilitazione: linkAbilitazione,
linkProfiloMembro: tools.getLinkUserProfile(idapp, user.username),
linkProfiloInvitante: tools.getLinkUserProfile(idapp, user.aportador_solidario),
telegramMembro: user.profile?.username_telegram,
telegramInvitante: userInvitante.profile?.username_telegram,
};
const quale_email_inviare = this.getPathEmail(idapp, 'circuit_chiedi_facilitatori_di_entrare') + '/' + lang;
const ris = await this.sendEmail_base(idapp, quale_email_inviare, emailto, mylocalsconf, '');
// await telegrambot.notifyToTelegram(telegrambot.phase.AMMETTI_UTENTE, mylocalsconf);
return ris;
} catch (e) {
console.error('Err sendEmail_Richiesta_Al_Facilitatore_Di_FarEntrare_AlCircuito', e);
}
},
sendEmail_IscrizioneConacreis: async function (lang, emailto, iscritto, idapp) { sendEmail_IscrizioneConacreis: async function (lang, emailto, iscritto, idapp) {
// console.log('idapp', idapp, tools.getNomeAppByIdApp(idapp)); // console.log('idapp', idapp, tools.getNomeAppByIdApp(idapp));
@@ -685,6 +838,7 @@ module.exports = {
mylocalsconf = this.setParamsForTemplate(iscritto, mylocalsconf); mylocalsconf = this.setParamsForTemplate(iscritto, mylocalsconf);
await this.sendEmail_base( await this.sendEmail_base(
idapp,
'iscrizione_conacreis/' + lang, 'iscrizione_conacreis/' + lang,
emailto, emailto,
mylocalsconf, mylocalsconf,
@@ -693,6 +847,7 @@ module.exports = {
// Send to the Admin an Email // Send to the Admin an Email
await this.sendEmail_base( await this.sendEmail_base(
idapp,
'admin/iscrizione_conacreis/' + tools.LANGADMIN, 'admin/iscrizione_conacreis/' + tools.LANGADMIN,
tools.getAdminEmailByIdApp(idapp), tools.getAdminEmailByIdApp(idapp),
mylocalsconf, mylocalsconf,
@@ -713,6 +868,7 @@ module.exports = {
if (tools.isManagAndAdminDifferent(idapp)) { if (tools.isManagAndAdminDifferent(idapp)) {
await this.sendEmail_base( await this.sendEmail_base(
idapp,
'admin/iscrizione_conacreis/' + tools.LANGADMIN, 'admin/iscrizione_conacreis/' + tools.LANGADMIN,
tools.getManagerEmailByIdApp(idapp), tools.getManagerEmailByIdApp(idapp),
mylocalsconf, mylocalsconf,
@@ -736,7 +892,7 @@ module.exports = {
mylocalsconf = this.setParamsForTemplate(user, mylocalsconf); mylocalsconf = this.setParamsForTemplate(user, mylocalsconf);
await this.sendEmail_base('resetpwd/' + lang, emailto, mylocalsconf, ''); await this.sendEmail_base(idapp, 'resetpwd/' + lang, emailto, mylocalsconf, '');
}, },
sendEmail_RisRicevuti: async function (lang, userDest, emailto, idapp, myrec, extrarec) { sendEmail_RisRicevuti: async function (lang, userDest, emailto, idapp, myrec, extrarec) {
@@ -763,7 +919,7 @@ module.exports = {
mylocalsconf = this.setParamsForTemplate(userDest, mylocalsconf); mylocalsconf = this.setParamsForTemplate(userDest, mylocalsconf);
await this.sendEmail_base('risricevuti/' + lang, emailto, mylocalsconf, ''); await this.sendEmail_base(idapp, 'risricevuti/' + lang, emailto, mylocalsconf, '');
}, },
sendEmail_Booking: async function (res, lang, emailto, user, idapp, recbooking) { sendEmail_Booking: async function (res, lang, emailto, user, idapp, recbooking) {
@@ -801,6 +957,7 @@ module.exports = {
} }
await this.sendEmail_base( await this.sendEmail_base(
idapp,
'booking/' + texthtml + '/' + lang, 'booking/' + texthtml + '/' + lang,
emailto, emailto,
mylocalsconf, mylocalsconf,
@@ -809,6 +966,7 @@ module.exports = {
// Send Email also to the Admin // Send Email also to the Admin
await this.sendEmail_base( await this.sendEmail_base(
idapp,
'admin/' + texthtml + '/' + tools.LANGADMIN, 'admin/' + texthtml + '/' + tools.LANGADMIN,
tools.getAdminEmailByIdApp(idapp), tools.getAdminEmailByIdApp(idapp),
mylocalsconf, mylocalsconf,
@@ -817,6 +975,7 @@ module.exports = {
if (tools.isManagAndAdminDifferent(idapp)) { if (tools.isManagAndAdminDifferent(idapp)) {
await this.sendEmail_base( await this.sendEmail_base(
idapp,
'admin/' + texthtml + '/' + tools.LANGADMIN, 'admin/' + texthtml + '/' + tools.LANGADMIN,
tools.getManagerEmailByIdApp(idapp), tools.getManagerEmailByIdApp(idapp),
mylocalsconf, mylocalsconf,
@@ -914,6 +1073,7 @@ module.exports = {
telegrambot.sendMsgTelegramToTheManagers(idapp, msgtelegram); telegrambot.sendMsgTelegramToTheManagers(idapp, msgtelegram);
await this.sendEmail_base( await this.sendEmail_base(
idapp,
'booking/cancelbooking/' + lang, 'booking/cancelbooking/' + lang,
emailto, emailto,
mylocalsconf, mylocalsconf,
@@ -922,6 +1082,7 @@ module.exports = {
// Send Email also to the Admin // Send Email also to the Admin
await this.sendEmail_base( await this.sendEmail_base(
idapp,
'admin/cancelbooking/' + tools.LANGADMIN, 'admin/cancelbooking/' + tools.LANGADMIN,
tools.getAdminEmailByIdApp(idapp), tools.getAdminEmailByIdApp(idapp),
mylocalsconf, mylocalsconf,
@@ -930,6 +1091,7 @@ module.exports = {
if (tools.isManagAndAdminDifferent(idapp)) { if (tools.isManagAndAdminDifferent(idapp)) {
await this.sendEmail_base( await this.sendEmail_base(
idapp,
'admin/cancelbooking/' + tools.LANGADMIN, 'admin/cancelbooking/' + tools.LANGADMIN,
tools.getManagerEmailByIdApp(idapp), tools.getManagerEmailByIdApp(idapp),
mylocalsconf, mylocalsconf,
@@ -961,7 +1123,7 @@ module.exports = {
if (mylocalsconf.infoevent !== '') replyto = user.email; if (mylocalsconf.infoevent !== '') replyto = user.email;
else replyto = tools.getreplyToEmailByIdApp(idapp); else replyto = tools.getreplyToEmailByIdApp(idapp);
return await this.sendEmail_base('msg/sendmsg/' + lang, emailto, mylocalsconf, replyto); return await this.sendEmail_base(idapp, 'msg/sendmsg/' + lang, emailto, mylocalsconf, replyto);
// Send Email also to the Admin // Send Email also to the Admin
// this.sendEmail_base('admin/sendmsg/' + lang, tools.getAdminEmailByIdApp(idapp), mylocalsconf); // this.sendEmail_base('admin/sendmsg/' + lang, tools.getAdminEmailByIdApp(idapp), mylocalsconf);
@@ -1083,6 +1245,7 @@ module.exports = {
if (sendnews) { if (sendnews) {
// Send to the Admin an Email // Send to the Admin an Email
await this.sendEmail_base( await this.sendEmail_base(
idapp,
'admin/added_to_newsletter/' + tools.LANGADMIN, 'admin/added_to_newsletter/' + tools.LANGADMIN,
tools.getAdminEmailByIdApp(idapp), tools.getAdminEmailByIdApp(idapp),
mylocalsconf, mylocalsconf,
@@ -1091,6 +1254,7 @@ module.exports = {
if (tools.isManagAndAdminDifferent(idapp)) { if (tools.isManagAndAdminDifferent(idapp)) {
await this.sendEmail_base( await this.sendEmail_base(
idapp,
'admin/added_to_newsletter/' + tools.LANGADMIN, 'admin/added_to_newsletter/' + tools.LANGADMIN,
tools.getManagerEmailByIdApp(idapp), tools.getManagerEmailByIdApp(idapp),
mylocalsconf, mylocalsconf,
@@ -1389,6 +1553,7 @@ module.exports = {
if (status !== shared_consts.OrderStatus.CANCELED && status !== shared_consts.OrderStatus.COMPLETED) { if (status !== shared_consts.OrderStatus.CANCELED && status !== shared_consts.OrderStatus.COMPLETED) {
const esito = await this.sendEmail_base( const esito = await this.sendEmail_base(
idapp,
'ecommerce/' + ordertype + '/' + lang, 'ecommerce/' + ordertype + '/' + lang,
mylocalsconf.emailto, mylocalsconf.emailto,
mylocalsconf, mylocalsconf,
@@ -1485,6 +1650,7 @@ module.exports = {
// Send Email to the User // Send Email to the User
// console.log('-> Invio Email (', mynewsrec.numemail_sent, '/', mynewsrec.numemail_tot, ')'); // console.log('-> Invio Email (', mynewsrec.numemail_sent, '/', mynewsrec.numemail_tot, ')');
const esito = await this.sendEmail_base( const esito = await this.sendEmail_base(
idapp,
'newsletter/' + lang, 'newsletter/' + lang,
mylocalsconf.emailto, mylocalsconf.emailto,
mylocalsconf, mylocalsconf,
@@ -1624,6 +1790,7 @@ module.exports = {
console.log('-> Invio Email TEST a', mylocalsconf.emailto, 'previewonly', previewonly); console.log('-> Invio Email TEST a', mylocalsconf.emailto, 'previewonly', previewonly);
return await this.sendEmail_base( return await this.sendEmail_base(
idapp,
'newsletter/' + lang, 'newsletter/' + lang,
mylocalsconf.emailto, mylocalsconf.emailto,
mylocalsconf, mylocalsconf,
@@ -1664,6 +1831,7 @@ module.exports = {
console.log('-> Invio Email ' + mylocalsconf.subject + ' a', mylocalsconf.emailto, 'in corso...'); console.log('-> Invio Email ' + mylocalsconf.subject + ' a', mylocalsconf.emailto, 'in corso...');
const risult = await this.sendEmail_base( const risult = await this.sendEmail_base(
idapp,
'newsletter/' + userto.lang, 'newsletter/' + userto.lang,
mylocalsconf.emailto, mylocalsconf.emailto,
mylocalsconf, mylocalsconf,

View File

@@ -120,6 +120,10 @@ async function runStartupTasks() {
await inizia(); await inizia();
if (true) {
// const Seed = require('../scripts/seedTemplates');
}
// 4) reset job pendenti // 4) reset job pendenti
await resetProcessingJob(); await resetProcessingJob();

View File

@@ -36,6 +36,7 @@ function setupRouters(app) {
['/aitools', 'aitools_router'], ['/aitools', 'aitools_router'],
['/apisqlsrv', 'articleRoutes'], ['/apisqlsrv', 'articleRoutes'],
['/api', 'api_router'], ['/api', 'api_router'],
['/api2', 'api2_router'],
['/api/telegram', 'telegram_router'], ['/api/telegram', 'telegram_router'],
['/inviti', 'invitaAmicoRoutes'], ['/inviti', 'invitaAmicoRoutes'],
]; ];
@@ -58,6 +59,10 @@ function setupRouters(app) {
}); });
}); });
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
return true; return true;
} }

View File

@@ -146,6 +146,7 @@ connectToDatabase(connectionUrl, options)
const aitools_router = require('./router/aitools_router'); const aitools_router = require('./router/aitools_router');
const article_router = require('./router/articleRoutes'); const article_router = require('./router/articleRoutes');
const api_router = require('./router/api_router'); const api_router = require('./router/api_router');
const api2_router = require('./router/api2_router');
const { MyEvent } = require('./models/myevent'); const { MyEvent } = require('./models/myevent');
@@ -252,6 +253,7 @@ connectToDatabase(connectionUrl, options)
app.use('/aitools', aitools_router); app.use('/aitools', aitools_router);
app.use('/apisqlsrv', article_router); app.use('/apisqlsrv', article_router);
app.use('/api', api_router); app.use('/api', api_router);
app.use('/api2', api2_router);
mystart(); mystart();
}); });

View File

@@ -0,0 +1,151 @@
const { createCanvas, loadImage } = require('canvas');
class PosterEditor {
/**
* Crea poster con testi sovrapposti (compatibile con tua API originale)
*/
async createPoster(backgroundImageUrl, data) {
const { titolo, data: eventDate, ora, luogo, contatti } = data;
const width = 1080;
const height = 1920;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
// Carica e disegna background
try {
let img;
if (backgroundImageUrl.startsWith('data:')) {
img = await loadImage(backgroundImageUrl);
} else {
const fetch = require('node-fetch');
const response = await fetch(backgroundImageUrl);
const buffer = await response.buffer();
img = await loadImage(buffer);
}
// Cover fit
const imgRatio = img.width / img.height;
const canvasRatio = width / height;
let dw, dh, dx, dy;
if (imgRatio > canvasRatio) {
dh = height;
dw = height * imgRatio;
dx = (width - dw) / 2;
dy = 0;
} else {
dw = width;
dh = width / imgRatio;
dx = 0;
dy = (height - dh) / 2;
}
ctx.drawImage(img, dx, dy, dw, dh);
} catch (e) {
// Fallback colore solido
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, width, height);
}
// Overlay gradient
const gradient = ctx.createLinearGradient(0, 0, 0, height);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(0.5, 'rgba(0,0,0,0.3)');
gradient.addColorStop(1, 'rgba(0,0,0,0.85)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// Titolo
if (titolo) {
ctx.save();
ctx.font = 'bold 68px Arial, sans-serif';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.shadowColor = 'rgba(0,0,0,0.9)';
ctx.shadowBlur = 20;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
// Word wrap manuale
const maxWidth = width * 0.9;
const lines = this._wrapText(ctx, titolo.toUpperCase(), maxWidth);
const lineHeight = 80;
const startY = height * 0.50 - ((lines.length - 1) * lineHeight) / 2;
lines.forEach((line, i) => {
ctx.fillText(line, width / 2, startY + i * lineHeight);
});
ctx.restore();
}
// Data e ora
if (eventDate) {
ctx.save();
ctx.font = 'bold 44px Arial, sans-serif';
ctx.fillStyle = '#ffd700';
ctx.textAlign = 'center';
ctx.shadowColor = 'rgba(0,0,0,0.8)';
ctx.shadowBlur = 10;
const dateText = ora ? `${eventDate} • ORE ${ora}` : eventDate;
ctx.fillText(dateText.toUpperCase(), width / 2, height * 0.68);
ctx.restore();
}
// Luogo
if (luogo) {
ctx.save();
ctx.font = '600 30px Arial, sans-serif';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.fillText(`📍 ${luogo}`, width / 2, height * 0.76);
ctx.restore();
}
// Contatti
if (contatti) {
ctx.save();
ctx.font = '400 24px Arial, sans-serif';
ctx.fillStyle = '#cccccc';
ctx.textAlign = 'center';
ctx.fillText(contatti, width / 2, height * 0.85);
ctx.restore();
}
// Ritorna base64
return canvas.toDataURL('image/png');
}
/**
* Word wrap utility
*/
_wrapText(ctx, text, maxWidth) {
const words = text.split(' ');
const lines = [];
let currentLine = '';
words.forEach(word => {
const testLine = currentLine ? `${currentLine} ${word}` : word;
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && currentLine) {
lines.push(currentLine);
currentLine = word;
} else {
currentLine = testLine;
}
});
if (currentLine) {
lines.push(currentLine);
}
return lines;
}
}
module.exports = new PosterEditor();

125
src/services/SyncManager.js Normal file
View File

@@ -0,0 +1,125 @@
// server/services/SyncManager.js
class SyncManager {
constructor() {
this.syncConfig = {
resps: {
model: 'User',
method: 'getusersRespList',
trackChanges: true,
params: ['idapp'] // parametri necessari
},
workers: {
model: 'User',
method: 'getusersWorkersList',
trackChanges: true,
params: ['idapp']
},
groups: {
model: 'Group',
method: 'findAllIdApp',
trackChanges: true,
params: ['idapp']
},
mygroups: {
model: 'MyGroup',
method: 'findAllGroups',
trackChanges: true,
params: ['idapp']
},
products: {
model: 'Product',
method: 'findAllIdApp',
trackChanges: true,
params: ['idapp']
},
cart: {
model: 'Cart',
method: 'getCartByUserId',
trackChanges: true,
params: ['userId', 'idapp'],
requiresAuth: true
},
orderscart: {
model: 'OrdersCart',
method: 'getOrdersCartByUserId',
trackChanges: true,
params: ['userId', 'idapp'],
requiresAuth: true
}
};
}
async sync(syncRequest, idapp, user) {
const results = {};
const timestamp = Date.now();
for (const [tableName, params] of Object.entries(syncRequest)) {
const config = this.syncConfig[tableName];
if (!config) {
console.warn(`Tabella ${tableName} non configurata`);
continue;
}
// Controlla autenticazione
if (config.requiresAuth && !user) {
results[tableName] = {
data: null,
timestamp: timestamp,
fullSync: true
};
continue;
}
try {
const data = await this.fetchTableData(
tableName,
config,
params.lastSync,
idapp,
user
);
results[tableName] = {
data: data,
timestamp: timestamp,
fullSync: params.lastSync === 0 || !config.trackChanges
};
} catch (error) {
console.error(`Errore sync ${tableName}:`, error);
results[tableName] = {
error: error.message,
timestamp: timestamp
};
}
}
return results;
}
async fetchTableData(tableName, config, lastSync, idapp, user) {
const ModelClass = require(`../models/${config.model}`);
// Prepara parametri
const args = config.params.map(param => {
if (param === 'idapp') return idapp;
if (param === 'userId') return user?._id.toString() || '0';
return null;
});
// Prima sync o no trackChanges: tutti i dati
if (!config.trackChanges || lastSync === 0) {
return await ModelClass[config.method](...args);
}
// Sync incrementale
const query = {
idapp: idapp,
updatedAt: { $gt: new Date(parseInt(lastSync)) }
};
return await ModelClass.find(query).lean();
}
}
module.exports = new SyncManager();

View File

@@ -441,6 +441,10 @@ class UserService {
{ _id: mydata._id }, { _id: mydata._id },
{ $set: { 'profile.noCircuit': mydata.value } } { $set: { 'profile.noCircuit': mydata.value } }
), ),
'noComune': () => User.findOneAndUpdate(
{ _id: mydata._id },
{ $set: { 'profile.noComune': mydata.value } }
),
'noCircIta': () => User.findOneAndUpdate( 'noCircIta': () => User.findOneAndUpdate(
{ _id: mydata._id }, { _id: mydata._id },
{ $set: { 'profile.noCircIta': mydata.value } } { $set: { 'profile.noCircIta': mydata.value } }

View File

@@ -0,0 +1,154 @@
const fetch = require('node-fetch');
class ImageGenerator {
constructor() {
this.falKey = process.env.FAL_KEY;
this.hfToken = process.env.HF_TOKEN;
this.ideogramKey = process.env.IDEOGRAM_KEY;
}
async generate(provider, prompt, options = {}) {
const {
negativePrompt,
aspectRatio = '9:16',
model,
seed,
steps,
cfg
} = options;
switch (provider) {
case 'ideogram':
return this._generateIdeogram(prompt, { aspectRatio });
case 'fal':
return this._generateFal(prompt, { aspectRatio, seed, steps, cfg });
case 'hf':
default:
return this._generateHuggingFace(prompt, { negativePrompt });
}
}
// Ideogram V2 (via Fal.ai) - Ottimo per testo
async _generateIdeogram(prompt, options = {}) {
console.log('--- Generazione Ideogram V2 ---');
const response = await fetch('https://fal.run/fal-ai/ideogram/v2', {
method: 'POST',
headers: {
Authorization: `Key ${this.falKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt,
aspect_ratio: options.aspectRatio || '9:16',
style_type: 'DESIGN',
expand_prompt: true
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Ideogram error: ${response.status} - ${errorText}`);
}
const result = await response.json();
const imageUrl = result.images?.[0]?.url;
if (!imageUrl) throw new Error('Ideogram: nessun URL restituito');
return imageUrl;
}
// Flux Dev (via Fal.ai)
async _generateFal(prompt, options = {}) {
console.log('--- Generazione Fal Flux Dev ---');
const imageSizeMap = {
'9:16': 'portrait_16_9',
'16:9': 'landscape_16_9',
'1:1': 'square'
};
const response = await fetch('https://fal.run/fal-ai/flux/dev', {
method: 'POST',
headers: {
Authorization: `Key ${this.falKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt,
image_size: imageSizeMap[options.aspectRatio] || 'portrait_16_9',
num_images: 1,
enable_safety_checker: false,
seed: options.seed,
num_inference_steps: options.steps || 28,
guidance_scale: options.cfg || 7.5
}),
});
if (!response.ok) {
throw new Error(`Fal error: ${response.status}`);
}
const result = await response.json();
return result.images?.[0]?.url;
}
// Flux Dev (via Hugging Face) - GRATIS
async _generateHuggingFace(prompt, options = {}) {
console.log('--- Generazione HuggingFace (Gratis) ---');
const response = await fetch(
'https://router.huggingface.co/hf-inference/models/black-forest-labs/FLUX.1-dev',
{
headers: {
Authorization: `Bearer ${this.hfToken}`,
'Content-Type': 'application/json',
},
method: 'POST',
body: JSON.stringify({
inputs: prompt,
parameters: {
negative_prompt: options.negativePrompt
}
}),
}
);
if (!response.ok) {
const err = await response.text();
throw new Error(`HuggingFace error: ${response.status} - ${err}`);
}
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
return `data:image/jpeg;base64,${buffer.toString('base64')}`;
}
// Utility: verifica disponibilità provider
async checkProvider(provider) {
const checks = {
hf: !!this.hfToken,
fal: !!this.falKey,
ideogram: !!this.falKey // Ideogram via Fal
};
return checks[provider] || false;
}
// Lista provider disponibili
getAvailableProviders() {
const providers = [];
if (this.hfToken) {
providers.push({ id: 'hf', name: 'HuggingFace (Gratis)', cost: 'free' });
}
if (this.falKey) {
providers.push({ id: 'fal', name: 'Fal Flux Dev', cost: 'paid' });
providers.push({ id: 'ideogram', name: 'Ideogram V2', cost: 'paid' });
}
return providers;
}
}
module.exports = new ImageGenerator();

View File

@@ -0,0 +1,870 @@
const { createCanvas, loadImage, registerFont } = require('canvas');
const fs = require('fs').promises;
const path = require('path');
const sharp = require('sharp');
// Registra font personalizzati
const FONTS_DIR = process.env.FONTS_DIR || './fonts';
const registerFonts = async () => {
const fontMappings = [
{ file: 'Montserrat-Black.ttf', family: 'Montserrat', weight: '900' },
{ file: 'Montserrat-Bold.ttf', family: 'Montserrat', weight: '700' },
{ file: 'Montserrat-Regular.ttf', family: 'Montserrat', weight: '400' },
{ file: 'BebasNeue-Regular.ttf', family: 'Bebas Neue', weight: '400' },
{ file: 'OpenSans-Bold.ttf', family: 'Open Sans', weight: '700' },
{ file: 'OpenSans-SemiBold.ttf', family: 'Open Sans', weight: '600' },
{ file: 'OpenSans-Regular.ttf', family: 'Open Sans', weight: '400' },
{ file: 'OpenSans-Light.ttf', family: 'Open Sans', weight: '300' },
{ file: 'PlayfairDisplay-Bold.ttf', family: 'Playfair Display', weight: '700' },
{ file: 'PlayfairDisplay-Regular.ttf', family: 'Playfair Display', weight: '400' }
];
for (const font of fontMappings) {
const fontPath = path.join(FONTS_DIR, font.file);
try {
await fs.access(fontPath);
registerFont(fontPath, { family: font.family, weight: font.weight });
} catch (e) {
// Font non trovato, usa fallback
}
}
};
// Inizializza fonts al caricamento modulo
registerFonts().catch(console.warn);
class PosterRenderer {
constructor() {
this.version = '1.0.0';
}
/**
* Render principale
*/
async render(options) {
const {
template,
content,
assets,
layerOverrides = {},
outputDir,
posterId
} = options;
const startTime = Date.now();
// Dimensioni canvas
const width = template.format?.width || 2480;
const height = template.format?.height || 3508;
// Crea canvas
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
// 1. Disegna background
await this._drawBackground(ctx, template, assets, width, height);
// 2. Ordina layer per zIndex
const sortedLayers = [...(template.layers || [])]
.sort((a, b) => (a.zIndex || 0) - (b.zIndex || 0));
// 3. Disegna ogni layer
for (const layer of sortedLayers) {
if (layer.visible === false) continue;
const override = layerOverrides[layer.id] || {};
const mergedLayer = this._mergeLayerOverride(layer, override);
await this._drawLayer(ctx, mergedLayer, content, assets, width, height, template);
}
// 4. Disegna loghi
if (template.logoSlots?.enabled && assets?.logos?.length > 0) {
await this._drawLogos(ctx, template.logoSlots, assets.logos, width, height);
}
// 5. Salva output
await fs.mkdir(outputDir, { recursive: true });
const baseName = `poster_${posterId}_${Date.now()}`;
const pngPath = path.join(outputDir, `${baseName}.png`);
const jpgPath = path.join(outputDir, `${baseName}.jpg`);
// Salva PNG
const pngBuffer = canvas.toBuffer('image/png');
await fs.writeFile(pngPath, pngBuffer);
// Salva JPG con Sharp (migliore qualità)
await sharp(pngBuffer)
.jpeg({ quality: 95, progressive: true })
.toFile(jpgPath);
const [pngStats, jpgStats] = await Promise.all([
fs.stat(pngPath),
fs.stat(jpgPath)
]);
return {
pngPath,
jpgPath,
pngSize: pngStats.size,
jpgSize: jpgStats.size,
dimensions: { width, height },
duration: Date.now() - startTime,
engineVersion: this.version
};
}
/**
* Disegna background
*/
async _drawBackground(ctx, template, assets, width, height) {
// Colore di sfondo base
ctx.fillStyle = template.backgroundColor || '#1a1a2e';
ctx.fillRect(0, 0, width, height);
// Background image
const bgAsset = assets?.backgroundImage;
const bgLayer = template.layers?.find(l => l.type === 'backgroundImage');
if (bgAsset?.url) {
try {
const img = await this._loadImageFromUrl(bgAsset.url);
// Calcola dimensioni per cover
const imgRatio = img.width / img.height;
const canvasRatio = width / height;
let drawWidth, drawHeight, drawX, drawY;
if (imgRatio > canvasRatio) {
drawHeight = height;
drawWidth = height * imgRatio;
drawX = (width - drawWidth) / 2;
drawY = 0;
} else {
drawWidth = width;
drawHeight = width / imgRatio;
drawX = 0;
drawY = (height - drawHeight) / 2;
}
// Applica blur se definito
if (bgLayer?.style?.blur > 0) {
ctx.filter = `blur(${bgLayer.style.blur}px)`;
}
ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight);
ctx.filter = 'none';
// Applica overlay gradient
const overlay = bgLayer?.style?.overlay;
if (overlay?.enabled) {
this._drawOverlay(ctx, overlay, width, height);
}
} catch (e) {
console.warn('Background image load failed:', e.message);
// Usa fallback
if (bgLayer?.fallback) {
this._drawFallback(ctx, bgLayer.fallback, width, height);
}
}
} else if (bgLayer?.fallback) {
this._drawFallback(ctx, bgLayer.fallback, width, height);
}
}
/**
* Disegna overlay gradient
*/
_drawOverlay(ctx, overlay, width, height) {
if (overlay.type === 'solid') {
ctx.fillStyle = overlay.color || 'rgba(0,0,0,0.5)';
ctx.fillRect(0, 0, width, height);
return;
}
// Gradient
let gradient;
const dir = overlay.direction || 'to-bottom';
if (dir === 'to-bottom') {
gradient = ctx.createLinearGradient(0, 0, 0, height);
} else if (dir === 'to-top') {
gradient = ctx.createLinearGradient(0, height, 0, 0);
} else if (dir === 'to-right') {
gradient = ctx.createLinearGradient(0, 0, width, 0);
} else if (dir === 'to-left') {
gradient = ctx.createLinearGradient(width, 0, 0, 0);
} else if (dir === 'to-bottom-right') {
gradient = ctx.createLinearGradient(0, 0, width, height);
} else {
gradient = ctx.createLinearGradient(0, 0, 0, height);
}
if (overlay.stops) {
overlay.stops.forEach(stop => {
gradient.addColorStop(stop.position, stop.color);
});
} else {
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, 'rgba(0,0,0,0.7)');
}
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
}
/**
* Disegna fallback
*/
_drawFallback(ctx, fallback, width, height) {
if (fallback.type === 'solid') {
ctx.fillStyle = fallback.color || '#333333';
ctx.fillRect(0, 0, width, height);
} else if (fallback.type === 'gradient' && fallback.colors) {
const gradient = ctx.createLinearGradient(0, 0, 0, height);
fallback.colors.forEach((color, i) => {
gradient.addColorStop(i / (fallback.colors.length - 1), color);
});
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
}
}
/**
* Disegna singolo layer
*/
async _drawLayer(ctx, layer, content, assets, canvasWidth, canvasHeight, template) {
const pos = this._calculatePosition(layer.position, layer.anchor, canvasWidth, canvasHeight);
switch (layer.type) {
case 'backgroundImage':
// Già gestito in _drawBackground
break;
case 'mainImage':
await this._drawMainImage(ctx, assets?.mainImage, pos, layer.style);
break;
case 'title':
this._drawText(ctx, content?.title, pos, layer.style, template.palette);
break;
case 'subtitle':
this._drawText(ctx, content?.subtitle, pos, layer.style, template.palette);
break;
case 'eventDate':
const dateText = content?.eventTime
? `${content.eventDate}${content.eventTime}`
: content?.eventDate;
this._drawText(ctx, dateText, pos, layer.style, template.palette);
break;
case 'eventTime':
this._drawText(ctx, content?.eventTime, pos, layer.style, template.palette);
break;
case 'location':
this._drawTextWithIcon(ctx, content?.location, pos, layer, template.palette);
break;
case 'contacts':
this._drawText(ctx, content?.contacts, pos, layer.style, template.palette);
break;
case 'extraText':
const extraTexts = Array.isArray(content?.extraText)
? content.extraText.join(' • ')
: content?.extraText;
this._drawText(ctx, extraTexts, pos, layer.style, template.palette);
break;
case 'customText':
const customValue = content?.customFields?.get(layer.id);
this._drawText(ctx, customValue, pos, layer.style, template.palette);
break;
case 'divider':
this._drawDivider(ctx, pos, layer.style);
break;
case 'shape':
this._drawShape(ctx, pos, layer.style);
break;
default:
console.warn(`Layer type non gestito: ${layer.type}`);
}
}
/**
* Calcola posizione assoluta da coordinate relative
*/
_calculatePosition(position, anchor, canvasWidth, canvasHeight) {
const relX = position.x || 0;
const relY = position.y || 0;
const relW = position.w || 1;
const relH = position.h || 0.1;
const absW = relW * canvasWidth;
const absH = relH * canvasHeight;
let absX = relX * canvasWidth;
let absY = relY * canvasHeight;
// Aggiusta per anchor
switch (anchor) {
case 'top-center':
absX -= absW / 2;
break;
case 'top-right':
absX -= absW;
break;
case 'center-left':
absY -= absH / 2;
break;
case 'center':
absX -= absW / 2;
absY -= absH / 2;
break;
case 'center-right':
absX -= absW;
absY -= absH / 2;
break;
case 'bottom-left':
absY -= absH;
break;
case 'bottom-center':
absX -= absW / 2;
absY -= absH;
break;
case 'bottom-right':
absX -= absW;
absY -= absH;
break;
// top-left è default, nessun aggiustamento
}
return { x: absX, y: absY, w: absW, h: absH };
}
/**
* Disegna main image
*/
async _drawMainImage(ctx, asset, pos, style = {}) {
if (!asset?.url) return;
try {
const img = await this._loadImageFromUrl(asset.url);
ctx.save();
// Border radius (clip)
const radius = style.borderRadius || 0;
if (radius > 0) {
this._roundRect(ctx, pos.x, pos.y, pos.w, pos.h, radius);
ctx.clip();
}
// Shadow
if (style.shadow?.enabled) {
ctx.shadowColor = style.shadow.color || 'rgba(0,0,0,0.5)';
ctx.shadowBlur = style.shadow.blur || 20;
ctx.shadowOffsetX = style.shadow.offsetX || 0;
ctx.shadowOffsetY = style.shadow.offsetY || 10;
}
// Calcola dimensioni per object-fit
const { sx, sy, sw, sh, dx, dy, dw, dh } = this._calculateObjectFit(
img.width, img.height, pos.w, pos.h, style.objectFit || 'cover'
);
ctx.drawImage(img, sx, sy, sw, sh, pos.x + dx, pos.y + dy, dw, dh);
// Border
if (style.border?.enabled) {
ctx.strokeStyle = style.border.color || '#ffffff';
ctx.lineWidth = style.border.width || 2;
if (radius > 0) {
this._roundRect(ctx, pos.x, pos.y, pos.w, pos.h, radius);
} else {
ctx.strokeRect(pos.x, pos.y, pos.w, pos.h);
}
ctx.stroke();
}
ctx.restore();
} catch (e) {
console.warn('Main image load failed:', e.message);
}
}
/**
* Calcola object-fit
*/
_calculateObjectFit(imgW, imgH, boxW, boxH, fit) {
let sx = 0, sy = 0, sw = imgW, sh = imgH;
let dx = 0, dy = 0, dw = boxW, dh = boxH;
const imgRatio = imgW / imgH;
const boxRatio = boxW / boxH;
if (fit === 'cover') {
if (imgRatio > boxRatio) {
sw = imgH * boxRatio;
sx = (imgW - sw) / 2;
} else {
sh = imgW / boxRatio;
sy = (imgH - sh) / 2;
}
} else if (fit === 'contain') {
if (imgRatio > boxRatio) {
dh = boxW / imgRatio;
dy = (boxH - dh) / 2;
} else {
dw = boxH * imgRatio;
dx = (boxW - dw) / 2;
}
}
// 'fill' usa valori default
return { sx, sy, sw, sh, dx, dy, dw, dh };
}
/**
* Disegna testo
*/
_drawText(ctx, text, pos, style = {}, palette = {}) {
if (!text) return;
ctx.save();
// Font
const fontWeight = style.fontWeight || 400;
const fontSize = style.fontSize || 48;
const fontFamily = style.fontFamily || 'Open Sans';
ctx.font = `${fontWeight} ${fontSize}px "${fontFamily}"`;
// Colore
ctx.fillStyle = style.color || palette.text || '#ffffff';
// Allineamento
const align = style.textAlign || 'center';
ctx.textAlign = align;
ctx.textBaseline = 'middle';
// Transform
let displayText = text;
if (style.textTransform === 'uppercase') {
displayText = text.toUpperCase();
} else if (style.textTransform === 'lowercase') {
displayText = text.toLowerCase();
} else if (style.textTransform === 'capitalize') {
displayText = text.replace(/\b\w/g, c => c.toUpperCase());
}
// Calcola X in base ad allineamento
let textX;
if (align === 'center') {
textX = pos.x + pos.w / 2;
} else if (align === 'right') {
textX = pos.x + pos.w;
} else {
textX = pos.x;
}
const textY = pos.y + pos.h / 2;
// Letter spacing (manuale)
if (style.letterSpacing && style.letterSpacing > 0) {
this._drawTextWithSpacing(ctx, displayText, textX, textY, style, pos);
} else {
// Shadow
if (style.shadow?.enabled) {
ctx.shadowColor = style.shadow.color || 'rgba(0,0,0,0.8)';
ctx.shadowBlur = style.shadow.blur || 10;
ctx.shadowOffsetX = style.shadow.offsetX || 2;
ctx.shadowOffsetY = style.shadow.offsetY || 2;
}
// Stroke
if (style.stroke?.enabled) {
ctx.strokeStyle = style.stroke.color || 'rgba(0,0,0,0.5)';
ctx.lineWidth = style.stroke.width || 2;
ctx.strokeText(displayText, textX, textY);
}
// Fill
ctx.fillText(displayText, textX, textY);
}
ctx.restore();
}
/**
* Disegna testo con letter-spacing
*/
_drawTextWithSpacing(ctx, text, x, y, style, pos) {
const spacing = style.letterSpacing || 0;
const chars = text.split('');
// Calcola larghezza totale
let totalWidth = 0;
chars.forEach(char => {
totalWidth += ctx.measureText(char).width + spacing;
});
totalWidth -= spacing; // Rimuovi ultimo spacing
// Calcola startX in base ad allineamento
let startX;
if (style.textAlign === 'center') {
startX = x - totalWidth / 2;
} else if (style.textAlign === 'right') {
startX = x - totalWidth;
} else {
startX = x;
}
// Shadow
if (style.shadow?.enabled) {
ctx.shadowColor = style.shadow.color || 'rgba(0,0,0,0.8)';
ctx.shadowBlur = style.shadow.blur || 10;
ctx.shadowOffsetX = style.shadow.offsetX || 2;
ctx.shadowOffsetY = style.shadow.offsetY || 2;
}
// Disegna ogni carattere
ctx.textAlign = 'left';
let currentX = startX;
chars.forEach(char => {
if (style.stroke?.enabled) {
ctx.strokeStyle = style.stroke.color || 'rgba(0,0,0,0.5)';
ctx.lineWidth = style.stroke.width || 2;
ctx.strokeText(char, currentX, y);
}
ctx.fillText(char, currentX, y);
currentX += ctx.measureText(char).width + spacing;
});
}
/**
* Disegna testo con icona
*/
_drawTextWithIcon(ctx, text, pos, layer, palette) {
if (!text) return;
const icon = layer.icon;
const style = layer.style || {};
// Se icona abilitata, disegna simbolo prima del testo
if (icon?.enabled) {
ctx.save();
const iconSize = icon.size || 24;
const iconColor = icon.color || palette?.accent || '#e74c3c';
// Disegna simbolo location semplificato
ctx.fillStyle = iconColor;
ctx.font = `${iconSize}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const iconChar = '📍'; // Emoji o usa font icon
const textWithIcon = `${iconChar} ${text}`;
// Ora disegna testo normale con icona
this._drawText(ctx, textWithIcon, pos, style, palette);
ctx.restore();
} else {
this._drawText(ctx, text, pos, style, palette);
}
}
/**
* Disegna loghi
*/
async _drawLogos(ctx, logoSlots, logos, canvasWidth, canvasHeight) {
const slots = logoSlots.slots || [];
const maxCount = Math.min(logos.length, logoSlots.maxCount || 3, slots.length);
for (let i = 0; i < maxCount; i++) {
const logo = logos[i];
const slot = slots[i];
if (!logo?.url || !slot) continue;
try {
const img = await this._loadImageFromUrl(logo.url);
const pos = this._calculatePosition(slot.position, slot.anchor, canvasWidth, canvasHeight);
ctx.save();
// Opacity
ctx.globalAlpha = slot.style?.opacity ?? 0.9;
// Object fit contain per loghi
const { sx, sy, sw, sh, dx, dy, dw, dh } = this._calculateObjectFit(
img.width, img.height, pos.w, pos.h, 'contain'
);
ctx.drawImage(img, sx, sy, sw, sh, pos.x + dx, pos.y + dy, dw, dh);
ctx.restore();
} catch (e) {
console.warn(`Logo ${i} load failed:`, e.message);
}
}
}
/**
* Disegna divider
*/
_drawDivider(ctx, pos, style = {}) {
ctx.save();
ctx.strokeStyle = style.color || '#ffffff';
ctx.lineWidth = style.width || 2;
ctx.globalAlpha = style.opacity || 0.5;
ctx.beginPath();
ctx.moveTo(pos.x, pos.y + pos.h / 2);
ctx.lineTo(pos.x + pos.w, pos.y + pos.h / 2);
ctx.stroke();
ctx.restore();
}
/**
* Disegna shape
*/
_drawShape(ctx, pos, style = {}) {
ctx.save();
ctx.fillStyle = style.fill || 'rgba(255,255,255,0.1)';
ctx.strokeStyle = style.stroke || 'transparent';
ctx.lineWidth = style.strokeWidth || 0;
ctx.globalAlpha = style.opacity || 1;
const shape = style.shape || 'rectangle';
const radius = style.borderRadius || 0;
if (shape === 'rectangle') {
if (radius > 0) {
this._roundRect(ctx, pos.x, pos.y, pos.w, pos.h, radius);
ctx.fill();
if (style.strokeWidth) ctx.stroke();
} else {
ctx.fillRect(pos.x, pos.y, pos.w, pos.h);
if (style.strokeWidth) ctx.strokeRect(pos.x, pos.y, pos.w, pos.h);
}
} else if (shape === 'circle' || shape === 'ellipse') {
ctx.beginPath();
ctx.ellipse(
pos.x + pos.w / 2,
pos.y + pos.h / 2,
pos.w / 2,
pos.h / 2,
0, 0, Math.PI * 2
);
ctx.fill();
if (style.strokeWidth) ctx.stroke();
}
ctx.restore();
}
/**
* Utility: rounded rectangle
*/
_roundRect(ctx, x, y, w, h, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + w - radius, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + radius);
ctx.lineTo(x + w, y + h - radius);
ctx.quadraticCurveTo(x + w, y + h, x + w - radius, y + h);
ctx.lineTo(x + radius, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
}
/**
* Utility: carica immagine da URL o path locale
*/
async _loadImageFromUrl(url) {
if (!url) throw new Error('URL mancante');
// Base64
if (url.startsWith('data:')) {
return loadImage(url);
}
// Path locale
if (url.startsWith('/uploads') || url.startsWith('./uploads')) {
const localPath = url.startsWith('/')
? path.join(process.cwd(), url)
: url;
return loadImage(localPath);
}
// URL remoto
if (url.startsWith('http://') || url.startsWith('https://')) {
const fetch = require('node-fetch');
const response = await fetch(url);
const buffer = await response.buffer();
return loadImage(buffer);
}
// Assume path locale
return loadImage(url);
}
/**
* Merge layer con override
*/
_mergeLayerOverride(layer, override) {
if (!override || Object.keys(override).length === 0) {
return layer;
}
return {
...layer,
position: override.position ? { ...layer.position, ...override.position } : layer.position,
visible: override.visible !== undefined ? override.visible : layer.visible,
style: override.style ? { ...layer.style, ...override.style } : layer.style
};
}
/**
* Quick render (semplificato per quick-generate)
*/
async quickRender(options) {
const {
backgroundUrl,
content,
outputPath,
width = 1080,
height = 1920
} = options;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
// Background
if (backgroundUrl) {
try {
const img = await this._loadImageFromUrl(backgroundUrl);
const imgRatio = img.width / img.height;
const canvasRatio = width / height;
let dw, dh, dx, dy;
if (imgRatio > canvasRatio) {
dh = height;
dw = height * imgRatio;
dx = (width - dw) / 2;
dy = 0;
} else {
dw = width;
dh = width / imgRatio;
dx = 0;
dy = (height - dh) / 2;
}
ctx.drawImage(img, dx, dy, dw, dh);
} catch (e) {
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, width, height);
}
}
// Overlay gradient
const gradient = ctx.createLinearGradient(0, 0, 0, height);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(0.4, 'rgba(0,0,0,0.2)');
gradient.addColorStop(0.7, 'rgba(0,0,0,0.6)');
gradient.addColorStop(1, 'rgba(0,0,0,0.85)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// Title
if (content.title) {
ctx.save();
ctx.font = 'bold 72px "Montserrat", sans-serif';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.shadowColor = 'rgba(0,0,0,0.8)';
ctx.shadowBlur = 15;
ctx.shadowOffsetY = 4;
ctx.fillText(content.title.toUpperCase(), width / 2, height * 0.52);
ctx.restore();
}
// Subtitle
if (content.subtitle) {
ctx.save();
ctx.font = '400 32px "Open Sans", sans-serif';
ctx.fillStyle = '#f0f0f0';
ctx.textAlign = 'center';
ctx.shadowColor = 'rgba(0,0,0,0.6)';
ctx.shadowBlur = 8;
ctx.fillText(content.subtitle, width / 2, height * 0.60);
ctx.restore();
}
// Date
if (content.eventDate) {
ctx.save();
ctx.font = '400 48px "Bebas Neue", sans-serif';
ctx.fillStyle = '#ffd700';
ctx.textAlign = 'center';
ctx.shadowColor = 'rgba(0,0,0,0.8)';
ctx.shadowBlur = 10;
const dateText = content.eventTime
? `${content.eventDate} • ORE ${content.eventTime}`
: content.eventDate;
ctx.fillText(dateText.toUpperCase(), width / 2, height * 0.70);
ctx.restore();
}
// Location
if (content.location) {
ctx.save();
ctx.font = '600 28px "Open Sans", sans-serif';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.fillText(`📍 ${content.location}`, width / 2, height * 0.78);
ctx.restore();
}
// Contacts
if (content.contacts) {
ctx.save();
ctx.font = '400 22px "Open Sans", sans-serif';
ctx.fillStyle = '#cccccc';
ctx.textAlign = 'center';
ctx.fillText(content.contacts, width / 2, height * 0.86);
ctx.restore();
}
// Salva
const buffer = canvas.toBuffer('image/png');
await fs.writeFile(outputPath, buffer);
return {
path: outputPath,
size: buffer.length,
dimensions: { width, height }
};
}
}
module.exports = new PosterRenderer();

View File

@@ -451,7 +451,7 @@ const txt = {
MSG_ACCEPT_NEWENTRY_INGROUP: '❇️👥 🧍‍♂️ Accetta Ingresso nel GRUPPO %s:', MSG_ACCEPT_NEWENTRY_INGROUP: '❇️👥 🧍‍♂️ Accetta Ingresso nel GRUPPO %s:',
MSG_FRIENDS_NOT_ACCEPTED_CONFIRMED: '🚫 Hai rifiutato la richiesta di Amicizia di %s !', MSG_FRIENDS_NOT_ACCEPTED_CONFIRMED: '🚫 Hai rifiutato la richiesta di Amicizia di %s !',
MSG_HANDSHAKE_NOT_ACCEPTED_CONFIRMED: '🚫 Hai rifiutato la richiesta di Stretta di mano di %s !', MSG_HANDSHAKE_NOT_ACCEPTED_CONFIRMED: '🚫 Hai rifiutato la richiesta di Stretta di mano di %s !',
MSG_APORTADOR_CONFIRMED: '✅ %s è stato Ammesso correttamente (da %s)!', MSG_APORTADOR_CONFIRMED: '✅ %s è stato Ammesso correttamente (da %s) tramite Telegram!',
MSG_APORTADOR_DEST_CONFIRMED: MSG_APORTADOR_DEST_CONFIRMED:
'✅ La tua registrazione a %s è stata accettata da %s!\n' + 'Vai sulla App oppure clicca qui per entrare\n👉🏻 %s', '✅ La tua registrazione a %s è stata accettata da %s!\n' + 'Vai sulla App oppure clicca qui per entrare\n👉🏻 %s',
MSG_GROUP_CONFIRMED: '✅ Sei stato Aggiunto sul Gruppo %s!', MSG_GROUP_CONFIRMED: '✅ Sei stato Aggiunto sul Gruppo %s!',
@@ -838,6 +838,8 @@ const MyTelegramBot = {
groupname = '' groupname = ''
) { ) {
try { try {
const sendemail = require('../sendemail');
const cl = getclTelegByidapp(idapp); const cl = getclTelegByidapp(idapp);
if (!cl) return false; if (!cl) return false;
@@ -1000,6 +1002,7 @@ const MyTelegramBot = {
groupid + tools.SEP + groupname, groupid + tools.SEP + groupname,
},*/ },*/
]); ]);
send_notif = true; send_notif = true;
} else { } else {
msg_notifpush = i18n.__({ phrase: 'CIRCUIT_ACCEPT_NEWENTRY', locale: langdest }, myuser.username, name); msg_notifpush = i18n.__({ phrase: 'CIRCUIT_ACCEPT_NEWENTRY', locale: langdest }, myuser.username, name);
@@ -1032,6 +1035,39 @@ const MyTelegramBot = {
]); ]);
send_notif = true; send_notif = true;
} }
const mycircuit = await Circuit.getCircuitByCircuitId(circuitId);
// Invia Email ai facilitatori
// const usersmanagers = await Circuit.getListAdminsByCircuitPath(myuser.idapp, mycircuit.path);
// Ottiene il token relativo all'Utente e a quel circuito
const token = await User.getTokenByUsernameAndCircuitName(myuser.idapp, myuser.username, mycircuit.name);
if (token) {
const data = {
token_circuito_da_ammettere: token,
nomeTerritorio: mycircuit.name,
myusername: userDest,
circuitId: mycircuit._id,
};
// if (usersmanagers) {
// for (const recadminCirc of usersmanagers) {
data.nomeFacilitatore = userrecDest.username;
const myusercompleto = await User.getUserByUsername(myuser.idapp, myuser.username);
const userInvitante = await User.getUserByUsername(myuser.idapp, myusercompleto.aportador_solidario);
await sendemail.sendEmail_Richiesta_Al_Facilitatore_Di_FarEntrare_AlCircuito(
myuser.lang,
userrecDest.email,
myusercompleto,
userInvitante,
idapp,
data
);
}
// }
// }
} else if (myfunc === shared_consts.CallFunz.RICHIESTA_CIRCUIT) { } else if (myfunc === shared_consts.CallFunz.RICHIESTA_CIRCUIT) {
if (groupname) { if (groupname) {
msg_notifpush = i18n.__({ phrase: 'CIRCUIT_ACCEPT_NEWENTRY_BYGROUP_CIRC', locale: langdest }, groupname); msg_notifpush = i18n.__({ phrase: 'CIRCUIT_ACCEPT_NEWENTRY_BYGROUP_CIRC', locale: langdest }, groupname);
@@ -4574,7 +4610,7 @@ if (true) {
userDest, userDest,
null, null,
user.idapp, user.idapp,
null, null
); );
await local_sendMsgTelegram(user.idapp, data.username, msgOrig); await local_sendMsgTelegram(user.idapp, data.username, msgOrig);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1198,6 +1198,7 @@ module.exports = {
let paramsObj = { let paramsObj = {
usernameDest, usernameDest,
circuitnameDest: circuitname, circuitnameDest: circuitname,
circuitId: myreccircuit ? myreccircuit._id : '',
path, path,
username_action: username_action, username_action: username_action,
singleadmin_username: usernameDest, singleadmin_username: usernameDest,
@@ -1217,6 +1218,7 @@ module.exports = {
if (mycircuit && extrarec) { if (mycircuit && extrarec) {
extrarec.fido_scoperto_default = mycircuit.fido_scoperto_default; extrarec.fido_scoperto_default = mycircuit.fido_scoperto_default;
extrarec.fido_scoperto_default_grp = mycircuit.fido_scoperto_default_grp; extrarec.fido_scoperto_default_grp = mycircuit.fido_scoperto_default_grp;
extrarec.link_group = mycircuit.link_group;
} }
if (cmd) { if (cmd) {
@@ -1489,6 +1491,7 @@ module.exports = {
) { ) {
const { Circuit } = require('../models/circuit'); const { Circuit } = require('../models/circuit');
const { SendNotif } = require('../models/sendnotif'); const { SendNotif } = require('../models/sendnotif');
var { User } = require('../models/user');
const circuit = await Circuit.findOne( const circuit = await Circuit.findOne(
{ idapp, name: circuitname }, { idapp, name: circuitname },
@@ -1626,6 +1629,8 @@ module.exports = {
if (singleadmin.username) { if (singleadmin.username) {
if (usernameOrig === singleadmin.username) giainviato = true; if (usernameOrig === singleadmin.username) giainviato = true;
extrarec.send_email = !!singleadmin.enable_to_receive_email ? singleadmin.enable_to_receive_email : true;
await this.sendNotifCircuitByUsername( await this.sendNotifCircuitByUsername(
cmd, cmd,
idapp, idapp,
@@ -1649,6 +1654,8 @@ module.exports = {
} }
if (!giainviato && cmd !== shared_consts.CIRCUITCMD.REQ) { if (!giainviato && cmd !== shared_consts.CIRCUITCMD.REQ) {
extrarec.send_email = await User.isEnableToReceiveEmailByUsernameECmd(idapp, usernameOrig, cmd);
// SEND TO THE USER DEST THE NOTIFICATION // SEND TO THE USER DEST THE NOTIFICATION
ris = await this.sendNotifCircuitByUsername( ris = await this.sendNotifCircuitByUsername(
cmd, cmd,
@@ -2017,6 +2024,19 @@ module.exports = {
return false; return false;
}, },
getContactEmailSupportBydApp: function (idapp, option) {
const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
if (myapp) {
if (myapp.hasOwnProperty('contacts')) {
if (myapp.confsite.hasOwnProperty('email')) {
return myapp.contacts.email;
}
}
}
return '';
},
getEnableTokenExpiredByIdApp: function (idapp) { getEnableTokenExpiredByIdApp: function (idapp) {
const myapp = this.MYAPPS.find((item) => item.idapp === idapp); const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
if (myapp && myapp.confpages && myapp.confpages.hasOwnProperty('enableTokenExpired')) { if (myapp && myapp.confpages && myapp.confpages.hasOwnProperty('enableTokenExpired')) {
@@ -2149,7 +2169,9 @@ module.exports = {
getLinkGruppiTerritorialiTelegram: function (idapp) { getLinkGruppiTerritorialiTelegram: function (idapp) {
try { try {
const myapp = this.MYAPPS.find((item) => item.idapp === idapp); const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
return myapp && myapp.telegram_gruppi_territoriali_senzainvito ? myapp.telegram_gruppi_territoriali_senzainvito : ''; return myapp && myapp.telegram_gruppi_territoriali_senzainvito
? myapp.telegram_gruppi_territoriali_senzainvito
: '';
} catch (e) { } catch (e) {
return ''; return '';
} }
@@ -3325,11 +3347,13 @@ module.exports = {
if (params.sortBy && !params.searchByBoundariesMap) { if (params.sortBy && !params.searchByBoundariesMap) {
// maybe we want to sort by blog title or something // maybe we want to sort by blog title or something
const mysort = { $sort: params.sortBy }; const mysort = params.sortBy && Object.keys(params.sortBy).length > 0 ? { $sort: params.sortBy } : {};
// console.log('sortBy', params.sortBy); // console.log('sortBy', params.sortBy);
// console.table(mysort); // console.table(mysort);
if (mysort.$sort) {
query.push(mysort); query.push(mysort);
} }
}
query.push( query.push(
{ {
@@ -5195,7 +5219,22 @@ module.exports = {
let mystr = ''; let mystr = '';
const rec = secgoodrec.find((rec) => rec._id === myrec.idSectorGood); const rec = secgoodrec.find((rec) => rec._id === myrec.idSectorGood);
// const rec = goodrec.find((rec) => rec._id === myrec.idSectorGood); if (rec) {
mystr += rec.descr;
}
return mystr;
} catch (e) {}
},
async getCategoriaBachecaByRec(myrec) {
const { SectorBacheca } = require('../models/sectorbacheca');
try {
const idapp = myrec.idapp;
if (!idapp) return '';
const secbachecarec = await SectorBacheca.findAllIdApp(idapp);
let mystr = '';
const rec = secbachecarec.find((rec) => rec._id === myrec.idSectorBacheca);
if (rec) { if (rec) {
mystr += rec.descr; mystr += rec.descr;
} }
@@ -5409,6 +5448,7 @@ module.exports = {
} else if (tablerec === shared_consts.TABLES_MYHOSPS) { } else if (tablerec === shared_consts.TABLES_MYHOSPS) {
cat = ''; cat = '';
} else if (tablerec === shared_consts.TABLES_MYBACHECAS) { } else if (tablerec === shared_consts.TABLES_MYBACHECAS) {
cat = await this.getCategoriaBachecaByRec(myrec);
if (myrec.website) { if (myrec.website) {
sitoweb = myrec.website; sitoweb = myrec.website;
} }
@@ -5823,6 +5863,10 @@ module.exports = {
let mystr = ''; let mystr = '';
let userfrom = ''; let userfrom = '';
let userto = ''; let userto = '';
let namefrom = '';
let surnamefrom = '';
let nameto = '';
let surnameto = '';
let profilefrom = null; let profilefrom = null;
let profileto = null; let profileto = null;
@@ -5840,6 +5884,8 @@ module.exports = {
} }
if (mov.userfrom) { if (mov.userfrom) {
userfrom += mov.userfrom.username; userfrom += mov.userfrom.username;
namefrom = mov.userfrom.name;
surnamefrom = mov.userfrom.surname;
profilefrom = mov.userfrom.profile; profilefrom = mov.userfrom.profile;
} }
@@ -5853,14 +5899,16 @@ module.exports = {
} }
if (mov.userto) { if (mov.userto) {
userto += mov.userto.username; userto += mov.userto.username;
nameto = mov.userto.name;
surnameto = mov.userto.surname;
profileto = mov.userto.profile; profileto = mov.userto.profile;
} }
// mystr = t('movement.from') + userfrom + ' ' + t('movement.to') + userto // mystr = t('movement.from') + userfrom + ' ' + t('movement.to') + userto
return { return {
userfrom: { profile: profilefrom, username: userfrom }, userfrom: { profile: profilefrom, username: userfrom, name: namefrom, surname: surnamefrom },
userto: { profile: profileto, username: userto }, userto: { profile: profileto, username: userto, name: nameto, surname: surnameto },
tipocontofrom, tipocontofrom,
tipocontoto, tipocontoto,
}; };
@@ -6370,4 +6418,21 @@ module.exports = {
} }
} }
}, },
/* Converte una stringa di versione (es. "1.2.55") in un numero per confronti
* @param {string} version - Stringa versione in formato "major.minor.patch"
* @returns {number} Numero rappresentante la versione
*/
versionToNumber(version) {
const parts = version.split('.').map((num) => parseInt(num, 10));
// Gestisce versioni con 1, 2 o 3 componenti
const major = parts[0] || 0;
const minor = parts[1] || 0;
const patch = parts[2] || 0;
// Usa padding di 3 cifre per minor e patch (supporta fino a 999)
return major * 1000000 + minor * 1000 + patch;
},
}; };

View File

@@ -26,6 +26,7 @@ const { Skill } = require('../models/skill');
const { Catalog } = require('../models/catalog'); const { Catalog } = require('../models/catalog');
const { RaccoltaCataloghi } = require('../models/raccoltacataloghi'); const { RaccoltaCataloghi } = require('../models/raccoltacataloghi');
const { Good } = require('../models/good'); const { Good } = require('../models/good');
const { Bacheca } = require('../models/bacheca');
const { SubSkill } = require('../models/subskill'); const { SubSkill } = require('../models/subskill');
const { MySkill } = require('../models/myskill'); const { MySkill } = require('../models/myskill');
const { Attivita } = require('../models/attivita'); const { Attivita } = require('../models/attivita');
@@ -171,6 +172,7 @@ module.exports = {
else if (tablename === 'catalogs') mytable = Catalog; else if (tablename === 'catalogs') mytable = Catalog;
else if (tablename === 'raccoltacataloghis') mytable = RaccoltaCataloghi; else if (tablename === 'raccoltacataloghis') mytable = RaccoltaCataloghi;
else if (tablename === 'goods') mytable = Good; else if (tablename === 'goods') mytable = Good;
else if (tablename === 'bachecas') mytable = Bacheca;
else if (tablename === 'subskills') mytable = SubSkill; else if (tablename === 'subskills') mytable = SubSkill;
else if (tablename === shared_consts.TABLES_MYSKILLS) mytable = MySkill; else if (tablename === shared_consts.TABLES_MYSKILLS) mytable = MySkill;
else if (tablename === shared_consts.TABLES_ATTIVITAS) mytable = Attivita; else if (tablename === shared_consts.TABLES_ATTIVITAS) mytable = Attivita;
@@ -201,7 +203,7 @@ module.exports = {
return process.env.ENABLE_PUSHNOTIFICATION === '1'; return process.env.ENABLE_PUSHNOTIFICATION === '1';
}, },
async sendNotifCmd(typenotif, idnotif, res, idapp, user, recnotif, cmd) { async sendNotifCmd(typenotif, idnotif, res, idapp, user, recnotif, paramsObj) {
// Controlla nelle impostazioni che tipo di Notifica visualizzare // Controlla nelle impostazioni che tipo di Notifica visualizzare
const sendemail = require('../sendemail'); const sendemail = require('../sendemail');
@@ -250,8 +252,12 @@ module.exports = {
invia = true; invia = true;
} }
if (recnotif.extrarec?.send_email) {
params.typesend = params.typesend + shared_consts.TypeSend.EMAIL;
}
if (invia) { if (invia) {
ris = await this.SendMsgToParam(idapp, params); ris = await this.SendMsgToParam(idapp, params, recnotif, paramsObj);
} }
// Send Msg by EMAIL // Send Msg by EMAIL
@@ -270,11 +276,12 @@ module.exports = {
} }
}, },
SendMsgToParam: async function (idapp, params) { SendMsgToParam: async function (idapp, params, recnotif, paramsObj) {
try { try {
// console.log('SendMsgToParam', params.typesend, params.typemsg); // console.log('SendMsgToParam', params.typesend, params.typemsg);
const { User } = require('../models/user'); const { User } = require('../models/user');
const sendemail = require('../sendemail');
let textsent = ''; let textsent = '';
@@ -432,6 +439,22 @@ module.exports = {
} catch (e) { } } catch (e) { }
} }
} }
if (tools.isBitActive(params.typesend, shared_consts.TypeSend.EMAIL)) {
// Invia una Email
if (params.tag === 'setfido') {
const usertosend = await User.getUserByUsername(params.idapp, params.usernameDest);
if (paramsObj) {
}
const dati = {
usernameInvitante: paramsObj.extrarec?.username_admin_abilitante,
nomeTerritorio: paramsObj.circuitnameDest,
link_group: paramsObj.extrarec?.link_group,
circuitId: paramsObj.circuitId,
};
await sendemail.sendEmail_Utente_Abilitato_Circuito_FidoConcesso(usertosend.lang, usertosend.email, usertosend, params.idapp, dati);
}
}
} }
numrec++; numrec++;
@@ -470,7 +493,7 @@ module.exports = {
params.sendreally = true; params.sendreally = true;
params.typesend = shared_consts.TypeSend.PUSH_NOTIFICATION; params.typesend = shared_consts.TypeSend.PUSH_NOTIFICATION;
return await this.SendMsgToParam(idapp, params); return await this.SendMsgToParam(idapp, params, null);
}, },
SearchString: async function (idapp, searchString) { SearchString: async function (idapp, searchString) {

View File

@@ -1,6 +1,6 @@
module.exports = { module.exports = {
USER_ADMIN_CIRCUITS: ['surya1977', 'ElenaEspx'], USER_ADMIN_CIRCUITS: ['surya1977', 'ElenaEspx'],
ADMIN_IDTELEGRAM_TEST_USERNAME: ['surya1977', 'SuryaArena', 'surya4'], ADMIN_IDTELEGRAM_TEST_USERNAME: ['surya1977', 'SuryaArena', 'surya4', 'test1234'],
ADMIN_USER_SERVER: 'surya1977', ADMIN_USER_SERVER: 'surya1977',
Accepted: { Accepted: {
CHECK_READ_GUIDELINES: 1, CHECK_READ_GUIDELINES: 1,
@@ -205,7 +205,7 @@ module.exports = {
TABLES_GETCOMPLETEREC: ['myskills', 'mybachecas', 'myhosps', 'mygoods', 'attivitas'], TABLES_GETCOMPLETEREC: ['myskills', 'mybachecas', 'myhosps', 'mygoods', 'attivitas'],
//++Todo: per abilitare gli utenti ad inserire un Circuito aggiungere 'circuits' alla lista TABLES_PERM_NEWREC //++Todo: per abilitare gli utenti ad inserire un Circuito aggiungere 'circuits' alla lista TABLES_PERM_NEWREC
TABLES_PERM_NEWREC: ['skills', 'goods', 'subskills', 'mygroups', 'myhosps', 'catalogs', 'raccoltacataloghis'], TABLES_PERM_NEWREC: ['skills', 'goods', 'bachecas', 'subskills', 'mygroups', 'myhosps', 'catalogs', 'raccoltacataloghis'],
TABLES_REACTIONS: ['mybachecas', 'myhosps', 'myskills', 'mygoods', 'attivitas'], TABLES_REACTIONS: ['mybachecas', 'myhosps', 'myskills', 'mygoods', 'attivitas'],
@@ -233,6 +233,8 @@ module.exports = {
'sectors', 'sectors',
'goods', 'goods',
'sectorgoods', 'sectorgoods',
'bachecas',
'sectorbachecas',
'catgrps', 'catgrps',
'skills', 'skills',
'subskills', 'subskills',
@@ -604,6 +606,7 @@ module.exports = {
TypeSend: { TypeSend: {
PUSH_NOTIFICATION: 1, PUSH_NOTIFICATION: 1,
TELEGRAM: 2, TELEGRAM: 2,
EMAIL: 4,
}, },
UsersNotif: { UsersNotif: {
NEW_ADV_CITY: 1, NEW_ADV_CITY: 1,
@@ -961,10 +964,9 @@ module.exports = {
} else if (table === this.TABLES_MYBACHECAS) { } else if (table === this.TABLES_MYBACHECAS) {
proj = { proj = {
recSkill: 1, recSkill: 1,
sector: 1, idSectorBacheca: 1,
idSector: 1, idBacheca: 1,
idSkill: 1, sectorBacheca: 1,
// 'idSubSkill': 1,
idStatusSkill: 1, idStatusSkill: 1,
idContribType: 1, idContribType: 1,
dateTimeStart: 1, dateTimeStart: 1,
@@ -1281,7 +1283,6 @@ module.exports = {
DASHBOARD: 140, DASHBOARD: 140,
DASHGROUP: 145, DASHGROUP: 145,
MOVEMENTS: 148, MOVEMENTS: 148,
CSENDRISTO: 150,
STATUSREG: 160, STATUSREG: 160,
CHECKIFISLOGGED: 170, CHECKIFISLOGGED: 170,
INFO_VERSION: 180, INFO_VERSION: 180,
@@ -1326,4 +1327,8 @@ module.exports = {
}, },
TOK_INIZIALE_VERIF_TELEG: '', TOK_INIZIALE_VERIF_TELEG: '',
JOB_TO_EXECUTE: {
MIGRATION_SECTORS_DIC25: 'Migration_Sectors_Dic_2025',
},
}; };

View File

@@ -1 +1 @@
1.2.85 1.2.86

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

1134
yarn.lock

File diff suppressed because it is too large Load Diff