require('dotenv').config(); const express = require('express'); const cors = require('cors'); const fs = require('fs'); const path = require('path'); const { getDb, saveDb } = require('./db'); const { hashPassword, comparePassword, generateToken, authMiddleware, adminMiddleware } = require('./auth'); const app = express(); app.use(cors()); app.use(express.json()); app.use((req, res, next) => { res.setHeader('Content-Type', 'application/json; charset=utf-8'); next(); }); // ── HEALTH ── app.get('/', (req, res) => { res.json({ status: 'ok', message: 'InstaPro API dziala!' }); }); // ── LOGIN ── app.post('/api/login', async (req, res) => { const { instagram, password } = req.body; const db = getDb(); const user = db.prepare('SELECT * FROM users WHERE LOWER(instagram) = LOWER(?)').get(instagram); if (!user) return res.status(401).json({ error: 'Nieprawidlowy login' }); if (!user.password) return res.status(401).json({ error: 'Haslo nie zostalo ustawione' }); const ok = await comparePassword(password, user.password); if (!ok) return res.status(401).json({ error: 'Nieprawidlowe haslo' }); const token = generateToken(user); res.json({ token, user: { instagram: user.instagram, role: user.role, expires_at: user.expires_at, plan: user.plan } }); }); // ── ADMIN: create ── app.post('/api/admin/create', async (req, res) => { const { password } = req.body; if (password !== (process.env.ADMIN_PASSWORD || 'zmien_to_haslo')) { return res.status(401).json({ error: 'Nieprawidlowe haslo admina' }); } const db = getDb(); const existing = db.prepare("SELECT * FROM users WHERE instagram = 'admin'").get(); if (existing) return res.status(400).json({ error: 'Admin juz istnieje' }); const hash = await hashPassword(password); db.prepare("INSERT INTO users (instagram, password, role) VALUES ('admin', ?, 'admin')").run(hash); res.json({ success: true, message: 'Admin utworzony' }); }); // ── ADMIN: dodaj klienta ── app.post('/api/admin/users', authMiddleware, adminMiddleware, (req, res) => { const { instagram, expires_at, plan } = req.body; const db = getDb(); try { db.prepare('INSERT INTO users (instagram, role, expires_at, plan) VALUES (?, ?, ?, ?)').run( instagram.toLowerCase(), 'client', expires_at || '', plan || 'basic' ); // Dodaj do data.json jesli nie ma const dataFile = path.join(__dirname, 'public', 'data.json'); if (fs.existsSync(dataFile)) { try { const data = JSON.parse(fs.readFileSync(dataFile, 'utf-8')); if (!data[instagram.toLowerCase()]) { data[instagram.toLowerCase()] = []; fs.writeFileSync(dataFile, JSON.stringify(data, null, 2)); } } catch {} } res.json({ success: true, message: 'Klient @' + instagram + ' dodany' }); } catch (e) { res.status(400).json({ error: 'Klient juz istnieje' }); } }); // ── ADMIN: lista klientow ── app.get('/api/admin/users', authMiddleware, adminMiddleware, (req, res) => { const db = getDb(); const users = db.prepare('SELECT id, instagram, role, expires_at, plan, created_at, CASE WHEN password IS NOT NULL THEN 1 ELSE 0 END as has_password FROM users').all(); res.json(users); }); // ── ADMIN: edytuj klienta ── app.put('/api/admin/users/:instagram', authMiddleware, adminMiddleware, (req, res) => { const { expires_at, plan } = req.body; const db = getDb(); const user = db.prepare('SELECT * FROM users WHERE LOWER(instagram) = LOWER(?)').get(req.params.instagram); if (!user) return res.status(404).json({ error: 'Klient nie istnieje' }); const newExpires = expires_at !== undefined ? expires_at : user.expires_at; const newPlan = plan !== undefined ? plan : (user.plan || 'basic'); db.prepare('UPDATE users SET expires_at = ?, plan = ? WHERE LOWER(instagram) = LOWER(?)').run( newExpires, newPlan, req.params.instagram ); res.json({ success: true }); }); // ── ADMIN: usun klienta ── app.delete('/api/admin/users/:instagram', authMiddleware, adminMiddleware, (req, res) => { const db = getDb(); db.prepare('DELETE FROM users WHERE LOWER(instagram) = LOWER(?)').run(req.params.instagram); res.json({ success: true }); }); // ── USTAW HASLO ── app.post('/api/set-password', async (req, res) => { const { instagram, password } = req.body; const db = getDb(); const user = db.prepare('SELECT * FROM users WHERE LOWER(instagram) = LOWER(?)').get(instagram); if (!user) return res.status(404).json({ error: 'Klient nie istnieje' }); if (user.password) return res.status(400).json({ error: 'Haslo juz ustawione' }); const hash = await hashPassword(password); db.prepare('UPDATE users SET password = ? WHERE LOWER(instagram) = LOWER(?)').run(hash, instagram); res.json({ success: true }); }); // ── WIADOMOSCI ── app.post('/api/messages', authMiddleware, (req, res) => { const { content } = req.body; const db = getDb(); db.prepare('INSERT INTO messages (from_user, to_user, content) VALUES (?, ?, ?)').run(req.user.instagram, 'admin', content); res.json({ success: true }); }); app.get('/api/messages', authMiddleware, (req, res) => { const db = getDb(); const isAdmin = req.user.role === 'admin'; const messages = isAdmin ? db.prepare('SELECT * FROM messages ORDER BY created_at DESC').all() : db.prepare('SELECT * FROM messages WHERE from_user = ? OR to_user = ? ORDER BY created_at DESC').all(req.user.instagram, req.user.instagram); res.json(messages); }); app.post('/api/admin/messages/reply', authMiddleware, adminMiddleware, (req, res) => { const { to_user, content } = req.body; const db = getDb(); db.prepare('INSERT INTO messages (from_user, to_user, content) VALUES (?, ?, ?)').run('admin', to_user, content); res.json({ success: true }); }); // ── SCRAPER VPS ── let scraperRunning = false; app.post('/api/scraper/run', authMiddleware, adminMiddleware, async (req, res) => { if (scraperRunning) { return res.status(429).json({ error: 'Scraper juz dziala. Poczekaj az skonczy.' }); } const { profiles } = req.body; // opcjonalnie: konkretne profile // Odpowiedz od razu, scraper dziala w tle res.json({ ok: true, message: 'Scraper uruchomiony w tle. Sprawdz logi za chwile.' }); scraperRunning = true; try { const { runScraper } = require('./scraper_hiker'); const result = await runScraper(profiles || null); console.log('[Scraper] Zakończony:', result); } catch (e) { console.error('[Scraper] Blad:', e.message); } finally { scraperRunning = false; } }); app.get('/api/scraper/status', authMiddleware, adminMiddleware, (req, res) => { const logFile = path.join(__dirname, 'scraper.log'); let lastLines = 'Brak logow'; try { if (fs.existsSync(logFile)) { const content = fs.readFileSync(logFile, 'utf-8'); const lines = content.trim().split('\n'); lastLines = lines.slice(-20).join('\n'); } } catch {} res.json({ running: scraperRunning, logs: lastLines }); }); // ── ADMIN: import z data.json do bazy ── app.post('/api/admin/import-from-data-json', authMiddleware, adminMiddleware, (req, res) => { try { const dataFile = path.join(__dirname, 'public', 'data.json'); if (!fs.existsSync(dataFile)) return res.status(404).json({ error: 'data.json nie istnieje' }); const data = JSON.parse(fs.readFileSync(dataFile, 'utf-8')); const db = getDb(); const profiles = Object.keys(data).filter(k => !k.startsWith('_meta_')); let added = 0, skipped = 0; profiles.forEach(username => { const existing = db.prepare('SELECT id FROM users WHERE LOWER(instagram) = LOWER(?)').get(username); if (!existing) { db.prepare('INSERT OR IGNORE INTO users (instagram, role, plan) VALUES (?, ?, ?)').run(username, 'client', 'basic'); added++; } else { skipped++; } }); res.json({ ok: true, added, skipped, total: profiles.length }); } catch (e) { res.status(500).json({ error: e.message }); } }); // ── STATYSTYKI SUMMARY (dla dashboardu) ── app.get('/api/stats/summary/:instagram', authMiddleware, (req, res) => { const ig = req.params.instagram; // Tylko admin lub sam klient if (req.user.role !== 'admin' && req.user.instagram.toLowerCase() !== ig.toLowerCase()) { return res.status(403).json({ error: 'Brak dostepu' }); } const dataFile = path.join(__dirname, 'public', 'data.json'); try { const data = JSON.parse(fs.readFileSync(dataFile, 'utf-8')); const foundKey = Object.keys(data).find(k => k.toLowerCase() === ig.toLowerCase() && !k.startsWith('_meta_')); if (!foundKey) return res.json({ found: false }); const entries = data[foundKey] || []; const meta = data['_meta_' + foundKey] || {}; const last = entries[entries.length - 1]; const prev = entries.length > 1 ? entries[entries.length - 2] : null; const month = entries.length > 30 ? entries[entries.length - 31] : entries[0]; res.json({ found: true, followers: last?.followers || 0, delta_day: prev ? (last.followers - prev.followers) : 0, delta_month: month ? (last?.followers - month.followers) : 0, last_update: last?.timestamp, total_entries: entries.length }); } catch { res.json({ found: false }); } }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => console.log('InstaPro API dziala na porcie ' + PORT));