Skip to content

Commit

Permalink
Feature/#93 - 좋아요 기능 구현 (#193)
Browse files Browse the repository at this point in the history
  • Loading branch information
xjfcnfw3 authored Nov 19, 2024
2 parents 65cbb9d + 011f4f0 commit f0c72df
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 9 deletions.
2 changes: 1 addition & 1 deletion packages/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ChatModule } from '@/chat/chat.module';
import {
typeormDevelopConfig,
typeormProductConfig,
} from '@/configs/devTypeormConfig';
} from '@/configs/typeormConfig';
import { logger } from '@/configs/logger.config';
import { StockModule } from '@/stock/stock.module';
import { UserModule } from '@/user/user.module';
Expand Down
20 changes: 18 additions & 2 deletions packages/backend/src/chat/chat.controller.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import { Controller, Get, Query } from '@nestjs/common';
import { Body, Controller, Get, Post, Query, UseGuards } from '@nestjs/common';
import {
ApiBadRequestResponse,
ApiOkResponse,
ApiOperation,
} from '@nestjs/swagger';
import SessionGuard from '@/auth/session/session.guard';
import { ChatService } from '@/chat/chat.service';
import { ToggleLikeApi } from '@/chat/decorator/like.decorator';
import { ChatScrollRequest } from '@/chat/dto/chat.request';
import { ChatScrollResponse } from '@/chat/dto/chat.response';
import { LikeRequest } from '@/chat/dto/like.request';
import { LikeService } from '@/chat/like.service';
import { GetUser } from '@/common/decorator/user.decorator';
import { User } from '@/user/domain/user.entity';

@Controller('chat')
export class ChatController {
constructor(private readonly chatService: ChatService) {}
constructor(
private readonly chatService: ChatService,
private readonly likeService: LikeService,
) {}

@ApiOperation({
summary: '채팅 스크롤 조회 API',
Expand All @@ -36,4 +45,11 @@ export class ChatController {
request.pageSize,
);
}

@UseGuards(SessionGuard)
@ToggleLikeApi()
@Post('like')
async toggleChatLike(@Body() request: LikeRequest, @GetUser() user: User) {
return await this.likeService.toggleLike(user.id, request.chatId);
}
}
6 changes: 4 additions & 2 deletions packages/backend/src/chat/chat.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { ChatController } from '@/chat/chat.controller';
import { ChatGateway } from '@/chat/chat.gateway';
import { ChatService } from '@/chat/chat.service';
import { Chat } from '@/chat/domain/chat.entity';
import { Like } from '@/chat/domain/like.entity';
import { LikeService } from '@/chat/like.service';
import { StockModule } from '@/stock/stock.module';

@Module({
imports: [TypeOrmModule.forFeature([Chat]), StockModule, SessionModule],
imports: [TypeOrmModule.forFeature([Chat, Like]), StockModule, SessionModule],
controllers: [ChatController],
providers: [ChatGateway, ChatService],
providers: [ChatGateway, ChatService, LikeService],
})
export class ChatModule {}
31 changes: 31 additions & 0 deletions packages/backend/src/chat/decorator/like.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { applyDecorators } from '@nestjs/common';
import {
ApiBadRequestResponse,
ApiCookieAuth,
ApiOkResponse,
ApiOperation,
} from '@nestjs/swagger';
import { LikeResponse } from '@/chat/dto/like.response';

// eslint-disable-next-line @typescript-eslint/naming-convention
export function ToggleLikeApi() {
return applyDecorators(
ApiCookieAuth(),
ApiOperation({
summary: '채팅 좋아요 토글 API',
description: '채팅 좋아요를 토글한다.',
}),
ApiOkResponse({
description: '좋아요 성공',
type: LikeResponse,
}),
ApiBadRequestResponse({
description: '채팅이 존재하지 않음',
example: {
message: 'Chat not found',
error: 'Bad Request',
statusCode: 400,
},
}),
);
}
5 changes: 5 additions & 0 deletions packages/backend/src/chat/domain/chat.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import {
Entity,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { ChatType } from '@/chat/domain/chatType.enum';
import { Like } from '@/chat/domain/like.entity';
import { DateEmbedded } from '@/common/dateEmbedded.entity';
import { Stock } from '@/stock/domain/stock.entity';
import { User } from '@/user/domain/user.entity';
Expand All @@ -23,6 +25,9 @@ export class Chat {
@JoinColumn({ name: 'stock_id' })
stock: Stock;

@OneToMany(() => Like, (like) => like.chat)
likes?: Like[];

@Column()
message: string;

Expand Down
28 changes: 28 additions & 0 deletions packages/backend/src/chat/domain/like.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
CreateDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Chat } from '@/chat/domain/chat.entity';
import { User } from '@/user/domain/user.entity';

@Index('chat_user_unique', ['chat', 'user'], { unique: true })
@Entity()
export class Like {
@PrimaryGeneratedColumn()
id: number;

@ManyToOne(() => Chat, (chat) => chat.id)
@JoinColumn({ name: 'chat_id' })
chat: Chat;

@ManyToOne(() => User, (user) => user.id)
@JoinColumn({ name: 'user_id' })
user: User;

@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
}
13 changes: 13 additions & 0 deletions packages/backend/src/chat/dto/like.request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber } from 'class-validator';

export class LikeRequest {
@ApiProperty({
required: true,
type: Number,
description: '좋아요를 누를 채팅의 ID',
example: 1,
})
@IsNumber()
chatId: number;
}
31 changes: 31 additions & 0 deletions packages/backend/src/chat/dto/like.response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ApiProperty } from '@nestjs/swagger';

export class LikeResponse {
@ApiProperty({
type: Number,
description: '좋아요를 누른 채팅의 ID',
example: 1,
})
chatId: number;

@ApiProperty({
type: Number,
description: '채팅의 좋아요 수',
example: 45,
})
likeCount: number;

@ApiProperty({
type: String,
description: '결과 메시지',
example: 'like chat',
})
message: string;

@ApiProperty({
type: Date,
description: '좋아요를 누른 시간',
example: '2021-08-01T00:00:00',
})
date: Date;
}
46 changes: 46 additions & 0 deletions packages/backend/src/chat/like.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { DataSource, EntityManager } from 'typeorm';
import { Chat } from '@/chat/domain/chat.entity';
import { Like } from '@/chat/domain/like.entity';
import { LikeResponse } from '@/chat/dto/like.response';

@Injectable()
export class LikeService {
constructor(private readonly dataSource: DataSource) {}

async toggleLike(userId: number, chatId: number) {
return await this.dataSource.transaction(async (manager) => {
const chat = await this.findChat(chatId, manager);
return await this.saveLike(manager, chat, userId);
});
}

private async findChat(chatId: number, manager: EntityManager) {
const chat = await manager.findOne(Chat, { where: { id: chatId } });
if (!chat) {
throw new BadRequestException('Chat not found');
}
return chat;
}

private async saveLike(
manager: EntityManager,
chat: Chat,
userId: number,
): Promise<LikeResponse> {
chat.likeCount += 1;
await Promise.all([
manager.save(Like, {
user: { id: userId },
chat,
}),
manager.save(Chat, chat),
]);
return {
likeCount: chat.likeCount,
message: 'like chat',
chatId: chat.id,
date: chat.date.updatedAt,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,3 @@ export const typeormDevelopConfig: TypeOrmModuleOptions = {
//logging: true,
synchronize: true,
};

10 changes: 7 additions & 3 deletions packages/backend/src/stock/domain/stock.entity.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Column, Entity, OneToMany, PrimaryColumn } from 'typeorm';
import { DateEmbedded } from '@/common/dateEmbedded.entity';
import { UserStock } from '@/stock/domain/userStock.entity';
import {
StockDaily,
StockMinutely,
StockMonthly,
StockWeekly,
StockYearly,
} from './stockData.entity';
import { Like } from '@/chat/domain/like.entity';
import { DateEmbedded } from '@/common/dateEmbedded.entity';
import { UserStock } from '@/stock/domain/userStock.entity';

@Entity()
export class Stock {
Expand All @@ -26,8 +27,11 @@ export class Stock {
@Column({ name: 'group_code' })
groupCode?: string;

@OneToMany(() => Like, (like) => like.chat)
likes?: Like[];

@Column(() => DateEmbedded, { prefix: '' })
dare?: DateEmbedded;
date?: DateEmbedded;

@OneToMany(() => UserStock, (userStock) => userStock.stock)
userStocks?: UserStock[];
Expand Down

0 comments on commit f0c72df

Please sign in to comment.