NODE.JS

Youtube 만들기 - 8) 구독기능 만들기

dodop 2021. 5. 14. 19:15

이번에는 동영상의 구독기능을 만들 것이다. 

 

순서

model은 구독 받는 사람과 구독하는 사람으로 나누어 나타낸다. 

구독하고 있으면 subscribed (회색)

구독 하지 않고 있으면 subscribe(빨간색)

버튼으로 나오도록 지정할 것이다. 

 

 

먼저 server>models>Subscribers.js파일을 만들어 준다. 

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const subscriberSchema = mongoose.Schema({
    userTo: {
        type:Schema.Types.ObjectId,
        ref:'User'
    },
    userFrom:{
        type:Schema.Types.ObjectId,
        ref:'User'
    }

}, {timestamps:true})//업데이트 시간 표시

const Subscriber = mongoose.model('Subscriber', subscriberSchema)
module.exports = {Subscriber }

 

 

이번엔 subscribe 버튼을 만들자. 

videodetail페이지의 actions에 들어가야 하는데, 

여기에 넣으면 디테일페이지의 양이 너무 길어지니까 subscribe기능을 구현하는 새로운 페이지를 이용해보자. 

views>videoDetailPage>Sections>Subscribe.js파일을 새로 하나 만들어 준다. 

 

 

우선 videoDetailPage안에 다음과 같이 Subscribe.js파일을 넣어준다. 

import Subscribe from './Sections/Subscribe';
<List.Item
                        actions ={[<Subscribe/>]}
                        <List.Item.Meta
                            avatar = {<Avatar src={VideoDetail.writer.image}/>}
                            title = {VideoDetail.writer.name}
                            description = {VideoDetail.description}
                        />


                    </List.Item>

 

Subscribe.js

import Axios from 'axios'
import { response } from 'express'
import React, {useEffect, useState} from 'react'

function Subscribe(props) {

   
    return (

        <div>
            <button
                style={{backgroundColor:#CC0000, borderRadius:'4px', color:'white', padding:'10px 16px', fontWeight:'500', fontSize:'1rem', textTransform:'uppercase'}}   
                onClick={onSubscritbe}
            >
            0 Subscribe
            </button>
        </div>
    )
}

export default Subscribe

 

 

db에서 구독자 수 정보를 가져와서 화면에 나타내도록 해보자. 

 

function Subscribe(props) {

    const [SubscribeNumber, setSubscribeNumber] = useState(0)
    const [Subscribed, setSubscribed] = useState(false)
    useEffect(() => {
        
        let variable = {userTo: props.userTo}
        Axios.post('/api/subscribe/subscribeNumber', variable)
        .then(response=>{
            if(response.data.success){
                setSubscribeNumber(response.data.SubscribeNumber)
            }else{
                alert('구독자 수 정보를 가져오는데 실패했습니다.')
            }
        })
       

    }, [])

 

 

여기서 props.userTo는 비디오 디테일 페이지에서 subscribe에서 보내줌으로서 설정할 수 있다. 

actions ={[<Subscribe userTo = {VideoDetail.writer._id} userFrom= {localStorage.getItem('userId')}/>]}//여기서 userTO 보내주면 Subscribe에서 prop으로 받아서 사용가능하다. 

 

axios를 작성했으니 router를 구현해줘야 한다. 

 

server의 index.js에 다음과 같은 구문을 추가해주고,

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

 

routes>subscribe.js파일을 새로 생성해서 작성해준다. 

const express = require('express');
const router = express.Router();
const {Subscriber } = require("../models/Subscriber");
const { route } = require('./users');


//=================================
//             Subscribe
//=================================

route.post("/subscribeNumber", (req, res)=>{
    Subscriber.find({'userTo': req.body.userTo})
    .exec((err, subscribe)=>{
        if(err){
            return res.status(400).send(err);
        }
        return res.status(200).json({success: true, subscribeNumger :subscribe.length})//몇명이 구독하고 있는지 길이를 센다.
    }) 

});



module.exports = router;

 

 

이번엔 사용자가 구독했는지 구독여부 정보를 가져오자. 

userFrom은 로그인할때 유저의 아이디만 localStorage에 저장해두므로 그것을 가져와서 조회한다. 

import Axios from 'axios'
import { response } from 'express'
import React, {useEffect, useState} from 'react'

function Subscribe(props) {

    const [SubscribeNumber, setSubscribeNumber] = useState(0)
    const [Subscribed, setSubscribed] = useState(false)
    useEffect(() => {

        let subscribedVariable = {userTo: props.userTo, userFrom:localStorage.getItem('userId')}

        Axios.post('/api/subscribe/subscribed',subscribedVariable)
        .then(response=>{
            if(response.data.success){
                setSubscribed(response.data.subscribed)
            }else{
                alert('정보를 가져오지 못했습니다.')
            }
        })

    }, [])

route에 가서 다시 구독여부 정보를 가져오는 라우터를 작성해준다. 

subscriber.find의 결과가 1만 되어도 이미 구독하고 있다는 뜻이 된다. 

route.post("/subscribed", (req, res)=>{
    Subscriber.find({'userTo': req.body.userTo, 'userFrom': req.body.userFrom})
    .exec((err, subscribe)=>{
        if(err){
            return res.status(400).send(err);
        }
        let result = false
        if(subscribe.length!==0){
            result = true
        }
        return res.status(200).json({success: true, subscribed : result})
    }) 

});

 

 

구독 여부에 따라서 나타나는 버튼의 모양도 바뀌도록 설정해준다. (Subscribe.js)

    return (

        <div>
            <button
                style={{backgroundColor:`${Subscribed? '#AAAAAA' : '#CC0000'}`, borderRadius:'4px', color:'white', padding:'10px 16px', fontWeight:'500', fontSize:'1rem', textTransform:'uppercase'}}   
                onClick={onSubscritbe}
            >
                {SubscribeNumber} {Subscribed ? 'Subscribed' : 'Subscribe'}
            </button>
        </div>
    )
}

 

 

완성된 화면

 

이번에는 버튼을 클릭하므로서 생기는 구독기능과 구독취소기능을 만들 것이다. 

 

순서

우선 Subscribe.js에 onSubscribe 함수를 만들어 준다. 

userTo와 userFrom은 비디오 디테일 페이지에서 props로 보내준 것을 가지고 온 것이다. 

actions ={[<Subscribe userTo = {VideoDetail.writer._id} userFrom= {localStorage.getItem('userId')}/>]}//여기서 userTO 보내주면 Subscribe에서 prop으로 받아서 사용가능하다. 
const onSubscribe = ()=>{

        let subscribedVariable = {
            userTo: props.userTo,
            userFrom:props.userFrom
        }
        // 이미 구독중이라면
        if(Subscribed){
                Axios.post('/api/subscribe/unssubcribe', subscribedVariable)
                .then(response =>{
                    if(response.data.success){
                        setSubscribeNumber(SubscribeNumber-1)
                        setSubscribed(!Subscribed)

                    }else{
                        alert('구독을 취소하는데에 실패했습니다.')
                    }

                })
            
        }

        // 아직 구독중이 아니라면
        else{
            Axios.post('/api/subscribe/subscribe', subscribedVariable)
            .then(response =>{
                if(response.data.success){
                    setSubscribeNumber(SubscribeNumber+1)
                    setSubscribed(!Subscribed)

                }else{
                    alert('구독하는데에 실패했습니다.')
                }

            })

        }
    }

 

 

axios를 작성해 주었으니 router도 작성해준다. 

//구독취소 기능
route.post("/unsubscribe", (req, res)=>{ 
    Subscriber.findOneAndDelete({'userTo': req.body.userTo, 'userFrom': req.body.userFrom})
    .exec((err, doc)=>{
        if(err){
            return res.status(400).json({success:false, err});
        }

        return res.status(200).json({success: true, doc})
    }) 

});

//구독기능
route.post("/subscribe", (req, res)=>{

		//새로운 구독자를 생성(userTo, userFrom 저장)
    const subscribe = new Subscriber(req.body)
    	//db에 저장
    subscribe.save((err, doc)=>{
        if(err){
            return res.status(400).json({success:false, err});
        }
        return res.status(200).json({success: true})


    })


});

 

 

추가로 만약 동영상 올린사람이 지금 로그인 된 사람과 같은 사람이라면 구독버튼이 나타나면 안되므로, 

비디오 디테일 페이지를 다음과 같이 수정해준다. 

import React, {useEffect, useState}from 'react'
import {Row, Col, List, Avatar} from 'antd';
import Axios from 'axios';
import {response} from 'express';
import SideVideo from './Sections/SideVideo';
import Subscribe from './Sections/Subscribe';

function VideoDetailPage(props) {

    const videoId =props.match.params.videoId//app.js에서 :videoId 보냈기 때문에 가져올 수 있다. 
    const variable = {videoId: videoId}

    const [VideoDetail, setVideoDetail] = useState([])
    useEffect(() => {
        Axios.post('/api/video/getVideoDetail', variable)
        .then(response =>{
            if(response.data.success){
                setVideoDetail(response.data.videoDetail)


            }else{
                alert('비디오 정보를 가져오길 실패했습니다.')
            }
        })
    }, [])

    if(VideoDetail.writer){

		//영상주인과 구독하려는 사람이 다를때만 subscribeButton 설정 
        const subscribeButton = VideoDetail.writer._id !==localStorage.getItem('userId') &&<Subscribe userTo = {VideoDetail.writer._id} userFrom= {localStorage.getItem('userId')}/>
    return (
        <Row gutter={[16, 16]}>
            <Col lg = {18} xs = {24}>

                <div tyle = {{ width:'100%', padding = '3rem 4rem' }}>
                    <video style = {{width:'100%' }}src={`http:/localhost:5000/${VideoDetail.filePath}`} controls />
                    <List.Item
                        actions ={[subscribeButton]}//여기서 userTO 보내주면 Subscribe에서 prop으로 받아서 사용가능하다. 
                    >
                        <List.Item.Meta
                            avatar = {<Avatar src={VideoDetail.writer.image}/>}
                            title = {VideoDetail.writer.name}
                            description = {VideoDetail.description}
                        />


                    </List.Item>

                    {/* Comments */}

                </div>
            </Col>
            <Col lg = {6} xs={24}>
                <SideVideo />
            </Col>

        </Row>
    )
    }
    else {
        return (
            <div>...loading</div>
        )
    }
}

export default VideoDetailPage

 

 

완성된 화면을 보면, 

 

구독하지 않은 상태

 

구독한 상태

 

구독을 다시 시작한 상태