NODE.JS

Social Media 만들기 - 18) SavePost 구현하기

dodop 2021. 8. 1. 23:09

 

먼저 아이콘을 눌러서 포스트를 저장하는 기능을 만들자. 

user 모델에 다음과 같이 save :post레퍼런스를 만들자. 

    saved: [{type: mongoose.Types.ObjectId, ref: 'Post'}]

}, {
    timestamps: true
})

 

 

post Router에 포스트를 저장하고 지우는 라우터를 만들자. 

 

postRouter.patch('/save/:id', auth, async(req, res)=>{
    try{
        const user = await User.find({_id:req.user.id, saved:req.params.id})
        if(user.length>0) return res.status(400).json({msg:"You already saved this post."})

        const save = await User.findOneAndUpdate({_id:req.user.id},{
            $push:{saved:req.params.id}
        }, {new:true})

        if(!save) return res.status(400).json({msg:'This post does not exist.'})

        res.json({save})
    }catch(err){
        return res.status(500).json({message:err.message})
    }
})

postRouter.patch('/unsave/:id', auth, async(req, res)=>{
    try{
        const unsave = await User.findOneAndUpdate({_id:req.user.id},{
            $pull:{saved:req.params.id}
        }, {new:true})

        if(!unsave) return res.status(400).json({msg:'This post does not exist.'})

        res.json({unsave})
    }catch(err){
        return res.status(500).json({message:err.message})
    }
})

 

ponst Constants

export const SET_SAVE_POST_REQUEST = 'SET_SAVE_POST_REQUEST'
export const SET_SAVE_POST_SUCCESS = 'SET_SAVE_POST_SUCCESS'
export const SET_SAVE_POST_FAIL='SET_SAVE_POST_FAIL'

export const SET_UNSAVE_POST_REQUEST = 'SET_UNSAVE_POST_REQUEST'
export const SET_UNSAVE_POST_SUCCESS = 'SET_UNSAVE_POST_SUCCESS'
export const SET_UNSAVE_POST_FAIL='SET_UNSAVE_POST_FAIL'

 

 

postActions

export const setSavePost=(post) =>async(dispatch, getState)=>{
    dispatch({
        type:SET_SAVE_POST_REQUEST,
        payload:{loading:true}
    })

    const {userLogin:{userInfo}} = getState();

    try{
        const res = await axios.patch(`/api/post/save/${post._id}`,null, {
            headers:{authorization:`Bearer ${userInfo.token}`}
        })

        dispatch({
            type:SET_SAVE_POST_SUCCESS,
            payload:res.data.save
        })

        dispatch({
            type:USER_LOGIN_SUCCESS,
            payload:{user:res.data.save, token:userInfo.token}
        })

        localStorage.removeItem('userInfo')
        localStorage.setItem('userInfo',JSON.stringify({user:res.data.save, token:userInfo.token}))
        
    }catch(error){
        dispatch({
            type:SET_SAVE_POST_FAIL,
            payload:                
            error.response && error.response.data.message
            ? error.response.data.message
            : error.message
        })
    }
}



export const setUnsavePost=(post) =>async(dispatch, getState)=>{
    dispatch({
        type:SET_UNSAVE_POST_REQUEST,
        payload:{loading:true}
    })

    const {userLogin:{userInfo}} = getState();

    try{
        const res = await axios.patch(`/api/post/unsave/${post._id}`,null, {
            headers:{authorization:`Bearer ${userInfo.token}`}
        })

        dispatch({
            type:SET_UNSAVE_POST_SUCCESS,
            payload:res.data.unsave
        })

        dispatch({
            type:USER_LOGIN_SUCCESS,
            payload:{user:res.data.unsave, token:userInfo.token}
        })
        localStorage.removeItem('userInfo')
        localStorage.setItem('userInfo',JSON.stringify({user:res.data.unsave, token:userInfo.token}))
        
        
    }catch(error){
        dispatch({
            type:SET_UNSAVE_POST_FAIL,
            payload:                
            error.response && error.response.data.message
            ? error.response.data.message
            : error.message
        })
    }
}

 

 

post Reducer

export const postSaveReducer = (state={}, action)=>{
    switch(action.type){
        case SET_SAVE_POST_REQUEST:
            return {loading:true, success:false}
        case SET_SAVE_POST_SUCCESS:
            return {loading:false, success:true, savedpost:action.payload}
        case SET_SAVE_POST_FAIL:
            return {loading:false, success:false, error:action.payload}
        default:
            return state;
    }
}


export const postUnsaveReducer = (state={}, action)=>{
    switch(action.type){
        case SET_UNSAVE_POST_REQUEST:
            return {loading:true, success:false}
        case SET_UNSAVE_POST_SUCCESS:
            return {loading:false, success:true, unsavedpost:action.payload}
        case SET_UNSAVE_POST_FAIL:
            return {loading:false, success:false, error:action.payload}
        default:
            return state;
    }
}

 

 

store.js

    savepost:postSaveReducer,
    unsavepost:postUnsaveReducer,

 

CardFooter.js

import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import {Link} from 'react-router-dom'
import Send from '../../images/send.svg'
import { likePost, setSavePost, setUnsavePost, unlikePost } from '../../_actions/postActions'
import LikeButton from '../LIkeButton'
import ShareModal from '../ShareModal'
import {BASE_URL} from '../../utils'

function CardFooter({post}) {

    const [isSaved, setIsSaved] = useState(false)

    
    useEffect(() => {
        if(userInfo.user.saved.find(id=>id===post._id)){
            setIsSaved(true)
        }else{
            setIsSaved(false)
        }
    }, [userInfo.user.saved, post._id])

    const handleSavePost= () =>{
        if(!isSaved){
            dispatch(setSavePost(post))

        }else{
            dispatch(setUnsavePost(post))
        }
        setIsSaved(!isSaved)
    }

    return (
        <div className="card_footer">
            <div className="card_icon_menu">
                
                {
                    isSaved
                    ? <i className="fas fa-bookmark text-info" onClick={handleSavePost}></i>
                    : <i className="far fa-bookmark " onClick={handleSavePost}></i>
                }

            </div>

        </div>
    )
}

export default CardFooter

 

잘 들어와있다. 

localStorage에 잘 set해주었으므로 새로고침해도 잘 유지된다. 

 

이제 저장한 포스트들을 프로필페이지에서 보이도록 하자. 

Profile.js



function Profile(props) {
    


    const [saveTab, setSaveTab] = useState(false)


    return (

            }

            {
                <div className="profile_tab">
                    <button className={saveTab? '': 'active'} onClick={()=>setSaveTab(false)}>Posts</button>
                    <button className={saveTab? 'active': ''} onClick={()=>setSaveTab(true)}>Saved</button>
                </div>
            }



            { 
                posts && user&& saveTab
                ? <SavedPost/>
                :
                <>
                {
                    posts && posts.length ===0
                    ? <h2 className="text-center text-danger">NO POST</h2>
                    : <>
                    <div className="post_thumb">
                        {
                            posts?.map(post=>(
                                <Link key={post._id} to={`/post/${post._id}`}>
                                    <div className="post_thumb_display">
                                        {
                                            post.images[0].config.url.match(/video/i)
                                            ? <video controls src={post.images[0].data} alt={post.images[0].data}></video>
                                            : <img src={post.images[0].data} alt={post.images[0].data}></img>
                                        }

                                        <div className="post_thumb_menu">
                                            <i className="far fa-heart">{post.likes.length}</i>
                                            <i className="far fa-comment">{post.comments.length}</i>
                                        </div>
                                    </div>
                                </Link>
                            ))
                        }
                    </div>
                    </>
                    }
                </>
            }
        </div>
    )
}

export default Profile

저장된 포스트들을 가지고 오는 라우터를 작성해주자. 

postRouter.get('/profile/savedposts', auth, async(req, res)=>{
    try{
        const user = await User.findById({_id:req.user.id})

        const limit = req.query.limit ||9

        const savedposts = await Post.find({
            _id:{$in:user.saved}
        }).limit(Number(limit)).sort('-createdAt')

        console.log(savedposts)

        res.json({savedposts, result:savedposts.length})
    }catch(err){
        return res.status(500).json({message:err.message})
    }
})

 

 

 

SavedPost 컴포넌트를 만들어준다. 

import axios from 'axios'
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getSavePost, setSavePost } from '../_actions/postActions'
import { ALERT } from '../_constants/globalConstants'
import { GET_SAVE_POST_UPDATE } from '../_constants/postConstants'
import PostThumb from './PostThumb'
import LoadIcon from '../images/loading.gif'

function SavedPost() {

    const [savedPosts, setSavedPosts] = useState([])
    const [result, setResult] = useState(9)
    const [page, setPage] = useState(2)
    const [load, setLoad] = useState(false)

    const userLogin = useSelector(state => state.userLogin)
    const {userInfo} = userLogin

    const dispatch = useDispatch()

    useEffect(() => {
        setLoad(true)
        axios.get('/api/post/profile/savedposts', {
            headers:{authorization:`Bearer ${userInfo.token}`},
        }).then(response=>{
            setSavedPosts(response.data.savedposts)
            setResult(response.data.result)
            setLoad(false)
        })
        .catch(err=>{
            dispatch({
                type:ALERT, 
                payload:{error:err.data.message}
            })
        })
        return ()=>setSavedPosts([])
    }, [userInfo.token, dispatch])

    const handleLoadMore = async() =>{
        setLoad(true)
        const res = await axios.get(`/api/post/profile/savedposts?limit=${page*9}`,{
        headers:{authorization:`Bearer ${userInfo.token}`},
        })
        setSavedPosts(res.data.savedposts)
        setResult(res.data.result)
        setPage(page+1)
        setLoad(false)

    }

    return (
        <div>
            

            {
                load
                ? <img src={LoadIcon} alt="loading" className="d-block mx-auto" />
                :  
                    <>
                    
                        <PostThumb posts={savedPosts} result={result}/>
                        {
                            result < 9*(page-1) 
                            ? ''
                            :<button className="btn btn-dark mx-auto d-block" onClick={handleLoadMore}>LoadMore</button>
                        }
                                     
                    </>
            }



        </div>
    )
}

export default SavedPost

 

리덕스 스토어에 저장하지 않고, 

그대로 axios로 보낼것이다. 

 

 

 

 

 

save 탭누르면 저장된 포스트들 나오고, 

loadmore 버튼 누르면 axios 보내면서 포스트 잘 가지고 온다.