initial commit
This commit is contained in:
commit
0144534910
5 changed files with 1333 additions and 0 deletions
153
index.js
Normal file
153
index.js
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
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({ 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(401).json({ error: 'Missing or malformed Authorization header' });
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
if (token !== SERVER_STATIC_TOKEN) {
|
||||
return res.status(403).json({ error: 'Forbidden: Invalid server token' });
|
||||
}
|
||||
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 = {
|
||||
user: req.user,
|
||||
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: "0402", 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({ 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 (!checkResult.rows[0].is_friend) {
|
||||
return res.status(403).json({ 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({ 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' });
|
||||
}
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`running on port ${PORT}`);
|
||||
});
|
||||
Loading…
Reference in a new issue