게시판 - Post-User 관계(relationship) 만들기

소스코드

이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.

이 게시물의 소스코드는 게시판 만들기 / 게시판 - Post Error 처리에서 이어집니다.

board.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.

git reset --hard
git pull
git reset --hard e5c8bf9
git reset --soft d4b9478
npm install
atom .

board.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 board.git 을 clone 합니다.

git clone https://github.com/a-mean-blogger/board.git
cd board
git reset --hard e5c8bf9
git reset --soft d4b9478
npm install
atom .

- Github에서 소스코드 보기: https://github.com/a-mean-blogger/board/tree/e5c8bf94fc67b1a3a53c932ba6cc664c6af05ec9


게시물과 사용자 사이에 관계(relationship)을 만들어 봅시다. 게시물 document에 작성자 document id를 기록하여 글 작성자 정보를 알 수 있게 됩니다.

이게 왜 중요한가 하면, 로그인 기능으로 현재 사이트에 로그인 한 유저가 누군지 알 수 있게 됐고, 여기에 작성자가 기록되면 자신이 작성한 글, 다른 사람이 작성한 글의 구분이 가능해 집니다. 즉 자신의 글은 삭제가 가능/남의 글은 삭제가 불가능하게 할 수 있습니다. 이번 게시물에서는 일단 게시물에 작성자를 만드는 법만 알아보겠습니다.

폴더구조

코드 - js

// models/Post.js

// schema
var postSchema = mongoose.Schema({
 title:{type:String, required:[true,"Title is required!"]},
 body:{type:String, required:[true,"Body is required!"]},
 author:{type:mongoose.Schema.Types.ObjectId, ref:"user", required:true}, //1
 createdAt:{type:Date, default:Date.now},
 updatedAt:{type:Date},
},{
 toObject:{virtuals:true}
});

1. post schema에 author를 추가해 줍니다. 즉 post에 작성자 정보(user.id)를 기록하고, 이 정보는 user collection에서 가져오는 것임을 ref를 통해서 지정합니다.

// routes/posts.js

// ...

// Index
router.get("/", function(req, res){
 Post.find({})
 .populate("author") // 1
 .sort("-createdAt")
 .exec(function(err, posts){
  if(err) return res.json(err);
  res.render("posts/index", {posts:posts});
 });
});

//...

// Create
router.post("/", function(req, res){
 req.body.author = req.user._id; // 2
 Post.create(req.body, function(err, post){
  if(err){
   req.flash("post", req.body);
   req.flash("errors", util.parseError(err));
   return res.redirect("/posts/new");
  }
  res.redirect("/posts");
 });
});

// Show
router.get("/:id", function(req, res){
 Post.findOne({_id:req.params.id}) // 3
 .populate("author")               // 3
 .exec(function(err, post){        // 3
  if(err) return res.json(err);
  res.render("posts/show", {post:post});
 });
});

// ...

1. Model.populate()함수는 relationship이 형성되어 있는 항목의 값을 생성해 줍니다. 현재 post의 author에는 user의 id가 기록되어 있는데, 이 값을 바탕으로 실제 user의 값을 author에 생성하게 됩니다.

2. 글을 작성할때는 req.user._id를 가져와서 post의 author에 기록합니다.
(req.user는 로그인을 하면 passport에서 자동으로 생성해 주는 것.. 기억하고 계시죠?)

3. index와 마찬가지로 show에도 .populate()함수를 추가하였습니다.

코드 - ejs

<!-- views/posts/index.ejs -->

<!-- ... -->
   <table class="table table-striped posts">
    <thead>
     <tr>
      <th class="title">Title</th>
      <th class="author">Author</th> <!-- 1 -->
      <th class="date">Date</th>
     </tr>
    </thead>
    <tbody>
     <% if(posts == null || posts.length == 0){ %>
      <tr>
       <td class="noData" colspan=100> There is no data to show :( </td>
      </tr>
     <% } %>
     <% posts.forEach(function(post) { %>
      <tr>
       <td class="title">
        <a href="/posts/<%= post._id %>"><div class="ellipsis"><%= post.title %></div></a> <!-- 3 -->
       </td>
       <td class="author"> <!-- 2 -->
        <div class="ellipsis"><%= post.author ? post.author.username : "" %></div> <!-- 3 -->
       </td>
       <td class="date">
        <%= post.createdDate %>
       </td>
      </tr>
     <% }) %>
    </tbody>
   </table>
<!-- ... -->

1. tabletheader에 author 항목을 추가합니다.

2. tabletbody에 author 항목을 추가하고 author가 있는 경우 author의 username을 표시합니다.

3. tabletbody의 title과 author에 ellipsis class를 주었습니다. ellipsis custom CSS로 text의 내용이 width보다 긴 경우 긴 부분을 잘라내고 "..."을 추가해줍니다. 아래 CSS 항목에서 상세값을 볼 수 있습니다.

<!-- views/posts/show.ejs -->

<!-- ... -->
   <div class="contentBox">
    <h3 class="contentBoxTop"><%= post.title %></h3>
    <div class="row">
     <div class="col-sm-4 col-sm-push-8">
      <div class="post-info">
       <div><span>Author</span> : <%= post.author ? post.author.username : "" %></div> <!-- 1 -->
       <div><span>Created</span> : <%= post.createdDate %> <%= post.createdTime %></div>
       <% if(post.updatedAt) { %
        <div><span>Updated</span> : <%= post.updatedDate %> <%= post.updatedTime %></div>
       <% } %>
      </div> <!-- post-info end -->
     </div> <!-- col end-->
     <div class="col-sm-8 col-sm-pull-4">
      <div class="post-body"><%= post.body %></div>
     </div> <!-- col end-->
    </div> <!-- row end -->
   </div> <!-- post-container end -->
<!-- ... -->

1. index와 마찬가지로 show에도 author 항목을 추가하고 author가 있는 경우 author의 username을 표시합니다.

코드 - css

/* pulbic/css/master.css */

/* ... */
.ellipsis{ /* 1 */
 display: block;
 width: 100%;
 white-space: nowrap;
 overflow: hidden;
 text-overflow: ellipsis;
}
/* ... */
.post-index .posts{
 border-top: 1px solid #ccc;
 border-bottom: 1px solid #ccc;
 table-layout: fixed; /* 2 */
}
/* ... */
.post .posts .author{ /* 3 */
 text-align: center;
 width: 80px;
}
/* ... */

1. text의 내용이 width보다 긴 경우 긴 부분을 잘라내고 "..."을 추가해줍니다. 핵심은 text-overflow: ellipsis; 랑 overflow: hidden; 이며 둘 중 하나라도 없으면 작동하지 않습니다. white-space: nowrap;은 text에 space가 있고 text 길이가 width보다 긴 경우 자동으로 줄바꿈을 하는데, 이것을 방지하기 위함합니다.

2. .ellipsis를 table안의 title항목에 적용하기 위해 기존의 .post-index .posts에 추가된 항목입니다.
table의 경우 cell의 길이에 맞춰 유동적으로 width가 변하게 되어 있는데, table-layout: fixed;를 넣어주면 cell이 table보다 더 길어지지 않게 됩니다.

3. author 를 가운데 정렬하고 길이를 지정해 줬습니다.

실행결과

로그인을 한 후에 글을 작성해 봅시다.

게시물에 들어가면 author가 표시됩니다.

ellipsis도 테스트 해 봅시다.

제목이 긴 경우 뒷부분이 ... 으로 표시됩니다.

만약 로그인 하지 않고 글을 작성하려고 한다면?

req.user의 값을 읽어 오지 못하므로 에러가 납니다. 이 에러의 처리는 다음 게시물에서...

마치며...

이제 작성자 정보를 기록했으니 작성자를 기준으로 게시물의 삭제, 수정을 가능하게 할 수 있습니다.
다음 강의에서는 로그인 유무, 글 작성자 본인 확인을 통해 사이트의 기능을 제한하는 방법을 알아보겠습니다.

댓글

김진호 2016.12.22
네이버 블로그부터 새로운 블로그까지 잘 보고 배우고 있습니다. 다음편도 빨리 나왔으면 좋겠네요!
I
Ian H 2016.12.28
@김진호,
요즘 바빠서 자주 못올리고 있어요 ㅠㅠ 코딩은 해놨는데.. 분발하겠습니다.
쯔쮸바 2017.01.04
이 블로그를 알게 된 이후로 네이버 블로그까지 보면서 소스코드를 짬뽕해서 이전 블로그에 있던 댓글 까지 완료했습니다. 게시판 글의 body 부분에 사진까지 넣어서 게시판 글을 작성하게 끔 예제가 있었으면 좋겠습니다. 웹 문외한이다보니 혼자하려니 막히는 부분이 많네요. P.s 좋은 포스팅 감사드립니다.
I
Ian H 2017.01.04
@쯔쮸바,
와.. 여기에 이어서 댓글달기까지 하셨다니 대단합니다. 파일첨부는 꽤 여럽죠. 파일첨부 강좌는 한참 뒤에나 나올 예정인데요, 힌트를 드리자만 1. front-end에서 어떻게 서버로 파일을 보내는지, 2. 서버에 받은 파일을 어디에 저장할지 가 포인트입니다. 1번은 구글링을 해보면 거의 한가지 방법으로 처리되지만 2번은 내 상황에 따라서 다양한 방법이 요구됩니다.  heroku를 사용한다면 front-end를 통해 server로 파일 저장이 불가능하기 때문이죠. 따라서 server에 전송받은 파일을 저장할 곳이 필요한데, DB에 저장할 수도 있고, 다른 싸이트(dropbox등) API를 통해서 파일을 저장한 후 파일 주소만 저장할 수도 있고.. 아니면 처음부터 파일저장이 가능한 웹서버를 구매한다거나 하는 방법도 있습니다.
K
Kyowon Im 2017.02.16
잘보고 있습니다. 감사합니다 ^^
I
Ian H 2017.02.17
@Kyowon Im,
도움이 되었으면 좋겠네요^^
전준혁 2018.03.10
오타가 있습니다. router/post.js에 "//1"이 .populate()에 있어야 하는데 .sort()에 있습니다!
I
Ian H 2018.03.12
@전준혁,
알려주신 부분 수정하였습니다! 감사합니다^^
댓글쓰기

이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기

UP