diff --git a/.dockerignore b/.dockerignore index 10c5075..b3e90c2 100644 Binary files a/.dockerignore and b/.dockerignore differ diff --git a/Dockerfile b/Dockerfile index 14f42eb..e69de29 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +0,0 @@ -# Stage 1: Build -FROM node:20-alpine as build -WORKDIR /app -# Copia package.json -COPY package*.json ./ -# Usa npm install invece di ci per evitare errori se manca package-lock.json -RUN npm install -# Copia tutto il resto -COPY . . -# Esegui la build (genera la cartella dist) -RUN npm run build - -# Stage 2: Serve con Nginx -FROM nginx:alpine -# Copia la build di React nella cartella di Nginx -COPY --from=build /app/dist /usr/share/nginx/html -# Copia la configurazione custom di Nginx -COPY nginx.conf /etc/nginx/conf.d/default.conf -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] diff --git a/nginx.conf b/nginx.conf index 1851ca1..97684f0 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,22 +1 @@ -server { - listen 80; - - # Serve i file statici del frontend React - location / { - root /usr/share/nginx/html; - index index.html index.htm; - # Fondamentale per il routing client-side (SPA) - try_files $uri $uri/ /index.html; - } - - # Proxy per le chiamate API verso il backend - location /api { - # 'backend' deve corrispondere al nome del servizio nel docker-compose.yml - proxy_pass http://backend:3001; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - } -} +���z \ No newline at end of file diff --git a/pages/Settings.tsx b/pages/Settings.tsx index ffad62b..15c8141 100644 --- a/pages/Settings.tsx +++ b/pages/Settings.tsx @@ -224,7 +224,7 @@ export const SettingsPage: React.FC = () => { const handleCondoSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { - // FIX: Do not generate ID for new condo, let backend/service handle it (POST vs PUT check) + // If editingCondo exists, use its ID. If not, empty string tells service to create new. const payload: Condo = { id: editingCondo ? editingCondo.id : '', name: condoForm.name, @@ -247,7 +247,10 @@ export const SettingsPage: React.FC = () => { setShowCondoModal(false); window.dispatchEvent(new Event('condo-updated')); - } catch (e) { console.error(e); alert("Errore nel salvataggio del condominio"); } + } catch (e) { + console.error(e); + alert("Errore nel salvataggio del condominio. Assicurati di essere amministratore."); + } }; const handleDeleteCondo = async (id: string) => { @@ -288,7 +291,10 @@ export const SettingsPage: React.FC = () => { const handleFamilySubmit = async (e: React.FormEvent) => { e.preventDefault(); try { - const quota = familyForm.customMonthlyQuota ? parseFloat(familyForm.customMonthlyQuota) : undefined; + // Handle parsing safely + const quota = familyForm.customMonthlyQuota && familyForm.customMonthlyQuota.trim() !== '' + ? parseFloat(familyForm.customMonthlyQuota) + : undefined; if (editingFamily) { const updatedFamily = { @@ -312,7 +318,7 @@ export const SettingsPage: React.FC = () => { setShowFamilyModal(false); } catch (e: any) { console.error(e); - alert(`Errore: ${e.message || "Impossibile salvare la famiglia"}`); + alert(`Errore: ${e.message || "Impossibile salvare la famiglia. Controlla i permessi."}`); } }; @@ -345,7 +351,7 @@ export const SettingsPage: React.FC = () => { } setUsers(await CondoService.getUsers()); setShowUserModal(false); - } catch (e) { alert("Errore"); } + } catch (e) { alert("Errore nel salvataggio utente"); } }; const handleDeleteUser = async (id: string) => { if(!window.confirm("Eliminare utente?")) return; diff --git a/server/Dockerfile b/server/Dockerfile index 9d04552..e69de29 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,10 +0,0 @@ -FROM node:20-alpine -WORKDIR /app -# Copia package.json del server -COPY package*.json ./ -# Installa le dipendenze -RUN npm install -# Copia il codice sorgente del server -COPY . . -EXPOSE 3001 -CMD ["npm", "start"] diff --git a/server/db.js b/server/db.js index f000304..7918b44 100644 --- a/server/db.js +++ b/server/db.js @@ -114,9 +114,6 @@ const initDb = async () => { if (!hasCondoId) { console.log('Migrating: Adding condo_id to families...'); await connection.query("ALTER TABLE families ADD COLUMN condo_id VARCHAR(36)"); - if (DB_CLIENT !== 'postgres') { // Add FK for mysql specifically if needed, simplified here - // await connection.query("ALTER TABLE families ADD CONSTRAINT fk_condo FOREIGN KEY (condo_id) REFERENCES condos(id)"); - } } if (!hasQuota) { console.log('Migrating: Adding custom_monthly_quota to families...'); @@ -207,6 +204,7 @@ const initDb = async () => { ); } + // ENSURE ADMIN EXISTS AND HAS CORRECT ROLE const [admins] = await connection.query('SELECT * FROM users WHERE email = ?', ['fcarra79@gmail.com']); if (admins.length === 0) { const hashedPassword = await bcrypt.hash('Mr10921.', 10); @@ -216,6 +214,10 @@ const initDb = async () => { [uuidv4(), 'fcarra79@gmail.com', hashedPassword, 'Amministratore', 'admin'] ); console.log('Default Admin user created.'); + } else { + // Force update role to admin just in case it was changed or created wrongly + await connection.query('UPDATE users SET role = ? WHERE email = ?', ['admin', 'fcarra79@gmail.com']); + console.log('Ensured default user has admin role.'); } console.log('Database tables initialized.'); diff --git a/server/server.js b/server/server.js index b096082..20145d6 100644 --- a/server/server.js +++ b/server/server.js @@ -16,7 +16,6 @@ app.use(cors()); app.use(bodyParser.json()); // --- EMAIL & SCHEDULER (Same as before) --- -// ... (Keeping simple for brevity, logic remains same but using pool) async function sendEmailToUsers(subject, body) { try { const [settings] = await pool.query('SELECT smtp_config FROM settings WHERE id = 1'); @@ -44,22 +43,32 @@ async function sendEmailToUsers(subject, body) { }); } catch (error) { console.error('Email error:', error.message); } } -// ... Scheduler logic ... // --- MIDDLEWARE --- const authenticateToken = (req, res, next) => { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; + if (!token) return res.sendStatus(401); + jwt.verify(token, JWT_SECRET, (err, user) => { - if (err) return res.sendStatus(403); + // Return 401 for token errors (expired/invalid) to trigger frontend logout + if (err) { + console.error("Token verification failed:", err.message); + return res.sendStatus(401); + } req.user = user; next(); }); }; + const requireAdmin = (req, res, next) => { - if (req.user && req.user.role === 'admin') next(); - else res.status(403).json({ message: 'Access denied: Admins only' }); + if (req.user && req.user.role === 'admin') { + next(); + } else { + console.warn(`Access denied for user ${req.user?.email} with role ${req.user?.role}`); + res.status(403).json({ message: 'Access denied: Admins only' }); + } }; // --- AUTH --- @@ -72,6 +81,7 @@ app.post('/api/auth/login', async (req, res) => { const validPassword = await bcrypt.compare(password, user.password_hash); if (!validPassword) return res.status(401).json({ message: 'Invalid credentials' }); + // Ensure role is captured correctly from DB const token = jwt.sign( { id: user.id, email: user.email, role: user.role, familyId: user.family_id }, JWT_SECRET, { expiresIn: '24h' } @@ -237,9 +247,7 @@ app.delete('/api/notices/:id', authenticateToken, requireAdmin, async (req, res) app.post('/api/notices/:id/read', authenticateToken, async (req, res) => { const { userId } = req.body; try { - // Ignore duplicate reads await pool.query('INSERT IGNORE INTO notice_reads (user_id, notice_id, read_at) VALUES (?, ?, NOW())', [userId, req.params.id]); - // Note: For Postgres, INSERT IGNORE is ON CONFLICT DO NOTHING res.json({ success: true }); } catch (e) { res.status(500).json({ error: e.message }); } });