feat: per-board activity log
Adds an audit trail per board, visible from a new clock-icon button on the toolbar. Useful for review-style work where someone wants to see who contributed which references and when. Logged events (high-signal only — canvas-edit noise intentionally skipped): - image / video / pdf added (whether dropped, pasted, or pulled from a URL) - board created / renamed / deleted - thread started, resolved, reopened - comment posted on a thread Backend: - new activity_logs table (id, board_id, user_id, denormalised actor name + email, action, target_type/id/label, metadata JSON, created_at) with an index on (board_id, created_at DESC). - logActivity helper resolves the user once at log time and stores their display name + email so entries survive deactivation/rename. - recordActivity wraps logActivity + a Socket.IO emit to the board's room so the panel updates live without polling. - GET /api/boards/:id/activity?limit=&before= for pagination (collection-membership gated, viewer+). Frontend: - ActivityPanel side-drawer: time-grouped feed (Today / Yesterday / older), per-action icons + tone colours (add/remove/edit/comment), pagination via "Load older", live append on Socket.IO 'activity:new'. - Relative timestamps refresh every 30s. - Wired into Editor + Toolbar. README updated; roadmap entry checked off.
This commit is contained in:
@@ -207,6 +207,82 @@ catch { db.exec('ALTER TABLE images ADD COLUMN page_count INTEGER'); }
|
||||
try { db.prepare('SELECT priority FROM media_jobs LIMIT 0').get(); }
|
||||
catch { db.exec('ALTER TABLE media_jobs ADD COLUMN priority INTEGER DEFAULT 0'); }
|
||||
|
||||
// ---------------------
|
||||
// Activity log (per-board audit trail)
|
||||
// ---------------------
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS activity_logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
board_id TEXT NOT NULL,
|
||||
user_id TEXT,
|
||||
actor_name TEXT,
|
||||
actor_email TEXT,
|
||||
action TEXT NOT NULL,
|
||||
target_type TEXT,
|
||||
target_id TEXT,
|
||||
target_label TEXT,
|
||||
metadata TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_activity_board_time
|
||||
ON activity_logs(board_id, created_at DESC);
|
||||
`);
|
||||
|
||||
const { v4: _activityUuid } = require('uuid');
|
||||
|
||||
/**
|
||||
* Append a single activity entry. Resolves user → denormalised actor fields
|
||||
* at log time so the entry survives user rename / deactivation.
|
||||
*/
|
||||
function logActivity({ boardId, userId, action, targetType, targetId, targetLabel, metadata }) {
|
||||
if (!boardId || !action) return null;
|
||||
let actorName = null;
|
||||
let actorEmail = null;
|
||||
if (userId) {
|
||||
const user = db.prepare('SELECT display_name, email FROM users WHERE id = ?').get(userId);
|
||||
if (user) {
|
||||
actorName = user.display_name;
|
||||
actorEmail = user.email;
|
||||
}
|
||||
}
|
||||
const id = _activityUuid();
|
||||
db.prepare(`
|
||||
INSERT INTO activity_logs (id, board_id, user_id, actor_name, actor_email, action, target_type, target_id, target_label, metadata)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
boardId,
|
||||
userId || null,
|
||||
actorName,
|
||||
actorEmail,
|
||||
action,
|
||||
targetType || null,
|
||||
targetId || null,
|
||||
targetLabel || null,
|
||||
metadata ? JSON.stringify(metadata) : null,
|
||||
);
|
||||
return db.prepare('SELECT * FROM activity_logs WHERE id = ?').get(id);
|
||||
}
|
||||
|
||||
function getBoardActivity(boardId, { limit = 50, before = null } = {}) {
|
||||
const lim = Math.max(1, Math.min(parseInt(limit, 10) || 50, 200));
|
||||
if (before) {
|
||||
return db.prepare(`
|
||||
SELECT * FROM activity_logs
|
||||
WHERE board_id = ? AND created_at < ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ?
|
||||
`).all(boardId, before, lim);
|
||||
}
|
||||
return db.prepare(`
|
||||
SELECT * FROM activity_logs
|
||||
WHERE board_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ?
|
||||
`).all(boardId, lim);
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
// Settings (runtime-tunable key/value pairs)
|
||||
// ---------------------
|
||||
@@ -747,6 +823,8 @@ module.exports = {
|
||||
getCommentsByThread, getCommentsByBoard, getComment, createComment, updateComment, deleteComment,
|
||||
// Settings
|
||||
getSetting, setSetting, getAllSettings, getBoolSetting,
|
||||
// Activity log
|
||||
logActivity, getBoardActivity,
|
||||
// Bootstrap
|
||||
seedAdminFromEnv,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user