diff --git a/Backend/src/controllers/announcementsController.ts b/Backend/src/controllers/announcementsController.ts index e3e3ad49..eb1d40be 100644 --- a/Backend/src/controllers/announcementsController.ts +++ b/Backend/src/controllers/announcementsController.ts @@ -1,4 +1,4 @@ -import { Body, Delete, Get, Path, Post, Put, Query, Request, Response, Route, Security, SuccessResponse, Tags, UploadedFile } from 'tsoa'; +import { Body, Delete, Get, Path, Post, Put, Query, Request, Response, Route, Security, SuccessResponse, Tags, UploadedFile, UploadedFiles, FormField } from 'tsoa'; import { ValidationError } from '../middleware/errorHandler'; import { AnnouncementsService } from '../services/announcements.service'; import { @@ -47,8 +47,8 @@ export class AnnouncementsController { } /** - * สร้างประกาศใหม่ - * Create a new announcement + * สร้างประกาศใหม่ (พร้อมไฟล์แนบ) + * Create a new announcement with optional file attachments * @param courseId - รหัสคอร์ส / Course ID */ @Post() @@ -59,17 +59,23 @@ export class AnnouncementsController { public async createAnnouncement( @Request() request: any, @Path() courseId: number, - @Body() body: CreateAnnouncementBody + @FormField() data: string, + @UploadedFiles() files?: Express.Multer.File[] ): Promise { const token = request.headers.authorization?.replace('Bearer ', ''); if (!token) throw new ValidationError('No token provided'); + + // Parse JSON data field + const parsed = JSON.parse(data) as CreateAnnouncementBody; + return await announcementsService.createAnnouncement({ token, course_id: courseId, - title: body.title, - content: body.content, - status: body.status, - is_pinned: body.is_pinned, + title: parsed.title, + content: parsed.content, + status: parsed.status, + is_pinned: parsed.is_pinned, + files, }); } diff --git a/Backend/src/services/announcements.service.ts b/Backend/src/services/announcements.service.ts index 070d0262..470c95c0 100644 --- a/Backend/src/services/announcements.service.ts +++ b/Backend/src/services/announcements.service.ts @@ -110,17 +110,18 @@ export class AnnouncementsService { } /** - * สร้างประกาศใหม่ - * Create a new announcement + * สร้างประกาศใหม่ (พร้อมอัปโหลดไฟล์แนบ) + * Create a new announcement with optional file attachments */ async createAnnouncement(input: CreateAnnouncementInput): Promise { try { - const { token, course_id, title, content, status, is_pinned } = input; + const { token, course_id, title, content, status, is_pinned, files } = input; const decoded = jwt.verify(token, config.jwt.secret) as { id: number }; // Validate instructor access await CoursesInstructorService.validateCourseInstructor(token, course_id); + // Create announcement const announcement = await prisma.announcement.create({ data: { course_id, @@ -131,11 +132,52 @@ export class AnnouncementsService { published_at: status === 'PUBLISHED' ? new Date() : null, created_by: decoded.id, }, - include: { - attachments: true, - }, }); + // Upload attachments if provided + const attachments: { + id: number; + announcement_id: number; + file_name: string; + file_path: string; + created_at: Date; + updated_at: Date; + }[] = []; + + if (files && files.length > 0) { + for (const file of files) { + const timestamp = Date.now(); + const uniqueId = Math.random().toString(36).substring(2, 15); + const fileName = file.originalname || 'file'; + const extension = fileName.split('.').pop() || ''; + const safeFilename = `${timestamp}-${uniqueId}.${extension}`; + const filePath = `courses/${course_id}/announcements/${announcement.id}/${safeFilename}`; + + // Upload to MinIO + await uploadFile(filePath, file.buffer, file.mimetype || 'application/octet-stream'); + + // Create attachment record + const attachment = await prisma.announcementAttachment.create({ + data: { + announcement_id: announcement.id, + file_name: fileName, + file_path: filePath, + file_size: file.size, + mime_type: file.mimetype || 'application/octet-stream', + }, + }); + + attachments.push({ + id: attachment.id, + announcement_id: attachment.announcement_id, + file_name: attachment.file_name, + file_path: attachment.file_path, + created_at: attachment.created_at, + updated_at: attachment.created_at, + }); + } + } + return { code: 201, message: 'Announcement created successfully', @@ -147,7 +189,7 @@ export class AnnouncementsService { is_pinned: announcement.is_pinned, created_at: announcement.created_at, updated_at: announcement.updated_at, - attachments: [], + attachments, }, }; } catch (error) { diff --git a/Backend/src/types/announcements.types.ts b/Backend/src/types/announcements.types.ts index c526906f..a1a87c7c 100644 --- a/Backend/src/types/announcements.types.ts +++ b/Backend/src/types/announcements.types.ts @@ -43,7 +43,7 @@ export interface CreateAnnouncementInput{ content: MultiLanguageText; status: string; is_pinned: boolean; - attachments?: AnnouncementAttachment[]; + files?: Express.Multer.File[]; } export interface UploadAnnouncementAttachmentInput{