import express from 'express'; import dotenv from 'dotenv'; import crypto from 'crypto'; import mysql from 'mysql2/promise'; dotenv.config(); const { APP_PORT = 3000, DB_HOST = 'localhost', DB_PORT = 3306, DB_NAME = 'telefonkonyv', DB_USER = 'appuser', DB_PASSWORD = 'apppass', } = process.env; const app = express(); app.use(express.json()); // --- DB pool --- const pool = mysql.createPool({ host: DB_HOST, port: Number(DB_PORT), user: DB_USER, password: DB_PASSWORD, database: DB_NAME, waitForConnections: true, connectionLimit: 10, queueLimit: 0, }); // --- Helper: SHA-256 hash --- function sha256(text) { return crypto.createHash('sha256').update(String(text)).digest('hex'); } // --- Middleware: auth --- // A body-ban jön: { uname, pw } async function auth(req, res, next) { try { const { authUname, authPw } = req.body || {}; if (!authUname || !authPw) { return res.status(401).json({ ok: false, error: 'Missing authUname or authPw' }); } const hashed = sha256(authPw); const [rows] = await pool.query( 'SELECT id, uname, admin FROM users WHERE uname = ? AND pw = ? LIMIT 1', [authUname, hashed] ); if (!rows.length) return res.status(401).json({ ok: false, error: 'Invalid credentials' }); req.user = rows[0]; next(); } catch (e) { console.error('auth error:', e); res.status(500).json({ ok: false, error: 'Auth failed' }); } } // --- Middleware: adminCheck --- function adminCheck(req, res, next) { if (!req.user?.admin) { return res.status(403).json({ ok: false, error: 'Admin required' }); } next(); } // --- Root --- app.get('/', (req, res) => { res.json({ status: 'ok' }); }); // --- /auth --- // POST { uname, pw } -> { ok, admin, userId } app.post('/auth', async (req, res) => { try { const { authUname, authPw } = req.body || {}; if (!authUname || !authPw) { return res.status(400).json({ ok: false, error: 'Missing authUname or authPw' }); } const hashed = sha256(authPw); const [rows] = await pool.query( 'SELECT id, admin FROM users WHERE uname = ? AND pw = ? LIMIT 1', [authUname, hashed] ); if (!rows.length) return res.json({ ok: false, admin: false }); res.json({ ok: true, admin: !!rows[0].admin, userId: rows[0].id }); } catch (e) { console.error('/auth error:', e); res.status(500).json({ ok: false, error: 'Auth error' }); } }); // --- /users (admin only) --- app.get('/users', auth, adminCheck, async (req, res) => { try { const [rows] = await pool.query( 'SELECT id, uname, admin, note FROM users ORDER BY id ASC' ); res.json({ ok: true, users: rows }); } catch (err) { console.error('GET /users error:', err); res.status(500).json({ ok: false, error: 'DB error' }); } }); // POST: { uname, pw, admin?, note? } app.post('/users', auth, adminCheck, async (req, res) => { try { const { uname, pw, admin = false, note = null } = req.body || {}; if (!uname || !pw) { return res.status(400).json({ ok: false, error: 'uname and pw required' }); } const hashed = sha256(pw); const [result] = await pool.query( 'INSERT INTO users (uname, pw, admin, note) VALUES (?, ?, ?, ?)', [uname, hashed, !!admin, note] ); res.status(201).json({ ok: true, id: result.insertId }); } catch (err) { console.error('POST /users error:', err); res.status(500).json({ ok: false, error: 'DB error' }); } }); // PATCH: body tartalmazza az id-t és a módosítandó mezőket // Pl.: { id, uname?, pw?(plain), admin?, note? } app.patch('/users', auth, adminCheck, async (req, res) => { try { const { id, uname, pw, admin, note } = req.body || {}; if (!id) return res.status(400).json({ ok: false, error: 'id required' }); // Olvassuk ki az aktuális rekordot const [rows] = await pool.query('SELECT * FROM users WHERE id = ? LIMIT 1', [id]); if (!rows.length) return res.status(404).json({ ok: false, error: 'User not found' }); const current = rows[0]; const newUname = uname ?? current.uname; const newPw = pw ? sha256(pw) : current.pw; const newAdmin = (admin === undefined ? current.admin : !!admin); const newNote = (note === undefined ? current.note : note); await pool.query( 'UPDATE users SET uname = ?, pw = ?, admin = ?, note = ? WHERE id = ?', [newUname, newPw, newAdmin, newNote, id] ); res.json({ ok: true }); } catch (err) { console.error('PATCH /users error:', err); res.status(500).json({ ok: false, error: 'DB error' }); } }); // DELETE: { id } app.delete('/users', auth, adminCheck, async (req, res) => { try { const { id } = req.body || {}; if (!id) return res.status(400).json({ ok: false, error: 'id required' }); const [result] = await pool.query('DELETE FROM users WHERE id = ?', [id]); res.json({ ok: true, deleted: result.affectedRows }); } catch (err) { console.error('DELETE /users error:', err); res.status(500).json({ ok: false, error: 'DB error' }); } }); // --- /contacts (auth required) --- // GET: listázás app.get('/contacts', auth, async (req, res) => { try { const [rows] = await pool.query( 'SELECT id, name, phone, address, note FROM contacts ORDER BY id ASC' ); res.json({ ok: true, contacts: rows }); } catch (err) { console.error('GET /contacts error:', err); res.status(500).json({ ok: false, error: 'DB error' }); } }); // POST: { name, phone, address?, note? } — elfogadja a "phome"-ot is app.post('/contacts', auth, async (req, res) => { try { const { name, phone: rawPhone, phome, address = null, note = null } = req.body || {}; const phone = rawPhone ?? phome; // ha véletlen "phome" érkezik if (!name || !phone) { return res.status(400).json({ ok: false, error: 'name and phone required' }); } const [result] = await pool.query( 'INSERT INTO contacts (name, phone, address, note) VALUES (?, ?, ?, ?)', [name, phone, address, note] ); res.status(201).json({ ok: true, id: result.insertId }); } catch (err) { console.error('POST /contacts error:', err); res.status(500).json({ ok: false, error: 'DB error' }); } }); // PATCH: { id, name?, phone?/phome?, address?, note? } app.patch('/contacts', auth, async (req, res) => { try { const { id, name, phone: rawPhone, phome, address, note } = req.body || {}; if (!id) return res.status(400).json({ ok: false, error: 'id required' }); const [rows] = await pool.query('SELECT * FROM contacts WHERE id = ? LIMIT 1', [id]); if (!rows.length) return res.status(404).json({ ok: false, error: 'Contact not found' }); const current = rows[0]; const phone = (rawPhone ?? phome); const newName = name ?? current.name; const newPhone = (phone === undefined ? current.phone : phone); const newAddress = (address === undefined ? current.address : address); const newNote = (note === undefined ? current.note : note); await pool.query( 'UPDATE contacts SET name = ?, phone = ?, address = ?, note = ? WHERE id = ?', [newName, newPhone, newAddress, newNote, id] ); res.json({ ok: true }); } catch (err) { console.error('PATCH /contacts error:', err); res.status(500).json({ ok: false, error: 'DB error' }); } }); // DELETE: { id } app.delete('/contacts', auth, async (req, res) => { try { const { id } = req.body || {}; if (!id) return res.status(400).json({ ok: false, error: 'id required' }); const [result] = await pool.query('DELETE FROM contacts WHERE id = ?', [id]); res.json({ ok: true, deleted: result.affectedRows }); } catch (err) { console.error('DELETE /contacts error:', err); res.status(500).json({ ok: false, error: 'DB error' }); } }); // --- Start --- app.listen(APP_PORT, () => { console.log(`API listening on port ${APP_PORT}`); });