Files
2026-07-31 15:11:57 +02:00

242 lines
7.4 KiB
JavaScript

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;