feat: add comments functionality and integrate Socket.io for real-time updates
- Implemented a full comments module in the backend with repository, service, controller, and DTOs using NestJS. - Added frontend support for comments with a `CommentSection` component and integration into content pages. - Introduced `SocketProvider` on the frontend and integrated Socket.io for real-time communication. - Updated dependencies and configurations for Socket.io and WebSockets support.
This commit is contained in:
41
backend/src/comments/comments.controller.ts
Normal file
41
backend/src/comments/comments.controller.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Req,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||
import type { AuthenticatedRequest } from "../common/interfaces/request.interface";
|
||||
import { CommentsService } from "./comments.service";
|
||||
import { CreateCommentDto } from "./dto/create-comment.dto";
|
||||
|
||||
@Controller()
|
||||
export class CommentsController {
|
||||
constructor(private readonly commentsService: CommentsService) {}
|
||||
|
||||
@Get("contents/:contentId/comments")
|
||||
findAllByContentId(@Param("contentId") contentId: string) {
|
||||
return this.commentsService.findAllByContentId(contentId);
|
||||
}
|
||||
|
||||
@Post("contents/:contentId/comments")
|
||||
@UseGuards(AuthGuard)
|
||||
create(
|
||||
@Req() req: AuthenticatedRequest,
|
||||
@Param("contentId") contentId: string,
|
||||
@Body() dto: CreateCommentDto,
|
||||
) {
|
||||
return this.commentsService.create(req.user.sub, contentId, dto);
|
||||
}
|
||||
|
||||
@Delete("comments/:id")
|
||||
@UseGuards(AuthGuard)
|
||||
remove(@Req() req: AuthenticatedRequest, @Param("id") id: string) {
|
||||
const isAdmin = req.user.role === "admin" || req.user.role === "moderator";
|
||||
return this.commentsService.remove(req.user.sub, id, isAdmin);
|
||||
}
|
||||
}
|
||||
13
backend/src/comments/comments.module.ts
Normal file
13
backend/src/comments/comments.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { AuthModule } from "../auth/auth.module";
|
||||
import { CommentsController } from "./comments.controller";
|
||||
import { CommentsService } from "./comments.service";
|
||||
import { CommentsRepository } from "./repositories/comments.repository";
|
||||
|
||||
@Module({
|
||||
imports: [AuthModule],
|
||||
controllers: [CommentsController],
|
||||
providers: [CommentsService, CommentsRepository],
|
||||
exports: [CommentsService],
|
||||
})
|
||||
export class CommentsModule {}
|
||||
37
backend/src/comments/comments.service.ts
Normal file
37
backend/src/comments/comments.service.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from "@nestjs/common";
|
||||
import { CommentsRepository } from "./repositories/comments.repository";
|
||||
import type { CreateCommentDto } from "./dto/create-comment.dto";
|
||||
|
||||
@Injectable()
|
||||
export class CommentsService {
|
||||
constructor(private readonly commentsRepository: CommentsRepository) {}
|
||||
|
||||
async create(userId: string, contentId: string, dto: CreateCommentDto) {
|
||||
return this.commentsRepository.create({
|
||||
userId,
|
||||
contentId,
|
||||
text: dto.text,
|
||||
});
|
||||
}
|
||||
|
||||
async findAllByContentId(contentId: string) {
|
||||
return this.commentsRepository.findAllByContentId(contentId);
|
||||
}
|
||||
|
||||
async remove(userId: string, commentId: string, isAdmin = false) {
|
||||
const comment = await this.commentsRepository.findOne(commentId);
|
||||
if (!comment) {
|
||||
throw new NotFoundException("Comment not found");
|
||||
}
|
||||
|
||||
if (!isAdmin && comment.userId !== userId) {
|
||||
throw new ForbiddenException("You cannot delete this comment");
|
||||
}
|
||||
|
||||
await this.commentsRepository.delete(commentId);
|
||||
}
|
||||
}
|
||||
8
backend/src/comments/dto/create-comment.dto.ts
Normal file
8
backend/src/comments/dto/create-comment.dto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { IsNotEmpty, IsString, MaxLength } from "class-validator";
|
||||
|
||||
export class CreateCommentDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(1000)
|
||||
text!: string;
|
||||
}
|
||||
53
backend/src/comments/repositories/comments.repository.ts
Normal file
53
backend/src/comments/repositories/comments.repository.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { and, desc, eq, isNull } from "drizzle-orm";
|
||||
import { DatabaseService } from "../../database/database.service";
|
||||
import { comments, users } from "../../database/schemas";
|
||||
import type { NewCommentInDb } from "../../database/schemas/comments";
|
||||
|
||||
@Injectable()
|
||||
export class CommentsRepository {
|
||||
constructor(private readonly databaseService: DatabaseService) {}
|
||||
|
||||
async create(data: NewCommentInDb) {
|
||||
const [comment] = await this.databaseService.db
|
||||
.insert(comments)
|
||||
.values(data)
|
||||
.returning();
|
||||
return comment;
|
||||
}
|
||||
|
||||
async findAllByContentId(contentId: string) {
|
||||
return this.databaseService.db
|
||||
.select({
|
||||
id: comments.id,
|
||||
text: comments.text,
|
||||
createdAt: comments.createdAt,
|
||||
updatedAt: comments.updatedAt,
|
||||
user: {
|
||||
uuid: users.uuid,
|
||||
username: users.username,
|
||||
displayName: users.displayName,
|
||||
avatarUrl: users.avatarUrl,
|
||||
},
|
||||
})
|
||||
.from(comments)
|
||||
.innerJoin(users, eq(comments.userId, users.uuid))
|
||||
.where(and(eq(comments.contentId, contentId), isNull(comments.deletedAt)))
|
||||
.orderBy(desc(comments.createdAt));
|
||||
}
|
||||
|
||||
async findOne(id: string) {
|
||||
const [comment] = await this.databaseService.db
|
||||
.select()
|
||||
.from(comments)
|
||||
.where(and(eq(comments.id, id), isNull(comments.deletedAt)));
|
||||
return comment;
|
||||
}
|
||||
|
||||
async delete(id: string) {
|
||||
await this.databaseService.db
|
||||
.update(comments)
|
||||
.set({ deletedAt: new Date() })
|
||||
.where(eq(comments.id, id));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user