Skip to content

Commit 589e491

Browse files
Merge pull request #124 from DevKor-github/feature/club-detail
feat:: 동아리 상세 정보 조회 api 추가
2 parents 0cb3c49 + 99ef051 commit 589e491

File tree

5 files changed

+141
-0
lines changed

5 files changed

+141
-0
lines changed

src/decorators/docs/club.decorator.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { GetRecommendClubResponseDto } from 'src/home/club/dto/get-recommend-clu
1919
import { UpdateClubRequestDto } from 'src/home/club/dto/update-club-request-dto';
2020
import { UpdateClubResponseDto } from 'src/home/club/dto/update-club-response-dto';
2121
import { ApiKukeyExceptionResponse } from '../api-kukey-exception-response';
22+
import { GetClubDetailResponseDto } from 'src/home/club/dto/get-club-detail-response.dto';
2223

2324
type ClubEndPoints = MethodNames<ClubController>;
2425

@@ -61,6 +62,27 @@ const ClubDocsMap: Record<ClubEndPoints, MethodDecorator[]> = {
6162
}),
6263
ApiKukeyExceptionResponse(['LOGIN_REQUIRED']),
6364
],
65+
getClubDetail: [
66+
ApiOperation({
67+
summary: '동아리 상세 조회',
68+
description: '동아리 상세 정보를 조회합니다.',
69+
}),
70+
ApiQuery({
71+
name: 'clubId',
72+
description: 'club id',
73+
required: true,
74+
}),
75+
ApiQuery({
76+
name: 'isLogin',
77+
description: '로그인 여부',
78+
required: true,
79+
}),
80+
ApiOkResponse({
81+
description: '동아리 상세 정보 반환',
82+
type: GetClubDetailResponseDto,
83+
}),
84+
ApiKukeyExceptionResponse(['LOGIN_REQUIRED', 'CLUB_NOT_FOUND']),
85+
],
6486
toggleLikeClub: [
6587
ApiOperation({
6688
summary: '동아리 좋아요 등록/해제',

src/home/club/club.controller.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import { CreateClubResponseDto } from './dto/create-club-response-dto';
3434
import { UpdateClubRequestDto } from './dto/update-club-request-dto';
3535
import { DeleteClubResponseDto } from './dto/delete-club-response-dto';
3636
import { ClubDocs } from 'src/decorators/docs/club.decorator';
37+
import { GetClubDetailResponseDto } from './dto/get-club-detail-response.dto';
38+
import { GetClubDetailRequestDto } from './dto/get-club-detail-request.dto';
3739

3840
@Controller('club')
3941
@ApiTags('club')
@@ -51,6 +53,15 @@ export class ClubController {
5153
return await this.clubService.getClubList(user, getClubRequestDto);
5254
}
5355

56+
@UseGuards(OptionalJwtAuthGuard)
57+
@Get('/:clubId')
58+
async getClubDetail(
59+
@User() user: AuthorizedUserDto | null,
60+
@Query() getClubDetailRequestDto: GetClubDetailRequestDto,
61+
): Promise<GetClubDetailResponseDto> {
62+
return await this.clubService.getClubDetail(user, getClubDetailRequestDto);
63+
}
64+
5465
@UseGuards(JwtAuthGuard)
5566
@UseInterceptors(TransactionInterceptor)
5667
@Post('/like/:clubId')

src/home/club/club.service.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import { UpdateClubRequestDto } from './dto/update-club-request-dto';
1717
import { UpdateClubResponseDto } from './dto/update-club-response-dto';
1818
import { DeleteClubResponseDto } from './dto/delete-club-response-dto';
1919
import { throwKukeyException } from 'src/utils/exception.util';
20+
import { GetClubDetailResponseDto } from './dto/get-club-detail-response.dto';
21+
import { GetClubDetailRequestDto } from './dto/get-club-detail-request.dto';
2022

2123
@Injectable()
2224
export class ClubService {
@@ -64,6 +66,35 @@ export class ClubService {
6466
return clubList;
6567
}
6668

69+
async getClubDetail(
70+
user: AuthorizedUserDto | null,
71+
requetDto: GetClubDetailRequestDto,
72+
): Promise<GetClubDetailResponseDto> {
73+
const { clubId, isLogin } = requetDto;
74+
75+
// isLogin이 true이나 user가 없을 경우 refresh를 위해 401 던짐
76+
if (!user && isLogin) {
77+
throwKukeyException('LOGIN_REQUIRED');
78+
}
79+
80+
const club = await this.clubRepository.findOne({
81+
where: { id: clubId },
82+
relations: ['clubLikes', 'clubLikes.user'],
83+
});
84+
85+
if (!club) {
86+
throwKukeyException('CLUB_NOT_FOUND');
87+
}
88+
89+
const isLiked = club.clubLikes.some((clubLike) =>
90+
user && isLogin && clubLike.user ? clubLike.user.id === user.id : false,
91+
);
92+
93+
const linkCount = (club.instagramLink ? 1 : 0) + (club.youtubeLink ? 1 : 0);
94+
95+
return new GetClubDetailResponseDto(club, isLiked, linkCount);
96+
}
97+
6798
async toggleLikeClub(
6899
transactionManager: EntityManager,
69100
userId: number,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2+
import { IsBoolean, IsNotEmpty, IsNumber } from 'class-validator';
3+
import { ToBoolean } from 'src/decorators/to-boolean.decorator';
4+
5+
export class GetClubDetailRequestDto {
6+
@IsNotEmpty()
7+
@IsNumber()
8+
@ApiProperty({ description: 'club id' })
9+
clubId: number;
10+
11+
@IsNotEmpty()
12+
@ToBoolean()
13+
@IsBoolean()
14+
@ApiPropertyOptional({ description: '로그인 여부' })
15+
isLogin: boolean;
16+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2+
import { ClubCategory } from 'src/common/types/club-category-type';
3+
import { ClubEntity } from 'src/entities/club.entity';
4+
5+
export class GetClubDetailResponseDto {
6+
@ApiProperty({ description: 'club table의 PK' })
7+
clubId: number;
8+
9+
@ApiProperty({ description: '동아리명' })
10+
name: string;
11+
12+
@ApiProperty({ description: '동아리 카테고리' })
13+
category: ClubCategory;
14+
15+
@ApiProperty({ description: '동아리 요약' })
16+
summary: string;
17+
18+
@ApiProperty({ description: '정기 모임' })
19+
regularMeeting: string;
20+
21+
@ApiProperty({ description: '모집 기간' })
22+
recruitmentPeriod: string;
23+
24+
@ApiProperty({ description: '동아리 설명' })
25+
description: string;
26+
27+
@ApiProperty({ description: '동아리 사진 URL 목록' })
28+
imageUrl: string[];
29+
30+
@ApiProperty({ description: '좋아요 개수' })
31+
likeCount: number;
32+
33+
@ApiPropertyOptional({ description: '인스타그램 링크' })
34+
instagramLink: string;
35+
36+
@ApiPropertyOptional({ description: '유튜브 링크' })
37+
youtubeLink: string;
38+
39+
@ApiProperty({ description: '좋아요 여부' })
40+
isLiked: boolean;
41+
42+
@ApiProperty({ description: '링크 개수' })
43+
linkCount: number;
44+
45+
constructor(club: ClubEntity, isLiked: boolean, linkCount: number) {
46+
this.clubId = club.id;
47+
this.name = club.name;
48+
this.category = club.category;
49+
this.summary = club.summary;
50+
this.regularMeeting = club.regularMeeting;
51+
this.recruitmentPeriod = club.recruitmentPeriod;
52+
this.description = club.description;
53+
this.imageUrl = [];
54+
this.imageUrl[0] = club.imageUrl;
55+
this.likeCount = club.allLikes;
56+
this.instagramLink = club.instagramLink;
57+
this.youtubeLink = club.youtubeLink;
58+
this.isLiked = isLiked;
59+
this.linkCount = linkCount;
60+
}
61+
}

0 commit comments

Comments
 (0)