eduverse-backend / src /modules /discussions /controllers /discussions.controller.ts
tarekdx3's picture
feat: implement core backend modules, notification system, and AI agent skill documentation
a8a7b69
Raw
History Blame Contribute Delete
7.43 kB
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';
@ApiTags('💭 Discussions')
@ApiBearerAuth('JWT-auth')
@Controller('api/discussions')
@UseGuards(JwtAuthGuard, RolesGuard)
export class DiscussionsController {
constructor(private readonly discussionsService: DiscussionsService) {}
@Get()
@ApiOperation({
summary: 'List discussion threads',
description: 'List discussion threads with optional course filter. Pinned threads appear first.',
})
@ApiResponse({ status: 200, description: 'Threads retrieved' })
async findAll(@Query() query: ThreadQueryDto, @Req() 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);
}
@Post()
@HttpCode(HttpStatus.CREATED)
@ApiOperation({
summary: 'Create discussion thread',
description: 'Create a new discussion thread in a course.',
})
@ApiBody({ type: CreateThreadDto })
@ApiResponse({ status: 201, description: 'Thread created' })
async create(@Body() dto: CreateThreadDto, @Req() 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);
}
@Get(':id')
@ApiOperation({
summary: 'Get thread with replies',
description: 'Get a discussion thread with paginated replies. Answers appear first. Increments view count.',
})
@ApiParam({ name: 'id', description: 'Thread ID', example: 1 })
@ApiResponse({ status: 200, description: 'Thread with replies' })
async getThreadWithReplies(
@Param('id', ParseIntPipe) id: number,
@Req() req: any,
@Query('page') page?: number,
@Query('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);
}
@Put(':id')
@ApiOperation({
summary: 'Update thread',
description: 'Update thread title/description. Author, instructors, and admins can update.',
})
@ApiParam({ name: 'id', description: 'Thread ID', example: 1 })
@ApiBody({ type: UpdateThreadDto })
@ApiResponse({ status: 200, description: 'Thread updated' })
async update(
@Param('id', ParseIntPipe) id: number,
@Body() dto: UpdateThreadDto,
@Req() 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);
}
@Delete(':id')
@Roles(RoleName.INSTRUCTOR, RoleName.ADMIN, RoleName.IT_ADMIN)
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({
summary: 'Delete thread',
description: 'Delete a discussion thread and all its replies. Instructors and admins only.',
})
@ApiParam({ name: 'id', description: 'Thread ID', example: 1 })
@ApiResponse({ status: 204, description: 'Thread deleted' })
async remove(@Param('id', ParseIntPipe) id: number) {
return this.discussionsService.remove(id);
}
@Post(':id/reply')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({
summary: 'Post reply',
description: 'Post a reply to a discussion thread. Fails if thread is locked.',
})
@ApiParam({ name: 'id', description: 'Thread ID', example: 1 })
@ApiBody({ type: CreateReplyDto })
@ApiResponse({ status: 201, description: 'Reply posted' })
@ApiResponse({ status: 400, description: 'Thread is locked' })
async addReply(
@Param('id', ParseIntPipe) id: number,
@Body() dto: CreateReplyDto,
@Req() 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);
}
@Patch(':id/pin')
@Roles(RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN, RoleName.IT_ADMIN)
@ApiOperation({
summary: 'Toggle pin thread',
description: 'Pin or unpin a discussion thread. Pinned threads appear at the top of the list.',
})
@ApiParam({ name: 'id', description: 'Thread ID', example: 1 })
@ApiResponse({ status: 200, description: 'Pin status toggled' })
async togglePin(@Param('id', ParseIntPipe) id: number) {
return this.discussionsService.togglePin(id);
}
@Patch(':id/lock')
@Roles(RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN, RoleName.IT_ADMIN)
@ApiOperation({
summary: 'Toggle lock thread',
description: 'Lock or unlock a discussion thread. Locked threads cannot receive new replies.',
})
@ApiParam({ name: 'id', description: 'Thread ID', example: 1 })
@ApiResponse({ status: 200, description: 'Lock status toggled' })
async toggleLock(@Param('id', ParseIntPipe) id: number, @Req() req: any) {
const userId = req.user.userId || req.user.id;
return this.discussionsService.toggleLock(id, userId);
}
@Patch('replies/:replyId/mark-answer')
@Roles(RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN, RoleName.IT_ADMIN)
@ApiOperation({
summary: 'Toggle mark as answer',
description: 'Mark or unmark a reply as the accepted answer. Answers appear first in the reply list.',
})
@ApiParam({ name: 'replyId', description: 'Reply message ID', example: 1 })
@ApiResponse({ status: 200, description: 'Answer status toggled' })
async markAnswer(@Param('replyId', ParseIntPipe) replyId: number) {
return this.discussionsService.markAnswer(replyId);
}
@Patch('replies/:replyId/endorse')
@Roles(RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN, RoleName.IT_ADMIN)
@ApiOperation({
summary: 'Toggle endorse reply',
description: 'Endorse or un-endorse a reply. Shows instructor approval of the answer.',
})
@ApiParam({ name: 'replyId', description: 'Reply message ID', example: 1 })
@ApiResponse({ status: 200, description: 'Endorsement toggled' })
async endorseReply(@Param('replyId', ParseIntPipe) replyId: number, @Req() req: any) {
const userId = req.user.userId || req.user.id;
return this.discussionsService.endorseReply(replyId, userId);
}
@Post('replies/:replyId/upvote')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Toggle upvote on reply',
description: 'Add or remove an upvote on a discussion reply.',
})
@ApiParam({ name: 'replyId', description: 'Reply message ID', example: 1 })
@ApiResponse({ status: 200, description: 'Upvote toggled' })
async toggleMessageUpvote(@Param('replyId', ParseIntPipe) replyId: number, @Req() req: any) {
const userId = req.user.userId || req.user.id;
return this.discussionsService.toggleMessageUpvote(replyId, userId);
}
}