const express = require("express"); const router = express.Router(); const pool = require("../../db"); const driver = require("../../graph_db"); const { strictInput } = require("../../middleware/strict_input"); const { authenticateToken } = require("../../middleware/authenticate_token"); const { graphIdToNumber } = require("./friend_graph"); const GRAPH = { database: process.env.GRAPH_DB_NAME }; function validId(value) { return Number.isInteger(value) && value > 0; } async function personIdsFromGraph(records) { return records.map((record) => graphIdToNumber(record.get("id"))); } async function personDetails(ids) { if (ids.length === 0) return []; const { rows } = await pool.query( "SELECT id, name, username, profile_link, private FROM people WHERE id = ANY($1) ORDER BY name", [ids], ); return rows; } async function sendRequest(req, res) { const me = Number(req.user.id); const to = Number(req.query.to); if (!validId(to)) { return res.status(400).send("Invalid user id"); } if (me === to) { return res.status(400).send("You cannot send a friend request to yourself"); } try { const target = await pool.query("SELECT id FROM people WHERE id = $1", [to]); if (target.rowCount === 0) { return res.status(404).send("User not found"); } const result = await driver.executeQuery( `MATCH (me:Person {db_id: $me}), (them:Person {db_id: $them}) WHERE NOT EXISTS((me)-[:FRIENDS_WITH]-(them)) AND NOT EXISTS((me)-[:REQUESTED]->(them)) AND NOT EXISTS((them)-[:REQUESTED]->(me)) CREATE (me)-[:REQUESTED {created_at: datetime()}]->(them) RETURN me`, { me, them: to }, GRAPH, ); if (result.records.length === 0) { return res.status(409).send("Request already exists or you are already friends"); } return res.status(200).json({ message: "Friend request sent" }); } catch (err) { console.error("friend request failed", { message: err.message, code: err.code, detail: err.detail, hint: err.hint, }); return res.status(500).send("Friend request was not sent, request failed"); } } async function acceptRequest(req, res) { const me = Number(req.user.id); const from = Number(req.query.from); if (!validId(from)) { return res.status(400).send("Invalid user id"); } try { const result = await driver.executeQuery( `MATCH (f:Person {db_id: $from})-[r:REQUESTED]->(me:Person {db_id: $me}) DELETE r CREATE (f)-[:FRIENDS_WITH]->(me) CREATE (me)-[:FRIENDS_WITH]->(f) RETURN f`, { me, from }, GRAPH, ); if (result.records.length === 0) { return res.status(404).send("No pending friend request from this user"); } return res.status(200).json({ message: "Friend request accepted" }); } catch (err) { console.error("friend request accept failed", { message: err.message, code: err.code, detail: err.detail, hint: err.hint, }); return res.status(500).send("Friend request was not accepted, request failed"); } } async function declineRequest(req, res) { const me = Number(req.user.id); const from = Number(req.query.from); if (!validId(from)) { return res.status(400).send("Invalid user id"); } try { const result = await driver.executeQuery( `MATCH (f:Person {db_id: $from})-[r:REQUESTED]->(me:Person {db_id: $me}) DELETE r RETURN f`, { me, from }, GRAPH, ); if (result.records.length === 0) { return res.status(404).send("No pending friend request from this user"); } return res.status(200).json({ message: "Friend request declined" }); } catch (err) { console.error("friend request decline failed", { message: err.message, code: err.code, detail: err.detail, hint: err.hint, }); return res.status(500).send("Friend request was not declined, request failed"); } } async function cancelRequest(req, res) { const me = Number(req.user.id); const to = Number(req.query.to); if (!validId(to)) { return res.status(400).send("Invalid user id"); } try { const result = await driver.executeQuery( `MATCH (me:Person {db_id: $me})-[r:REQUESTED]->(t:Person {db_id: $to}) DELETE r RETURN t`, { me, to }, GRAPH, ); if (result.records.length === 0) { return res.status(404).send("No pending friend request to this user"); } return res.status(200).json({ message: "Friend request cancelled" }); } catch (err) { console.error("friend request cancel failed", { message: err.message, code: err.code, detail: err.detail, hint: err.hint, }); return res.status(500).send("Friend request was not cancelled, request failed"); } } async function listFriends(req, res) { try { const { records } = await driver.executeQuery( "MATCH (me:Person {db_id: $me})-[:FRIENDS_WITH]-(f:Person) RETURN f.db_id AS id", { me: Number(req.user.id) }, GRAPH, ); const ids = await personIdsFromGraph(records); const friends = await personDetails(ids); return res.status(200).json(friends); } catch (err) { console.error("list friends failed", { message: err.message, code: err.code, detail: err.detail, hint: err.hint, }); return res.status(500).send("Friends could not be retrieved, request failed"); } } async function listRequests(req, res) { try { const { records } = await driver.executeQuery( "MATCH (f:Person)-[:REQUESTED]->(me:Person {db_id: $me}) RETURN f.db_id AS id", { me: Number(req.user.id) }, GRAPH, ); const ids = await personIdsFromGraph(records); const requesters = await personDetails(ids); return res.status(200).json(requesters); } catch (err) { console.error("list friend requests failed", { message: err.message, code: err.code, detail: err.detail, hint: err.hint, }); return res.status(500).send("Friend requests could not be retrieved, request failed"); } } async function removeFriend(req, res) { const me = Number(req.user.id); const id = Number(req.query.id); if (!validId(id)) { return res.status(400).send("Invalid user id"); } try { const result = await driver.executeQuery( `MATCH (me:Person {db_id: $me})-[r:FRIENDS_WITH]-(f:Person {db_id: $id}) DELETE r RETURN f`, { me, id }, GRAPH, ); if (result.records.length === 0) { return res.status(404).send("Friend not found or not in your friend list"); } return res.status(200).json({ message: "Friend removed" }); } catch (err) { console.error("friend removal failed", { message: err.message, code: err.code, detail: err.detail, hint: err.hint, }); return res.status(500).send("Friend could not be removed, request failed"); } } router.get("/", authenticateToken, strictInput(), listFriends); router.get("/requests", authenticateToken, strictInput(), listRequests); router.post("/request", authenticateToken, strictInput({ query: ["to"] }), sendRequest); router.post("/accept", authenticateToken, strictInput({ query: ["from"] }), acceptRequest); router.post("/decline", authenticateToken, strictInput({ query: ["from"] }), declineRequest); router.post("/cancel", authenticateToken, strictInput({ query: ["to"] }), cancelRequest); router.delete("/remove", authenticateToken, strictInput({ query: ["id"] }), removeFriend); module.exports = router;