-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: best 댓글 가져오기 추가 * fix: comment에 user_id가 저장되지 않는 오류 수정 * feat: 베스트 댓글 가져오는 api 추가 * feat: 아이템 선택 api 추가
- Loading branch information
Showing
10 changed files
with
272 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { Controller, Param, Post, Req, UseGuards } from '@nestjs/common'; | ||
import { SelectedItemService } from './selected-item.service'; | ||
import { AuthGuard } from '@nestjs/passport'; | ||
import { Request } from 'express'; | ||
|
||
@Controller('games/:gameId/items/:itemId/select') | ||
export class SelectedItemController { | ||
constructor(private readonly selectedItemService: SelectedItemService) {} | ||
|
||
@UseGuards(AuthGuard('jwt')) | ||
@Post() | ||
async selectOrToggleItem( | ||
@Param('gameId') gameId: string, | ||
@Param('itemId') itemId: string, | ||
@Req() req: Request, | ||
): Promise<void> { | ||
const kakaoId = req.user.kakaoId; | ||
|
||
await this.selectedItemService.selectOrToggleItem(kakaoId, gameId, itemId); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { ApiProperty } from '@nestjs/swagger'; | ||
import { User } from 'src/user/user.entity'; | ||
import { Item } from 'src/item/item.entity'; | ||
import { Game } from 'src/game/game.entity'; | ||
import { | ||
Entity, | ||
PrimaryGeneratedColumn, | ||
ManyToOne, | ||
CreateDateColumn, | ||
Unique, | ||
JoinColumn, | ||
} from 'typeorm'; | ||
|
||
@Entity() | ||
@Unique(['user', 'game']) // 특정 사용자가 같은 게임에서 한 번만 선택하도록 제한 | ||
export class SelectedItem { | ||
@PrimaryGeneratedColumn('uuid') | ||
@ApiProperty({ | ||
description: '선택된 아이템 기록의 고유 식별자', | ||
example: '12312-12312-12312-12312', | ||
}) | ||
selected_item_id: string; | ||
|
||
@ManyToOne(() => User, (user) => user.selectedItems, { onDelete: 'CASCADE' }) | ||
@JoinColumn({ name: 'user_id' }) // 외래 키 이름을 'user_id'로 명시적으로 설정 | ||
@ApiProperty({ | ||
description: '아이템을 선택한 사용자', | ||
type: () => User, | ||
}) | ||
user: User; | ||
|
||
@ManyToOne(() => Game, (game) => game.selectedItems, { onDelete: 'CASCADE' }) | ||
@JoinColumn({ name: 'game_id' }) // 외래 키 이름을 'game_id'로 명시적으로 설정 | ||
@ApiProperty({ | ||
description: '선택이 이루어진 게임', | ||
type: () => Game, | ||
}) | ||
game: Game; | ||
|
||
@ManyToOne(() => Item, (item) => item.selectedItems, { onDelete: 'CASCADE' }) | ||
@JoinColumn({ name: 'item_id' }) // 외래 키 이름을 'item_id'로 명시적으로 설정 | ||
@ApiProperty({ | ||
description: '선택된 아이템', | ||
type: () => Item, | ||
}) | ||
item: Item; | ||
|
||
@CreateDateColumn() | ||
@ApiProperty({ | ||
description: '아이템이 선택된 날짜와 시간', | ||
example: '2024-09-21T10:20:30Z', | ||
}) | ||
selected_at: Date; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { SelectedItemService } from './selected-item.service'; | ||
import { SelectedItemController } from './selected-item.controller'; | ||
import { TypeOrmModule } from '@nestjs/typeorm'; | ||
import { Item } from 'src/item/item.entity'; | ||
import { User } from 'src/user/user.entity'; | ||
import { Game } from 'src/game/game.entity'; | ||
import { SelectedItem } from 'src/selected-item/selected-item.entity'; | ||
|
||
@Module({ | ||
imports: [TypeOrmModule.forFeature([Item, User, Game, SelectedItem])], | ||
controllers: [SelectedItemController], | ||
providers: [SelectedItemService], | ||
}) | ||
export class SelectedItemModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { InjectRepository } from '@nestjs/typeorm'; | ||
import { Repository, DataSource } from 'typeorm'; | ||
import { SelectedItem } from './selected-item.entity'; | ||
import { User } from 'src/user/user.entity'; | ||
import { Item } from 'src/item/item.entity'; | ||
import { Game } from 'src/game/game.entity'; | ||
import { NotFoundException, ConflictException } from '@nestjs/common'; | ||
|
||
@Injectable() | ||
export class SelectedItemService { | ||
constructor( | ||
@InjectRepository(SelectedItem) | ||
private selectedItemRepository: Repository<SelectedItem>, | ||
@InjectRepository(Item) | ||
private itemRepository: Repository<Item>, | ||
@InjectRepository(Game) | ||
private gameRepository: Repository<Game>, | ||
@InjectRepository(User) | ||
private userRepository: Repository<User>, | ||
private readonly dataSource: DataSource, | ||
) {} | ||
|
||
async selectOrToggleItem( | ||
user_id: string, | ||
game_id: string, | ||
item_id: string, | ||
): Promise<void> { | ||
const queryRunner = this.dataSource.createQueryRunner(); | ||
|
||
await queryRunner.connect(); | ||
await queryRunner.startTransaction(); | ||
|
||
try { | ||
const user = await this.userRepository.findOneBy({ user_id }); | ||
if (!user) { | ||
throw new NotFoundException('User not found'); | ||
} | ||
|
||
const game = await this.gameRepository.findOneBy({ game_id }); | ||
const item = await this.itemRepository.findOneBy({ item_id }); | ||
if (!game || !item) { | ||
throw new NotFoundException('Game or item not found'); | ||
} | ||
|
||
const existingSelection = await queryRunner.manager.findOne( | ||
SelectedItem, | ||
{ | ||
where: { | ||
user: { user_id: user.user_id }, | ||
game: { game_id: game.game_id }, | ||
}, | ||
relations: ['item'], | ||
}, | ||
); | ||
|
||
if (existingSelection) { | ||
if (existingSelection.item.item_id === item.item_id) { | ||
// 동일한 아이템을 선택하면 취소 (삭제) | ||
await queryRunner.manager.remove(existingSelection); | ||
await this.updateItemSelectionCount(queryRunner, item, -1); // 선택 횟수 감소 | ||
} else { | ||
// 다른 아이템을 선택하면 이전 선택을 취소하고 새로운 아이템 선택 | ||
await queryRunner.manager.remove(existingSelection); | ||
await this.updateItemSelectionCount( | ||
queryRunner, | ||
existingSelection.item, | ||
-1, | ||
); // 이전 아이템 선택 횟수 감소 | ||
await this.createNewSelection(queryRunner, user, game, item); // 새로운 선택 | ||
} | ||
} else { | ||
// 중복 확인 | ||
const duplicateCheck = await queryRunner.manager.findOne(SelectedItem, { | ||
where: { user, game, item }, | ||
}); | ||
if (duplicateCheck) { | ||
throw new ConflictException('Duplicate selection exists'); | ||
} | ||
// 처음 선택하는 경우 | ||
await this.createNewSelection(queryRunner, user, game, item); | ||
} | ||
|
||
await queryRunner.commitTransaction(); | ||
} catch (error) { | ||
await queryRunner.rollbackTransaction(); | ||
throw error; | ||
} finally { | ||
await queryRunner.release(); | ||
} | ||
} | ||
|
||
private async createNewSelection( | ||
queryRunner: any, | ||
user: User, | ||
game: Game, | ||
item: Item, | ||
): Promise<void> { | ||
const newSelection = this.selectedItemRepository.create({ | ||
user, | ||
game, | ||
item, | ||
}); | ||
await queryRunner.manager.save(newSelection); | ||
await this.updateItemSelectionCount(queryRunner, item, 1); // 선택 횟수 증가 | ||
} | ||
|
||
private async updateItemSelectionCount( | ||
queryRunner: any, | ||
item: Item, | ||
countChange: number, | ||
): Promise<void> { | ||
if (item.selected_count === undefined || item.selected_count === null) { | ||
item.selected_count = 0; // 기본값 설정 | ||
} | ||
item.selected_count += countChange; | ||
await queryRunner.manager.save(item); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,52 @@ | ||
import { ApiProperty } from '@nestjs/swagger'; | ||
import { Comment } from 'src/comment/comment.entity'; | ||
import { Game } from 'src/game/game.entity'; | ||
import { Like } from 'src/like/like.entity'; | ||
import { SelectedItem } from 'src/selected-item/selected-item.entity'; | ||
import { Entity, Column, OneToMany, Unique, PrimaryColumn } from 'typeorm'; | ||
|
||
@Entity() | ||
@Unique(['user_id']) | ||
export class User { | ||
@PrimaryColumn() | ||
@ApiProperty({ | ||
description: '사용자의 고유 식별자', | ||
example: '1234567890', | ||
}) | ||
user_id: string; | ||
|
||
@Column({ unique: true }) | ||
@ApiProperty({ | ||
description: '사용자의 닉네임', | ||
example: 'username', | ||
}) | ||
username: string; | ||
|
||
@OneToMany(() => Game, (balanceGame) => balanceGame.user) | ||
@ApiProperty({ | ||
description: '사용자가 만든 게임들', | ||
type: () => [Game], | ||
}) | ||
games: Game[]; | ||
|
||
@OneToMany(() => Comment, (comment) => comment.user) | ||
@ApiProperty({ | ||
description: '사용자가 단 댓글들', | ||
type: () => [Comment], | ||
}) | ||
comments: Comment[]; | ||
|
||
@OneToMany(() => Like, (like) => like.user) | ||
@ApiProperty({ | ||
description: '사용자가 누른 좋아요들', | ||
type: () => [Like], | ||
}) | ||
likes: Like[]; | ||
|
||
@OneToMany(() => SelectedItem, (selectedItem) => selectedItem.user) | ||
@ApiProperty({ | ||
description: '사용자가 선택한 아이템들', | ||
type: () => [SelectedItem], | ||
}) | ||
selectedItems: SelectedItem[]; | ||
} |