Compare commits
13 Commits
c46d23cb83
...
ChatBox
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a0cdec7bd | ||
|
|
3d87c336de | ||
|
|
037ff6f7f9 | ||
|
|
b8dcd7f5e0 | ||
|
|
b35c99c8fb | ||
|
|
086e4ab8ba | ||
|
|
139d3fe241 | ||
|
|
6fe3ed7c8b | ||
|
|
81ae2df8ef | ||
|
|
997a7b8b98 | ||
|
|
331a5451b2 | ||
|
|
514c2488cc | ||
|
|
33e51bac0e |
@@ -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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"
|
||||||
@@ -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"}]
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
492
emails/RISO/circuit_abilitato_al_fido_membro/it/html.pug
Executable file
492
emails/RISO/circuit_abilitato_al_fido_membro/it/html.pug
Executable 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}
|
||||||
1
emails/RISO/circuit_abilitato_al_fido_membro/it/subject.pug
Executable file
1
emails/RISO/circuit_abilitato_al_fido_membro/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
|||||||
|
=`Abilitazione avvenuta su ${nomeTerritorio} in ${nomeapp} - (${usernameMembro})`
|
||||||
401
emails/RISO/circuit_chiedi_facilitatori_di_entrare/it/html.pug
Executable file
401
emails/RISO/circuit_chiedi_facilitatori_di_entrare/it/html.pug
Executable 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}
|
||||||
1
emails/RISO/circuit_chiedi_facilitatori_di_entrare/it/subject.pug
Executable file
1
emails/RISO/circuit_chiedi_facilitatori_di_entrare/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
|||||||
|
=`Richiesta ingresso di ${usernameMembro} - ${nomeMembro} ${cognomeMembro} su ${nomeTerritorio} in ${nomeapp}`
|
||||||
@@ -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à
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
=`🎉 Il tuo invito è stato accettato su RISO da ${name ? ', ' + name : username} !`
|
=`🎉 Il tuo invito è stato accettato su RISO da ${name ? name : username} !`
|
||||||
|
|||||||
@@ -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à:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
=`🎉 Il tuo invito è stato accettato su ${nomeapp} da ${name ? ', ' + name : username} !`
|
=`🎉 Il tuo invito è stato accettato su ${nomeapp} da ${name ? name : username} !`
|
||||||
|
|||||||
404
emails/defaultSite/reg_resend_email_to_verifiyng/it/html.pug
Executable file
404
emails/defaultSite/reg_resend_email_to_verifiyng/it/html.pug
Executable 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}
|
||||||
1
emails/defaultSite/reg_resend_email_to_verifiyng/it/subject.pug
Executable file
1
emails/defaultSite/reg_resend_email_to_verifiyng/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
|||||||
|
Verifica la tua Email - ${nomeapp}`
|
||||||
77
logtrans.txt
77
logtrans.txt
@@ -514,4 +514,79 @@ 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]
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
398
src/controllers/assetController.js
Normal file
398
src/controllers/assetController.js
Normal 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;
|
||||||
647
src/controllers/posterController.js
Normal file
647
src/controllers/posterController.js
Normal 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;
|
||||||
383
src/controllers/templateController.js
Normal file
383
src/controllers/templateController.js
Normal 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
45
src/data/asset.json
Normal 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
150
src/data/poster.json
Normal 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
272
src/data/template.json
Normal 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"
|
||||||
|
}
|
||||||
@@ -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
42
src/middleware/upload.js
Normal 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
137
src/models/Asset.js
Normal 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
262
src/models/Poster.js
Normal 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
253
src/models/Template.js
Normal 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);
|
||||||
@@ -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
88
src/models/bacheca.js
Executable 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 };
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,18 +41,21 @@ 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
|
||||||
},
|
},
|
||||||
numLevel: {
|
numLevel: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
88
src/models/sectorbacheca.js
Executable 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 };
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
|||||||
1182
src/models/user.js
1182
src/models/user.js
File diff suppressed because it is too large
Load Diff
163
src/models/version.js
Executable file
163
src/models/version.js
Executable 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 };
|
||||||
@@ -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') {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@@ -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' },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
25
src/populate/OLD_sectorgoods.js
Normal file
25
src/populate/OLD_sectorgoods.js
Normal 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'},
|
||||||
|
],
|
||||||
|
};
|
||||||
17
src/populate/OLD_sectors.js
Normal file
17
src/populate/OLD_sectors.js
Normal 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'},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -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' },
|
|
||||||
*/
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -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,
|
||||||
}
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|||||||
539
src/populate/migration-categories.js
Normal file
539
src/populate/migration-categories.js
Normal 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,
|
||||||
|
};
|
||||||
107
src/populate/new_bachecas.js
Normal file
107
src/populate/new_bachecas.js
Normal 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
87
src/populate/new_goods.js
Normal 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' },
|
||||||
|
],
|
||||||
|
};
|
||||||
19
src/populate/new_sectorbachecas.js
Normal file
19
src/populate/new_sectorbachecas.js
Normal 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' },
|
||||||
|
],
|
||||||
|
};
|
||||||
24
src/populate/new_sectorgoods.js
Normal file
24
src/populate/new_sectorgoods.js
Normal 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' },
|
||||||
|
],
|
||||||
|
};
|
||||||
19
src/populate/new_sectors.js
Normal file
19
src/populate/new_sectors.js
Normal 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' },
|
||||||
|
],
|
||||||
|
};
|
||||||
163
src/populate/new_subskills.js
Normal file
163
src/populate/new_subskills.js
Normal 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' },
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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'},
|
|
||||||
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -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
913
src/router/api2_router.js
Normal 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;
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
21
src/routes/assets.js
Normal 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
25
src/routes/posters.js
Normal 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
29
src/routes/sync.js
Normal 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
17
src/routes/templates.js
Normal 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;
|
||||||
33
src/scripts/seedTemplates.js
Normal file
33
src/scripts/seedTemplates.js
Normal 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();
|
||||||
@@ -14,4 +14,3 @@ const seedTemplates = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
seedTemplates();
|
seedTemplates();
|
||||||
s
|
|
||||||
220
src/sendemail.js
220
src/sendemail.js
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
151
src/services/PosterEditor.js
Normal file
151
src/services/PosterEditor.js
Normal 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
125
src/services/SyncManager.js
Normal 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();
|
||||||
@@ -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 } }
|
||||||
|
|||||||
154
src/services/imageGenerator.js
Normal file
154
src/services/imageGenerator.js
Normal 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();
|
||||||
870
src/services/posterRenderer.js
Normal file
870
src/services/posterRenderer.js
Normal 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();
|
||||||
@@ -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
2353
src/templates/template-seeds.js
Normal file
2353
src/templates/template-seeds.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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,10 +3347,12 @@ 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);
|
||||||
query.push(mysort);
|
if (mysort.$sort) {
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.2.85
|
1.2.86
|
||||||
BIN
uploads/ai-generated/ai_1765716970209_55yyoiuf2.jpg
Normal file
BIN
uploads/ai-generated/ai_1765716970209_55yyoiuf2.jpg
Normal file
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 |
Reference in New Issue
Block a user