feat: add published_at field support to announcements with scheduled publishing and student visibility filtering
Add published_at field to announcement creation and updates, allowing instructors to schedule announcement publishing. Update student announcement filtering to only show PUBLISHED announcements where published_at <= now. Modify announcement creation to set published_at to provided value or current time for PUBLISHED status. Update announcement updates to handle published_at changes while
This commit is contained in:
parent
67f10c4287
commit
05755992a7
3 changed files with 41 additions and 8 deletions
|
|
@ -75,6 +75,7 @@ export class AnnouncementsController {
|
|||
content: parsed.content,
|
||||
status: parsed.status,
|
||||
is_pinned: parsed.is_pinned,
|
||||
published_at: parsed.published_at ? new Date(parsed.published_at) : undefined,
|
||||
files,
|
||||
});
|
||||
}
|
||||
|
|
@ -107,6 +108,7 @@ export class AnnouncementsController {
|
|||
content: body.content,
|
||||
status: body.status,
|
||||
is_pinned: body.is_pinned,
|
||||
published_at: body.published_at ? new Date(body.published_at) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,12 +58,19 @@ export class AnnouncementsService {
|
|||
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Students only see PUBLISHED announcements
|
||||
const statusFilter = (isAdmin || isInstructor) ? {} : { status: 'PUBLISHED' as const };
|
||||
// Students only see PUBLISHED announcements with published_at <= now
|
||||
const now = new Date();
|
||||
const whereClause: any = { course_id };
|
||||
|
||||
if (!(isAdmin || isInstructor)) {
|
||||
// Students: only show PUBLISHED and published_at <= now
|
||||
whereClause.status = 'PUBLISHED';
|
||||
whereClause.published_at = { lte: now };
|
||||
}
|
||||
|
||||
const [announcements, total] = await Promise.all([
|
||||
prisma.announcement.findMany({
|
||||
where: { course_id, ...statusFilter },
|
||||
where: whereClause,
|
||||
include: {
|
||||
attachments: true,
|
||||
},
|
||||
|
|
@ -74,7 +81,7 @@ export class AnnouncementsService {
|
|||
skip,
|
||||
take: limit,
|
||||
}),
|
||||
prisma.announcement.count({ where: { course_id } }),
|
||||
prisma.announcement.count({ where: whereClause }),
|
||||
]);
|
||||
|
||||
// Generate presigned URLs for attachments
|
||||
|
|
@ -105,6 +112,7 @@ export class AnnouncementsService {
|
|||
content: a.content as { th: string; en: string },
|
||||
status: a.status,
|
||||
is_pinned: a.is_pinned,
|
||||
published_at: a.published_at,
|
||||
created_at: a.created_at,
|
||||
updated_at: a.updated_at,
|
||||
attachments: attachmentsWithUrls,
|
||||
|
|
@ -132,12 +140,18 @@ export class AnnouncementsService {
|
|||
*/
|
||||
async createAnnouncement(input: CreateAnnouncementInput): Promise<CreateAnnouncementResponse> {
|
||||
try {
|
||||
const { token, course_id, title, content, status, is_pinned, files } = input;
|
||||
const { token, course_id, title, content, status, is_pinned, published_at, files } = input;
|
||||
const decoded = jwt.verify(token, config.jwt.secret) as { id: number };
|
||||
|
||||
// Validate instructor access
|
||||
await CoursesInstructorService.validateCourseInstructor(token, course_id);
|
||||
|
||||
// Determine published_at: use provided value or default to now if status is PUBLISHED
|
||||
let finalPublishedAt: Date | null = null;
|
||||
if (status === 'PUBLISHED') {
|
||||
finalPublishedAt = published_at ? new Date(published_at) : new Date();
|
||||
}
|
||||
|
||||
// Create announcement
|
||||
const announcement = await prisma.announcement.create({
|
||||
data: {
|
||||
|
|
@ -146,7 +160,7 @@ export class AnnouncementsService {
|
|||
content,
|
||||
status: status as any,
|
||||
is_pinned,
|
||||
published_at: status === 'PUBLISHED' ? new Date() : null,
|
||||
published_at: finalPublishedAt,
|
||||
created_by: decoded.id,
|
||||
},
|
||||
});
|
||||
|
|
@ -204,6 +218,7 @@ export class AnnouncementsService {
|
|||
content: announcement.content as { th: string; en: string },
|
||||
status: announcement.status,
|
||||
is_pinned: announcement.is_pinned,
|
||||
published_at: announcement.published_at,
|
||||
created_at: announcement.created_at,
|
||||
updated_at: announcement.updated_at,
|
||||
attachments,
|
||||
|
|
@ -221,7 +236,7 @@ export class AnnouncementsService {
|
|||
*/
|
||||
async updateAnnouncement(input: UpdateAnnouncementInput): Promise<UpdateAnnouncementResponse> {
|
||||
try {
|
||||
const { token, course_id, announcement_id, title, content, status, is_pinned } = input;
|
||||
const { token, course_id, announcement_id, title, content, status, is_pinned, published_at } = input;
|
||||
const decoded = jwt.verify(token, config.jwt.secret) as { id: number };
|
||||
|
||||
// Validate instructor access
|
||||
|
|
@ -236,6 +251,16 @@ export class AnnouncementsService {
|
|||
throw new NotFoundError('Announcement not found');
|
||||
}
|
||||
|
||||
// Determine published_at
|
||||
let finalPublishedAt: Date | null = existing.published_at;
|
||||
if (status === 'PUBLISHED') {
|
||||
if (published_at) {
|
||||
finalPublishedAt = new Date(published_at);
|
||||
} else if (existing.status !== 'PUBLISHED') {
|
||||
finalPublishedAt = new Date();
|
||||
}
|
||||
}
|
||||
|
||||
const announcement = await prisma.announcement.update({
|
||||
where: { id: announcement_id },
|
||||
data: {
|
||||
|
|
@ -243,7 +268,7 @@ export class AnnouncementsService {
|
|||
content,
|
||||
status: status as any,
|
||||
is_pinned,
|
||||
published_at: status === 'PUBLISHED' && existing.status !== 'PUBLISHED' ? new Date() : existing.published_at,
|
||||
published_at: finalPublishedAt,
|
||||
updated_by: decoded.id,
|
||||
},
|
||||
include: {
|
||||
|
|
@ -260,6 +285,7 @@ export class AnnouncementsService {
|
|||
content: announcement.content as { th: string; en: string },
|
||||
status: announcement.status,
|
||||
is_pinned: announcement.is_pinned,
|
||||
published_at: announcement.published_at,
|
||||
created_at: announcement.created_at,
|
||||
updated_at: announcement.updated_at,
|
||||
attachments: announcement.attachments.map(att => ({
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export interface Announcement {
|
|||
content: MultiLanguageText;
|
||||
status: string;
|
||||
is_pinned: boolean;
|
||||
published_at: Date | null;
|
||||
created_at: Date;
|
||||
updated_at: Date | null;
|
||||
attachments: AnnouncementAttachment[];
|
||||
|
|
@ -44,6 +45,7 @@ export interface CreateAnnouncementInput{
|
|||
content: MultiLanguageText;
|
||||
status: string;
|
||||
is_pinned: boolean;
|
||||
published_at?: Date;
|
||||
files?: Express.Multer.File[];
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +88,7 @@ export interface UpdateAnnouncementInput{
|
|||
content: MultiLanguageText;
|
||||
status: string;
|
||||
is_pinned: boolean;
|
||||
published_at?: Date;
|
||||
attachments?: AnnouncementAttachment[];
|
||||
}
|
||||
|
||||
|
|
@ -112,6 +115,7 @@ export interface CreateAnnouncementBody {
|
|||
content: MultiLanguageText;
|
||||
status: string;
|
||||
is_pinned: boolean;
|
||||
published_at?: string;
|
||||
}
|
||||
|
||||
export interface UpdateAnnouncementBody {
|
||||
|
|
@ -119,4 +123,5 @@ export interface UpdateAnnouncementBody {
|
|||
content: MultiLanguageText;
|
||||
status: string;
|
||||
is_pinned: boolean;
|
||||
published_at?: string;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue