Files
boc/landvex-admin-api/index.mjs
T
Bernt 6989a98d75 feat: Passwordless cross-device authentication
- 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
2026-07-07 07:11:50 +00:00

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`);
});