import express from 'express'; import { Pool } from 'pg'; import axios from 'axios'; import 'dotenv/config'; const app = express(); app.use(express.json()); const pool = new Pool({ connectionString: process.env.DATABASE_URL, connectionTimeoutMillis: 5000 }); pool.on('error', (err) => { console.error('pool error: ', err); }); const SERVER_STATIC_TOKEN = process.env.SERVER_STATIC_TOKEN; async function authenticateUser(req, res, next) { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ code: "0100", error: 'Missing or malformed Authorization header' }); } const token = authHeader.split(' ')[1]; try { const startAuthTime = Date.now(); const response = await axios.get('https://account.spbr.net/api/v2/users/@me/profile', { headers: { 'Authorization': `Bearer ${token}` }, timeout: 5000 }); if (!response.data || !response.data.pid) { return res.status(401).json({ error: 'Invalid user token response' }); } req.user = response.data; next(); } catch (err) { return res.status(401).json({ error: 'Authentication failed', details: err.message }); } } function authenticateServer(req, res, next) { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(404).json({ error: 'Not found' }); } const token = authHeader.split(' ')[1]; if (token !== SERVER_STATIC_TOKEN) { return res.status(404).json({ error: 'Not found' }); } next(); } app.get('/api/v1/@me', authenticateUser, async (req, res) => { const userPid = req.user.pid; try { const queryText = ` SELECT f.pid AS friend_pid, f.since AS friended_since, p.presence, p.updated_at FROM friendships_of_pid($1) f LEFT JOIN user_presences p ON f.pid = p.pid; `; const queryStartTime = Date.now(); const { rows } = await pool.query(queryText, [userPid]); const responseData = { friends: rows.map(row => ({ pid: row.friend_pid, since: row.friended_since, presence: row.presence || null, last_updated: row.updated_at || null })) }; return res.status(200).json(responseData); } catch (err) { return res.status(500).json({ code: "0200", error: 'Internal server error' }); } }); app.get('/api/v1/presence/:pid', authenticateUser, async (req, res) => { const targetPid = parseInt(req.params.pid, 10); const userPid = req.user.pid; if (isNaN(targetPid)) { return res.status(400).json({ code: "0300", error: 'Invalid target PID' }); } try { const friendshipCheck = ` SELECT EXISTS ( SELECT 1 FROM friendships_of_pid($1) WHERE pid = $2 ) AS is_friend; `; const checkResult = await pool.query(friendshipCheck, [userPid, targetPid]); if (targetPid != userPid) { if (!checkResult.rows[0].is_friend) { return res.status(403).json({ code: "0301", error: 'Request denied: target user is not in your friends list' }); } } const presenceQuery = `SELECT presence, updated_at FROM user_presences WHERE pid = $1;`; const presenceResult = await pool.query(presenceQuery, [targetPid]); if (presenceResult.rows.length === 0) { return res.status(204).json({ error: 'Presence not found for target PID' }); } return res.status(200).json({ pid: targetPid, presence: presenceResult.rows[0].presence, last_updated: presenceResult.rows[0].updated_at }); } catch (err) { return res.status(500).json({ code: "0302", error: 'Internal server error' }); } }); app.post('/api/v1/presence', authenticateServer, async (req, res) => { const { pid, presence } = req.body; if (!pid || !presence) { return res.status(400).json({ error: 'Missing pid or presence body' }); } try { const queryText = ` INSERT INTO user_presences (pid, presence, updated_at) VALUES ($1, $2, NOW()) ON CONFLICT (pid) DO UPDATE SET presence = EXCLUDED.presence, updated_at = NOW(); `; await pool.query(queryText, [pid, JSON.stringify(presence)]); return res.status(200).json({ status: 'success' }); } catch (err) { return res.status(500).json({ error: 'Internal server error' }); } }); app.delete('/api/v1/presence/:pid', authenticateServer, async (req, res) => { const targetPid = parseInt(req.params.pid, 10); if (isNaN(targetPid)) { return res.status(400).json({ error: 'Invalid PID' }); } try { await pool.query('DELETE FROM user_presences WHERE pid = $1;', [targetPid]); return res.status(200).json({ status: 'deleted', pid: targetPid }); } catch (err) { console.error('db error in DELETE /api/v1/presence/:pid:', err); return res.status(500).json({ error: 'Internal server error' }); } }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`running on port ${PORT}`); });