Skip to content

Commit

Permalink
feat: 댓글 삭제 api 추가 및 item isSelected 추가 (#23)
Browse files Browse the repository at this point in the history
* fix: deleteComment body에서 params로 가져오는 방식 변경

* feat: 댓글 삭제 api 추가

* feat: item isSelected 추가
  • Loading branch information
Hellol77 authored Sep 30, 2024
1 parent 1b46654 commit ec1f1da
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 30 deletions.
18 changes: 12 additions & 6 deletions backend/src/comment/comment.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { Body, Controller, Delete, Post, Req, UseGuards } from '@nestjs/common';
import {
Body,
Controller,
Delete,
Param,
Post,
Req,
UseGuards,
} from '@nestjs/common';
import { CommentService } from './comment.service';
import { CreateCommentDto } from 'src/comment/dto/create-comment.dto';
import { AuthGuard } from '@nestjs/passport';
import { Request } from 'express';
import { DeleteCommentDto } from 'src/comment/dto/delete-comment.dto';
import {
ApiBody,
ApiCookieAuth,
Expand Down Expand Up @@ -39,15 +46,14 @@ export class CommentController {
}

@UseGuards(AuthGuard('jwt'))
@Delete()
@Delete(':comment_id')
@ApiCookieAuth('accessToken')
@ApiOperation({ summary: '아이템에 댓글을 삭제합니다.' })
@ApiBody({ type: DeleteCommentDto })
async deleteComment(
@Req() req: Request,
@Body() deleteCommentDto: DeleteCommentDto,
@Param('comment_id') comment_id: string,
): Promise<void> {
const kakaoId = req.user.kakaoId;
return this.commentService.deleteComment(kakaoId, deleteCommentDto);
return this.commentService.deleteComment(kakaoId, comment_id);
}
}
8 changes: 1 addition & 7 deletions backend/src/comment/comment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Comment } from 'src/comment/comment.entity';
import { CommentDto } from 'src/comment/dto/comment.dto';
import { CreateCommentDto } from 'src/comment/dto/create-comment.dto';
import { DeleteCommentDto } from 'src/comment/dto/delete-comment.dto';
import { Item } from 'src/item/item.entity';
import { User } from 'src/user/user.entity';
import { Repository } from 'typeorm';
Expand Down Expand Up @@ -154,12 +153,7 @@ export class CommentService {
await this.commentsRepository.save(comment);
}

async deleteComment(
userId: string,
deleteCommentDto: DeleteCommentDto,
): Promise<void> {
const { comment_id } = deleteCommentDto;

async deleteComment(userId: string, comment_id: string): Promise<void> {
const comment = await this.commentsRepository.findOne({
where: { comment_id, user: { user_id: userId } },
});
Expand Down
22 changes: 20 additions & 2 deletions backend/src/game/game.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@ import {
} from '@nestjs/swagger';
import { GameResponseDto } from 'src/game/dto/gameResponse.dto';
import { ItemsResponseDto } from 'src/item/dto/itemsResponse.dto';
import { JwtService } from '@nestjs/jwt';

@Controller('game')
@ApiTags('게임 api')
export class GameController {
private logger = new Logger('GameController');
constructor(private readonly gameService: GameService) {}
constructor(
private readonly gameService: GameService,
private readonly jwtService: JwtService,
) {}

@Get()
@ApiOperation({ summary: '모든 게임들을 불러옵니다.' })
Expand All @@ -53,9 +57,23 @@ export class GameController {
type: ItemsResponseDto,
})
async getItemsByGameId(
@Req() req: Request,
@Param('game_id') game_id: string,
): Promise<ItemsResponseDto> {
return this.gameService.findItemsByGameId(game_id);
const token = req.cookies['accessToken']; // 쿠키에서 accessToken 읽기
let user_id: string | null = null;
if (token) {
try {
const decoded = this.jwtService.verify(token, {
secret: process.env.JWT_SECRET,
}); // 토큰 검증 및 디코딩
user_id = decoded.userId; // user_id 추출
} catch (error) {
// 토큰 검증 실패 시 userId를 null로 유지하고 계속 진행
console.log('유효하지 않은 토큰입니다.', error.message);
}
}
return this.gameService.findItemsByGameId(game_id, user_id);
}

@UseGuards(AuthGuard('jwt'))
Expand Down
6 changes: 4 additions & 2 deletions backend/src/game/game.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { Game } from 'src/game/game.entity';
import { Item } from 'src/item/item.entity';
import { User } from 'src/user/user.entity';
import { JwtStrategy } from 'src/auth/strategies/jwt.strategy';
import { SelectedItem } from 'src/selected-item/selected-item.entity';
import { JwtService } from '@nestjs/jwt';

@Module({
imports: [TypeOrmModule.forFeature([Game, User, Item])],
imports: [TypeOrmModule.forFeature([Game, User, Item, SelectedItem])],
controllers: [GameController],
providers: [GameService, JwtStrategy],
providers: [GameService, JwtStrategy, JwtService],
})
export class GameModule {}
37 changes: 32 additions & 5 deletions backend/src/game/game.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Game } from 'src/game/game.entity';
import { ItemDto } from 'src/item/dto/item.dto';
import { ItemsResponseDto } from 'src/item/dto/itemsResponse.dto';
import { Item } from 'src/item/item.entity';
import { SelectedItem } from 'src/selected-item/selected-item.entity';
import { User } from 'src/user/user.entity';
import { Repository } from 'typeorm';

Expand All @@ -19,6 +20,8 @@ export class GameService {
private usersRepository: Repository<User>,
@InjectRepository(Item)
private itemsRepository: Repository<Item>,
@InjectRepository(SelectedItem)
private selectedItemsRepository: Repository<SelectedItem>,
) {}

async findAll({
Expand Down Expand Up @@ -52,22 +55,45 @@ export class GameService {
return this.gamesRepository.findOneBy({ game_id: gameId });
}

async findItemsByGameId(game_id: string): Promise<ItemsResponseDto> {
async findItemsByGameId(
game_id: string,
user_id: string | null,
): Promise<ItemsResponseDto> {
// Game 엔티티에서 game_id를 기준으로 Item들을 가져옵니다.
const items = await this.itemsRepository
.createQueryBuilder('item')
.leftJoinAndSelect('item.game', 'game')
.where('game.game_id = :game_id', { game_id })
.getMany();

if (items.length === 0) {
throw new Error('No items found for the given game_id');
}

if (user_id === null) {
const [firstItem, secondItem] = items;

const response = new ItemsResponseDto();
response.firstItem = this.toItemDto(firstItem, false); // 항상 false
response.secondItem = this.toItemDto(secondItem, false); // 항상 false
return response;
}

const selectedItem = await this.selectedItemsRepository.findOne({
where: { user: { user_id }, game: { game_id } },
relations: ['item'],
});

const [firstItem, secondItem] = items;

const response = new ItemsResponseDto();
response.firstItem = this.toItemDto(firstItem);
response.secondItem = this.toItemDto(secondItem);
response.firstItem = this.toItemDto(
firstItem,
selectedItem?.item.item_id === firstItem.item_id,
);
response.secondItem = this.toItemDto(
secondItem,
selectedItem?.item.item_id === secondItem.item_id,
);

return response;
}
Expand Down Expand Up @@ -139,11 +165,12 @@ export class GameService {
await this.gamesRepository.remove(game);
}

private toItemDto(item: Item): ItemDto {
private toItemDto(item: Item, isSelected: boolean): ItemDto {
const itemDto = new ItemDto();
itemDto.item_id = item.item_id;
itemDto.item_text = item.item_text;
itemDto.game_id = item.game.game_id;
itemDto.isSelected = isSelected;
itemDto.selected_count = item.comments ? item.comments.length : 0;
return itemDto;
}
Expand Down
4 changes: 4 additions & 0 deletions backend/src/item/dto/item.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ export class ItemDto {
@IsNotEmpty()
@ApiProperty({ description: 'The number of comments on the item' })
selected_count: number;

@IsNotEmpty()
@ApiProperty({ description: 'The boolean of user selected' })
isSelected: boolean;
}
7 changes: 7 additions & 0 deletions frontend/src/apis/deleteComment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { axiosInstance } from '.';
import END_POINTS from '../constants/api';

export default async function deleteComment(commentId) {
const { data } = await axiosInstance.delete(END_POINTS.DELETE_COMMENT(commentId));
return data;
}
14 changes: 6 additions & 8 deletions frontend/src/components/comments/comment/Comment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import HeartIconImg from '../../common/icon/HeartIcon';
import timeAgo from '../../../utils/timeAgo';
import useBestCommentLike from '../../../hooks/queries/useBestCommentLike';
import useCommentLike from '../../../hooks/queries/useCommentLike';
import useDeleteComment from '../../../hooks/queries/useDeleteComment';

export default function Comment(props) {
const { mutate: bestCommentLikeMutate } = useBestCommentLike(props.commentId, props.itemId);
const { mutate: commentLikeMutate } = useCommentLike(props.commentId, props.itemId);
const { mutate: deleteCommentMutate } = useDeleteComment(props.commentId, props.itemId);
const handleClick = () => {
if (props.isBest) {
bestCommentLikeMutate({ isHeart: props.isHeart });
Expand All @@ -18,13 +20,9 @@ export default function Comment(props) {
}
};

// const handleLike = () => {
// if (isHeart) {
// //좋아요 취소
// return;
// }
// //좋아요
// };
const handleDeleteComment = () => {
deleteCommentMutate();
};

return (
<S.CommentContainer isBest={props.isBest}>
Expand All @@ -34,7 +32,7 @@ export default function Comment(props) {
</S.BestButton>
<S.NickNameStyle>{props.nickname}</S.NickNameStyle>
<S.TimeStyle>{timeAgo(new Date(props.time))}</S.TimeStyle>
<S.TrashIcon isTrash={props.isTrash}>
<S.TrashIcon onClick={handleDeleteComment} isTrash={props.isTrash}>
<TrashIconImg />
</S.TrashIcon>
<S.LikeContainer>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const END_POINTS = {
CREATE_COMMENT: '/comment',
GET_MY_GAMES: (page, limit) => `/game/user?page=${page}&limit=${limit}`,
DELETE_MY_GAMES: (gameId) => `/game/${gameId}`,
DELETE_COMMENT: (commentId) => `/comment/${commentId}`,
};

export default END_POINTS;
22 changes: 22 additions & 0 deletions frontend/src/hooks/queries/useDeleteComment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import deleteComment from '../../apis/deleteComment';
import QUERY_KEYS from '../../constants/queryKeys';
import { toast } from 'react-toastify';

export default function useDeleteComment(commentId, itemId) {
const queryClient = useQueryClient();
const itemIdNumber = parseInt(itemId);
return useMutation({
mutationFn: async () => {
return await deleteComment(commentId);
},
onSuccess: () => {
queryClient.invalidateQueries([QUERY_KEYS.COMMENTS, { itemId: itemIdNumber }]);
queryClient.invalidateQueries([QUERY_KEYS.BEST_COMMENTS, { itemId: itemIdNumber }]);
toast.success('댓글이 삭제되었습니다.');
},
onError: () => {
toast.error('댓글 삭제에 실패했습니다.');
},
});
}

0 comments on commit ec1f1da

Please sign in to comment.