import { randomUUID } from 'node:crypto' import { requireAdminAuth } from '../auth.js' import { requireStudyAuth, normalizeStudySlug, normalizeLessonSectionId, isStudyUserEnrolled } from '../study-helpers.js' import { state } from '../state.js' import { queueStudyCommentsWrite } from '../data.js' import { MAX_STUDY_COMMENTS, MAX_STUDY_COMMENT_LENGTH } from '../config.js' export function register(app) { // GET public-approved comments for a section (no auth) app.get('/api/study-comments/:studySlug/:sectionId', (req, res) => { const studySlug = normalizeStudySlug(req.params.studySlug) const sectionId = normalizeLessonSectionId(req.params.sectionId) if (!studySlug || !sectionId) { res.status(400).json({ message: 'Invalid study slug or section id.' }); return } const comments = state.studyComments .filter(c => c.studySlug === studySlug && c.sectionId === sectionId && c.isApproved === true) .sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)) .map(c => ({ id: c.id, displayName: c.displayName, text: c.text, createdAt: c.createdAt, })) res.json({ comments }) }) // POST — enrolled study users post a comment (auto-approved for enrolled users) app.post('/api/study-comments/:studySlug/:sectionId', requireStudyAuth, (req, res) => { const user = req.studyUser const studySlug = normalizeStudySlug(req.params.studySlug) const sectionId = normalizeLessonSectionId(req.params.sectionId) if (!studySlug || !sectionId) { res.status(400).json({ message: 'Invalid study slug or section id.' }); return } if (!isStudyUserEnrolled(user, studySlug)) { res.status(403).json({ message: 'Please enroll in this study to leave a comment.' }); return } const text = typeof req.body?.text === 'string' ? req.body.text.trim() : '' if (!text || text.length < 2 || text.length > MAX_STUDY_COMMENT_LENGTH) { res.status(400).json({ message: `Comment must be between 2 and ${MAX_STUDY_COMMENT_LENGTH} characters.` }); return } // Auto-approve for enrolled users const now = new Date().toISOString() const comment = { id: randomUUID(), studySlug, sectionId, userId: user.id, displayName: user.displayName || user.username, text, createdAt: now, isApproved: true, approvedAt: now, } state.studyComments.unshift(comment) if (state.studyComments.length > MAX_STUDY_COMMENTS) { state.studyComments = state.studyComments.slice(0, MAX_STUDY_COMMENTS) } queueStudyCommentsWrite() res.status(201).json({ ok: true, comment: { id: comment.id, displayName: comment.displayName, text: comment.text, createdAt: comment.createdAt, }, }) }) // DELETE — enrolled users can delete their own comment app.delete('/api/study-comments/:commentId', requireStudyAuth, (req, res) => { const user = req.studyUser const { commentId } = req.params const idx = state.studyComments.findIndex(c => c.id === commentId && c.userId === user.id) if (idx === -1) { res.status(404).json({ message: 'Comment not found.' }); return } state.studyComments.splice(idx, 1) queueStudyCommentsWrite() res.json({ ok: true }) }) // Admin: list all comments (optionally filter by studySlug) app.get('/api/admin-study-comments', requireAdminAuth, (req, res) => { const { studySlug, sectionId, approved } = req.query let comments = state.studyComments if (studySlug) comments = comments.filter(c => c.studySlug === studySlug) if (sectionId) comments = comments.filter(c => c.sectionId === sectionId) if (approved === 'true') comments = comments.filter(c => c.isApproved === true) if (approved === 'false') comments = comments.filter(c => c.isApproved !== true) res.json({ comments: comments.slice(0, 500) }) }) // Admin: approve a comment app.post('/api/admin-study-comments/:commentId/approve', requireAdminAuth, (req, res) => { const comment = state.studyComments.find(c => c.id === req.params.commentId) if (!comment) { res.status(404).json({ message: 'Comment not found.' }); return } comment.isApproved = true comment.approvedAt = new Date().toISOString() queueStudyCommentsWrite() res.json({ ok: true, comment }) }) // Admin: delete any comment app.delete('/api/admin-study-comments/:commentId', requireAdminAuth, (req, res) => { const idx = state.studyComments.findIndex(c => c.id === req.params.commentId) if (idx === -1) { res.status(404).json({ message: 'Comment not found.' }); return } state.studyComments.splice(idx, 1) queueStudyCommentsWrite() res.json({ ok: true }) }) }