Skip to content

Commit

Permalink
feat: Add base operations of getting meets
Browse files Browse the repository at this point in the history
  • Loading branch information
lsjbh45 committed Nov 25, 2023
1 parent f25adf5 commit 2c583c6
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 74 deletions.
109 changes: 91 additions & 18 deletions src/apis/meet/meet.controller.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,119 @@
import { Controller, Get, UseGuards, Query } from '@nestjs/common';
import {
Controller,
Get,
UseGuards,
Query,
BadRequestException,
Param,
} from '@nestjs/common';
import { FcmService } from '../fcm/fcm.service';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/guard/jwt.auth.guard';
import { MeetService } from './meet.service';
import { RoleGuard } from '../auth/guard/role.guard';
import { AuthUserDto } from 'src/dtos/common/auth.user.dto';
import { AuthUser } from 'src/lib/decorators/auth.user.decorator';
import { Role } from 'src/lib/enums/user.role.enum';
import {
MeetCommentRequestDto,
MeetCommentResponseDto,
MeetGetRequestDto,
AuthUserDto,
MeetDetailRequestDto,
MeetDetailResponseDto,
MeetRequestDto,
MeetResponseDto,
PaginationDto,
} from 'src/dtos';
import { AuthUser } from 'src/lib/decorators';
import { paginate } from 'src/lib/utils/pagination.util';
import { UserService } from '../user/user.service';
import {
FindOptionsWhere,
In,
IsNull,
LessThanOrEqual,
MoreThanOrEqual,
} from 'typeorm';
import { Meet } from 'src/entities';
import { productArray } from 'src/lib/utils/array.util';

@Controller('meet')
@ApiTags('meet')
export class MeetController {
constructor(
private readonly meetService: MeetService,
private readonly userService: UserService,
private readonly fcmService: FcmService,
) {}

@Get('/')
@Get()
@UseGuards(JwtAuthGuard, RoleGuard(Role.USER))
@ApiOperation({ summary: 'isTag에 따라 category 포함해 meet를 반환' })
@ApiOperation({
summary: '참여 가능한 모임 목록을 반환; 성별, 나이, 시간, (구독) 필터링',
})
@ApiBearerAuth()
async getMeet(
async getMeets(
@AuthUser() user: AuthUserDto,
@Query() payload: MeetGetRequestDto,
@Query() payload: MeetRequestDto,
): Promise<MeetResponseDto[]> {
const { isTag } = payload;
const result = await this.meetService.getMeets(user, isTag);
const { id: userId } = user;
const { subscribed, ...pagination } = payload;

const { gender, age, subscriptions } =
await this.userService.getUserWithTagsById(userId);
if (!gender || !age) {
throw new BadRequestException(
'사용자의 성별 또는 나이가 입력되지 않았습니다.',
);
}

const tagOption = subscribed
? await (async () => {
const tagIds = subscriptions.map((sub) => sub.tagId);
return { tagId: In(tagIds) };
})()
: {};
const expiredOption = { meetTime: MoreThanOrEqual(new Date()) };

const where = productArray<FindOptionsWhere<Meet>>([
[{ ...tagOption, ...expiredOption }],
[{ gender: IsNull() }, { gender }],
[{ minAge: IsNull() }, { minAge: LessThanOrEqual(age) }],
[{ maxAge: IsNull() }, { maxAge: MoreThanOrEqual(age) }],
]);
const page = paginate(pagination);

const result = await this.meetService.getMeets(where, page);
return result.map(MeetResponseDto.of);
}

async getComment(
@Get('/join')
@UseGuards(JwtAuthGuard, RoleGuard(Role.USER))
@ApiOperation({ summary: '참여 완료 및 참여 상태인 모임 목록을 반환' })
@ApiBearerAuth()
async getJoinedMeet(
@AuthUser() user: AuthUserDto,
@Query() payload: MeetCommentRequestDto,
) {
const { meetId } = payload;
const result = await this.meetService.getComment(user, meetId);
return result.map(MeetCommentResponseDto.of);
@Query() payload: PaginationDto,
): Promise<MeetResponseDto[]> {
const { id: userId } = user;
const { ...pagination } = payload;

const where: FindOptionsWhere<Meet>[] = [
{ participations: { user: { id: userId } } },
{ host: { id: userId } },
];
const page = paginate(pagination);

const result = await this.meetService.getMeets(where, page);
return result.map(MeetResponseDto.of);
}

@Get('/:id')
@UseGuards(JwtAuthGuard, RoleGuard(Role.USER))
@ApiOperation({ summary: '모임 상세 정보를 반환' })
@ApiBearerAuth()
async getMeet(
@Param() payload: MeetDetailRequestDto,
): Promise<MeetDetailResponseDto> {
const { id } = payload;

const meet = await this.meetService.getMeetById(id);
return MeetDetailResponseDto.of(meet);
}
}
3 changes: 2 additions & 1 deletion src/apis/meet/meet.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { MeetController } from './meet.controller';
import { MeetService } from './meet.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Meet, Comment, User } from 'src/entities';
import { UserService } from '../user/user.service';

@Module({
imports: [FcmModule, TypeOrmModule.forFeature([Meet, Comment, User])],
controllers: [MeetController],
providers: [MeetService],
providers: [MeetService, UserService],
})
export class MeetModule {}
29 changes: 17 additions & 12 deletions src/apis/meet/meet.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Meet, Comment } from 'src/entities';
import { Repository, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
import { FindOptionsWhere, Repository } from 'typeorm';
import { AuthUser } from 'src/lib/decorators/auth.user.decorator';
import { FindOptionsPage } from 'src/lib/utils/pagination.util';

@Injectable()
export class MeetService {
Expand All @@ -13,18 +14,22 @@ export class MeetService {
private readonly commentRepository: Repository<Comment>,
) {}

async getMeets(@AuthUser() user, isTag): Promise<Meet[]> {
const { age, gender } = user;
const tagCompare = isTag ? user.tag : undefined;
async getMeets(
where: FindOptionsWhere<Meet> | FindOptionsWhere<Meet>[],
page: FindOptionsPage,
): Promise<Meet[]> {
return this.meetRepository.find({
where: {
meetTime: MoreThanOrEqual(new Date()),
minAge: MoreThanOrEqual(age),
maxAge: LessThanOrEqual(age),
gender: gender,
id: user.id,
tag: tagCompare,
},
where,
order: { createdAt: 'DESC' },
...page,
relations: { host: {}, tag: {} },
});
}

async getMeetById(id: number): Promise<Meet> {
return this.meetRepository.findOne({
where: { id },
relations: { host: {}, tag: {}, participations: { user: {} } },
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/dtos/common/auth.user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export class AuthUserDto {
id: number;
refreshToken: string;
email: string;
}
3 changes: 2 additions & 1 deletion src/dtos/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AuthUserDto } from './auth.user.dto';
import { PaginationDto } from './pagination.dto';
import { TokenPayloadDto } from './token.payload.dto';

export { AuthUserDto, TokenPayloadDto };
export { AuthUserDto, TokenPayloadDto, PaginationDto };
14 changes: 14 additions & 0 deletions src/dtos/common/pagination.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Type } from 'class-transformer';
import { IsNumber, IsOptional } from 'class-validator';

export class PaginationDto {
@IsOptional()
@IsNumber()
@Type(() => Number)
page?: number = 1;

@IsOptional()
@IsNumber()
@Type(() => Number)
size?: number = 10;
}
8 changes: 6 additions & 2 deletions src/dtos/meet/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { MeetResponseDto } from './meet.res.dto';
import { MeetGetRequestDto } from './meet.get.req.dto';
import { MeetRequestDto } from './meet.req.dto';
import { MeetCommentRequestDto } from './meet.comment.req.dto';
import { MeetCommentResponseDto } from './meet.comment.res.dto';
import { MeetDetailRequestDto } from './meet.detail.req.dto';
import { MeetDetailResponseDto } from './meet.detail.res.dto';

export {
MeetResponseDto,
MeetDetailResponseDto,
MeetCommentRequestDto,
MeetGetRequestDto,
MeetRequestDto,
MeetDetailRequestDto,
MeetCommentResponseDto,
};
8 changes: 8 additions & 0 deletions src/dtos/meet/meet.detail.req.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Transform } from 'class-transformer';
import { IsNumber } from 'class-validator';

export class MeetDetailRequestDto {
@IsNumber()
@Transform(({ value }) => parseInt(value))
id: number;
}
44 changes: 44 additions & 0 deletions src/dtos/meet/meet.detail.res.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Meet } from 'src/entities';
import { MeetResponseDto } from './meet.res.dto';
import { Gender } from 'src/lib/enums';
import { UserResponseDto } from '../user';

export class MeetDetailResponseDto extends MeetResponseDto {
body: string;
maxParticipants: number;
isOnline: boolean;
location: string;
minAge: number;
maxAge: number;
gender: Gender;
createdAt: Date;
participants: UserResponseDto[];

static of(meet: Meet): MeetDetailResponseDto {
const {
body,
maxParticipants,
isOnline,
location,
minAge,
maxAge,
gender,
createdAt,
participations,
} = meet;
return {
...super.of(meet),
body,
maxParticipants,
isOnline,
location,
minAge,
maxAge,
gender,
createdAt,
participants: participations
.map((participation) => participation.user)
.map(UserResponseDto.of),
};
}
}
3 changes: 0 additions & 3 deletions src/dtos/meet/meet.get.req.dto.ts

This file was deleted.

10 changes: 10 additions & 0 deletions src/dtos/meet/meet.req.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Transform } from 'class-transformer';
import { IsBoolean, IsOptional } from 'class-validator';
import { PaginationDto } from '../common';

export class MeetRequestDto extends PaginationDto {
@IsBoolean()
@IsOptional()
@Transform(({ value }) => value === 'true' || value === true)
subscribed?: boolean = false;
}
43 changes: 7 additions & 36 deletions src/dtos/meet/meet.res.dto.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,23 @@
import { Meet } from 'src/entities';
import { Gender } from 'src/lib/enums';
import { TagResponseDto } from '../tag';
import { UserResponseDto } from '../user';

export class MeetResponseDto {
id: number;
hostId: number;
host: UserResponseDto;
title: string;
body: string;
maxParticipants: number;
meetTime: Date;
isOnline: boolean;
location: string;
minAge: number;
maxAge: number;
gender: Gender;
createdAt: Date;
tagId: number;
tag: TagResponseDto;

static of(meet: Meet): MeetResponseDto {
const {
id,
hostId,
title,
body,
maxParticipants,
meetTime,
isOnline,
location,
minAge,
maxAge,
gender,
createdAt,
tagId,
} = meet;
const { id, host, title, meetTime, tag } = meet;

return {
id,
hostId,
host: UserResponseDto.of(host),
title,
body,
maxParticipants,
meetTime,
isOnline,
location,
minAge,
maxAge,
gender,
createdAt,
tagId,
tag: TagResponseDto.of(tag),
};
}
}
8 changes: 8 additions & 0 deletions src/lib/utils/array.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const productArray = <T>(array: T[][]): T[] =>
array.reduce((acc, cur) => {
return acc.length
? acc.map((a) => cur.map((c) => ({ ...a, ...c }))).flat()
: cur;
}, []);

export { productArray };
11 changes: 11 additions & 0 deletions src/lib/utils/pagination.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PaginationDto } from 'src/dtos/common/pagination.dto';
import { FindManyOptions } from 'typeorm';

type FindOptionsPage = Pick<FindManyOptions, 'skip' | 'take'>;

const paginate = ({ page, size }: PaginationDto): FindOptionsPage => ({
skip: (page - 1) * size,
take: size,
});

export { FindOptionsPage, paginate };

0 comments on commit 2c583c6

Please sign in to comment.