[mini-project] SNS mini project(2)

김민재·2024년 4월 16일

mini_project

목록 보기
4/5

링크: sns mini project(1)

깃허브 소스코드: 깃허브 소스코드

1. Post 수정하기

// app.js
app.use(methodOverride('_method'))
// method override 미들웨어 설정

// 미들웨어
// post를 만든 사람인지 미들웨어
function checkPostOwnership(req, res, next) {
  if (req.isAuthenticated()) {
    Post.findById(req.params.id)
      .then((post) => {
        if (post.author.id.equals(req.user._id)) {
          req.post = post // posts.router 사용할 수 있다.
        next()
        } else {
          req.flash('error', '권한이 없습니다.')
          res.redirect('/login')
      }
      }).catch((error) => {
        if (error) {
          req.falsh('error', "포스트가 없거나 에러가 발생했습니다.")
        res.redirect('back')}
    })
  }
}

// posts.router.js
router.get('/:id/edit', checkPostOwnership, (req, res) => {
  // const post = Post.findById(req.params.id)
  // post를 직접 찾아서 넣어주든가
  // 미들웨어에서 보내주든가
  res.render('posts/edit',{post: req.post})
})

// posts/edit.ejs
<%- include('../partials/header') %>

<div class="container col-lg-8">
    <div class="card">
        <h5 class="card-header">
            포스트 수정하기
        </h5>
        <div class="card-body">
            <form action="/posts/<%= post._id %>?_method=PUT" method="POST">
                <div class="form-group">
                    <textarea class="form-control" id="desc" name="description"><%= post.description %></textarea>
                </div>
                <button type="submit" class="btn btn-primary mt-3">수정하기</button>
            </form>
        </div>
    </div>


</div>



<%- include('../partials/footer') %>

// posts.router.js
// 포스트 수정 시 put method 
// 포스트 수정 시 수정해주기
router.put('/:id', checkPostOwnership, (req, res) => {
  Post.findByIdAndUpdate(req.params.id, req.body)
    .then((result) => {
      console.log(result)
      if (result) {
        req.flash('success', '포스트 수정에 성공했습니다.')
        res.redirect('/posts')
      }
    }).catch((error) => {
      if (error) {
        req.flash('error', '포스트 수정에 실패했습니다.')
        res.redirect('/posts')
    }
  })
})

2. 포스트 삭제하기

// 포스트 삭제
router.delete('/:id', checkPostOwnership,async (req, res) => {
  const post = await Post.findByIdAndDelete(req.params.id);

  if (!post) {
    req.flash('error','포스트 삭제에 실패했습니다.')
  }

  req.flash('success', '포스트 삭제에 성공했습니다.')
  res.redirect('/posts')
})

3. 댓글 생성하기

// comments.router.js
router.post('/', checkAuthenticated, async (req, res) => {
    // app.js에서 router 경로를 정의 해줬기 때문에 여기서는 할 필요가 없다.
    // 그런데 post를 찾으려면 req.params.id를 가져와야 하는데 app.js router 경로에 있기 때문에
    // 여기서는 사용이 불가능하다. 그때  const router = express.Router({mergeParams: true}); 설정 해주면 된다.
    const post = await Post.findById(req.params.id);

    let text = req.body.text
    if (post) {
         Comment.create({
            text,
            author: {
              id: req.user._id,
              username: req.user.username
            }
         }).then(() => {
             post.comments.push(comment)
             post.save()

             req.flash('success', '댓글이 생성되었습니다.')
             res.redirect('/posts')
         }).catch(() => {
            req.flash('error', '댓글 생성이 실패했습니다.')
            res.redirect('/posts')
            })
    } else {
        req.flash('error', '포스트가 존재하지 않습니다.')
        res.redirect('/posts')
    }
})

4. 댓글 삭제하기

// 댓글 생성자 미들웨어 체크
function checkCommentOwnership(req, res, next) {
  if (req.isAuthenticated()) {
    Comment.findById(req.params.commentId)
      .then((comment) => {
        if (comment.author.id.equals(req.user._id)) {
        next()
        } else {
          req.flash('error', '권한이 없습니다.')
          res.redirect('/posts')
      }
      }).catch((error) => {
        req.flash('error', '댓글이 존재하지 않거나 에러가 발생했습니다.')
        res.redirect('back')
    })
  }
}

// comment.router.js
router.delete('/:commentId', checkCommentOwnership, async (req, res) => {
    const comment = await Comment.findByIdAndDelete(req.params.commentId)

    if (!comment) {
        req.flash('error', '댓글 삭제에 실패했습니다.')
        res.redirect('back')
      }
      req.flash('success', '댓글 삭제에 성공했습니다.')
      res.redirect('/posts')
})

5. 댓글 수정하기

// comment.router.js
// 댓글 수정 UI 가져오기
router.get('/:commentId/edit', checkCommentOwnership, async (req, res) => {
    const post = await Post.findById(req.params.id)
    if (post) {
        res.render('comments/edit',
            {
                post: post,
            comment: req.comment})
    }
})

// 댓글 수정
router.put('/:commentId', checkCommentOwnership, async (req, res) => {
    const comment = await Comment.findByIdAndUpdate(req.params.commentId, req.body)

    if (comment) {
        req.flash('success', '댓글 수정에 성공했습니다.')
        res.redirect('/posts')
    } else {
        req.flash('error', '댓글 수정에 실패했습니다.')
        res.redirect('/posts')
    }
})

6. 좋아요 기능

// post-item.ejs
// 좋아요 기능
        <hr class="mt-1" >
        <div class="d-flex justify-content-between">
            <div class="row">
                <form action="/posts/<%= post._id %>/like?_method=PUT" method="POST">
                    <!-- 이미 좋아요를 눌렀는지  -->
                    <% if (post.likes.find(like => like === currentUser._id.toString())) { %>
                        <button type="submit" class="no-outline">
                            <img src="/assets/images/like-1.png" height="20px" >
                            <span class="ms-1"> <%= post.likes.length %></span>
                        </button>
                    <% } else { %>
                        <button type="submit" class="no-outline">
                            <img src="/assets/images/like.png" height="20px" >
                            <span class="ms-1"> <%= post.likes.length %></span>
                        </button>
                    <% } %>
                </form>
            </div>
            
// likes.router.js
router.put('/posts/:id/like', checkAuthenticated, async (req, res) => {
    try {
        const post = await Post.findById(req.params.id);

        if (post) {
            if (post.likes.includes(req.user._id.toString())) {
                post.likes = post.likes.filter(like => like !== req.user._id.toString());
            } else {
                post.likes.push(req.user._id);
            }

            await post.save();
            req.flash('success', '좋아요가 업데이트되었습니다.');
        } else {
            req.flash('error', '포스트가 존재하지 않습니다.');
        }

        res.redirect('/posts');
    } catch (err) {
        req.flash('error', '좋아요 업데이트에 실패했습니다.');
        console.log(err);
        res.redirect('/posts');
    }
});

7. 친구 목록 UI

// friends/index.ejs
<%- include('../partials/header') %>
<div class="container col-lg-8">


    <div class="mb-2">
        <div class="card">
            <h5 class="card-header text-start">친구 요청</h5>
            <div class="card-body">
                <% if (currentUser.friendsRequests.length === 0) { %>
                    <div class="card-text text-center text-muted">
                        아직 친구 요청이 없습니다.
                    </div>
                <% } else { %>
                    <% currentUser.friendsRequests.forEach((friendId) => { %>
                        
                        <% const friend = users.find(user => user._id.toString() === friendId) %>

                            <div class="card" style="border:none;">
                                <div class="card-body">
                                    <div class="d-flex justify-content-between">
                                        <a href="/profile/<%= friendId %>">
                                            <p><%= friend.username %></p>
                                        </a>
                                        <div class="d-flex">
                                            <form action="/friends/<%= friend.id %>/accept-friend-request?_method=PUT" method="POST" class="me-3">
                                                <button class="btn btn-primary btn-sm" type="submit">친구 추가 요청 수락</button>
                                            </form>
                                            <form action="/friends/<%= currentUser._id %>/remove-friend-request/<%= friend.id %>?_method=PUT" method="POST">
                                                <button class="btn btn-primary btn-sm" type="submit">친구 추가 요청 거절</button>
                                            </form>
                                        </div>
                                    </div>
                                </div>
                            </div>
                    <% }) %>
                <% } %>
            </div>
        </div>
    </div>


    <div class="mb-2">
        <div class="card">
            <h5 class="card-header text-start">친구 요청</h5>
            <div class="card-body">
                <% users.forEach((user) => {  %>

                    <% if (
                    // 상대방이 자기 자신인지
                    !(user._id.equals(currentUser.id)) && 
                    // 나의 친구 목록에 상대방이 이미 있는지
                    !(currentUser.friends.find(friendId => friendId === user._id.toString())) &&
                    // 내가 친구 요청받은 목록에 상대방이 있는지 
                    !(currentUser.friendsRequests.find(friendId => friendId === user._id.toString()))
                    ) { %>
                        <div class="card" style="border:none;">
                            <div class="card-body">
                                <div class="d-flex justify-content-between">
                                    <a href="/profile/<%= user._id %>">
                                        <p><%= user.username %></p>
                                    </a>

                                    <% if (user.friendsRequests.find(friendId => friendId === currentUser._id.toString())) { %>
                                        <form action="/friends/<%= user._id %>/remove-friend-request/<%= currentUser._id %>?_method=PUT" method="POST" class="ms-auto">
                                            <button class="btn btn-sm btn-primary">친구 요청 취소</button>
                                        </form>
                                    <% } else { %>
                                        <form action="/friends/<%= user._id %>/add-friend?_method=PUT" method="POST" class="ms-auto">
                                            <button class="btn btn-sm btn-primary">친구 요청</button>
                                        </form>
                                    <% } %>
                                </div>
                            </div>
                        </div>
                    <% } %>
                <% }) %>
            </div>
        </div>
    </div>




    <div class="mb-2">
        <div class="card">
            <h5 class="card-header text-start">친구들</h5>
            <div class="card-body">
                <% if (currentUser.friends.length === 0) { %>
                    <div class="card-text text-center text-muted">
                        아직 친구가 없습니다.
                    </div>
                <% } else { %>
                    <% currentUser.friends.forEach((friendId) => { %>
                        <% const friend = users.find(user => user._id.toString() === friendId) %>
                            <div class="card" style="border:none;">
                                <div class="card-body">
                                    <div class="d-flex justify-content-between">
                                        <a href="/profile/<%= friendId %>">
                                            <p><%= friend.username %></p>
                                        </a>
                                        <form action="/friends/<%= friendId %>/remove-friend?_method=PUT" method="POST">
                                            <button class="btn btn-primary btn-sm" type="submit">친구 취소</button>
                                        </form>
                                    </div>
                                </div>
                            </div>
                    <% }) %>
                <% } %>
            </div>
        </div>
    </div>



</div>
<%- include('../partials/footer') %>

9. 친구 신청하기

// friends.router.js
// 친구 요청
router.put('/:id/add-friend', checkAuthenticated, async (req, res) => {
    // 몽고db 연산자 $push는 항상 값을 추가한다. 이미 존재 했어도
    // $addToSet은 중복된 경우 하지 않는다.
    await User.findByIdAndUpdate(req.params.id,
        {$addToSet: { friendsRequests: req.user._id }})
       .then(async (user) => {
            req.flash('success', '친구 추가에 성공했습니다.')
        }).catch((err) => {
            console.log(err)
            req.flash('error', '친구 추가에 실패했습니다.')
        })
})

10. 친구 요청 거절 및 취소

// 친구 요청 취소
router.put('/:id/remove-friend-request/:currentUserId', checkAuthenticated, async (req, res) => {
    // $pull 몽고디비 배열에서 제거
    await User.findByIdAndUpdate(req.params.id, {
        $pull: {friendsRequests: req.params.currentUserId}
    }).then((user) => {
        req.flash('success', '친구 요청이 취소되었습니다.')
        res.redirect('back')
    }).catch((err) => {
        console.log(err)
        req.flash('error', '친구 요청 취소 중 에러가 발생했습니다.')
        res.redirect('back')
    })
})

11. 친구 요청 수락

// 수락
// 친구 요청 수락
router.put('/:id/accept-friend-request', checkAuthenticated, async (req, res) => {
    
    await User.findByIdAndUpdate(req.user._id, {
        $addToSet: { friends: req.params.id },
        $pull: {friendsRequests: req.params.id}
    })
    await User.findByIdAndUpdate(req.params.id, {
        $addToSet: { friends: req.user._id },
    })   
        .then(() => {
        req.flash('success', '친구 요청이 수락되었습니다.')
        res.redirect('back')
    }).catch(() => {
        req.flash('error', '친구 요청이 수락 중 에러가 발생했습니다..')
        res.redirect('back')
    })
})

12. 친구 취소

// 친구 취소
router.put('/:id/remove-friend', checkAuthenticated, async (req, res) => {
    
    await User.findByIdAndUpdate(req.user._id, {
        $pull: {friends: req.params.id}
    })
    await User.findByIdAndUpdate(req.params.id, {
        $pull: { friends: req.user._id },
    })   
        .then(() => {
        req.flash('success', '친구 삭제되었습니다.')
        res.redirect('back')
    }).catch(() => {
        req.flash('error', '친구 삭제 중 에러가 발생했습니다..')
        res.redirect('back')
    })
})

13. 내 프로필 UI

<%- include('../partials/header') %>

<div class="container col-lg-8">
    <div class="text-center">
        <h3><%= user.firstName %> <%= user.lastName %></h3>
        <p class="text-dark"> <%= user.bio %></p>
    </div>

    <div class="row">
        <div class="col-md-5">
            <div class="card mb-2">
                <div class="card-body">
                    <p>유저 정보</p>
                </div>
                <ul class="list-group list-group-flush">
                    <li class="list-group-item">
                        <i class="fa fa-map-marker fa-lg"></i>
                        <span class="ms-2"><%= user.hometown %></span>
                    </li>
                    <li class="list-group-item">
                        <i class="fa fa-briefcase fa-lg"></i>
                        <span class="ms-2"><%= user.workspace %></span>
                    </li>
                    <li class="list-group-item">
                        <i class="fa fa-book fa-lg"></i>
                        <span class="ms-2"><%= user.education %></span>
                    </li>
                    <li class="list-group-item">
                        <i class="fa fa-phone fa-lg"></i>
                        <span class="ms-2"><%= user.contact %></span>
                    </li>
                </ul>
                <% if (currentUser._id.toString() === user._id.toString()) { %>
                    <div class="card-body text-center">
                        <a href="/profile/<%= user._id %>/edit" class="btn btn-secondary btn-block">
                            <i class="fa fa-pencil"></i> 프로필 수정하기
                        </a>
                    </div>
                <% } %>
            </div>
        </div>

        <div class="col-md-7">
            <% if (currentUser._id.toString() === user._id.toString()) { %>
             <div class="mb-2">
                <%- include('../partials/create-post') %>
             </div>
            <% } %>
             <div class="mb-2">
                <div class="card">
                    <div class="card-body">
                        <p>포스트</p>
                    </div>
                </div>
             </div>
             <div>
                <% posts.forEach((post) => { %>
                    <%- include('../partials/post-item', {post: post}) %>
                <% }) %>
             </div>

        </div>
    </div>


</div>

<%- include('../partials/post-modal') %>

<%- include('../partials/footer') %>

14. 프로필 가져오기

router.get('/', checkAuthenticated, async (req, res) => {
    // Post model에서 author 에 id를 참조한다.
    await Post.find({"author.id":req.user._id}).populate('comments').sort({ createdAt: -1 }).exec()
        .then(async(posts) => {
            await User.findById(req.user._id)
                .then((user) => {
                    res.render('profile', {
                        posts: posts,
                        user:user,
                })
            })
        }).catch(() => {
            req.flash('error',' 에러 발생')
        })
    
})

15. 프로필 수정하기 및 미들웨어

// 프로필 수정 UI render
router.get('/edit', checkAuthenticated, async (req, res) => {
    await User.findById(req.params.id)
        .then((user) => {
            res.render('profile/edit',{user:user})
        }).catch(() => {
        req.flash('error','에러 발생')
    })
})

// 프로필 수정
router.put('/', checkIsMe, async (req, res) => {
    await User.findByIdAndUpdate(req.params.id, req.body)
        .then((user) => {
            req.flash('success', '프로필이 수정되었습니다.')
            res.redirect(`/profile/${req.user._id}`)
        }).catch((err) => {
            console.log(err)
            req.flash('error', '프로필 수정 실패했습니다.')
            res.redirect('back')
    })
})

// 미들웨어
async function checkIsMe(req, res, next) {
  if (req.isAuthenticated()) {
    await User.findById(req.params.id)
      .then((user) => {
        if (user._id.equals(req.user._id)) {
          next()
        }
      })
      .catch(() => {
      req.flash('error','접근 권한이 없습니다.')
    })
  }

}
profile
개발 경험치 쌓는 곳

0개의 댓글