LifeSports Application(ReactNative & Nest.js) - 23. post-service(4)

yellow_note·2021년 10월 25일
0

#1 댓글 기능

게시글에서의 댓글을 저장하거나 삭제할 때의 데이터 관리는 다음과 같이 진행됩니다.
1) 댓글 저장: 댓글 스키마를 추가하여 독립적으로 comment document를 저장합니다.

2) 댓글 삭제: 댓글 id값을 받아와 해당 document를 삭제합니다.

3) 게시글 불러오기: 게시글을 불러올 시 게시글 id값에 대한 find query를 이용하여 관련 댓글들을 불러옵니다.

4) 게시글 삭제: 해당 게시글 id 값을 deleteMany 쿼리를 이용하여 관련 댓글들을 삭제하도록 합니다.

post-service에서 댓글 기능을 구성해보도록 하겠습니다. 우선 컨트롤러에서 댓글 endpoint를 만들어 보도록 하겠습니다.

  • ./src/app.controller.ts
...

@Controller('post-service')
export class AppController {
    ...

    @Post('comment')
    public async comment(@Body() requestComment: RequestComment) : Promise<any> {
        try {
            const dto: any = await this.postService.comment(Builder(CommentDto).postId(requestComment.postId)
                                                                               .userId(requestComment.userId)
                                                                               .writer(requestComment.writer)
                                                                               .content(requestComment.content)
                                                                               .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: statusConstants.SUCCESS,
                message: "Successful save comment"
            });
        } catch(err) {
            return await Object.assign({
                status: HttpStatus.BAD_REQUEST,
                payload: null,
                message: "Error message: " + err
            });
        }
    }

    @Delete(':_id/comment')
    public async deleteComment(@Param('_id') _id: string) : Promise<any> {
        try {
            const dto: any = await this.postService.deleteComment(_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.NO_CONTENT,
                payload: null,
                message: "Successful delete comment"
            });
        } catch(err) {
            return await Object.assign({
                status: HttpStatus.BAD_REQUEST,
                payload: null,
                message: "Error message: " + err
            });
        }
    }
}

1) comment: 댓글을 작성할 수 있는 rest 요청입니다. 게시글 id값을 param으로 전달받고, vo 객체로 RequestComment를 전달받습니다.

2) deleteComment: 댓글을 삭제할 수 있는 rest 요청입니다. 댓글의 id값을 받아 서비스 클래스로 삭제 트랜잭션을 요청합니다.

Comment를 위한 dto, vo, interface, schema 클래스를 만들어 보도록 하겠습니다.

  • ./src/dto/comment.dto.ts
export class CommentDto {
    id: number;
    
    postId: string;
    
    userId: string;

    writer: string;

    content: string;

    createdAt: string;
}
  • ./src/vo/request.comment.ts
import { IsString } from "class-validator";

export class RequestComment {
	@IsString()
    postId: string;
    
    @IsString()
    userId: string;

    @IsString()
    writer: string;

    @IsString()
    content: string;
}
  • ./src/interfaces/comment.interface.ts
export class Comment {
    userId: string;

    writer: string;

    content: string;

    createdAt: string;
}
  • ./src/schema/comment.schema.ts
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";

export type CommentDocument = Comment & Document;

@Schema()
export class Comment {
    @Prop({ required: true })
    postId: string;

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

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

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

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

export const CommentSchema = SchemaFactory.createForClass(Comment);

댓글 dto ~ schema를 작성했습니다. 그러면 추가적으로 PostDto, PostSchema 객체에 속성으로써의 comment 객체를 추가하도록 하겠습니다.

  • ./src/schema/post.schema.ts
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Types } from "mongoose";
import { Comment } from "src/interfaces/comment.interface";
import { Rental } from "src/interfaces/rental.interface";

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({ required: false })
    rental: Rental;

     @Prop({ required: false })
    comments: Comment[];
}

export const PostSchema = SchemaFactory.createForClass(Post);
  • ./src/dto/comment.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;

    comments: any;
}

PostModule에 Comment 스키마를 등록하도록 하겠습니다.

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

@Module({
  imports: [
    MongooseModule.forFeature([{
      name: Post.name,
      schema: PostSchema,
    }]),
    MongooseModule.forFeature([{
      name: Comment.name,
      schema: CommentSchema
    }]),
  ],
  providers: [PostService],
  exports: [PostService]
})
export class PostModule {}

이를 바탕으로 하여 PostService를 수정하도록 하겠습니다.

  • ./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 { CommentDto } from 'src/dto/comment.dto';
import { PostDto } from 'src/dto/post.dto';
import { Comment, CommentDocument } from 'src/schema/comment.schema';
import { Post, PostDocument } from 'src/schema/post.schema';

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

    public async write(dto: PostDto): Promise<any> {
        try {
            const entity: any = await new this.postModel(Builder(Post).type(dto.type)
                                                                      .title(dto.title)
                                                                      .content(dto.content)
                                                                      .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(Builder(PostDto)._id(entity._id)
                                          .title(entity.title)
                                          .content(entity.content)
                                          .type(entity.type)
                                          .userId(entity.userId)
                                          .writer(entity.writer)
                                          .createdAt(entity.createdAt)
                                          .rental(entity.rental)
                                          .comments(await this.commentModel.find({ postId: entity._id }))
                                          .build());
            }

            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 post: any = await this.postModel.findOne({ _id: _id });
            const comments: any = await this.commentModel.find({ postId: post._id });

            if(!post) {
                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(post._id))
                                         .title(post.title)
                                         .content(post.content)
                                         .type(post.type)
                                         .userId(post.userId)
                                         .writer(post.writer)
                                         .createdAt(post.createdAt)
                                         .rental(post.rental)
                                         .comments(comments)
                                         .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(Builder(PostDto)._id(entity._id)
                                          .title(entity.title)
                                          .content(entity.content)
                                          .type(entity.type)
                                          .userId(entity.userId)
                                          .writer(entity.writer)
                                          .createdAt(entity.createdAt)
                                          .rental(entity.rental)
                                          .comments(await this.commentModel.find({ postId: entity._id }))
                                          .build());
            }

            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(Builder(PostDto)._id(entity._id)
                                          .title(entity.title)
                                          .content(entity.content)
                                          .type(entity.type)
                                          .userId(entity.userId)
                                          .writer(entity.writer)
                                          .createdAt(entity.createdAt)
                                          .rental(entity.rental)
                                          .comments(await this.commentModel.find({ postId: entity._id }))
                                          .build());
            }

            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(Builder(PostDto)._id(entity._id)
                                          .title(entity.title)
                                          .content(entity.content)
                                          .type(entity.type)
                                          .userId(entity.userId)
                                          .writer(entity.writer)
                                          .createdAt(entity.createdAt)
                                          .rental(entity.rental)
                                          .comments(await this.commentModel.find({ postId: entity._id }))
                                          .build());
            }

            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 });
            await this.commentModel.deleteMany({ postId: _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,
            });
        }
    } 

    public async comment(dto: CommentDto): Promise<any> {
        try {
            const entity: any = await new this.commentModel(Builder(Comment).postId(dto.postId)
                                                                             .userId(dto.userId)
                                                                             .writer(dto.writer)
                                                                             .content(dto.content)
                                                                             .createdAt(new Date().toString())
                                                                             .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: null,
                message: "Successful transaction"
            });
        } catch(err) {
            return await Object.assign({
                status: statusConstants.ERROR,
                payload: null,
                message: "post-service: " + err,
            })
        }
    }

    public async deleteComment(_id: string): Promise<any> {
        try {
            const result: any = await this.commentModel.deleteOne({ _id: _id });
            
            if(!result) {
                return await Object.assign({
                    status: statusConstants.ERROR,
                    payload: null,
                    message: "post-service: database error"
                });
            }

            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
            });
        }
    }
}

주요 수정된 메서드들과 추가된 메서드를 살펴보겠습니다.

1) getAll:

for(const entity of entities) {
    const comments: any = await this.commentModel.find({ postId: entity._id });
                
    dtos.push(entity.push(comments));
}

해당 부분이 수정된 코드입니다. for 구문을 이용하여 가져온 게시글 객체의 id값을 가져옵니다. 그리고 이 id값을 commentModel의 find 쿼리에 넣어 전체 댓글리스트를 가져온 후 entity에 넣어줍니다. 마지막으로 dtos 배열 객체에 entity들을 담아 데이터를 반환합니다.

2) deletePost:

const result: any = await this.postModel.deleteOne({ _id: _id });
await this.commentModel.deleteMany({ postId: _id });

게시글의 id값을 가져와 deleteOne 쿼리를 이용하여 게시글을 삭제하고, 이어서 deleteMany 쿼리를 이용하여 관련 댓글들을 전부 삭제합니다.

3) comment: 댓글을 저장하는 메서드입니다. 댓글 entity에 값을 담아 save쿼리를 이용하여 댓글을 데이터베이스에 저장합니다.

4) deleteComment: 댓글을 삭제하는 메서드입니다. 댓글 id값을 가져와 deleteOne 메서드를 이용하여 해당 댓글을 삭제합니다.

테스트를 진행해보도록 하겠습니다.

#2 테스트

1) 댓글 저장

2) 게시글 불러오기

  • 전체 response
{
    "status": 200,
    "payload": {
        "_id": "61720baeb2731c19e165a175",
        "type": "함께해요",
        "title": "test-03",
        "content": "test-03",
        "userId": "78e834ff-f270-4a9e-9b30-1a44daff9469",
        "writer": "asd",
        "createdAt": "Fri Oct 22 2021 09:54:06 GMT+0900 (대한민국 표준시)",
        "rental": null,
        "comments": [
            {
                "_id": "61760b20d7e1941bf80e208d",
                "createdAt": "Mon Oct 25 2021 10:40:48 GMT+0900 (대한민국 표준시)",
                "content": "test-01",
                "writer": "biuea",
                "userId": "bb49cad9-1d76-41b2-abad-7a271c4394ca",
                "postId": "61720baeb2731c19e165a175",
                "__v": 0
            },
            {
                "_id": "6176128e528e131ff92ea087",
                "createdAt": "Mon Oct 25 2021 11:12:30 GMT+0900 (대한민국 표준시)",
                "content": "test-01",
                "writer": "biuea",
                "userId": "bb49cad9-1d76-41b2-abad-7a271c4394ca",
                "postId": "61720baeb2731c19e165a175",
                "__v": 0
            },
            {
                "_id": "61761588528e131ff92ea08b",
                "createdAt": "Mon Oct 25 2021 11:25:12 GMT+0900 (대한민국 표준시)",
                "content": "test-03",
                "writer": "biuea",
                "userId": "bb49cad9-1d76-41b2-abad-7a271c4394ca",
                "postId": "61720baeb2731c19e165a175",
                "__v": 0
            }
        ]
    },
    "message": "Get post data"
}

3) 댓글 삭제

댓글이 정상적으로 삭제되는 모습을 볼 수 있습니다.

테스트가 잘 진행되었으니 다음 포스트에선 프론트에서 댓글 기능을 연동해보도록 하겠습니다.

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN