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, | |
| ApiQuery, | |
| } 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 { CommunityPostsService } from '../services/community-posts.service'; | |
| import { | |
| CreatePostDto, | |
| UpdatePostDto, | |
| PostQueryDto, | |
| CreateCommentDto, | |
| CreateReactionDto, | |
| } from '../dto'; | |
| ('🌐 Community Posts') | |
| ('JWT-auth') | |
| ('api/community/posts') | |
| (JwtAuthGuard, RolesGuard) | |
| export class CommunityPostsController { | |
| constructor(private readonly postsService: CommunityPostsService) {} | |
| () | |
| ({ | |
| summary: 'List community posts', | |
| description: ` | |
| List community posts with optional filtering by course, category, and post type. | |
| **Sorting Options**: | |
| - \`recent\`: Most recent posts first (default) | |
| - \`popular\`: Most upvoted posts first | |
| - \`most_comments\`: Posts with most comments first | |
| **Note**: Pinned posts always appear at the top regardless of sort order. | |
| `, | |
| }) | |
| ({ status: 200, description: 'Paginated list of posts' }) | |
| async findAll(() query: PostQueryDto) { | |
| return this.postsService.findAll(query); | |
| } | |
| () | |
| (HttpStatus.CREATED) | |
| ({ | |
| summary: 'Create a new post', | |
| description: ` | |
| Create a new community post in a course. | |
| **Post Types**: | |
| - \`discussion\`: General discussion topic | |
| - \`question\`: Question seeking answers | |
| - \`announcement\`: Important announcement (different from admin announcements) | |
| - \`resource\`: Shared resource or material | |
| `, | |
| }) | |
| ({ type: CreatePostDto }) | |
| ({ status: 201, description: 'Post created successfully' }) | |
| async create(() dto: CreatePostDto, () req: any) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.postsService.create(dto, userId); | |
| } | |
| (':id') | |
| ({ | |
| summary: 'Get post with comments', | |
| description: 'Get a single post with paginated comments and reaction summary. Increments view count.', | |
| }) | |
| ({ name: 'id', description: 'Post ID', example: 1 }) | |
| ({ name: 'page', required: false, description: 'Comment page number', example: 1 }) | |
| ({ name: 'limit', required: false, description: 'Comments per page', example: 50 }) | |
| ({ status: 200, description: 'Post with comments and reactions' }) | |
| ({ status: 404, description: 'Post not found' }) | |
| async findOne( | |
| ('id', ParseIntPipe) id: number, | |
| () req: any, | |
| ('page') page?: number, | |
| ('limit') limit?: number, | |
| ) { | |
| const userId = req.user?.userId || req.user?.id; | |
| return this.postsService.findOne(id, userId, page || 1, limit || 50); | |
| } | |
| (':id') | |
| ({ | |
| summary: 'Update post', | |
| description: ` | |
| Update a community post. | |
| **Note**: Only the post owner can update their posts. | |
| `, | |
| }) | |
| ({ name: 'id', description: 'Post ID', example: 1 }) | |
| ({ type: UpdatePostDto }) | |
| ({ status: 200, description: 'Post updated successfully' }) | |
| ({ status: 403, description: 'Forbidden - Not the owner' }) | |
| ({ status: 404, description: 'Post not found' }) | |
| async update( | |
| ('id', ParseIntPipe) id: number, | |
| () dto: UpdatePostDto, | |
| () req: any, | |
| ) { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = this.extractRoles(req.user); | |
| return this.postsService.update(id, dto, userId, roles); | |
| } | |
| (':id') | |
| (HttpStatus.NO_CONTENT) | |
| ({ | |
| summary: 'Delete post', | |
| description: ` | |
| Delete a community post. | |
| **Allowed**: Post owner or Admin | |
| `, | |
| }) | |
| ({ name: 'id', description: 'Post ID', example: 1 }) | |
| ({ status: 204, description: 'Post deleted successfully' }) | |
| ({ status: 403, description: 'Forbidden - Not owner or admin' }) | |
| ({ status: 404, description: 'Post not found' }) | |
| async remove(('id', ParseIntPipe) id: number, () req: any) { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = this.extractRoles(req.user); | |
| return this.postsService.remove(id, userId, roles); | |
| } | |
| (':id/comment') | |
| (HttpStatus.CREATED) | |
| ({ | |
| summary: 'Add comment to post', | |
| description: ` | |
| Add a comment to a community post. Supports nested replies via \`parentCommentId\`. | |
| **Note**: Cannot comment on locked posts. | |
| `, | |
| }) | |
| ({ name: 'id', description: 'Post ID', example: 1 }) | |
| ({ type: CreateCommentDto }) | |
| ({ status: 201, description: 'Comment added successfully' }) | |
| ({ status: 400, description: 'Post is locked' }) | |
| ({ status: 404, description: 'Post not found' }) | |
| async addComment( | |
| ('id', ParseIntPipe) id: number, | |
| () dto: CreateCommentDto, | |
| () req: any, | |
| ) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.postsService.addComment(id, dto, userId); | |
| } | |
| (':id/react') | |
| (HttpStatus.OK) | |
| ({ | |
| summary: 'Toggle reaction on post', | |
| description: ` | |
| Add or remove a reaction on a post. If the same reaction type exists, it will be removed (toggle behavior). | |
| **Reaction Types**: like, helpful, insightful, thanks | |
| `, | |
| }) | |
| ({ name: 'id', description: 'Post ID', example: 1 }) | |
| ({ type: CreateReactionDto }) | |
| ({ status: 200, description: 'Reaction toggled. Returns action (added/removed)' }) | |
| ({ status: 404, description: 'Post not found' }) | |
| async toggleReaction( | |
| ('id', ParseIntPipe) id: number, | |
| () dto: CreateReactionDto, | |
| () req: any, | |
| ) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.postsService.toggleReaction(id, dto, userId); | |
| } | |
| (':id/upvote') | |
| (HttpStatus.OK) | |
| ({ | |
| summary: 'Toggle upvote on post', | |
| description: 'Add or remove an upvote on a post.', | |
| }) | |
| ({ name: 'id', description: 'Post ID', example: 1 }) | |
| ({ status: 200, description: 'Upvote toggled. Returns action (added/removed)' }) | |
| ({ status: 404, description: 'Post not found' }) | |
| async toggleUpvote( | |
| ('id', ParseIntPipe) id: number, | |
| () req: any, | |
| ) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.postsService.toggleReaction(id, { reactionType: 'upvote' as any }, userId); | |
| } | |
| (':id/pin') | |
| (RoleName.INSTRUCTOR, RoleName.ADMIN, RoleName.IT_ADMIN) | |
| ({ | |
| summary: 'Toggle pin post', | |
| description: ` | |
| Pin or unpin a community post. Pinned posts appear at the top of listings. | |
| **Allowed Roles**: Instructor, Admin | |
| `, | |
| }) | |
| ({ name: 'id', description: 'Post ID', example: 1 }) | |
| ({ status: 200, description: 'Pin status toggled' }) | |
| ({ status: 403, description: 'Forbidden - Insufficient permissions' }) | |
| ({ status: 404, description: 'Post not found' }) | |
| async togglePin(('id', ParseIntPipe) id: number, () req: any) { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = this.extractRoles(req.user); | |
| return this.postsService.togglePin(id, userId, roles); | |
| } | |
| (':id/lock') | |
| (RoleName.INSTRUCTOR, RoleName.ADMIN, RoleName.IT_ADMIN) | |
| ({ | |
| summary: 'Toggle lock post', | |
| description: ` | |
| Lock or unlock a community post. Locked posts cannot receive new comments. | |
| **Allowed Roles**: Instructor, Admin | |
| `, | |
| }) | |
| ({ name: 'id', description: 'Post ID', example: 1 }) | |
| ({ status: 200, description: 'Lock status toggled' }) | |
| ({ status: 403, description: 'Forbidden - Insufficient permissions' }) | |
| ({ status: 404, description: 'Post not found' }) | |
| async toggleLock(('id', ParseIntPipe) id: number, () req: any) { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = this.extractRoles(req.user); | |
| return this.postsService.toggleLock(id, userId, roles); | |
| } | |
| /** | |
| * Helper to extract role names from user object | |
| */ | |
| private extractRoles(user: any): string[] { | |
| if (!user.roles) return []; | |
| return user.roles.map((r: any) => r.roleName || r.name || r); | |
| } | |
| } | |