238 lines
9.8 KiB
JavaScript
238 lines
9.8 KiB
JavaScript
|
|
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`);
|
||
|
|
});
|