- verifica email se non è stata verificata (componente)

- altri aggiornamenti grafica PAGERIS.
- OLLAMA AI
This commit is contained in:
Surya Paolo
2025-12-12 00:44:12 +01:00
parent b8dcd7f5e0
commit 037ff6f7f9
16 changed files with 1486 additions and 14 deletions

View File

@@ -39,3 +39,5 @@ AUTH_NEW_SITES=123123123
SCRIPTS_DIR=admin_scripts
CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}]
DS_API_KEY="sk-222e3addb3d8455d8b0516d93906eec7"
OLLAMA_URL=http://localhost:11434
OLLAMA_DEFAULT_MODEL=llama3.2:3b

View File

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

View File

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

View File

@@ -520,3 +520,18 @@ Gio 04/12 ORE 18:55: [<b>Circuito RIS Bologna</b>]: Inviate Monete da SurTest a
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]

View File

@@ -33,6 +33,7 @@
"email-templates": "^12.0.2",
"entities": "^7.0.0",
"express": "^4.21.2",
"express-rate-limit": "^7.1.5",
"fast-csv": "^5.0.5",
"formidable": "^3.5.2",
"fs-extra": "^11.3.2",

View File

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

View File

@@ -342,8 +342,12 @@ MovementSchema.statics.getQueryMovsByCircuitId = async function (idapp, username
'circuitfrom.symbol': 1,
'circuitto.symbol': 1,
'userfrom.verified_by_aportador': 1,
'userfrom.name': 1,
'userfrom.surname': 1,
'userfrom.username': 1,
'userfrom.profile.img': 1,
'userto.name': 1,
'userto.surname': 1,
'userto.username': 1,
'userto.profile.img': 1,
'userto.verified_by_aportador': 1,
@@ -579,7 +583,11 @@ MovementSchema.statics.getQueryAllUsersMovsByCircuitId = async function (idapp,
'circuitfrom.symbol': 1,
'circuitto.symbol': 1,
'userfrom.username': 1,
'userfrom.name': 1,
'userfrom.surname': 1,
'userfrom.profile.img': 1,
'userto.name': 1,
'userto.surname': 1,
'userto.username': 1,
'userto.profile.img': 1,
'groupfrom.groupname': 1,
@@ -1013,7 +1021,11 @@ MovementSchema.statics.getLastN_Transactions = async function (
'circuitto.name': 1,
'userfrom.verified_by_aportador': 1,
'userfrom.username': 1,
'userfrom.name': 1,
'userfrom.surname': 1,
'userfrom.profile.img': 1,
'userto.name': 1,
'userto.surname': 1,
'userto.username': 1,
'userto.profile.img': 1,
'userto.verified_by_aportador': 1,

View File

@@ -56,6 +56,9 @@ const UserSchema = new mongoose.Schema(
message: '{VALUE} is not a valid email'
}*/
},
link_verif_email: {
type: String,
},
hash: {
type: String,
},
@@ -2614,6 +2617,12 @@ UserSchema.statics.removeBookmark = async function (idapp, username, id, tab) {
UserSchema.statics.addBookmark = async function (idapp, username, id, tab) {
return await User.updateOne({ idapp, username }, { $push: { 'profile.bookmark': { id, tab } } });
};
UserSchema.statics.setLinkToVerifiedEmail = async function (idapp, username, link_verif_email) {
return await User.updateOne({ idapp, username }, { $set: { link_verif_email } });
};
UserSchema.statics.findByLinkVerifEmail = async function (idapp, link_verif_email) {
return await User.findOne({ idapp, link_verif_email });
};
// Rimuovo il Partecipa
UserSchema.statics.removeAttend = async function (idapp, username, id, tab) {
return await User.updateOne({ idapp, username }, { $pull: { 'profile.attend': { id: { $in: [id] }, tab } } });
@@ -6989,28 +6998,24 @@ UserSchema.statics.getTokenByUsernameAndCircuitName = async function (idapp, use
return user?.profile?.mycircuits?.[0]?.token || null;
};
UserSchema.statics.softDelete = async function(id) {
UserSchema.statics.softDelete = async function (id) {
return this.findByIdAndUpdate(
id,
{
deleted: true,
deletedAt: new Date()
deletedAt: new Date(),
},
{ new: true }
);
};
UserSchema.statics.getUsersList = function(idapp) {
UserSchema.statics.getUsersList = function (idapp) {
return this.find({
idapp: idapp,
$or: [
{ deleted: { $exists: false } },
{ deleted: false }
]
$or: [{ deleted: { $exists: false } }, { deleted: false }],
}).lean();
};
const User = mongoose.model('User', UserSchema);
class Hero {

View File

@@ -25,6 +25,7 @@ module.exports = {
});
}
}
} catch (e) {
console.log('error insertIntoDb', e);
}

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

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

View File

@@ -300,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) => {
try {
const body = req.body;

View File

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

View File

@@ -494,6 +494,21 @@ module.exports = {
const strlinkreg = tools.getHostByIdApp(idapp) + process.env.LINKVERIF_REG + `/?idapp=${idapp}&idlink=${idreg}`;
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) {
const strlinkreg = tools.getHostByIdApp(idapp) + `/invitetoreg/${dati.token}`;
return strlinkreg;
@@ -675,6 +690,38 @@ module.exports = {
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(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 {
let mylocalsconf = {

View File

@@ -36,6 +36,7 @@ function setupRouters(app) {
['/aitools', 'aitools_router'],
['/apisqlsrv', 'articleRoutes'],
['/api', 'api_router'],
['/api2', 'api2_router'],
['/api/telegram', 'telegram_router'],
['/inviti', 'invitaAmicoRoutes'],
];

View File

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

View File

@@ -2023,6 +2023,19 @@ module.exports = {
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) {
const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
if (myapp && myapp.confpages && myapp.confpages.hasOwnProperty('enableTokenExpired')) {
@@ -5849,6 +5862,10 @@ module.exports = {
let mystr = '';
let userfrom = '';
let userto = '';
let namefrom = '';
let surnamefrom = '';
let nameto = '';
let surnameto = '';
let profilefrom = null;
let profileto = null;
@@ -5866,6 +5883,8 @@ module.exports = {
}
if (mov.userfrom) {
userfrom += mov.userfrom.username;
namefrom = mov.userfrom.name;
surnamefrom = mov.userfrom.surname;
profilefrom = mov.userfrom.profile;
}
@@ -5879,14 +5898,16 @@ module.exports = {
}
if (mov.userto) {
userto += mov.userto.username;
nameto = mov.userto.name;
surnameto = mov.userto.surname;
profileto = mov.userto.profile;
}
// mystr = t('movement.from') + userfrom + ' ' + t('movement.to') + userto
return {
userfrom: { profile: profilefrom, username: userfrom },
userto: { profile: profileto, username: userto },
userfrom: { profile: profilefrom, username: userfrom, name: namefrom, surname: surnamefrom },
userto: { profile: profileto, username: userto, name: nameto, surname: surnameto },
tipocontofrom,
tipocontoto,
};