회원과 게시글의 연동

이재경·2022년 12월 28일
0

백엔드

목록 보기
7/7

1.회원과 게시글 연동 기능

1.게시글 작성 시 로그인된 회원정보를 작성자로 추가
2.게시글-작성자는 populate하여 사용하도록 구현
3.게시글 수정, 삭제 시 로그인된 유저와 작성자가 일치하는지 확인
4.작성자의 게시글 모아 보기 기능 구현

2.PostSchema 수정


PostSchema에 author 추가
populate를 사용하기 위해 ObjectID 사용
ref 를 유저 모델의 이름인 'User'로 선언

3.게시글 등록 요청 수정

게시글에 작성자 추가

req.user에는 strategy에서 최소한의 정보로 저장한 shortid, email,username만 가지고 있음
Post생성 시 user의 ObjectID를 전달해야하는데, 이를 위해 User에서 shortId로 회원을 검색하여 한번 더 검증
type:ObjectID로 선언한 필드에 객체가 주어지면 자동으로 ObjectID사용

4.게시글에 작성자 연동


게시글 find 시 populate를 추가하여 ObjectID로 저장된 author를 각 게시글에 주입
사용시 post.author.{field} 로 사용 가능

5.게시글 수정, 삭제시 유저 확인


게시글 수정, 삭제 시 작성자를 populate하여 로그인된 사용자와 일치하는지 확인

6.작성자 게시글 모아 보기 기능 구현

기본적으로 MongoDB는 Document 검색 시, 전체 문서를 하나씩 확인 함
하나씩 확인하기 때문에 매우 비효율적인 검색 수행
데이터가 많아질 경우 속도 저하의 가장 큰 원인이 됨

(1)Index
MongoDB는 검색을 위해 Document를 정렬하여 저장하는 기능을 제공 함
Index를 설정하면 주어진 쿼리를 효율적으로 수행하여 성능을 향상시킬 수 있음
*다중 키, 좌표, 텍스트 등의 특별한 값으로 정리되는 인덱스도 제공

(2)author에 index 설정하기

PostSchema의 author 속성에 index:true 옵션을 사용하면 mongoose가 자동으로 MongoDB에 인덱스를 생성해줌
이미 데이터가 많은 상태에서 인덱스를 추가할 시 작업 시간이 길어져, MongoDB가 응답하지 않을 수 있음 -> 예상되는 인덱스를 미치 추가하는 것이 좋음

(3)회원 게시글 라우팅 추가하기

RESTful한 구성을 위해, 회원->게시글의 경로를 /users/{userId}/posts로 구성
게시글 목록 view는 기존에 작성한 posts/list.pug를 재활용

7.게시글 목록 화면 수정


게시글 목록 화면을 재활용하기 위해 수정
유저의 게시글인 경우 "###의 게시글"이라는 제목 사용
게시글의 사용자 이름에 유저의 게시글 link 추가

8.코드로 구현하기

posts.js

const { Router } = require('express');
const { Post, User } = require('../models');
const asyncHandler = require('../utils/async-handler');

const router = Router();

router.get('/', asyncHandler(async (req, res) => {
  if (req.query.write) {
    res.render('post/edit');
    return;
  }
  
  const page = Number(req.query.page || 1);
  const perPage = Number(req.query.perPage || 10);
  
  // getPaginatedPosts 사용하기
 const [posts,totalPage] =await Post.getPaginatedPosts({},page,perPage)
  res.render('post/list', { posts, page, perPage, totalPage, path: req.baseUrl });
}));

router.get('/:shortId', asyncHandler(async (req, res) => {
  const { shortId } = req.params;
  const post = await Post.findOne({ shortId }).populate('author');
  
  if (req.query.edit) {
    res.render('post/edit', { post });
    return;
  }
  
  res.render('post/view', { post });
}));

router.post('/', asyncHandler(async (req, res) => {
  const { title, content } = req.body;
  
  if (!title || !content) {
    throw new Error('제목과 내용을 입력 해 주세요');
  }
  // 로그인 된 사용자의 shortId 로 사용자를 찾아 게시글 생성시 작성자로 추가

const author=await User.findOne({
    shortId:req.user.shortId
})

if(!author){
    throw new Error('No Author')
}
  const post = await Post.create({ title, content,author });
  res.redirect(`/posts/${post.shortId}`);
}));

router.post('/:shortId', asyncHandler(async (req, res) => {
  const { shortId } = req.params;
  const { title, content } = req.body;
  
  if (!title || !content) {
    throw new Error('제목과 내용을 입력 해 주세요');
  }
  
  const post = await Post.findOne({ shortId }).populate('author'); // 작성자 populate
  // 작성자와 로그인된 사용자의 shortId 가 다를경우 오류 발생
    
  await Post.updateOne({ shortId }, { title, content })

  if(post.author.shortId!==req.user.shortId){
      throw new Error('작성자가 아닙니다')
  }
  res.redirect(`/posts/${shortId}`);
}));

router.delete('/:shortId', asyncHandler(async (req, res) => {
  const { shortId } = req.params;
  
  const post = await Post.findOne({ shortId }).populate('author')
  
   // 작성자 populate
  // 작성자와 로그인된 사용자의 shortId 가 다를경우 오류 발생
  if(post.author.shortId!==req.user.shortId){
      throw new Error('작성자가 아닙니다')
  }
  await Post.deleteOne({ shortId });
  res.send('OK');
}));

module.exports = router;

index.js

const mongoose = require('mongoose');
const PostSchema = require('./schemas/post');
const UserSchema = require('./schemas/user');

const Post = mongoose.model('Post', PostSchema);
Post.getPaginatedPosts = async (query, page, perPage) => {  
  const [total, posts] = await Promise.all([
    Post.countDocuments(query),
    Post
      .find(query)
      .sort({ createdAt : -1 })
      .skip(perPage * (page - 1))
      .limit(perPage)
      .populate('author')
       // populate 추가하기
  ]);

  const totalPage = Math.ceil(total / perPage);
  
  return [posts, totalPage];
}

exports.Post = Post;
exports.User = mongoose.model('User', UserSchema);

users.js

const { Router } = require('express');

const { User, Post } = require('../models');
const asyncHandler = require('../utils/async-handler');

const router = Router();

router.get('/:shortId/posts', asyncHandler(async (req, res) => {
  const { shortId } = req.params;
  // 유저 게시글 모아보기 기능 완성하기
  const author =await User.findOne({shortId})
  if(!author){
      throw new Error('사용자가 없습니다')
  }
  const page=Number(req.query.page||1)
  const perPage=Number(req.query.perPage||10)
  const [posts,totalPage]=await Post.getPaginatedPosts({author},page,perPage)
  res.render('post/list', {posts, page, perPage, totalPage, user, path: req.baseUrl + req.url });
}));

module.exports = router;

models/schemas/post.js

const { Schema } = require('mongoose');
const shortId = require('./types/short-id');

const PostSchema = new Schema({
  shortId,
  title: {
    type: String,
    required: true,
  },
  content: {
    type: String,
    required: true,
  },
  author: {
    type: Schema.Types.ObjectId,
    ref: 'User',
    required: true,
    index:true
    // index 추가하기
  },
}, {
  timestamps: true,
});

module.exports = PostSchema;
profile
코딩으로 빛나게

0개의 댓글