Discussion only open while a study is active; v2.11.1
Publish Container / build (push) Has been cancelled
Publish Container / build (push) Has been cancelled
- Community posts, replies, likes and lesson comments are refused (403) for finished and planned studies - Reading a closed study's community/comments returns an empty list with closed:true - Discussion hub lists only active studies; community page shows a closed notice - Lesson page hides Lesson Discussion, 'Discuss this lesson' and 'Go to Community' for inactive studies; Community buttons hidden on study/dashboard pages - Tests for isStudyDiscussionOpen Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bb83f3e8c5
commit
f1d4909acb
7 files changed
+98
-14
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { requireAdminAuth } from '../auth.js'
|
||||
import { requireStudyAuth, normalizeStudySlug, normalizeLessonSectionId, hasStudyAccess, findStudyUserById, getCommunityDisplayName } from '../study-helpers.js'
|
||||
import { requireStudyAuth, normalizeStudySlug, normalizeLessonSectionId, hasStudyAccess, findStudyUserById, getCommunityDisplayName, isStudyDiscussionOpen, DISCUSSION_CLOSED_MESSAGE } 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'
|
||||
@@ -14,6 +14,10 @@ export function register(app) {
|
||||
res.status(400).json({ message: 'Invalid study slug or section id.' }); return
|
||||
}
|
||||
|
||||
if (!isStudyDiscussionOpen(studySlug)) {
|
||||
res.json({ comments: [], closed: true, message: DISCUSSION_CLOSED_MESSAGE }); 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))
|
||||
@@ -44,6 +48,10 @@ export function register(app) {
|
||||
res.status(403).json({ message: 'Please enroll in this study to leave a comment.' }); return
|
||||
}
|
||||
|
||||
if (!isStudyDiscussionOpen(studySlug)) {
|
||||
res.status(403).json({ message: DISCUSSION_CLOSED_MESSAGE, closed: true }); 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
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
getCommunityDisplayName,
|
||||
getAccessibleStudySlugs,
|
||||
markStudyCompletedIfDone,
|
||||
isStudyDiscussionOpen,
|
||||
DISCUSSION_CLOSED_MESSAGE,
|
||||
} from '../study-helpers.js'
|
||||
import { state } from '../state.js'
|
||||
import {
|
||||
@@ -368,6 +370,11 @@ export function register(app) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isStudyDiscussionOpen(studySlug)) {
|
||||
res.json({ studySlug, posts: [], closed: true, message: DISCUSSION_CLOSED_MESSAGE })
|
||||
return
|
||||
}
|
||||
|
||||
const posts = state.studyCommunityPosts
|
||||
.filter(post => post.studySlug === studySlug)
|
||||
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
||||
@@ -413,6 +420,11 @@ export function register(app) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isStudyDiscussionOpen(studySlug)) {
|
||||
res.status(403).json({ message: DISCUSSION_CLOSED_MESSAGE, closed: true })
|
||||
return
|
||||
}
|
||||
|
||||
const now = new Date().toISOString()
|
||||
const authorName = getCommunityDisplayName(user)
|
||||
const post = {
|
||||
@@ -455,6 +467,11 @@ export function register(app) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isStudyDiscussionOpen(post.studySlug)) {
|
||||
res.status(403).json({ message: DISCUSSION_CLOSED_MESSAGE, closed: true })
|
||||
return
|
||||
}
|
||||
|
||||
const reply = {
|
||||
id: randomUUID(),
|
||||
authorUserId: user.id,
|
||||
@@ -480,6 +497,9 @@ export function register(app) {
|
||||
if (!hasStudyAccess(user, post.studySlug)) {
|
||||
res.status(403).json({ message: 'Please enroll in this study to like posts.' }); return
|
||||
}
|
||||
if (!isStudyDiscussionOpen(post.studySlug)) {
|
||||
res.status(403).json({ message: DISCUSSION_CLOSED_MESSAGE, closed: true }); return
|
||||
}
|
||||
if (!Array.isArray(post.likedBy)) post.likedBy = []
|
||||
const idx = post.likedBy.indexOf(user.id)
|
||||
if (idx === -1) {
|
||||
|
||||
@@ -225,6 +225,18 @@ export async function backfillCompletedStudies() {
|
||||
return updated
|
||||
}
|
||||
|
||||
// Discussion (community posts, replies, likes and lesson comments) is only open while a
|
||||
// study is active. Finished and planned studies are closed.
|
||||
export function isStudyDiscussionOpen(studySlug) {
|
||||
const normalized = normalizeStudySlug(studySlug)
|
||||
const studies = state.cachedSiteContent?.studies
|
||||
if (!normalized || !Array.isArray(studies)) return false
|
||||
const study = studies.find(item => normalizeStudySlug(item?.slug) === normalized)
|
||||
return Boolean(study) && getEffectiveStudyStatus(study) === 'active'
|
||||
}
|
||||
|
||||
export const DISCUSSION_CLOSED_MESSAGE = 'Discussion is only open while a study is active.'
|
||||
|
||||
export function findStudyUserById(userId) {
|
||||
if (typeof userId !== 'string' || !userId.trim()) return undefined
|
||||
return state.studyUsers.find(user => user.id === userId)
|
||||
|
||||
Reference in new issue
Block a user