refactor: remove voting system, add author colors + pin numbering

- Remove votes backend (route, db functions, server mount)
- Remove votes frontend (store, socket, PinOverlay badges, FeedbackPanel UI)
- Add authorColors utility (8-color palette, deterministic hash)
- Add getPinNumber() to annotationStore
- Fix JWT to include username/display_name
- Add resolveAuthorName DB fallback for old tokens
This commit is contained in:
Hiren Kangad
2026-03-11 00:54:48 +05:30
parent f7a39a1726
commit f24414e25c
11 changed files with 60 additions and 200 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ const REFBOARD_API_KEY = process.env.REFBOARD_API_KEY || '';
function generateToken(user) {
return jwt.sign(
{ id: user.id, email: user.email, role: user.role },
{ id: user.id, email: user.email, role: user.role, username: user.username, display_name: user.display_name },
JWT_SECRET,
{ expiresIn: JWT_EXPIRES_IN }
);
-29
View File
@@ -527,33 +527,6 @@ function updateImageMedia(imageId, { posterAssetKey, duration, nativeWidth, nati
`).run(posterAssetKey || null, duration || null, nativeWidth || null, nativeHeight || null, imageId);
}
// ---------------------
// Vote helpers
// ---------------------
function getVotesByBoard(boardId) {
return db.prepare('SELECT * FROM object_votes WHERE board_id = ?').all(boardId);
}
const _toggleVoteTx = db.transaction((boardId, objectId, userId) => {
const existing = db.prepare(
'SELECT 1 FROM object_votes WHERE board_id = ? AND object_id = ? AND user_id = ?'
).get(boardId, objectId, userId);
if (existing) {
db.prepare('DELETE FROM object_votes WHERE board_id = ? AND object_id = ? AND user_id = ?')
.run(boardId, objectId, userId);
return false; // vote removed
} else {
db.prepare('INSERT INTO object_votes (board_id, object_id, user_id) VALUES (?, ?, ?)')
.run(boardId, objectId, userId);
return true; // vote added
}
});
function toggleVote(boardId, objectId, userId) {
return _toggleVoteTx(boardId, objectId, userId);
}
// ---------------------
// Thread helpers
// ---------------------
@@ -681,6 +654,4 @@ module.exports = {
incrementThreadCommentCount, decrementThreadCommentCount,
// Comments
getCommentsByThread, getCommentsByBoard, getComment, createComment, updateComment, deleteComment,
// Votes
getVotesByBoard, toggleVote,
};
+12 -2
View File
@@ -14,9 +14,19 @@ const {
deleteComment,
incrementThreadCommentCount,
decrementThreadCommentCount,
getUserById,
} = require('../db');
const { hasCollectionRole, resolveBoard } = require('./board-access');
function resolveAuthorName(reqUser) {
if (reqUser.display_name || reqUser.username) {
return reqUser.display_name || reqUser.username;
}
// Fallback: look up from DB (old JWT tokens lack these fields)
const dbUser = getUserById(reqUser.id);
return dbUser ? (dbUser.display_name || dbUser.username || dbUser.email) : 'Unknown';
}
const router = Router();
router.use(authMiddleware);
@@ -86,7 +96,7 @@ router.post('/:boardId/threads', (req, res) => {
id: commentId,
threadId,
userId,
authorName: req.user.display_name || req.user.username,
authorName: resolveAuthorName(req.user),
authorColor: null,
content: content.trim(),
});
@@ -198,7 +208,7 @@ router.post('/:boardId/threads/:threadId/comments', (req, res) => {
id: commentId,
threadId: req.params.threadId,
userId,
authorName: req.user.display_name || req.user.username,
authorName: resolveAuthorName(req.user),
authorColor: null,
content: content.trim(),
});
-54
View File
@@ -1,54 +0,0 @@
const { Router } = require('express');
const { authMiddleware } = require('../auth');
const { getVotesByBoard, toggleVote } = require('../db');
const { resolveBoard } = require('./board-access');
const router = Router();
router.use(authMiddleware);
// GET /api/boards/:boardId/votes — all votes for board
router.get('/:boardId/votes', (req, res) => {
try {
const result = resolveBoard(req, res, 'viewer');
if (!result) return;
const votes = getVotesByBoard(req.params.boardId);
return res.json({ votes });
} catch (err) {
console.error('[votes] list error:', err);
return res.status(500).json({ error: 'Internal server error' });
}
});
// POST /api/boards/:boardId/votes — toggle vote
router.post('/:boardId/votes', (req, res) => {
try {
const result = resolveBoard(req, res, 'viewer');
if (!result) return;
const { object_id } = req.body;
if (!object_id) {
return res.status(400).json({ error: 'object_id is required' });
}
const active = toggleVote(req.params.boardId, object_id, req.user.id);
const io = req.app.get('io');
if (io) {
io.to(`board:${req.params.boardId}`).emit('vote:toggle', {
boardId: req.params.boardId,
objectId: object_id,
userId: req.user.id,
active,
});
}
return res.json({ active });
} catch (err) {
console.error('[votes] toggle error:', err);
return res.status(500).json({ error: 'Internal server error' });
}
});
module.exports = router;
-2
View File
@@ -101,7 +101,6 @@ const uploadRoutes = require('./routes/upload');
const adminRoutes = require('./routes/admin');
const mmBridgeRoutes = require('./routes/mattermost-bridge');
const threadRoutes = require('./routes/threads');
const voteRoutes = require('./routes/votes');
app.use('/api/auth', authRoutes);
app.use('/api/collections', collectionRoutes);
@@ -110,7 +109,6 @@ app.use('/api/upload', uploadRoutes);
app.use('/api/admin', adminRoutes);
app.use('/api/boards', mmBridgeRoutes);
app.use('/api/boards', threadRoutes);
app.use('/api/boards', voteRoutes);
// Public shared collection route (no auth required)
app.get('/api/c/:shareToken', (req, res) => {