Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat BE 유저 참여 API 구현 #65

Merged
merged 17 commits into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ function App() {
<Route path="courses" element={<Courses />} />
<Route path="course">
<Route path="new" element={<NewCourse />} />
<Route path="detail" element={<CourseDetail />} />
<Route path=":id" element={<CourseDetail />} />
</Route>
<Route path="recruit">
<Route path="detail" element={<RecruitDetail />} />
<Route path=":id" element={<RecruitDetail />} />
</Route>
</Routes>
);
Expand Down
2 changes: 2 additions & 0 deletions server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { UserRecruit } from "./entities/user_recruit.entity";
import { UserModule } from "./user/user.module";
import { AuthModule } from "./auth/auth.module";
import type { ClientOpts } from "redis";
import { RecruitModule } from "./recruit/recruit.module";

@Module({
imports: [
Expand Down Expand Up @@ -42,6 +43,7 @@ import type { ClientOpts } from "redis";
}),
UserModule,
AuthModule,
RecruitModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
4 changes: 2 additions & 2 deletions server/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Request, Response } from "express";
import { AuthService } from "./auth.service";
import { LoginUserDto } from "./dto/login-user.dto";
import { plainToClass } from "class-transformer";
import { AuthGuard } from "src/common/guard/auth.guard";
import { AccessGuard } from "src/common/guard/access.guard";
import { RefreshGuard } from "src/common/guard/refresh.guard";

@Controller("auth")
Expand All @@ -30,7 +30,7 @@ export class AuthController {
});
}

@UseGuards(AuthGuard)
@UseGuards(AccessGuard)
@Get("/logout")
async logoutUser(@Req() req: Request, @Res() res: Response) {
const jwtString = req.headers["authorization"].split("Bearer")[1].trim();
Expand Down
38 changes: 38 additions & 0 deletions server/src/common/decorator/date.validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
registerDecorator,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
} from "class-validator";

@ValidatorConstraint({ name: "isValidDateTime", async: false })
class isValidDateTimeConstraint implements ValidatorConstraintInterface {
public validate(value: string) {
const dateRegex = /^(2\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))$/;
const timeRegex = /^(0[0-9]|1\d|2[0-3]):([0-5]\d):([0-5]\d)$/;

return (
typeof value === "string" &&
value.length === 19 &&
dateRegex.test(value.split(" ")[0]) &&
timeRegex.test(value.split(" ")[1])
);
}

public defaultMessage(): string {
return `user id must be between 6 and 20 character long, only letters and numbers allowed`;
}
}

export function IsValidDateTime(validationOptions?: ValidationOptions) {
return function (object: any, propertyName: string) {
registerDecorator({
name: "isValidDateTime",
target: object.constructor,
propertyName: propertyName,
constraints: [],
options: validationOptions,
validator: isValidDateTimeConstraint,
});
};
}
3 changes: 2 additions & 1 deletion server/src/common/decorator/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IsValidId } from "./id.validator";
import { IsValidPassword } from "./pw.validator";
import { IsValidDateTime } from "./date.validator";

export { IsValidId, IsValidPassword };
export { IsValidId, IsValidPassword, IsValidDateTime };
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { AuthService } from "src/auth/auth.service";

@Injectable()
export class AuthGuard implements CanActivate {
export class AccessGuard implements CanActivate {
constructor(private authService: AuthService) {}

canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
Expand Down
7 changes: 7 additions & 0 deletions server/src/course/course.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Controller } from "@nestjs/common";
import { CourseService } from "./course.service";

@Controller("course")
export class CourseController {
constructor(private readonly courseService: CourseService) {}
}
9 changes: 9 additions & 0 deletions server/src/course/course.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Module } from "@nestjs/common";
import { CourseService } from "./course.service";
import { CourseController } from "./course.controller";

@Module({
controllers: [CourseController],
providers: [CourseService],
})
export class CourseModule {}
4 changes: 4 additions & 0 deletions server/src/course/course.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Injectable } from "@nestjs/common";

@Injectable()
export class CourseService {}
2 changes: 1 addition & 1 deletion server/src/entities/course.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class Course {
@Column({ type: "varchar", length: 512 })
img: string;

@Column({ type: "json" })
@Column({ type: "varchar", length: 512 })
path: string;

@Column()
Expand Down
37 changes: 35 additions & 2 deletions server/src/entities/recruit.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import {
CreateDateColumn,
UpdateDateColumn,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
OneToOne,
PrimaryGeneratedColumn,
JoinColumn,
} from "typeorm";
import { Course } from "./course.entity";
import { UserRecruit } from "./user_recruit.entity";
Expand All @@ -27,6 +26,12 @@ export class Recruit {
@Column()
maxPpl: number;

@Column()
pace: number;

@Column()
zipCode: string;

@CreateDateColumn()
createdAt: Date;

Expand All @@ -36,9 +41,37 @@ export class Recruit {
@OneToMany(() => UserRecruit, (userRecruit) => userRecruit.user)
userRecruits: UserRecruit[];

@Column()
courseId: number;

@ManyToOne(() => Course, (course) => course.recruits, { nullable: false })
@JoinColumn({ name: "courseId", referencedColumnName: "id" })
course: Course;

@Column()
userId: number;

@ManyToOne(() => User, (user) => user.recruits, { nullable: false })
@JoinColumn({ name: "userId", referencedColumnName: "id" })
user: User;

static of(
title: string,
startTime: Date,
maxPpl: number,
pace: number,
zipCode: string,
userId: number,
courseId: number,
) {
const recruit = new Recruit();
recruit.title = title;
recruit.startTime = startTime;
recruit.maxPpl = maxPpl;
recruit.pace = pace;
recruit.zipCode = zipCode;
recruit.userId = userId;
recruit.courseId = courseId;
return recruit;
}
}
2 changes: 1 addition & 1 deletion server/src/entities/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class User {
@OneToMany(() => UserRecruit, (userRecruit) => userRecruit.recruit)
userRecruits: Recruit[];

static from(userId: string, password: string, pace: number, zipCode: string) {
static of(userId: string, password: string, pace: number, zipCode: string) {
const user = new User();
user.userId = userId;
user.password = password;
Expand Down
17 changes: 16 additions & 1 deletion server/src/entities/user_recruit.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { User } from "./user.entity";
import { Recruit } from "./recruit.entity";

Expand All @@ -8,8 +8,23 @@ export class UserRecruit {
id: number;

@ManyToOne(() => User, (user) => user.userRecruits)
@JoinColumn({ name: "userId", referencedColumnName: "id" })
user: User;

@Column()
userId: number;

@ManyToOne(() => Recruit, (recruit) => recruit.userRecruits)
@JoinColumn({ name: "recruitId", referencedColumnName: "id" })
recruit: Recruit;

@Column()
recruitId: number;

static of(userId: number, recruitId: number) {
const userRecruit = new UserRecruit();
userRecruit.userId = userId;
userRecruit.recruitId = recruitId;
return userRecruit;
}
}
4 changes: 2 additions & 2 deletions server/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { NestFactory } from "@nestjs/core";
import { NestFactory, Reflector } from "@nestjs/core";
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
import { AppModule } from "./app.module";
import * as cookieParser from "cookie-parser";
import { ValidationPipe } from "@nestjs/common";
import { ClassSerializerInterceptor, ValidationPipe } from "@nestjs/common";

async function bootstrap() {
const app = await NestFactory.create(AppModule);
Expand Down
35 changes: 35 additions & 0 deletions server/src/recruit/dto/create-recruit.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Type } from "class-transformer";
import { IsNumber, IsNumberString, IsString } from "class-validator";
import { IsValidDateTime } from "src/common/decorator";
import { Recruit } from "../../entities/recruit.entity";

export class CreateRecruitDto {
@IsString()
private title: string;

@IsValidDateTime()
private startTime: Date;

@Type(() => Number)
@IsNumber()
private maxPpl: number;

@Type(() => Number)
@IsNumber()
private pace: number;

@IsNumberString()
private zipCode: string;

@Type(() => Number)
@IsNumber()
private userId: number;

@Type(() => Number)
@IsNumber()
private courseId: number;

toEntity() {
return Recruit.of(this.title, this.startTime, this.maxPpl, this.pace, this.zipCode, this.userId, this.courseId);
}
}
24 changes: 24 additions & 0 deletions server/src/recruit/dto/join-recruit.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Type } from "class-transformer";
import { IsNumber } from "class-validator";
import { UserRecruit } from "../../entities/user_recruit.entity";

export class JoinRecruitDto {
@Type(() => Number)
@IsNumber()
private recruitId: number;

@Type(() => Number)
@IsNumber()
private userId: number;

getRecruitId() {
return this.recruitId;
}

getUserId() {
return this.userId;
}
toEntity() {
return UserRecruit.of(this.recruitId, this.userId);
}
}
75 changes: 75 additions & 0 deletions server/src/recruit/recruit.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Body, Controller, Get, Post, UseGuards } from "@nestjs/common";
import { AccessGuard } from "src/common/guard/access.guard";
import { CreateRecruitDto } from "./dto/create-recruit.dto";
import { JoinRecruitDto } from "./dto/join-recruit.dto";
import { RecruitService } from "./recruit.service";

@Controller("recruit")
export class RecruitController {
constructor(private readonly recruitService: RecruitService) {}

@Get()
async getRecruits() {
const recruitEntityArrays = await this.recruitService.findAll();
// TODO: 페이지네이션 적용
return {
statusCode: 200,
data: recruitEntityArrays,
};
}

@Post()
async create(@Body() createRecruitDto: CreateRecruitDto) {
const recruitEntity = await this.recruitService.create(createRecruitDto);
// TODO: 응답 리팩토링하기 entity -> 응답 dto 변환 후 인터셉터가 상태코드 넣어서 처리하게끔 바꾸기
// 매번 상태코드와 데이터 넣어주는 방식이 깔끔하지 못한 느낌.
return {
statusCode: 201,
data: {
recruitId: recruitEntity.id,
},
};
}
// @UseGuards(AccessGuard)
@Post("join")
async register(@Body() joinRecruitDto: JoinRecruitDto) {
const recruitId = joinRecruitDto.getRecruitId();
const userId = joinRecruitDto.getUserId();
if (!(await this.recruitService.isExistRecruit(recruitId))) {
return {
statusCode: 409,
error: {
message: "Does not exist or has been deleted",
},
};
}
if (await this.recruitService.isAuthorOfRecruit(recruitId, userId)) {
return {
statusCode: 423,
error: {
message: "Cannot participate in your own recruitment",
},
};
}
if (await this.recruitService.isParticipating(recruitId, userId)) {
return {
statusCode: 423,
error: {
message: "You have already participated.",
},
};
}
if (!(await this.recruitService.isVacancy(recruitId))) {
return {
statusCode: 423,
error: {
message: "Maximum cap reached",
},
};
}
return {
statusCode: 201,
success: true,
};
}
}
Loading