ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Social Media 만들기 - 20) socket io 사용(notify 생성하고 전달하기)
    NODE.JS 2021. 8. 4. 00:08

    이제 알람모델을 생성해보자. 

     

    우선, notify를 보여주는 모달을 만들것이다. 

    다음과 같이 header에서 원래 페이지로 존재하던것을, 

    navbar페이지가 아닌, dropdown모델로 바꿔주자. 

    우선 드롭다운 모델로 만들어주고, modal을 나열하는 형식으로 만든다. 

    function Haader() {
        const navLinks = [
            { label: 'Home', icon: 'home', path: '/'},
            { label: 'Message', icon: 'near_me', path: '/message'},
            { label: 'Discover', icon: 'explore', path: '/discover'},
            // {label:'Notify', icon:'favorite', path:'/notify'},
        ]
        
        
        
        
                            <li className="nav-item dropdown" style={{opacity:1}}>
                                <span className="nav-link position-relative" id="navbarDropdown"
                                role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                    <span className="material-icons" style={{color:notify.notify.length>0? 'crimson':''}}>
                                        favorite
                                    </span>
    
                                    {
                                        notify.notify.length>0 &&
                                            <span className="notify_length">{notify.notify.length}</span>
                                    }
                                </span>
    
                                <div className="dropdown-menu" aria-labelledby="navbarDropdown" style={{transform:'translateX(75px)'}}>
                                    <NotifyModal/>
                                </div>
                            </li>

     

    먼저 notify 모델을 만들어준다. 

     

    notify.js

    const mongoose = require('mongoose')
    
    const notifySchema = new mongoose.Schema({
       id:mongoose.Types.ObjectId,
       user:{
           type:mongoose.Types.ObjectId,
           ref:'User'
       },
       recipients:[
           mongoose.Types.ObjectId,
       ],
       url:String,
       text:String,
       content:String,
       image:String,
       isRead:{
           type:Boolean,
           default:false
       }
    },{timestamps:true})
    
    const Notify = mongoose.model('Notify', notifySchema)
    module.exports  = {Notify}

     

    notifyrouter를 만들어주자. 

    const express = require('express')
    const {auth} = require('../middleware/auth')
    const { Notify } = require('../models/Notify')
    
    
    const notifyRouter = express.Router()
    
    
    module.exports = notifyRouter

     

    server.js에 추가해준다. 

    app.use('/api/notify', require('./routes/notifyRouter'));

     

     

     

    이제 포스트를 생성하고 삭제할때, 

    notify모델을 생성해서  백엔드에 보내주고, 백엔드는 다시 프론트엔드로 notify에 대한 정보를 주도록 하자. 

     

     

    postAction.js

    (백엔드에 notify 정보전달)

     

    export const createPost = ({content, Images})=> async(dispatch, getState)=>{
    
        try{
            ...
            const res = await axios.post('/api/post', {content, images:imgArr, userId:userInfo.user._id,user:userInfo.user},{
                headers:{authorization:`Bearer ${userInfo.token}`}
            })
            dispatch({
                type:CREATE_POST_SUCCESS,
                payload:res.data.newPost
            })
    
    
            // console.log(res)
            // // Notify
            const msg={
                id:res.data.newPost._id,
                text:'added a new post.',
                recipients:res.data.newPost.user.followers,
                url:`/post/${res.data.newPost._id}`,
                content,
                image: imgArr[0].data
            }
    
            dispatch(createNotify({msg}))
    
        }catch(error){
    
    }
    
    
    
    
    export const deletePost = ({post}) => async (dispatch, getState)=>{
       ...
        try{
            const res = await axios.delete(`/api/post/${post._id}`, {
                headers:{authorization:`Bearer ${userInfo.token}`}
            })
            dispatch({
                type:DELETE_POST_SUCCESS,
                payload:res.data.deletedpost
            })
    
            console.log(res)
    
            const msg={
                id:post._id,
                text:'deleted a post.',
                recipients:res.data.deletedpost.user.followers,
                url:`/post/${post._id}`,
                // content:res.data.deletedpost.content,
                // image: res.data.deletedpost.images[0].data
            }
    
            dispatch(removeNotify({msg}))
        }catch(error){
     
    }

     

    notifyRouter

    (백엔드는 받은 nofity를 처리하고 다시 프론트로 전달)

    const express = require('express')
    const {auth} = require('../middleware/auth')
    const { Notify } = require('../models/Notify')
    
    
    const notifyRouter = express.Router()
    
    
    notifyRouter.post('/', auth, async(req, res)=>{
        try{
            const {id, recipients, url, text, content, image}= req.body.body
    
            if(recipients.includes(req.user.id.toString())) return ;
    
            const notify= new Notify({id, recipients, url, text, content, image, user:req.user.id})
    
            await notify.save()
            const newnotify = await Notify.findOne({id:id, url:url}).sort('-createdAt')
                                                                    .populate('user', 'avatar username')
    
            return res.json({newnotify})
    
        }catch(err){
            return res.status(500).json({message:err.message})
        }
    })
    
    notifyRouter.delete('/:id', auth, async(req, res)=>{
        try{
            const notify = await Notify.findOneAndDelete({id:req.params.id, url:req.query.url})
    
            return res.json({notify})
        }catch(err){
            return res.status(500).json({message:err.message})
        }
    })
    
    module.exports = notifyRouter

     

    notifyContants

    export const CREATE_NOTIFY_REQUEST = 'CREATE_NOTIFY_REQUEST'
    export const CREATE_NOTIFY_SUCCESS = 'CREATE_NOTIFY_SUCCESS'
    export const CREATE_NOTIFY_FAIL = 'CREATE_NOTIFY_FAIL'
    
    export const REMOVE_NOTIFY_REQUEST = 'REMOVE_NOTIFY_REQUEST'
    export const REMOVE_NOTIFY_SUCCESS = 'REMOVE_NOTIFY_SUCCESS'
    export const REMOVE_NOTIFY_FAIL = 'REMOVE_NOTIFY_FAIL'

     

    notifyActon을 만들어서 정보를 받아준다. 

     

    여기서 중간에 success가 없는이유는, 

    만약 post를 생성한다면, 내가 아닌 나의 팔로우들에게 notify가 추가되어야 하기 때문이다. 

    즉, socket Client에서 success로 생성된 Notify를 보내서 추가해줄 것이다. (팔로워들에게)

    import axios from "axios"
    import { CREATE_NOTIFY_FAIL, CREATE_NOTIFY_REQUEST, DELETE_ALL_NOTIFY_FAIL, DELETE_ALL_NOTIFY_REQUEST, DELETE_ALL_NOTIFY_SUCCESS, GET_NOTIFY_FAIL, GET_NOTIFY_REQUEST, GET_NOTIFY_SUCCESS, READ_NOTIFY_FAIL, READ_NOTIFY_REQUEST, READ_NOTIFY_SUCCESS, REMOVE_NOTIFY_FAIL, REMOVE_NOTIFY_REQUEST } from "../_constants/notifyConstants"
    
    export const createNotify = ({msg})=> async(dispatch, getState)=>{
        
        const {userLogin:{userInfo}} = getState()
        const {socket} = getState()
    
        dispatch({
            type:CREATE_NOTIFY_REQUEST
        })
        
        try{
            const res = await axios.post('/api/notify', {body:msg},{
                headers:{authorization:`Bearer ${userInfo.token}`}
            })
    
            socket.emit('createNotify', res.data.newnotify)
    
        }catch(error){
            dispatch({
                type:CREATE_NOTIFY_FAIL,
                payload:                
                error.response && error.response.data.message
                ? error.response.data.message
                : error.message
            })
        }
    }
    
    
    export const removeNotify = ({msg})=> async(dispatch, getState)=>{
    
        const {userLogin:{userInfo}} = getState()
        const {socket} = getState()
    
    
        dispatch({
            type:REMOVE_NOTIFY_REQUEST
        })
        try{
            const res = await axios.delete(`/api/notify/${msg.id}?url=${msg.url}`,{
                headers:{authorization:`Bearer ${userInfo.token}`}
            })
    
    
            socket.emit('removeNotify', msg)
    
    
        }catch(error){
            dispatch({
                type:REMOVE_NOTIFY_FAIL,
                payload:                
                error.response && error.response.data.message
                ? error.response.data.message
                : error.message
            })
        }
    }

     

    notifyReducer

    import { CREATE_NOTIFY_FAIL, CREATE_NOTIFY_REQUEST, CREATE_NOTIFY_SUCCESS, DELETE_ALL_NOTIFY_FAIL, DELETE_ALL_NOTIFY_REQUEST, DELETE_ALL_NOTIFY_SUCCESS, GET_NOTIFY_FAIL, GET_NOTIFY_REQUEST, GET_NOTIFY_SUCCESS, READ_NOTIFY_FAIL, READ_NOTIFY_REQUEST, READ_NOTIFY_SUCCESS, REMOVE_NOTIFY_FAIL, REMOVE_NOTIFY_REQUEST, REMOVE_NOTIFY_SUCCESS } from "../_constants/notifyConstants";
    
    export const notifyReducer = (state={ notify:[]}, action)=>{
        switch(action.type){
    
            case CREATE_NOTIFY_REQUEST:
                return {...state, loading:true}
            case CREATE_NOTIFY_SUCCESS:
                return {...state, loading:false, notify:[action.payload,...state.notify ]}
            case CREATE_NOTIFY_FAIL:
                return {...state, loading:false, error:action.payload}
            
            case REMOVE_NOTIFY_REQUEST:
                return {...state, loading:true}
            case REMOVE_NOTIFY_SUCCESS:
                return {...state, loading:false, notify: state.notify.filter(item=>item.id!==action.payload.id || item.url !== action.payload.url)}
            case REMOVE_NOTIFY_FAIL:
                return {...state, loading:false, error:action.payload}
                
            default :
                return state;
    
        }
    }

     

    store.js

        notify:notifyReducer,

     

     

     

    이제 처음 클라이언트 측에서 보낸 notify socket을 server에서 받자. 

        // Notification
        socket.on('createNotify', msg=>{
            const client = users.find(user=>msg.recipients.includes(user.id))
            console.log(msg)
            client && socket.to(`${client.socketId}`).emit('createNotifyToClient', msg)
        })
    
        socket.on('removeNotify', msg=>{
            const client = users.find(user=>msg.recipients.includes(user.id))
            // console.log(client)
            client && socket.to(`${client.socketId}`).emit('removeNotifyToClient', msg)
        })

     

    notify가 생겨나고 지워졌다는 소식을 들은 팔로워들은 

    notify를 생성하고 지운다. 

    serverClient

        //Notification
        useEffect(() => {
            socket.on('createNotifyToClient', msg=>{
                dispatch({
                    type:CREATE_NOTIFY_SUCCESS,
                    payload:msg
                })
            })
            return () => socket.off('removeNotifyToClient')
        }, [socket, dispatch])
    
        useEffect(() => {
            socket.on('removeNotifyToClient', msg=>{
                dispatch({
                    type:REMOVE_NOTIFY_SUCCESS,
                    payload:msg
                })
            })
            return () => socket.off('removeNotifyToClient')
        }, [socket, dispatch])
    
        return <> </>

     

     

    이제 같은 방식으로 notify를 가져오고, 읽은 상태로 변경하고, 모든 notify를 지우는 라우터와 reducer를 만들자. 

    notifyRouter

    notifyRouter.get('/', auth, async(req, res)=>{
        try{
            const notifies = await Notify.find({recipients:req.user.id})
                                            .sort('-createdAt')
                                            .populate('user', 'avatar username')
            return res.send({notifies})
        }catch(err){
            return res.status(500).json({message:err.message})
        }
    })
    
    notifyRouter.patch('/isReadNotify/:id', auth, async(req, res)=>{
        try{
            const updatedNotify = await Notify.findOneAndUpdate({_id:req.params.id}, {
                isRead:true
            },{new:true}).sort('-createdAt')
                        .populate('user', 'avatar username')
                        
            return res.json({updatedNotify})
        }catch(err){
            return res.status(500).json({message:err.message})
        }
    })
    
    notifyRouter.delete('/', auth, async(req,res)=>{
        try{
            const notifies = await Notify.deleteMany({recipients:req.user.id})
            return res.json({notifies})
    
        }catch(err){
            return res.status(500).json({message:err.message})
        }
    
    })

     

    notifyConstant

    export const GET_NOTIFY_REQUEST = 'GET_NOTIFY_REQUEST'
    export const GET_NOTIFY_SUCCESS = 'GET_NOTIFY_SUCCESS'
    export const GET_NOTIFY_FAIL = 'GET_NOTIFY_FAIL'
    
    export const READ_NOTIFY_REQUEST = 'READ_NOTIFY_REQUEST'
    export const READ_NOTIFY_SUCCESS = 'READ_NOTIFY_SUCCESS'
    export const READ_NOTIFY_FAIL = 'READ_NOTIFY_FAIL'
    
    export const DELETE_ALL_NOTIFY_REQUEST = 'DELETE_ALL_NOTIFY_REQUEST'
    export const DELETE_ALL_NOTIFY_SUCCESS = 'DELETE_ALL_NOTIFY_SUCCESS'
    export const DELETE_ALL_NOTIFY_FAIL = 'DELETE_ALL_NOTIFY_FAIL'

     

    notifyAction

    export const getNotify = ()=> async(dispatch, getState)=>{
    
        const {userLogin:{userInfo}} = getState()
    
        dispatch({
            type:GET_NOTIFY_REQUEST
        })
    
        try{
            const res = await axios.get('/api/notify', {
                headers:{authorization:`Bearer ${userInfo.token}`}
            })
    
            dispatch({
                type:GET_NOTIFY_SUCCESS,
                payload:res.data.notifies
            })
    
        }catch(error){
            dispatch({
                type:GET_NOTIFY_FAIL,
                payload:                
                error.response && error.response.data.message
                ? error.response.data.message
                : error.message
            })
        }
    }
    
    
    export const readNotify = ({msg})=> async(dispatch, getState)=>{
    
        const {userLogin:{userInfo}} = getState()
    
        dispatch({
            type:READ_NOTIFY_REQUEST
        })
    
        try{
            const res = await axios.patch(`/api/notify/isReadNotify/${msg._id}`,null, {
                headers:{authorization:`Bearer ${userInfo.token}`}
            })
    
            dispatch({
                type:READ_NOTIFY_SUCCESS,
                payload:res.data.updatedNotify
            })
    
        }catch(error){
            dispatch({
                type:READ_NOTIFY_FAIL,
                payload:                
                error.response && error.response.data.message
                ? error.response.data.message
                : error.message
            })
        }}
    
        export const deleteAllNotifies = ()=> async(dispatch, getState)=>{
            const {userLogin:{userInfo}} = getState()
    
            dispatch({
                type:DELETE_ALL_NOTIFY_REQUEST
            })
    
            try{
                await axios.delete('/api/notify/', {
                    headers:{authorization:`Bearer ${userInfo.token}`}
                })
                dispatch({
                    type:DELETE_ALL_NOTIFY_SUCCESS,
                    payload:[]
                })
            }catch(error){
                dispatch({
                    type:DELETE_ALL_NOTIFY_FAIL,
                    payload:                
                    error.response && error.response.data.message
                    ? error.response.data.message
                    : error.message
                })
            }}

     

     

     

    notifyReducer

    import { CREATE_NOTIFY_FAIL, CREATE_NOTIFY_REQUEST, CREATE_NOTIFY_SUCCESS, DELETE_ALL_NOTIFY_FAIL, DELETE_ALL_NOTIFY_REQUEST, DELETE_ALL_NOTIFY_SUCCESS, GET_NOTIFY_FAIL, GET_NOTIFY_REQUEST, GET_NOTIFY_SUCCESS, READ_NOTIFY_FAIL, READ_NOTIFY_REQUEST, READ_NOTIFY_SUCCESS, REMOVE_NOTIFY_FAIL, REMOVE_NOTIFY_REQUEST, REMOVE_NOTIFY_SUCCESS } from "../_constants/notifyConstants";
    
    export const notifyReducer = (state={ notify:[]}, action)=>{
        switch(action.type){
            case GET_NOTIFY_REQUEST:
                return {...state,loading:true}
            case GET_NOTIFY_SUCCESS:
                return {...state, loading:false, notify:action.payload}
            case GET_NOTIFY_FAIL:
                return {...state, loading:false, error:action.payload}
    
            case CREATE_NOTIFY_REQUEST:
                return {...state, loading:true}
            case CREATE_NOTIFY_SUCCESS:
                return {...state, loading:false, notify:[action.payload,...state.notify ]}
            case CREATE_NOTIFY_FAIL:
                return {...state, loading:false, error:action.payload}
            
            case REMOVE_NOTIFY_REQUEST:
                return {...state, loading:true}
            case REMOVE_NOTIFY_SUCCESS:
                return {...state, loading:false, notify: state.notify.filter(item=>item.id!==action.payload.id || item.url !== action.payload.url)}
            case REMOVE_NOTIFY_FAIL:
                return {...state, loading:false, error:action.payload}
    
            case READ_NOTIFY_REQUEST:
                return {...state, loading:true}
            case READ_NOTIFY_SUCCESS:
                return {...state, loading:false, notify: state.notify.map(item=>item._id===action.payload._id? action.payload:item)}
            case READ_NOTIFY_FAIL:
                return {...state, loading:false, error:action.payload}
    
            case DELETE_ALL_NOTIFY_REQUEST:
                return {...state, loading:true}
            case DELETE_ALL_NOTIFY_SUCCESS:
                return {...state, loading:false,notify:action.payload }
            case DELETE_ALL_NOTIFY_FAIL:
                return {...state, loading:false, error:action.payload}
                
            default :
                return state;
    
        }
    }

     

     

    이제 notify를 보여주자. 

    notifyModal.js

     

    import  Avatar from '../component/Avatar'
    import moment from 'moment'
    import React from 'react'
    import { useDispatch, useSelector } from 'react-redux'
    import { Link } from 'react-router-dom'
    import NoNotice from '../images/no-alarm.png'
    import { BASE_URL } from '../utils'
    import { deleteAllNotifies, readNotify } from '../_actions/NotifyActions'
    
    function NotifyModal() {
    
        const userLogin = useSelector(state => state.userLogin)
        const {userInfo} = userLogin
    
        const notify = useSelector(state => state.notify)
    
        const dispatch = useDispatch()
    
        const handleIsRead =(msg)=>{
            dispatch(readNotify({msg}))
        }
    
        const handleDeleteAll=() =>{
            const newArr = notify.notify.filter(item=> item.isRead===false)
    
            if(newArr.length===0) return dispatch(deleteAllNotifies())
    
            if(window.confirm(`You have ${newArr.length} unread notices. Are you sure to delete?`))
                 return dispatch(deleteAllNotifies())
            
        }
    
    
        return (
            <div style={{minWidth:'300px'}}>
                <div className="d-flex justify-content-between align-items-center px-3">
                    <h3>Notification</h3>
                    {
                        notify.sound
                        ? <i className="fas fa-bell text-danger"
                        style={{fontSize: '1.2rem', cursor:'pointer'}}
                        ></i>
                        : <i className="fas fa-bell-slash text-danger"
                        style={{fontSize: '1.2rem', cursor:'pointer'}}
                        ></i>
                    }
                </div>
                <hr className="mt-0" />
                {
                    notify.notify.length ===0 &&
                    <img src={NoNotice} alt="NoNotice" className="w-50"/>
                }
    
                <div style={{maxHeight:'calc(100vh-200px)', overflow:'auto'}}>
                    {
                        notify.notify && notify.notify.map((msg, index)=>(
                            <div className="px-2 mb-3" key={index}>
                                <Link to={`${msg.url}`} className="d-flex text-dark align-items-center" onClick={()=>handleIsRead(msg)}>
                                    <Avatar src={msg.user.avatar} size="big-avatar"/>
                                    <div className="mx-1 flex-fill">
                                        <div>
                                            <strong className="mr-1">{msg.user.username}</strong>
                                            <span>{msg.text}</span>
                                        </div>
                                        {msg.content && <small>{msg.content.slice(0,20)}...</small>}
                                    </div>
    
                                    {
                                        msg.image && 
                                        <div style={{width:'30px'}}>
                                            {
                                                msg.image.match(/video/i)
                                                ? <video src={msg.image} width="100%"/>
                                                : <Avatar src={msg.image} size="medium-avatar"></Avatar>
                                            }
                                        </div>
                                    }
                                </Link>
                                <small className="text-muted d-flex justify-content-between px-2">
                                    {moment(msg.createdAt).fromNow()}
                                    {
                                        !msg.isRead && <i className="fas fa-circle text-primary"></i>
                                    }
                                </small>
    
                            </div>
                        ))
                    }
                </div>
    
                <hr className="my-1" />
    
                <div className="text-right text-danger mr-2" style={{cursor:'pointer'}} onClick={handleDeleteAll}>
                    Delete All
                </div>
                
            </div>
        )
    }
    
    export default NotifyModal

     

     

    팔로우하는 사람중에 누군가 포스트를 올리면, 

    다음과 같이 하트가 변하고, 

     

     

    메세지 내용과 notice를 볼 수있다. 

     

    알림온 것을 누르면 다음과 같이 상세페이지로 (url) 이동하고, 

    is read 표시(파란색)이 사라진 것을 볼 수 있다. 

     

     

    알람모두 지우기를 누르면, 

    읽지 않은 알람이 존재할때 다음과 같이 경고창이 뜬다. 

     

     

     

     

    추가로 privateRouter를 만들어서 

    App.js를 수정해주자. 

     

    privateROuter

    import {Route, Redirect} from 'react-router-dom'
    
    const PrivateRouter = (props)=>{
        const userInfo = localStorage.getItem('userInfo')
        return userInfo? <Route {...props} /> : <Redirect to="/"/>
    
    }
    
    export default PrivateRouter

     

    App.js

    import  {BrowserRouter ,Route} from 'react-router-dom'
    import React, { useEffect, useState } from 'react';
    import LoginPage from './pages/LoginPage';
    import HomePage from './pages/HomePage';
    import RegisterPage from './pages/RegisterPage';
    import { useDispatch, useSelector } from 'react-redux';
    import Header from './component/Haader'
    import Notify from './pages/Notify';
    import Discover from './pages/Discover';
    import Message from './pages/Message';
    import Profile from './pages/Profile';
    import StatusModal from './component/StatusModal';
    import Post from './pages/Post';
    import SocketClient from './SocketClient';
    import { getSuggestions } from './_actions/suggestionActions';
    import { io } from 'socket.io-client';
    import { SOCKET } from './_constants/globalConstants';
    import PrivateRouter from './customRouter/PrivateRouter';
    
    
    function App() {
      
      const userLogin = useSelector(state => state.userLogin)
      const {userInfo} = userLogin
      const status = useSelector(state => state.status)
    
    
      return (
        <BrowserRouter>
          <input type="checkbox" id="theme"/>
          <div className="App">
            <div className="main">
              {userInfo && <Header/>}
              {status && <StatusModal/>}
              {userInfo && <SocketClient/>}
              <PrivateRouter exact path="/post/:id" component={Post}/>
              <PrivateRouter exact path="/profile/:id" component={Profile}/>
              <PrivateRouter exact path="/message" component={Message}/>
              <PrivateRouter exact path="/discover" component={Discover}/>
              <PrivateRouter exact path="/notify" component={Notify}/>
              <Route exact path="/register" component={RegisterPage}/>
              <Route exact path="/login" component={LoginPage}/>
              <Route exact path="/home" component={userInfo? HomePage: LoginPage}/>
              <Route exact path="/" component={LoginPage}/>
            </div>
          </div>
        </BrowserRouter>
      );
    }
    
    export default App;

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

Designed by Tistory.