Mi Lugarcito

Node.js - Twitter Clone Coding // The like button 본문

Express.js

Node.js - Twitter Clone Coding // The like button

selene park 2021. 3. 31. 15:42

PostSchema.js

const mongoose = require("mongoose");

const Schema = mongoose.Schema;

const PostSchema = new Schema({
    content :  { type: String, trim : true },
    postedBy :  { type: Schema.Types.ObjectId, ref: 'User'}, // Schema.Types.ObjectId -> mongodb가 자동으로 populate 할 수 있다. 
    pinned :  Boolean, // 고정하기 
    //likes도 마찬가지로 User의 object이다@@
    likes:[{ type: Schema.Types.ObjectId, ref: 'User'}]
}, {timestamps:true});

var Post = mongoose.model('Post', PostSchema);
module.exports = Post;

UserSchema.js

const mongoose = require("mongoose");



const Schema = mongoose.Schema;

const UserSchema = new Schema({//괄호 안에 옵션넣기 
   firstName : { type:String, required : true, trim : true },
   lastName : { type:String, equired : true,trim : true },
   userName : { type:String, equired : true,trim : true, unique : true },
   email : { type:String, equired : true,trim : true, unique : true },
   password : { type:String, equired : true},
   profilePic : { type:String, default : "/images/profilePic.png"},
   //PostSchema.js에 like 필드가 추가되었으니 여기도 추가해줘야한다.(User->Post라고 디비 이름 변경)
   likes:[{ type: Schema.Types.ObjectId, ref: 'Post'}]

}, {timestamps:true});

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

posts.js

const express = require('express');
const app = express();
const router = express.Router();
const bodyParser = require("body-parser");
const User = require('../../schemas/UserSchema');
const Post = require('../../schemas/PostSchema');



app.use(bodyParser.urlencoded({ extended:false}));



router.get("/", (req, res, next)=>{
   Post.find()
    .populate("postedBy")
    .sort({ "createdAt" : -1}) // 시간순으로 정렬하려면 -1 해야함. 1은 디폴트값
    .then(resultes=>res.status(200).send(resultes))
    .catch(error => {
        console.log(error);
        res.sendStatus(400);
    })
})



router.post("/", async (req, res, next)=>{
    //session 을 통해 누가 로그인 했는지 알 수 있다. 
    if(!req.body.content) {
        console.log("Content param not sent with request");
        return res.sendStatus(400);
    }

    var postData ={
        content : req.body.content,
        postedBy : req.session.user
    }

    //callback ??인지??
    Post.create(postData)
        .then( async newPost =>{//201 : Created!, 200 :Success!

            newPost = await User.populate(newPost, { path : "postedBy"})
            
            res.status(201).send(newPost);
        })
        .catch( error =>{
            console.log(error);
            res.sendStatus(400);
        })
    //res.status(200).send("it worked");
})


module.exports = router;

common.js (NOTE)

//like를 위해서 @@@@important@@@@@
$(document).on("click", ".likeButton", ()=>{//document 전체페이지가 로딩된 후 버튼 이벤트를 통해서 실행
    alert("button clicked");
});

//if you write like below code, it's not working
// $(".likeButton").click(()=>{
//     alert("button clicked");
// })
//이유 : 메인페이지가 로딩될때 이전 코멘트틀이 달렸던 페이지 로딩이 늦게 되기 때문에 

// $(document).ready(()=>{//from main-layout.png!!!
//     //alert("hola"); //파일에 js 연결 잘 됐는지 확인
// })


//버튼 활성화 시키기
$("#postTextarea").keyup(event =>{//여기서 파라미터가 1개면 () 필요없이 그냥 사용가능
    var textbox = $(event.target);
    var value=textbox.val().trim();
    //console.log(value);

    var submitButton = $("#submitPostButton");//# 중요해

    //==랑 ===차이점 @@@
    if(submitButton.lengh == 0) return alert("No submit button found");

    if (value==""){
        submitButton.prop("disabled", true);
        return;
    }
    submitButton.prop("disabled", false);


})

//작성한 글 저장
$("#submitPostButton").click(()=>{
    var button = $(event.target);
    var textbox = $("#postTextarea");

    var data={
        //object
        content:textbox.val()
    }

    //ajax만들기(request, which will send the data to the server without us having to reload the page)
    $.post("/api/posts", data, postData => { //()=>{} : callback 함수
    // 여기에서 data를 "/api/post"로 요청 req 하고 끝나면  ()=>{} 함수로 돌아오겠다, callback 해라는 이야기 
        //alert(postData);
        //console.log(postData);
        var html = createPostHtml(postData);
        //prepend(top) & apend(end)
        $(".postsContainer").prepend(html);
        textbox.val("");//remove the textbox
        button.prop("disabled", true);
    })

})


//like를 위해서 @@@@important@@@@@
//document 전체페이지가 로딩된 후 버튼 이벤트를 통해서 실행
$(document).on("click", ".likeButton", (event)=>{
    //alert("button clicked");
    var button = $(event.target);
    var postId = getPostIdFromElement(button);
    console.log(postId);
});

//각 포스팅된 것들의 pk 넘버 찾는것 
function getPostIdFromElement(element){
    var isRoot = element.hasClass("post");
    var rootElement = isRoot == true ? element : element.closest(".post");
    var postId = rootElement.data().id;//data().id 의미 : 밑에있는 data-id

    if(postId === undefined) return alert("Post id undefined");

    return postId;
}





function createPostHtml(postData){
    //return postData.content;




    //html 작업하기
    var postedBy = postData.postedBy;

    if(postedBy._id === undefined){
        return console.log("User object not populated");
    }


    var displayName=postedBy.firstName + " " + postedBy.lastName;
    //시간 변경하기
    var timestamps = timeDifference(new Date(), new Date(postData.createdAt));

    return `<div class='post' data-id='${postData._id}'>

                <div class='mainContentContainer'>
                    <div class='userImageContainer'>
                        <img src='${postedBy.profilePic}'>
                    </div>
                    <div class='postContentContainer'>
                        <div class='header'>
                            <a href='/profile/${postedBy.userName}' class='displayName'>${displayName}</a>
                            <span class='username'>@${postedBy.userName}</span>
                            <span class='date'>${timestamps}</span>
                        </div>
                        <div class='postBody'>
                            <span>${postData.content}</span>
                        </div>
                        <div class='postFooter'>
                            <div class='postButtonContainer'>
                                <button>
                                    <i class='far fa-comment'></i>
                                </button>
                            </div>
                            <div class='postButtonContainer'>
                                <button>
                                    <i class='fas fa-retweet'></i>
                                </button>
                            </div>
                            <div class='postButtonContainer'>
                                <button class='likeButton'>
                                    <i class='far fa-heart'></i>
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>`;
}

//posting 한 시간을 시간그대로 나타내는게 아니라 ㅇㅇ전이라고 표시하기
function timeDifference(current, previous) {

    var msPerMinute = 60 * 1000;
    var msPerHour = msPerMinute * 60;
    var msPerDay = msPerHour * 24;
    var msPerMonth = msPerDay * 30;
    var msPerYear = msPerDay * 365;

    var elapsed = current - previous;

    if (elapsed < msPerMinute) {
         if(elapsed/1000 < 30) return "Just Now"
         return Math.round(elapsed/1000) + ' seconds ago';   
    }

    else if (elapsed < msPerHour) {
         return Math.round(elapsed/msPerMinute) + ' minutes ago';   
    }

    else if (elapsed < msPerDay ) {
         return Math.round(elapsed/msPerHour ) + ' hours ago';   
    }

    else if (elapsed < msPerMonth) {
        return Math.round(elapsed/msPerDay) + ' days ago';   
    }

    else if (elapsed < msPerYear) {
        return  Math.round(elapsed/msPerMonth) + ' months ago';   
    }

    else {
        return Math.round(elapsed/msPerYear ) + ' years ago';   
    }
}

 

 

 

 

 

 

 

 

Basic log for like and unlike and checking at Database & Console log

posts.js

const express = require('express');
const app = express();
const router = express.Router();
const bodyParser = require("body-parser");
const User = require('../../schemas/UserSchema');
const Post = require('../../schemas/PostSchema');



app.use(bodyParser.urlencoded({ extended:false}));



router.get("/", (req, res, next)=>{
   Post.find()
    .populate("postedBy")
    .sort({ "createdAt" : -1}) // 시간순으로 정렬하려면 -1 해야함. 1은 디폴트값
    .then(resultes=>res.status(200).send(resultes))
    .catch(error => {
        console.log(error);
        res.sendStatus(400);
    })
})



router.post("/", async (req, res, next)=>{
    //session 을 통해 누가 로그인 했는지 알 수 있다. 
    if(!req.body.content) {
        console.log("Content param not sent with request");
        return res.sendStatus(400);
    }

    var postData ={
        content : req.body.content,
        postedBy : req.session.user
    }

    //callback ??인지??
    Post.create(postData)
        .then( async newPost =>{//201 : Created!, 200 :Success!

            newPost = await User.populate(newPost, { path : "postedBy"})
            
            res.status(201).send(newPost);
        })
        .catch( error =>{
            console.log(error);
            res.sendStatus(400);
        })
    //res.status(200).send("it worked");
})


//like를 위해 추가 
//url : `/api/posts/${postId}/like`
router.put("/:id/like", async (req, res, next)=>{//:id말고 다른 아이디 네임 작성해도 괜찮음
    
    //console.log(req.params.id);// :id라고 적어서 id라고 작성함 

    var postId = req.params.id;
    var userId = req.session.user._id;//session을 통해서 누군지 알 수 있음 

    // have to check same person already pressed like or not
    // 이미 좋아요 누른 경우 
    var isLiked = req.session.user.likes && req.session.user.likes.includes(postId);
    
    //console.log("Is liked: " + isLiked);

    var option = isLiked ? "$pull" : "$addToSet"; //mongoDB에서 쓰는..
    
    console.log("is liked : " + isLiked);
    console.log("option : " + option);
    console.log("User Id : " + userId);


    //Insert User Table like (need to update)
    //here have to put await !! 비동기 시스템이라 안하면 user table에 like 필드가 안나타난다. 
    //option을 valuable 하게 사용하려고 [] 씀 & like가 아니라 ! User테이블의 likes 필드명
    //unlike 가 실행이 안됐던 이유 : findByIdAndUpdate 가 실행되면서 새롭게 업데이트 된 document를 리턴하지 않아서 
    req.session.user = await User.findByIdAndUpdate(userId, { [option] : { likes: postId} }, { new : true})
        .catch(error => {
            console.log(error);
            res.sendStatus(400);
        })



    //Insert Post Table like (need to update)
    var post= await Post.findByIdAndUpdate(postId, { [option] : { likes: userId} }, { new : true})
        .catch(error => {
            console.log(error);
            res.sendStatus(400);
        })

    res.status(200).send(post);
})

module.exports = router;

 

common.js

// $(document).ready(()=>{//from main-layout.png!!!
//     //alert("hola"); //파일에 js 연결 잘 됐는지 확인
// })


//버튼 활성화 시키기
$("#postTextarea").keyup(event =>{//여기서 파라미터가 1개면 () 필요없이 그냥 사용가능
    var textbox = $(event.target);
    var value=textbox.val().trim();
    //console.log(value);

    var submitButton = $("#submitPostButton");//# 중요해

    //==랑 ===차이점 @@@
    if(submitButton.lengh == 0) return alert("No submit button found");

    if (value==""){
        submitButton.prop("disabled", true);
        return;
    }
    submitButton.prop("disabled", false);


})

//작성한 글 저장
$("#submitPostButton").click(()=>{
    var button = $(event.target);
    var textbox = $("#postTextarea");

    var data={
        //object
        content:textbox.val()
    }

    //ajax만들기(request, which will send the data to the server without us having to reload the page)
    $.post("/api/posts", data, postData => { //()=>{} : callback 함수
    // 여기에서 data를 "/api/post"로 요청 req 하고 끝나면  ()=>{} 함수로 돌아오겠다, callback 해라는 이야기 
        //alert(postData);
        //console.log(postData);
        var html = createPostHtml(postData);
        //prepend(top) & apend(end)
        $(".postsContainer").prepend(html);
        textbox.val("");//remove the textbox
        button.prop("disabled", true);
    })

})


//like를 위해서 @@@@important@@@@@
//document 전체페이지가 로딩된 후 버튼 이벤트를 통해서 실행
$(document).on("click", ".likeButton", (event)=>{
    
    //alert("button clicked");
    var button = $(event.target);
    var postId = getPostIdFromElement(button);
    //console.log(postId);

    //중요 : ajax로 put request(create@@@) 하기 (but 한번 사용했던 이름으로 $.post or $.get으로는 재사용 불가능)
    if(postId === undefined) return;
    
    $.ajax({
        url : `/api/posts/${postId}/like`,
        type : "PUT", // PUT 이나 POST 나 둘 다 작동함 
        //callback (arrow function)
        success: (postData)=>{//if on success they are gonna be return postData
            console.log(postData.likes.length);//크롬콘솔에 찍힐예정 -> like :1 , unlike :0
        }
    })


});

//각 포스팅된 것들의 pk 넘버 찾는것 
function getPostIdFromElement(element){
    var isRoot = element.hasClass("post");
    var rootElement = isRoot == true ? element : element.closest(".post");
    var postId = rootElement.data().id;//data().id 의미 : 밑에있는 data-id

    if(postId === undefined) return alert("Post id undefined");

    return postId;
}





function createPostHtml(postData){
    //return postData.content;




    //html 작업하기
    var postedBy = postData.postedBy;

    if(postedBy._id === undefined){
        return console.log("User object not populated");
    }


    var displayName=postedBy.firstName + " " + postedBy.lastName;
    //시간 변경하기
    var timestamps = timeDifference(new Date(), new Date(postData.createdAt));

    return `<div class='post' data-id='${postData._id}'>

                <div class='mainContentContainer'>
                    <div class='userImageContainer'>
                        <img src='${postedBy.profilePic}'>
                    </div>
                    <div class='postContentContainer'>
                        <div class='header'>
                            <a href='/profile/${postedBy.userName}' class='displayName'>${displayName}</a>
                            <span class='userName'>@${postedBy.userName}</span>
                            <span class='date'>${timestamps}</span>
                        </div>
                        <div class='postBody'>
                            <span>${postData.content}</span>
                        </div>
                        <div class='postFooter'>
                            <div class='postButtonContainer'>
                                <button>
                                    <i class='far fa-comment'></i>
                                </button>
                            </div>
                            <div class='postButtonContainer'>
                                <button>
                                    <i class='fas fa-retweet'></i>
                                </button>
                            </div>
                            <div class='postButtonContainer'>
                                <button class='likeButton'>
                                    <i class='far fa-heart'></i>
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>`;
}

//posting 한 시간을 시간그대로 나타내는게 아니라 ㅇㅇ전이라고 표시하기
function timeDifference(current, previous) {

    var msPerMinute = 60 * 1000;
    var msPerHour = msPerMinute * 60;
    var msPerDay = msPerHour * 24;
    var msPerMonth = msPerDay * 30;
    var msPerYear = msPerDay * 365;

    var elapsed = current - previous;

    if (elapsed < msPerMinute) {
         if(elapsed/1000 < 30) return "Just Now"
         return Math.round(elapsed/1000) + ' seconds ago';   
    }

    else if (elapsed < msPerHour) {
         return Math.round(elapsed/msPerMinute) + ' minutes ago';   
    }

    else if (elapsed < msPerDay ) {
         return Math.round(elapsed/msPerHour ) + ' hours ago';   
    }

    else if (elapsed < msPerMonth) {
        return Math.round(elapsed/msPerDay) + ' days ago';   
    }

    else if (elapsed < msPerYear) {
        return  Math.round(elapsed/msPerMonth) + ' months ago';   
    }

    else {
        return Math.round(elapsed/msPerYear ) + ' years ago';   
    }
}