Skip to content

Commit

Permalink
Merge pull request #65 from boostcampwm-2022/feat/participate-be
Browse files Browse the repository at this point in the history
feat BE ์œ ์ € ์ฐธ์—ฌ API ๊ตฌํ˜„
  • Loading branch information
claycat authored Nov 23, 2022
2 parents 9bb6709 + f8e9589 commit 27a916a
Show file tree
Hide file tree
Showing 26 changed files with 380 additions and 20 deletions.
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

0 comments on commit 27a916a

Please sign in to comment.