Skip to content
Merged
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
11 changes: 11 additions & 0 deletions database/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -322,5 +322,16 @@ CREATE TABLE user_levels (
UNIQUE(user_id, level_id)
);

CREATE TABLE activity_logs (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
organisation_id INTEGER REFERENCES organisations(id) ON DELETE SET NULL,
action VARCHAR(50) NOT NULL,
metadata JSONB DEFAULT '{}' NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
display_metadata JSONB NOT NULL DEFAULT '{}'
);



COMMIT;
49 changes: 49 additions & 0 deletions routes/activity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const express = require("express");
const pool = require("../database/db");
const router = express.Router();

function getAuthUser(req) {
const { auth } = req.cookies;
if (!auth) return null;
try {
return JSON.parse(auth);
} catch {
return null;
}
}

router.get("/", async (req, res) => {
const user = getAuthUser(req);
if (!user || !user.isLoggedIn) {
return res.status(401).json({ message: "Not logged in" });
}
const organisationId = user.organisation?.id;
if (!organisationId) {
return res.status(400).json({ message: "Organization required" });
}

try {
const { rows } = await pool.query(
`
SELECT
id,
user_id,
action,
display_metadata as metadata,
created_at
FROM activity_logs
WHERE organisation_id = $1 AND
user_id = $2
ORDER BY created_at DESC
LIMIT 100
`,
[organisationId, user.userId]
);
res.json({ logs: rows });
} catch (err) {
console.error("Error fetching activity logs:", err);
res.status(500).json({ message: "Server error" });
}
});

module.exports = router;
24 changes: 24 additions & 0 deletions routes/activityLogger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const pool = require("../database/db");

async function logActivity({
userId,
organisationId,
action,
metadata = {},
displayMetadata = {},
}) {
const sql = `
INSERT INTO activity_logs
(user_id, organisation_id, action, metadata, display_metadata)
VALUES ($1, $2, $3, $4, $5)
`;
await pool.query(sql, [
userId,
organisationId,
action,
metadata,
displayMetadata,
]);
}

module.exports = logActivity;
22 changes: 21 additions & 1 deletion routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const express = require("express");
const bcrypt = require("bcrypt");
const pool = require("../database/db");
const router = express.Router();
const logActivity = require("./activityLogger");

function setAuthCookie(res, payload) {
res.cookie("auth", JSON.stringify(payload), {
Expand Down Expand Up @@ -81,15 +82,28 @@ router.post("/login", async (req, res) => {
hasCompletedOnboarding: u.has_completed_onboarding,
organisation,
});
await logActivity({
userId: u.id,
organisationId: organisation ? organisation.id : null,
action: "login",
metadata: { email },
displayMetadata: { email },
});
return res.json({ success: true });
} catch (err) {
console.error(err);
return res.status(500).json({ message: "Server error" });
}
});

router.post("/logout", (req, res) => {
router.post("/logout", async (req, res) => {
const session = JSON.parse(req.cookies.auth || "{}");
res.clearCookie("auth", { path: "/" }).json({ success: true });
await logActivity({
userId: session.userId,
organisationId: session.organisation ? session.organisation.id : null,
action: "logout",
});
});

router.get("/me", (req, res) => {
Expand Down Expand Up @@ -161,6 +175,12 @@ router.post("/complete-onboarding", async (req, res) => {
organisation: organisation,
});

await logActivity({
userId: user.userId,
organisationId: organisation ? organisation.id : null,
action: "complete_onboarding",
});

res.json({ success: true });
} catch (err) {
console.error(err);
Expand Down
Loading