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
149 lines
4.0 KiB
JavaScript
149 lines
4.0 KiB
JavaScript
import express from 'express';
|
|
import pg from 'pg';
|
|
const { Pool } = pg;
|
|
import multer from 'multer';
|
|
import { randomUUID } from 'crypto';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import fs from 'fs';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const PORT = 7060;
|
|
const UPLOAD_DIR = '/opt/amos/data/quixzoom-uploads';
|
|
|
|
// PostgreSQL connection
|
|
const pool = new Pool({
|
|
host: 'localhost',
|
|
port: 5432,
|
|
database: 'amos',
|
|
user: 'postgres',
|
|
password: 'quixzo…2026'
|
|
});
|
|
|
|
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
|
|
|
|
const app = express();
|
|
app.use(express.json());
|
|
|
|
const upload = multer({
|
|
dest: UPLOAD_DIR,
|
|
limits: { fileSize: 50 * 1024 * 1024 },
|
|
});
|
|
|
|
// Serve uploaded images
|
|
app.use('/uploads', express.static(UPLOAD_DIR));
|
|
|
|
// Health check
|
|
app.get('/health', async (req, res) => {
|
|
try {
|
|
const result = await pool.query('SELECT COUNT(*) FROM quixzoom.missions');
|
|
res.json({
|
|
status: 'ok',
|
|
service: 'quixzoom-mission-engine',
|
|
port: PORT,
|
|
missions_total: parseInt(result.rows[0].count),
|
|
uptime_seconds: process.uptime(),
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (e) {
|
|
res.status(500).json({ status: 'error', error: e.message });
|
|
}
|
|
});
|
|
|
|
// GET /missions — list all missions
|
|
app.get('/missions', async (req, res) => {
|
|
try {
|
|
const result = await pool.query(`
|
|
SELECT
|
|
id,
|
|
title,
|
|
description,
|
|
latitude as lat,
|
|
longitude as lon,
|
|
area_name as address,
|
|
status,
|
|
reward_credits as reward_sek,
|
|
required_photos,
|
|
created_at,
|
|
expires_at
|
|
FROM quixzoom.missions
|
|
ORDER BY created_at DESC
|
|
`);
|
|
|
|
res.json({
|
|
missions: result.rows,
|
|
count: result.rows.length
|
|
});
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/v1/missions — API format for admin
|
|
app.get('/api/v1/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/stats — dashboard stats
|
|
app.get('/api/v1/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 });
|
|
}
|
|
});
|
|
|
|
// Start server
|
|
app.listen(PORT, () => {
|
|
console.log(`quixzoom-mission (PostgreSQL) running on port ${PORT}`);
|
|
});
|