6989a98d75
- Arkitektur: docs/auth/passwordless-architecture.md - Backend: iom/quixzoom-auth-service/ (FastAPI + Redis) - Webb: quixzoom-market-pages/se/login/ (QR-kod + polling) - App: iom/quixzoom-app/src/features/auth/ (push + deep links) Flöde: QR-kod → app-godkännande → webb-inloggad
179 lines
6.1 KiB
JavaScript
179 lines
6.1 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', async (req, res) => {
|
|
res.json({ status: 'ok', service: 'landvex-admin-api', port: PORT });
|
|
});
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// QUIXZOOM ENDPOINTS
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
// GET /api/v1/quixzoom/stats — dashboard 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 contributorsResult = await pool.query('SELECT COUNT(DISTINCT user_id) FROM quixzoom.submissions');
|
|
|
|
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(contributorsResult.rows[0].count)
|
|
});
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/v1/quixzoom/missions — list 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.area_name as address,
|
|
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.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 — list contributors
|
|
app.get('/api/v1/quixzoom/contributors', async (req, res) => {
|
|
try {
|
|
const result = await pool.query(`
|
|
SELECT
|
|
u.id,
|
|
u.email,
|
|
u.display_name as name,
|
|
u.city,
|
|
u.status,
|
|
COUNT(DISTINCT s.id) as submission_count,
|
|
COALESCE(SUM(s.reward_credits), 0) as total_earnings
|
|
FROM quixzoom.users u
|
|
LEFT JOIN quixzoom.submissions s ON s.user_id = u.id
|
|
GROUP BY u.id
|
|
ORDER BY total_earnings DESC
|
|
LIMIT 50
|
|
`);
|
|
|
|
res.json({
|
|
contributors: result.rows,
|
|
count: result.rows.length
|
|
});
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// LEDGER ENDPOINTS
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
// GET /api/v1/ledger/accounts — list accounts
|
|
app.get('/api/v1/ledger/accounts', async (req, res) => {
|
|
try {
|
|
// Proxy to aamos-ledger
|
|
const response = await fetch('http://localhost:3250/api/ledger/accounts');
|
|
const data = await response.json();
|
|
res.json(data);
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/v1/ledger/trial-balance — trial balance
|
|
app.get('/api/v1/ledger/trial-balance', async (req, res) => {
|
|
try {
|
|
const response = await fetch('http://localhost:3250/api/ledger/trial-balance');
|
|
const data = await response.json();
|
|
res.json(data);
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// SYSTEM ENDPOINTS
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
// GET /api/v1/system/services — list running services
|
|
app.get('/api/v1/system/services', async (req, res) => {
|
|
try {
|
|
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: 'aamos-command-center', port: 7071, status: 'running' },
|
|
{ name: 'nginx', port: 80, status: 'running' }
|
|
];
|
|
res.json({ services });
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
// Start server
|
|
app.listen(PORT, () => {
|
|
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/v1/ledger/accounts`);
|
|
console.log(` GET /api/v1/system/services`);
|
|
});
|