Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ if [ $? -ne 0 ]; then
fi

echo "🧪 Running tests..."
npm test
if docker ps --format '{{.Names}}' | grep -q '^safe-skies-api-backend'; then
echo "🛳 Detected backend running in Docker, running test:docker..."
npm run test:docker
else
echo "💻 Backend not running in Docker, running tests on host..."
npm test
fi

if [ $? -ne 0 ]; then
echo "❌ Tests failed. Commit aborted."
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ Getting the entire backend running with docker takes a few steps:

1. If you don't have Docker installed or would like to proceed without it you can skip this part and continue following the steps below. If you would like to try running the backend with Docker you can [install it here](https://www.docker.com/)

> [!NOTE]
> If you're running the backend with docker then you'll want to use the `npm run test:docker` command to run the tests.

2. Update your `.env` file

```diff
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ services:
- POSTGRES_PASSWORD=${PGPASSWORD}
expose:
- 5432
ports:
- "5432:5432"
healthcheck:
test: ["CMD", "pg_isready"]
interval: 10s
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
"build": "tsc",
"start": "node dist/src/server.js",
"test:docker": "NODE_ENV=test PGHOST=localhost jest --detectOpenHandles --forceExit",
"test": "NODE_ENV=test jest --detectOpenHandles --forceExit",
"test:watch": "NODE_ENV=test npm test -- --watch",
"test:coverage": "NODE_ENV=test jest --coverage --forceExit",
Expand Down
136 changes: 68 additions & 68 deletions src/controllers/logs.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,80 +14,80 @@ import { getUserRoleForFeed } from "../repos/permissions";
* - Regular users are forbidden.
*/
export const getLogsController = async (
req: Request,
res: Response,
req: Request,
res: Response,
): Promise<void> => {
try {
const sessionPayload = req.user;
if (!sessionPayload) {
res.status(401).json({ error: "Not authenticated" });
return;
}
const userDid = sessionPayload.did;
try {
const sessionPayload = req.user;
if (!sessionPayload) {
res.status(401).json({ error: "Not authenticated" });
return;
}
const userDid = sessionPayload.did;

// Construct an absolute URL using the host header.
const baseUrl = `${req.protocol}://${req.get("host")}`;
const url = new URL(req.url, baseUrl);
const uri = url.searchParams.get("uri");
if (!uri) {
res.status(400).json({ error: "Feed URI is required to view logs" });
return;
}
// Construct an absolute URL using the host header.
const baseUrl = `${req.protocol}://${req.get("host")}`;
const url = new URL(req.url, baseUrl);
const uri = url.searchParams.get("uri");
if (!uri) {
res.status(400).json({ error: "Feed URI is required to view logs" });
return;
}

// Determine the user's role for this feed.
const role = await getUserRoleForFeed(userDid, uri);
// Determine the user's role for this feed.
const role = await getUserRoleForFeed(userDid, uri);

if (role === "user") {
res
.status(403)
.json({ error: "Not authorized to view logs for this feed" });
return;
}
if (role === "user") {
res
.status(403)
.json({ error: "Not authorized to view logs for this feed" });
return;
}

// Build filters.
const filters: LogFilters = {
uri,
action: (url.searchParams.get("action") as ModAction) || undefined,
performedBy: url.searchParams.get("performedBy") || undefined,
targetUser: url.searchParams.get("targetUser") || undefined,
targetPost: url.searchParams.get("targetPost") || undefined,
sortBy: (url.searchParams.get("sortBy") || "descending") as
| "ascending"
| "descending",
dateRange:
url.searchParams.has("fromDate") && url.searchParams.has("toDate")
? {
fromDate: url.searchParams.get("fromDate")!,
toDate: url.searchParams.get("toDate")!,
}
: undefined,
};
// Build filters.
const filters: LogFilters = {
uri,
action: (url.searchParams.get("action") as ModAction) || undefined,
performedBy: url.searchParams.get("performedBy") || undefined,
targetUser: url.searchParams.get("targetUser") || undefined,
targetPost: url.searchParams.get("targetPost") || undefined,
sortBy: (url.searchParams.get("sortBy") || "descending") as
| "ascending"
| "descending",
dateRange:
url.searchParams.has("fromDate") && url.searchParams.has("toDate")
? {
fromDate: url.searchParams.get("fromDate")!,
toDate: url.searchParams.get("toDate")!,
}
: undefined,
};

// Fetch logs.
let logs = await getLogs(filters);
// Fetch logs.
let logs = await getLogs(filters);

// For moderators, filter logs to only show allowed actions and remove the performed_by field.
if (role === "mod") {
const allowedActions = [
"user_unban",
"user_ban",
"post_delete",
"post_restore",
];
logs = logs.filter((log) => allowedActions.includes(log.action));
// Create a new type that omits performed_by
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const filteredLogs = logs.map(({ performed_by_profile, ...rest }) => {
return rest;
});
res.json({ logs: filteredLogs });
return;
}
// For moderators, filter logs to only show allowed actions and remove the performed_by field.
if (role === "mod") {
const allowedActions = [
"user_unban",
"user_ban",
"post_delete",
"post_restore",
];
logs = logs.filter((log) => allowedActions.includes(log.action));
// Create a new type that omits performed_by
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const filteredLogs = logs.map(({ performed_by_profile, ...rest }) => {
return rest;
});
res.json({ logs: filteredLogs });
return;
}

// Admins see all logs.
res.json({ logs });
} catch (error) {
console.error("Error fetching logs:", error);
res.status(500).json({ error: "Failed to fetch logs" });
}
// Admins see all logs.
res.json({ logs });
} catch (error) {
console.error("Error fetching logs:", error);
res.status(500).json({ error: "Failed to fetch logs" });
}
};
Loading