깃허브 소스코드: 깃허브 소스코드
// 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')
}
})
})
// 포스트 삭제
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')
})
// 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')
}
})
// 댓글 생성자 미들웨어 체크
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')
})
// 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')
}
})
// 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');
}
});
// 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') %>
// 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', '친구 추가에 실패했습니다.')
})
})
// 친구 요청 취소
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')
})
})
// 수락
// 친구 요청 수락
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')
})
})
// 친구 취소
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')
})
})
<%- 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') %>
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',' 에러 발생')
})
})
// 프로필 수정 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','접근 권한이 없습니다.')
})
}
}