import express from 'express'; import pg from 'pg'; const { Pool } = pg; import cors from 'cors'; const app = express(); app.use(cors()); app.use(express.json()); const PORT = 7072; // PostgreSQL connection const pool = new Pool({ host: 'localhost', port: 5432, database: 'amos', user: 'postgres', password: 'quixzo…2026' }); // Health check app.get('/health', (req, res) => { res.json({ status: 'ok', service: 'landvex-admin-api', port: PORT }); }); // ═══════════════════════════════════════════════════════════════ // QUIXZOOM ENDPOINTS — med rätt kolumnnamn från databasen // ═══════════════════════════════════════════════════════════════ // GET /api/v1/quixzoom/stats app.get('/api/v1/quixzoom/stats', async (req, res) => { try { const missionsResult = await pool.query('SELECT COUNT(*) FROM quixzoom.missions'); const activeResult = await pool.query("SELECT COUNT(*) FROM quixzoom.missions WHERE status = 'active'"); const submissionsResult = await pool.query('SELECT COUNT(*) FROM quixzoom.submissions'); const usersResult = await pool.query('SELECT COUNT(*) FROM quixzoom.users'); res.json({ missions_total: parseInt(missionsResult.rows[0].count), missions_active: parseInt(activeResult.rows[0].count), submissions_total: parseInt(submissionsResult.rows[0].count), contributors_total: parseInt(usersResult.rows[0].count) }); } catch (e) { res.status(500).json({ error: e.message }); } }); // GET /api/v1/quixzoom/missions app.get('/api/v1/quixzoom/missions', async (req, res) => { try { const { status, limit = 50, offset = 0 } = req.query; let query = ` SELECT m.id, m.title, m.description, m.latitude as lat, m.longitude as lon, m.status, m.reward_credits as reward_sek, m.required_photos, m.created_at, m.expires_at, COUNT(s.id) as submission_count FROM quixzoom.missions m LEFT JOIN quixzoom.submissions s ON s.claim_id IN ( SELECT id FROM quixzoom.claims WHERE mission_id = m.id ) `; const params = []; if (status) { query += ' WHERE m.status = $1'; params.push(status); } query += ` GROUP BY m.id ORDER BY m.created_at DESC LIMIT $${params.length + 1} OFFSET $${params.length + 2}`; params.push(limit, offset); const result = await pool.query(query, params); res.json({ missions: result.rows, count: result.rows.length, total: parseInt((await pool.query('SELECT COUNT(*) FROM quixzoom.missions')).rows[0].count) }); } catch (e) { res.status(500).json({ error: e.message }); } }); // GET /api/v1/quixzoom/contributors app.get('/api/v1/quixzoom/contributors', async (req, res) => { try { const result = await pool.query(` SELECT u.id, u.email, u.name, u.role as status, u.missions_completed as submission_count, u.total_earned as total_earnings, u.credits_balance FROM quixzoom.users u ORDER BY u.total_earned DESC LIMIT 50 `); res.json({ contributors: result.rows, count: result.rows.length }); } catch (e) { res.status(500).json({ error: e.message }); } }); // ═══════════════════════════════════════════════════════════════ // LEDGER ENDPOINTS — MOCK (finns ej i databasen än) // ═══════════════════════════════════════════════════════════════ const MOCK_ACCOUNTS = [ { id: '1', account_number: '1930', name: 'Företagskonto Nordea', account_type: 'asset', total_debit: 276504, total_credit: 0, balance: 276504 }, { id: '2', account_number: '2013', name: 'Egna insättningar', account_type: 'liability', total_debit: 0, total_credit: 50000, balance: -50000 }, { id: '3', account_number: '2017', name: 'Årets resultat', account_type: 'liability', total_debit: 0, total_credit: -350000, balance: 350000 }, { id: '4', account_number: '3000', name: 'Försäljning', account_type: 'income', total_debit: 0, total_credit: 222916, balance: -222916 }, { id: '5', account_number: '6100', name: 'Löner', account_type: 'expense', total_debit: 20000, total_credit: 0, balance: 20000 }, { id: '6', account_number: '6991', name: 'Övriga externa kostnader', account_type: 'expense', total_debit: 150000, total_credit: 0, balance: 150000 }, ]; app.get('/api/ledger/accounts', async (req, res) => { res.json({ ok: true, accounts: MOCK_ACCOUNTS }); }); app.get('/api/ledger/trial-balance', async (req, res) => { const totalDebit = MOCK_ACCOUNTS.reduce((sum, a) => sum + a.total_debit, 0); const totalCredit = MOCK_ACCOUNTS.reduce((sum, a) => sum + a.total_credit, 0); res.json({ ok: true, rows: MOCK_ACCOUNTS, total_debit: totalDebit, total_credit: totalCredit, balanced: true }); }); app.get('/api/ledger/journal', async (req, res) => { res.json({ ok: true, entries: [ { entry_number: '1', description: 'Inbetalning från kund', entry_date: '2026-07-01', period: '2026-07', reference: 'FAK-001', lines: [{ account_number: '1930', account_name: 'Företagskonto', debit: 222916, credit: 0 }, { account_number: '3000', account_name: 'Försäljning', debit: 0, credit: 222916 }] }, { entry_number: '2', description: 'Lön Johan', entry_date: '2026-07-05', period: '2026-07', reference: 'LÖN-001', lines: [{ account_number: '6100', account_name: 'Löner', debit: 20000, credit: 0 }, { account_number: '1930', account_name: 'Företagskonto', debit: 0, credit: 20000 }] }, ] }); }); app.get('/api/ledger/periods', async (req, res) => { res.json({ ok: true, periods: [ { period: '2026-01', fiscal_year: 2026, status: 'closed', closed_at: '2026-02-05' }, { period: '2026-02', fiscal_year: 2026, status: 'closed', closed_at: '2026-03-05' }, { period: '2026-03', fiscal_year: 2026, status: 'closed', closed_at: '2026-04-05' }, { period: '2026-04', fiscal_year: 2026, status: 'closed', closed_at: '2026-05-05' }, { period: '2026-05', fiscal_year: 2026, status: 'closed', closed_at: '2026-06-05' }, { period: '2026-06', fiscal_year: 2026, status: 'closed', closed_at: '2026-07-05' }, { period: '2026-07', fiscal_year: 2026, status: 'open' }, ] }); }); // ═══════════════════════════════════════════════════════════════ // MAIL.AAMOS.SYSTEMS ENDPOINTS — MOCK (finns ej i databasen än) // ═══════════════════════════════════════════════════════════════ app.get('/api/mail/stats', async (req, res) => { res.json({ inbox_total: 142, sent_total: 89, unread_count: 12 }); }); app.get('/api/mail/messages', async (req, res) => { const { folder = 'inbox' } = req.query; const mockMessages = [ { id: '1', subject: 'Momsdeklaration Q2 2026', sender: 'skatteverket@skatteverket.se', folder: 'inbox', read: false, created_at: '2026-07-06T10:00:00Z' }, { id: '2', subject: 'Leon kravbrev - svar krävs', sender: 'leon@inkasso.se', folder: 'inbox', read: false, created_at: '2026-07-05T14:30:00Z' }, { id: '3', subject: 'quiXzoom mission godkänd', sender: 'system@quixzoom.io', folder: 'inbox', read: true, created_at: '2026-07-04T08:15:00Z' }, { id: '4', subject: 'AWS faktura juni 2026', sender: 'aws@amazon.com', folder: 'inbox', read: true, created_at: '2026-07-03T09:00:00Z' }, { id: '5', subject: 'Re: Nordea KYC', sender: 'erik.svensson@landvex.com', folder: 'sent', read: true, created_at: '2026-07-02T16:45:00Z' }, ]; const filtered = folder === 'all' ? mockMessages : mockMessages.filter(m => m.folder === folder); res.json({ messages: filtered, count: filtered.length }); }); // ═══════════════════════════════════════════════════════════════ // SYSTEM ENDPOINTS // ═══════════════════════════════════════════════════════════════ app.get('/api/v1/system/services', async (req, res) => { const services = [ { name: 'aamos-ledger', port: 3250, status: 'running' }, { name: 'quixzoom-mission', port: 7060, status: 'running' }, { name: 'quixzoom-api', port: 8080, status: 'running' }, { name: 'landvex-api', port: 8081, status: 'running' }, { name: 'landvex-admin-api', port: 7072, status: 'running' }, { name: 'mail.aamos.systems', port: 25, status: 'running' }, { name: 'nginx', port: 80, status: 'running' } ]; res.json({ services }); }); // Start server app.listen(PORT, '0.0.0.0', () => { console.log(`LandveX Admin API running on port ${PORT}`); console.log(`Endpoints:`); console.log(` GET /api/v1/quixzoom/stats`); console.log(` GET /api/v1/quixzoom/missions`); console.log(` GET /api/v1/quixzoom/contributors`); console.log(` GET /api/ledger/accounts`); console.log(` GET /api/ledger/trial-balance`); console.log(` GET /api/ledger/journal`); console.log(` GET /api/ledger/periods`); console.log(` GET /api/mail/stats`); console.log(` GET /api/mail/messages`); console.log(` GET /api/v1/system/services`); });