Lesson Discussion
Share a thought or reaction with other students in this lesson.{' '} See all community posts →
diff --git a/package-lock.json b/package-lock.json index b6f5edf..f1c0ee8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "siteforge", - "version": "2.11.0", + "version": "2.11.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "siteforge", - "version": "2.11.0", + "version": "2.11.1", "dependencies": { "@aws-sdk/client-s3": "^3.1135.0", "@dnd-kit/core": "^6.3.1", diff --git a/package.json b/package.json index 3a31868..c39a0b0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "2.11.0", + "version": "2.11.1", "type": "module", "scripts": { "dev": "vite", diff --git a/server/routes/study-comments.js b/server/routes/study-comments.js index 2037ec7..e31d37b 100644 --- a/server/routes/study-comments.js +++ b/server/routes/study-comments.js @@ -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 diff --git a/server/routes/study-data.js b/server/routes/study-data.js index fd7cb27..8689d05 100644 --- a/server/routes/study-data.js +++ b/server/routes/study-data.js @@ -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) { diff --git a/server/study-helpers.js b/server/study-helpers.js index e1a4cc5..e685148 100644 --- a/server/study-helpers.js +++ b/server/study-helpers.js @@ -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) diff --git a/src/studyPages.tsx b/src/studyPages.tsx index 4431755..ad04070 100644 --- a/src/studyPages.tsx +++ b/src/studyPages.tsx @@ -212,6 +212,11 @@ function getEffectiveStudyStatus(study: StudyProgram): 'active' | 'planned' | 'c return 'active' } +// Discussion (community board and lesson comments) is only open while a study is active. +function isDiscussionOpen(study: StudyProgram | undefined): boolean { + return Boolean(study) && getEffectiveStudyStatus(study as StudyProgram) === 'active' +} + function getNextReleaseLabel(study: StudyProgram): string { const upcoming = study.sections .map(getSectionReleaseDate) @@ -931,7 +936,7 @@ export function StudyLandingPage({ content }: Props) {
{enrollMessage}
} @@ -1856,6 +1861,7 @@ export function ColossiansStudySectionPage({ content }: Props) { const currentStudySlug = study?.slug ?? 'colossians' const noteId = section?.id ? getNoteId(currentStudySlug, section.id) : '' const isEnrolled = isEnrolledInStudy(auth, currentStudySlug) + const discussionOpen = isDiscussionOpen(study) const lessonCompleted = section?.id ? completedSectionIds.includes(section.id) : false usePageMeta( @@ -2371,7 +2377,7 @@ export function ColossiansStudySectionPage({ content }: Props) { ))}Share a thought or reaction with other students in this lesson.{' '} See all community posts →