Spaces:
Sleeping
Sleeping
| import { | |
| Controller, | |
| Get, | |
| Post, | |
| Put, | |
| Patch, | |
| Delete, | |
| Body, | |
| Param, | |
| Query, | |
| Req, | |
| UseGuards, | |
| ParseIntPipe, | |
| HttpCode, | |
| HttpStatus, | |
| } from '@nestjs/common'; | |
| import { | |
| ApiTags, | |
| ApiOperation, | |
| ApiParam, | |
| ApiBody, | |
| ApiResponse, | |
| ApiBearerAuth, | |
| } from '@nestjs/swagger'; | |
| import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; | |
| import { RolesGuard } from '../../auth/guards/roles.guard'; | |
| import { Roles } from '../../auth/roles.decorator'; | |
| import { RoleName } from '../../auth/entities/role.entity'; | |
| import { DiscussionsService } from '../services/discussions.service'; | |
| import { CreateThreadDto, UpdateThreadDto, CreateReplyDto, ThreadQueryDto } from '../dto'; | |
| ('💭 Discussions') | |
| ('JWT-auth') | |
| ('api/discussions') | |
| (JwtAuthGuard, RolesGuard) | |
| export class DiscussionsController { | |
| constructor(private readonly discussionsService: DiscussionsService) {} | |
| () | |
| ({ | |
| summary: 'List discussion threads', | |
| description: 'List discussion threads with optional course filter. Pinned threads appear first.', | |
| }) | |
| ({ status: 200, description: 'Threads retrieved' }) | |
| async findAll(() query: ThreadQueryDto, () req: any) { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = req.user.roles?.map((r: any) => r.roleName || r.name) || []; | |
| return this.discussionsService.findAll(query, userId, roles); | |
| } | |
| () | |
| (HttpStatus.CREATED) | |
| ({ | |
| summary: 'Create discussion thread', | |
| description: 'Create a new discussion thread in a course.', | |
| }) | |
| ({ type: CreateThreadDto }) | |
| ({ status: 201, description: 'Thread created' }) | |
| async create(() dto: CreateThreadDto, () req: any) { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = req.user.roles?.map((r: any) => r.roleName || r.name) || []; | |
| return this.discussionsService.create(dto, userId, roles); | |
| } | |
| (':id') | |
| ({ | |
| summary: 'Get thread with replies', | |
| description: 'Get a discussion thread with paginated replies. Answers appear first. Increments view count.', | |
| }) | |
| ({ name: 'id', description: 'Thread ID', example: 1 }) | |
| ({ status: 200, description: 'Thread with replies' }) | |
| async getThreadWithReplies( | |
| ('id', ParseIntPipe) id: number, | |
| () req: any, | |
| ('page') page?: number, | |
| ('limit') limit?: number, | |
| ) { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = req.user.roles?.map((r: any) => r.roleName || r.name) || []; | |
| return this.discussionsService.getThreadWithReplies(id, userId, roles, page || 1, limit || 50); | |
| } | |
| (':id') | |
| ({ | |
| summary: 'Update thread', | |
| description: 'Update thread title/description. Author, instructors, and admins can update.', | |
| }) | |
| ({ name: 'id', description: 'Thread ID', example: 1 }) | |
| ({ type: UpdateThreadDto }) | |
| ({ status: 200, description: 'Thread updated' }) | |
| async update( | |
| ('id', ParseIntPipe) id: number, | |
| () dto: UpdateThreadDto, | |
| () req: any, | |
| ) { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = req.user.roles?.map((r: any) => r.roleName || r.name) || []; | |
| return this.discussionsService.update(id, dto, userId, roles); | |
| } | |
| (':id') | |
| (RoleName.INSTRUCTOR, RoleName.ADMIN, RoleName.IT_ADMIN) | |
| (HttpStatus.NO_CONTENT) | |
| ({ | |
| summary: 'Delete thread', | |
| description: 'Delete a discussion thread and all its replies. Instructors and admins only.', | |
| }) | |
| ({ name: 'id', description: 'Thread ID', example: 1 }) | |
| ({ status: 204, description: 'Thread deleted' }) | |
| async remove(('id', ParseIntPipe) id: number) { | |
| return this.discussionsService.remove(id); | |
| } | |
| (':id/reply') | |
| (HttpStatus.CREATED) | |
| ({ | |
| summary: 'Post reply', | |
| description: 'Post a reply to a discussion thread. Fails if thread is locked.', | |
| }) | |
| ({ name: 'id', description: 'Thread ID', example: 1 }) | |
| ({ type: CreateReplyDto }) | |
| ({ status: 201, description: 'Reply posted' }) | |
| ({ status: 400, description: 'Thread is locked' }) | |
| async addReply( | |
| ('id', ParseIntPipe) id: number, | |
| () dto: CreateReplyDto, | |
| () req: any, | |
| ) { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = req.user.roles?.map((r: any) => r.roleName || r.name) || []; | |
| return this.discussionsService.addReply(id, userId, roles, dto); | |
| } | |
| (':id/pin') | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN, RoleName.IT_ADMIN) | |
| ({ | |
| summary: 'Toggle pin thread', | |
| description: 'Pin or unpin a discussion thread. Pinned threads appear at the top of the list.', | |
| }) | |
| ({ name: 'id', description: 'Thread ID', example: 1 }) | |
| ({ status: 200, description: 'Pin status toggled' }) | |
| async togglePin(('id', ParseIntPipe) id: number) { | |
| return this.discussionsService.togglePin(id); | |
| } | |
| (':id/lock') | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN, RoleName.IT_ADMIN) | |
| ({ | |
| summary: 'Toggle lock thread', | |
| description: 'Lock or unlock a discussion thread. Locked threads cannot receive new replies.', | |
| }) | |
| ({ name: 'id', description: 'Thread ID', example: 1 }) | |
| ({ status: 200, description: 'Lock status toggled' }) | |
| async toggleLock(('id', ParseIntPipe) id: number, () req: any) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.discussionsService.toggleLock(id, userId); | |
| } | |
| ('replies/:replyId/mark-answer') | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN, RoleName.IT_ADMIN) | |
| ({ | |
| summary: 'Toggle mark as answer', | |
| description: 'Mark or unmark a reply as the accepted answer. Answers appear first in the reply list.', | |
| }) | |
| ({ name: 'replyId', description: 'Reply message ID', example: 1 }) | |
| ({ status: 200, description: 'Answer status toggled' }) | |
| async markAnswer(('replyId', ParseIntPipe) replyId: number) { | |
| return this.discussionsService.markAnswer(replyId); | |
| } | |
| ('replies/:replyId/endorse') | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN, RoleName.IT_ADMIN) | |
| ({ | |
| summary: 'Toggle endorse reply', | |
| description: 'Endorse or un-endorse a reply. Shows instructor approval of the answer.', | |
| }) | |
| ({ name: 'replyId', description: 'Reply message ID', example: 1 }) | |
| ({ status: 200, description: 'Endorsement toggled' }) | |
| async endorseReply(('replyId', ParseIntPipe) replyId: number, () req: any) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.discussionsService.endorseReply(replyId, userId); | |
| } | |
| ('replies/:replyId/upvote') | |
| (HttpStatus.OK) | |
| ({ | |
| summary: 'Toggle upvote on reply', | |
| description: 'Add or remove an upvote on a discussion reply.', | |
| }) | |
| ({ name: 'replyId', description: 'Reply message ID', example: 1 }) | |
| ({ status: 200, description: 'Upvote toggled' }) | |
| async toggleMessageUpvote(('replyId', ParseIntPipe) replyId: number, () req: any) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.discussionsService.toggleMessageUpvote(replyId, userId); | |
| } | |
| } | |