LifeSports Application(ReactNative & Nest.js) - 20. post-service(1)

yellow_note·2021년 10월 21일
0

#1 post-service

post-service는 유저가 함께 운동할 사람을 구인한다던지, 아니면 운동 방법에 대한 조언을 구한다던지 다양한 종류의 게시글을 작성할 수 있게 해주는 서비스입니다. 게시글의 종류는 다음과 같습니다.

1) 함께해요: 함께 운동할 사람을 구할 수 있는 게시글입니다. 게시글의 댓글을 통해서 함께 운동할 사람을 구할 수 있는데 예상 시나리오는 다음과 같습니다.

시나리오 1)

  • user-a는 체육관 대관 후 함께 운동할 사람을 모집하고 싶습니다.
  • map-service를 이용해 대관을 진행합니다.
  • rental-service에서 데이터를 가져와 대관 내역을 바탕으로 함께 운동할 사람을 모집합니다.
  • 댓글 혹은 메시지를 통해서 함께 하고싶은 유저는 의사를 전달합니다.

시나리오 2)

  • user-a는 게시글을 함께 운동할 사람을 모으는 게시글을 작성합니다.
  • 댓글 혹은 메신저를 통해서 유저가 어느 정도 모였다고 판단이 됐을 경우 map-service에서 장소를 찾습니다.
  • 장소를 찾았으면 rental-service를 이용해 대관을 진행합니다.

2) 도와주세요: 도와주세요는 운동에 관한 방법을 물어보는 게시글입니다. 예상 시나리오는 다음과 같습니다.

시나리오 1)

  • user-a는 야구를 좋아하지만 룰, 하는 방법에 대해 잘 모르는 상황입니다.
  • 도움을 받기 위해 도와주세요 라는 게시글을 작성합니다.
  • 댓글 혹은 메신저를 통해 유저들에게 야구에 관한 룰과 진행 방법에 대해 배울 수 있습니다.

이상으로 게시글 타입과 관련하여 예상 시나리오를 작성해보았고, 이를 바탕으로 post-service를 작성해보도록 하겠습니다.

#2 프로젝트 생성

다음의 명령어로 post-service를 만들도록 하겠습니다.

nest new post-service
cd post-service
nest generate module post
nest generate service post

컨트롤러부터 작성을 해보겠습니다.

  • ./src/app.controller.ts
import { Controller, Delete, Get, HttpStatus, Param, Post } from "@nestjs/common";
import { Builder } from "builder-pattern";
import { statusConstants } from "./constants/status.constants";
import { PostDto } from "./dto/post.dto";
import { PostService } from "./post/post.service";
import { RequestPost } from "./vo/request.post";
import { ResponsePost } from "./vo/response.post";

@Controller('post-service')
export class AppController {
    constructor(private readonly postService: PostService) {}

    @Post('/')
    public async write(vo: RequestPost): Promise<any> {
        try {
            const dto: any = await this.postService.write(Builder(PostDto).type(vo.type)
                                                                    .title(vo.title)
                                                                    .content(vo.content)
                                                                    .userId(vo.userId)
                                                                    .writer(vo.writer)
                                                                    .rental(vo.rental ? vo.rental : null)
                                                                    .build());

            if(dto.status === statusConstants.ERROR) {
                return await Object.assign({
                    status: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: "Error message: " + dto.message,
                });
            }

            return await Object.assign({
                status: HttpStatus.CREATED,
                payload: Builder(ResponsePost).type(dto.payload.type)
                                              .title(dto.payload.title)
                                              .content(dto.payload.content)
                                              .userId(dto.payload.userId)
                                              .writer(dto.payload.writer)
                                              .createdAt(dto.payload.createdAt)
                                              .rental(dto.payload.rental)
                                              .build(),
                message: "Get post data"
            });
        } catch(err) {
            return await Object.assign({
                status: HttpStatus.BAD_REQUEST,
                payload: null,
                message: "Error message: " + err
            });
        }
    }

    @Get('/')
    public async getAll(): Promise<any> {
        try {
            const dtos: any = await this.postService.getAll();

            if(dtos.status === statusConstants.ERROR) {
                return await Object.assign({
                    status: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: "Error message: " + dtos.message,
                });
            }

            const responsePosts: Array<ResponsePost> = [];

            for(const dto of dtos.payload) {
                responsePosts.push(dto);
            }

            return await Object.assign({
                status: HttpStatus.OK,
                payload: responsePosts,
                message: "Get posts data"
            });
        } catch(err) {
            return await Object.assign({
                status: HttpStatus.BAD_REQUEST,
                payload: null,
                message: "Error message: " + err
            });
        }
    }

    @Get('posts/type/:type')
    public async getPostsByType(@Param('type') type: string): Promise<any> {
        try {
            const dtos: any = await this.postService.getPostsByType(type);

            if(dtos.status === statusConstants.ERROR) {
                return await Object.assign({
                    status: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: "Error message: " + dtos.message,
                });
            }

            const responsePosts: Array<ResponsePost> = [];

            for(const dto of dtos.payload) {
                responsePosts.push(dto);
            }

            return await Object.assign({
                status: HttpStatus.OK,
                payload: responsePosts,
                message: "Get posts data",    
            });
        } catch(err) {
            return await Object.assign({
                status: HttpStatus.BAD_REQUEST,
                payload: null,
                message: "Error message: " + err,
            });
        }
    }

    @Get(':_id/post')
    public async getOne(@Param('_id') _id: string): Promise<any> {
        try {
            const dto: any = await this.postService.getOne(_id);

            if(dto.status === statusConstants.ERROR) {
                return await Object.assign({
                    status: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: "Error message: " + dto.message,
                });
            }

            return await Object.assign({
                status: HttpStatus.OK,
                payload: Builder(ResponsePost)._id(dto.payload._id)
                                              .type(dto.payload.type)
                                              .title(dto.payload.title)
                                              .content(dto.payload.content)
                                              .userId(dto.payload.userId)
                                              .writer(dto.payload.writer)
                                              .createdAt(dto.payload.createdAt)
                                              .rental(dto.payload.rental)
                                              .build(),
                message: "Get post data",
            });
        } catch(err) {
            return await Object.assign({
                status: HttpStatus.BAD_REQUEST,
                payload: null,
                message: "Error message: " + err
            });
        }
    }

    @Get(':userId/posts')
    public async getPostsByUserId(@Param('userId') userId: string): Promise<any> {
        try {
            const dtos: any = await this.postService.getPostsByUserId(userId);

            if(dtos.status === statusConstants.ERROR) {
                return await Object.assign({
                    status: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: "Error message: " + dtos.message,
                });
            }

            const responsePosts: Array<ResponsePost> = [];

            for(const dto of dtos.payload) {
                responsePosts.push(dto);
            }

            return await Object.assign({
                status: HttpStatus.OK,
                payload: responsePosts,
                message: "Get posts data"
            });
        } catch(err) {
            return await Object.assign({
                status: HttpStatus.BAD_REQUEST,
                payload: null,
                message: "Error message: " + err,
            });
        }
    }

    @Get('posts/keyword/:keyword')
    public async getPostsByKeyword(@Param('keyword') keyword: string): Promise<any> {
        try {
            const dtos: any = await this.postService.getPostsByKeyword(keyword);

            if(dtos.status === statusConstants.ERROR) {
                return await Object.assign({
                    status: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: "Error message: " + dtos.message,
                });
            }

            const responsePosts: Array<ResponsePost> = [];

            for(const dto of dtos.payload) {
                responsePosts.push(dto);
            }

            return await Object.assign({
                status: HttpStatus.OK,
                payload: responsePosts,
                message: "Get posts data"
            });
        } catch(err) {
            return await Object.assign({
                status: HttpStatus.BAD_REQUEST,
                payload: null,
                message: "Error message: " + err
            });
        }
    }

    @Delete(':_id/post')
    public async deletePost(@Param('_id') _id: string): Promise<any> {
        try {    
            const result: any = await this.postService.deletePost(_id);

            if(result.status === statusConstants.ERROR) {
                return await Object.assign({
                    status: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: "Error message: " + result.message,
                });
            }

            return await Object.assign({
                status: HttpStatus.NO_CONTENT,
                payload: null,
                message: "Delete post"
            })
        } catch(err) {
            return await Object.assign({
                status: HttpStatus.BAD_REQUEST,
                payload: null,
                message: "Error message: " + err
            });
        } 
    }
}

컨트롤러의 메서드를 살펴 보겠습니다.

1) write: 게시글을 생성하는 메서드입니다. vo 객체를 전달받아 객체 내에 존재하는 type, title, content, userId, writer, rental 요소들을 dto 객체로 변환하여 전달하고 결과값을 반환받아 이 값을 유저에게 보여줍니다.

2) getAll: 게시글 홈에서 사용될 메서드입니다. 게시글 홈에서는 생성된 전체 게시글을 받아와 사용자 화면에 띄워 줍니다.

3) getPostsByType: 게시글 홈에 있는 정렬탭에서 사용될 메서드입니다. 사용자는 도와주세요, 함께해요의 값을 가진 타입을 기반으로 게시글들을 볼 수 있습니다.

4) getOne: 상세 게시글을 볼 수 있는 메서드입니다. 사용자는 게시글 리스트 중 원하는 게시글을 클릭하여 상세 게시글을 볼 수 있는데 이 때 사용되는 메서드입니다.

5) getPostsByUserId: 마이페이지의 게시글 내역에서 활용될 메서드입니다. 게시글 내역 클릭 시 userId를 기반으로 사용자가 작성한 게시글들을 불러오는 메서드입니다.

6) getPostsByKeyword: 게시글 홈 최상단에 있는 검색탭에서 사용될 메서드입니다. 사용자는 원하는 키워드를 바탕으로 게시글을 불러올 수 있습니다.

7) deletePost: 게시글을 삭제하는 메서드입니다. 사용자는 게시글 내역에 있는 리스트 중 원하는 게시글을 삭제할 수 있습니다.

컨트롤러의 오류코드들을 통과하기 위한 클래스들을 작성하도록 하겠습니다.

npm install --save class-validator class-transformer builder-pattern
  • ./src/dto/post.dto.ts
import { IsString } from "class-validator";

export class PostDto {
    @IsString()
    _id: string;

    @IsString()
    type: string;

    @IsString()
    title: string;

    @IsString()
    content: string;

    @IsString()
    userId: string;

    @IsString()
    writer: string;

    @IsString()
    createdAt: string;

    rental: any;
}
  • ./src/vo/request.post.ts
import { IsString } from "class-validator";

export class RequestPost {
    @IsString()
    type: string;

    @IsString()
    title: string;

    @IsString()
    content: string;

    @IsString()
    userId: string;

    @IsString()
    writer: string;

    rental: any;
}
  • ./src/vo/response.post.ts
import { IsString } from "class-validator";

export class ResponsePost {
    @IsString()
    _id: string;

    @IsString()
    type: string;

    @IsString()
    title: string;

    @IsString()
    content: string;

    @IsString()
    userId: string;

    @IsString()
    writer: string;

    @IsString()
    createdAt: string;

    rental: any;
}
  • ./src/constants/status.constants.ts
export const statusConstants = {
    SUCCESS: "SUCCESS",
    ERROR: "ERROR",
};

여기까지 작성을 하고 컨트롤러에서 클래스들을 import하면 service 메서드만 남게 됩ㄴ디다. 이어서 service클래스를 작성하도록 하겠습니다.

#3 PostService

PostService를 작성하기 전에 mongoose 라이브러리를 설치하도록 하겠습니다.

npm install --save mongoose @nestjs/mongoose

post 스키마에는 rental 데이터가 들어갈 수도 들어가지 않을수도 있습니다. 따라서 rental데이터에 대한 인터페이스를 만들도록 하겠습니다.

  • ./src/interfaces/rental.interface.ts
import { Payment } from "./payment.interface";

export class Rental {
    rentalId: string;

    price: number;

    borrower: string;

    tel: string;

    userId: string;

    date: string;

    time: string;

    mapId: string;

    mapName: string;

    status: string;   

    createdAt: string;

    payment: Payment;
}
  • ./src/interfaces/payment.interface.ts
export class Payment {
    paymentName: string;

    payer: string;

    rentalId: string;

    paymentId: string;

    price: number;

    createdAt: string;
}

이어서 post 스키마를 작성하도록 하겠습니다.

  • ./src/schema/post.schema.ts
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { SchemaTypes, Types } from "mongoose";

export type PostDocument = Post & Document;

@Schema()
export class Post {
    @Prop({ required: true })
    type: string;

    @Prop({ required: true})
    title: string;

    @Prop({ required: true })
    content: string;

    @Prop({ required: true })
    userId: string;

    @Prop({ required: true })
    writer: string;

    @Prop({ required: true })
    createdAt: string;

    @Prop()
    rental: any;
}

export const PostSchema = SchemaFactory.createForClass(Post);

mongoose 모듈을 import 하겠습니다.

  • ./src/post/post.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { Post, PostSchema } from 'src/schema/post.schema';
import { PostService } from './post.service';

@Module({
  imports: [
    MongooseModule.forFeature([{
      name: Post.name,
      schema: PostSchema,
    }])
  ],
  providers: [PostService],
  exports: [PostService]
})
export class PostModule {}
  • ./src/app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PostModule } from './post/post.module';

@Module({
  imports: [
    MongooseModule.forRoot("mongodb://localhost:27017/POSTSERVICE?readPreference=primary&appname=MongoDB%20Compass&directConnection=true&ssl=false"),
    PostModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

모듈을 import했으니 service에서 model을 호출하여 비즈니스 로직을 구현하도록 하겠습니다.

  • ./src/post/post.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Builder } from 'builder-pattern';
import { Model } from 'mongoose';
import { statusConstants } from 'src/constants/status.constants';
import { PostDto } from 'src/dto/post.dto';
import { Post, PostDocument } from 'src/schema/post.schema';

@Injectable()
export class PostService {
    constructor(@InjectModel(Post.name) private postModel: Model<PostDocument>) {}

    public async write(dto: PostDto): Promise<any> {
        try {
            const entity: any = await new this.postModel(Builder(Post).title(dto.title)
                                                                      .content(dto.content)
                                                                      .type(dto.type)
                                                                      .userId(dto.userId)
                                                                      .writer(dto.writer)
                                                                      .createdAt(new Date().toString())
                                                                      .rental(dto.rental ? dto.rental : null)
                                                                      .build())
                                                                      .save();

            if(!entity) {
                return await Object.assign({
                    status: statusConstants.ERROR,
                    payload: null,
                    message: "post-service: Database error!"
                });
            }

            return await Object.assign({
                status: statusConstants.SUCCESS,
                payload: Builder(PostDto).title(entity.title)
                                         .content(entity.content)
                                         .type(entity.type)
                                         .userId(entity.userId)
                                         .writer(entity.writer)
                                         .createdAt(entity.createdAt)
                                         .rental(entity.rental)
                                         .build(),
                message: "Successful transaction"
            });
        } catch(err) {
            return await Object.assign({
                status: statusConstants.SUCCESS,
                payload: null,
                message: "post-service: " + err,
            });
        }
    }

    public async getAll(): Promise<any> {
        try {
            const entities: any = await this.postModel.find();

            if(!entities) {
                return await Object.assign({
                    status: statusConstants.SUCCESS,
                    payload: null,
                    message: "Not exist posts data"
                });
            }

            const dtos: Array<PostDto> = [];

            for(const entity of entities) {
                dtos.push(entity);
            }

            return await Object.assign({
                status: statusConstants.SUCCESS,
                payload: dtos,
                message: "Successful transaction"
            });
        } catch(err) {
            return await Object.assign({
                status: statusConstants.ERROR,
                payload: null,
                message: "post-service: " + err
            });
        }
    }

    public async getOne(_id: string): Promise<any> {
        try {    
            const entity: any = await this.postModel.findOne({ _id: _id });

            if(!entity) {
                return await Object.assign({
                    status: statusConstants.SUCCESS,
                    payload: null,
                    message: "Not exist post data"
                });
            }

            return await Object.assign({
                status: statusConstants.SUCCESS,
                payload: Builder(PostDto)._id(String(entity._id))
                                         .title(entity.title)
                                         .content(entity.content)
                                         .type(entity.type)
                                         .userId(entity.userId)
                                         .writer(entity.writer)
                                         .createdAt(entity.createdAt)
                                         .rental(entity.rental)
                                         .build(),
                message: "Successful transaction"
            });
        } catch(err) {
            return await Object.assign({
                status: statusConstants.ERROR,
                payload: null,
                message: "post-service: " + err,
            });
        }
    }

    public async getPostsByType(type: string): Promise<any> {
        try {    
            const entities: any = await this.postModel.find({ type: type });

            if(!entities) {
                return await Object.assign({
                    status: statusConstants.SUCCESS,
                    payload: null,
                    message: "Not exist post data"
                });
            }

            const dtos: Array<PostDto> = [];

            for(const entity of entities) {
                dtos.push(entity);
            }

            return await Object.assign({
                status: statusConstants.SUCCESS,
                payload: dtos,
                message: "Successful transaction"
            });
        } catch(err) {
            return await Object.assign({
                status: statusConstants.ERROR,
                payload: null,
                message: "post-service: " + err
            });
        }
    }

    public async getPostsByUserId(userId: string): Promise<any> {
        try {
            const entities: any = await this.postModel.find({ userId: userId });

            if(!entities) {
                return await Object.assign({
                    status: statusConstants.SUCCESS,
                    payload: null,
                    message: "Not exist post data"
                });
            }

            const dtos: Array<PostDto> = [];

            for(const entity of entities) {
                dtos.push(entity);
            }

            return await Object.assign({
                status: statusConstants.SUCCESS,
                payload: dtos,
                message: "Successful transaction"
            });
        } catch(err) {
            return await Object.assign({
                status: statusConstants.ERROR,
                payload: null,
                message: "post-service: " + err,
            });
        }
    }

    public async getPostsByKeyword(keyword: string): Promise<any> {
        try {
            const entities: any = await this.postModel.aggregate([{ 
                $group: {
                    $or: [
                        { title: { $regex: '.*' + keyword + '.*' }},
                        { content: { $regex: '.*' + keyword + '.*' }}
                    ]
                }
            }]);

            if(!entities) {
                return await Object.assign({
                    status: statusConstants.SUCCESS,
                    payload: null,
                    message: "Not exist post data"
                });
            }

            const dtos: Array<PostDto> = [];

            for(const entity of entities) {
                dtos.push(entity);
            }

            return await Object.assign({
                status: statusConstants.SUCCESS,
                payload: dtos,
                message: "Successful transaction"  
            });
        } catch(err) {
            return await Object.assign({
                status: statusConstants.ERROR,
                payload: null,
                message: "post-service: " + err,
            });
        }
    }

    public async deletePost(_id: string): Promise<any> {
        try {    
            const result: any = await this.postModel.deleteOne({ _id: _id });

            if(!result) {
                return await Object.assign({
                    status: statusConstants.ERROR,
                    payload: null,
                    message: "Not exist post data"
                });
            }

            return await Object.assign({
                status: statusConstants.SUCCESS,
                payload: null,
                message: "Successful transaction"
            });
        } catch(err) {
            return await Object.assign({
                status: statusConstants.ERROR,
                payload: null,
                message: "post-service: " + err,
            });
        }
    } 
}

컨트롤러에 필요한 7개의 메서드를 완성했습니다. 메서드를 살펴보겠습니다.

1) write: 글쓰기를 위한 메서드입니다. dto 객체를 전달받아 이를 이용하여 entity를 구성하고, document로 저장합니다. 그리고 이 entity를 이용하여 다시 dto객체로 변환시킨 후 컨트롤러 레이어로 보내는 역할을 합니다.

2) getAll: 전체 글을 불러오는 메서드입니다. 전달받는 인자는 없으며 모든 게시글을 탐색하여 dto 배열에 담아 컨트롤러에 전송합니다.

3) getOne: 게시글 상세 페이지를 위한 메서드입니다. mongodb는 데이터 저장시 자체적으로 object형의 id값을 만드는데 이를 이용하여 하나의 entity를 불러와 dto 객체로 변환시킨 뒤 컨트롤러에 전송합니다.

4) getPostsByType: 정렬 탭에서 사용될 메서드입니다. 게시글은 기본적으로 '함께해요', '도와주세요' 라는 타입을 가지고 있기 때문에 사용자가 원하는 타입의 글만 불러오도록 하는 메서드입니다.

5) getPostsByUserId: 마이페이지의 게시글 내역에서 사용될 메서드입니다. 사용자는 자신이 작성한 게시글을 해당 메서드를 이용하여 파악할 수 있습니다.

6) getPostsByKeyword: 게시글 홈 최상단에 위치한 검색 바에서 사용될 메서드입니다. title, content를 중심으로 keyword에 해당하는 데이터를 찾고, 중복이 있는 데이터는 제거한 후 반환시켜주는 쿼리를 가지고 있습니다.

7) deletePost: 마이 페이지의 게시글 내역에서 사용될 포스트 제거 기능입니다. id값을 이용하여 원하는 게시글을 삭제할 수 있습니다.

이상으로 post-service에서 댓글을 제외한 부분을 완성했으니 UI작업을 다음 포스트에서 진행해보도록 하겠습니다.

0개의 댓글

관련 채용 정보