[유튜브 클론코딩] 7.3 Adding Creator to Video / 7.4 Protecting Video Routes

2021. 3. 15. 20:21Projects/유튜브 클론코딩

728x90
반응형

7.3  Adding Creator to Video

이제 비디오를 업로드한 사람을 추가해볼 것이다.

일단 그 전에 업로드한 비디오는 슬프지만 다 지워본다.

console > mongo - help - use mytube - db.videos.remove({})

 

Video.js에서 creator 항목을 추가해주기

import mongoose from "mongoose";

const VideoSchema = new mongoose.Schema({

    fileUrl: {

        type: String,

        required: 'File URL is required'

    },

    title : {

        type: String,

        required : "Title is required"

    },

    description : String,

    views:{

        type: Number,

        default: 0

    },

    createdAt : {

        type: Date,

        default: Date.now

    },

    comments: [{

        type: mongoose.Schema.Types.ObjectId,

        ref: "Comment"

    }],

    creator: {

        type: mongoose.Schema.Types.ObjectId,

        ref: "User"

    }

});

const model = mongoose.model("Video", VideoSchema);

export default model;

↑ Video.js

 

예전에 Comment와 Video 사이의 관계를 저렇게 표현했던 거 기억해?

그것처럼 creator(비디오 작성자)도 저렇게 하는 것이다!!

Comment.js에서 creator 항목을 추가해주기

똑같이 Comment.js에도 creator 넣어준다.

덧글도 creator를 갖고 있기 때문이다.

 

User.js에서 creator 항목을 추가해주기

그리고 User는 자기가 올린 비디오도 갖고, 자기가 쓴 덧글(Comment)도 가진다

그러니까 User에도 그 부분을 추가해준다.

import mongoose from "mongoose";

import passportLocalMongoose from "passport-local-mongoose";

const UserSchema = new mongoose.Schema({

    name : String,

    email : String,

    avatarUrl : String,

    kakaoId : Number,

    githubId: Number,

    videos: [

        {

        type: mongoose.Schema.Types.ObjectId,

        ref: "Video"

        }

    ],

    comment : [

        {

        type: mongoose.Schema.Types.ObjectId,

        ref: "Comment"

        }

    ]

});

UserSchema.plugin(passportLocalMongoose, { usernameField: 'email' });

const model = mongoose.model("User", UserSchema);

export default model;

↑ User.js

video와 comment는 여러 개 이니까 videos, comments로 해주고 배열로 해주는 것 잊지말기

 

 

videoController로 가서 postUpload 함수 수정하기

우리에게 이제 필요한건 비디오 크리에이터가 비디오를 만들 때 크리에이터의 아이디를 저장할 필요가 있다.

그리고 우리는 video id를 videos array에 넣을 필요가 있지(배열의 push 메소드 이용)

 

export const postUpload = async(req, res) => {

    const {

        body: {title,description},

        file: {path}

    } = req;

const newVideo = await Video.create({

        fileUrl: path,

        title,

        description,

        creator: req.user.id

    });

    req.user.videos.push(newVideo.id);

    req.user.save();

    res.redirect(routes.videoDetail(newVideo.id));

};

↑ videoController.js

req.user 라는 것은 현재 로그인한 사용자가 담겨있다고 했으니까

비디오의 creator는 req.user.id랑 같게 해주고

그리고 req.user.videos에다가 새 비디오의 id를 push 해주고 저장한다. (비디오 전체가 필요한 것은 아니고, 비디오의 id만 필요하다.)

 

이제 업로드하고 어떻게 되는지 보자

비디오를 업로드하고 데이터베이스에서 db.videos.find({}) 해보면 creator의 id가 나오는 것을 볼 수 있다.

유저에도 videos 배열 안에 video의 id가 담긴 것을 확인할 수 있다.

 

videoDetail.pug 수정

우선 videoDetail.pug에 가서 비디오를 작성한 사람을 보여주는 부분을 추가해보자.

.video__author

            |Uploaded by

            a(href=routes.userDetail(1))=video.creator.name

↑ videoDetail.pug

지금은 이거 이름이 안 뜰 것이다. 왜냐면 creator의 id만 받아왔기 때문에 name이 안뜰 것 이므로

 

populate 메소드 이용하기

단지 id만을 얻고 싶은게 아니라면 어떻게 해야 할까?

예를 들면 creator의 name이라던지, avatarUrl,,,

생각보다 간단하다. populate("데려오고 싶은 속성 이름") 메소드를 적어주면 된다.

 

export const videoDetail = async(req, res) => {

    const { params: {id}} = req;

    try{

        const video = await Video.findById(id).populate("creator");

console.log(video);

        res.render("videoDetail", {pageTitle: video.title, video});

    }catch(error){

        console.log(error);

        res.redirect(routes.home);

    }

};

↑ videoController.js

populate는 오직 objectID타입에만 쓸 수 있다. 아까 Video.js, User.js, Comment.js에서 서로 모델 간의 관계를 표현할 때 type: mongoose.Schema.Types.ObjectId 라는 것을 썼었지 않는가? 이게 objectID인 것이다.(video던 user던 comment던 새로운 녀석이 생길때마다 자동으로 생성해주는 그 본질적인 id)

video의 안을 들여다보기 위해 console로 찍어보는 코드를 추가하고 실행해보면,

{

  views: 0,

  comments: [],

  _id: 604f198f3f1ac85424a3ded9,

  fileUrl: 'uploads\\videos\\e31722f0c4b928743b99ec5ea9a7425e',

  title: '귀멸의 칼날 명장면 TOP 10',

  description: '존잼',

  creator: {

    videos: [ 604f198f3f1ac85424a3ded9 ],

    comment: [],

    _id: 604f1356d40bd004dc412c89,

    name: '냥냥이',

    email: 'zz12345@naver.com',

    __v: 1,

    avatarUrl: 'uploads\\avatars\\70bf94401d065c1b7f38475acda93c30'

  },

  createdAt: 2021-03-15T08:23:43.391Z,

  __v: 0

}

이제 비디오의 creator는 더 이상 objectID만 존재하는 것이 아니라, 전체 객체가 되었다.

그리고 이제 Uploaded by 냥냥이 이라고 이름이 잘 뜬다!

 

.video__author

            |Uploaded by 

            a(href=routes.userDetail(video.creator.id))=video.creator.name

↑ videoDetail.pug

이렇게 되면 videoDetail.pug에서도 video.creator.id를 주어서 해당 유저의 프로필 화면으로 이동하게 수정도 가능하다.

 

edit Video를 비디오를 업로드한 사람만 편집할 수 있도록 보호하기

extends layouts/main

block content 

    .video-detail__container

        .video__player

            video(src=`/${video.fileUrl}`, controls=true, autoplay=true)

        .video__info

if loggedUser

            if video.creator.id === loggedUser.id

                a(href=routes.editVideo(video.id))

                    button Edit video

            h5.video__title=video.title

            p.video__description=video.description

            if video.views === 1

                span.video__views 1 view

            else 

                span.video__views #{video.views} views

        .video__author

            |Uploaded by 

            a(href=routes.userDetail(video.creator.id))=video.creator.name

        .video__comments

            if video.comments.length === 1

                span.video__comment-number 1 comment

            else

                span.video__comment-number #{video.comments.length} comments 

↑ videoDetail.pug

if loggedUser 를 안적어주면 로그아웃 상태에서 비디오를 들어갔을 때 에러가 난다.

로그인한 사람이 아니더라도 비디오를 보는 것은 가능해야 하니까, 로그인했을 경우-> 비디오를 업로드한 사람의 id와 로그인한 사람의 id가 같아야만 비디오 편집 버튼이 보일 수 있도록 위의 코드처럼 if loggedUser도 추가해준다.

* 그런데 실험 결과, URL을 입력해서 localhost:4000/videos/비디오id/edit 으로 들어가면 수정이 된다.

 

 

7.4 Protecting Video Routes

비디오 편집 버튼이 가려진다고 해도 비디오 편집 주소로 들어가면 편집이 된다. 이건 보호할 필요가 있다.

 

getEditVideo를 수정하기

export const getEditVideo = async(req, res) => {

    const {params: {id}} = req;

    try{

        const video = await Video.findById(id);

        if(video.creator != req.user.id){

            throw Error();

        }else{

            res.render("editVideo", {pageTitle: `Edit ${video.title}`, video});

        }

    }catch(error){

        res.redirect(routes.home);

    }

}

↑ videoController.js

여기선 populate로 creator의 객체를 전체를 가져올 필요는 없다. 그냥 video.creator는 objectID이고 req.user.id도 objectID이니까 둘만 비교해서, 둘이 같지 않다면 error를 던진다. 그러면 자동으로 catch부분에서 홈으로 redirect를 해줄 것이다.

 

★ 5번째 줄 수정 ★  if(video.creator !== req.user.id) → if(video.creator != req.user.id)

 video.creator와 req.user.id는 눈으로 보기에는 같지만 video.creator는 object이고 req.user.id는 string이다. 그러므로 !== 으로 비교하면 타입까지 비교하기때문에 자꾸 오류가 난다. 실제로 console.log(typeof video.creator)이랑 console.log(typeof req.user.id)는 각각 object, string으로 나오는 것을 알 수가 있다.)

deleteVideo도 마찬가지로 수정해주기

export const deleteVideo = async(req, res) => {

    const { params: {id}} = req;

    try{

        const video = await Video.findById({id});

        if(video.creator !== req.user.id){

            throw Error();

        }else{

            await Video.findOneAndRemove({_id:id});

        }

    }catch(error){

        console.log(error);

    }

    res.redirect(routes.home); //비디오 삭제가 성공하던 실패하던 home으로 간다.

}

이렇게 되면 /videos/비디오id/delete로 접속해도 비디오 삭제는 되지 않고 home으로 튕긴다.

 

프로필에서 업로드한 비디오 볼 수 있게 하기

  • 내 프로필에서 내가 업로드한 비디오를 보게 하거나
  • 다른 사람 프로필을 눌렀을 때 그 사람이 업로드한 비디오를 볼 수 있게 하고 싶다.

 

🎈userDetail 함수 수정하기

export const userDetail = async (req,res) => {

    const {params: {id}} = req;

    try{

        const user = await User.findById(id).populate("videos");

        console.log(user);

        res.render("userDetail", {pageTitle: "User Detail", user});

    }catch(error){

        res.redirect(routes.home);

    }

}

↑ userController.js

userDetail에서 우선 video를 얻어와야 할 것이다. 마찬가지로 populate를 해주면 된다.

 

🎈userDetail.pug 수정하기

extends layouts/main

include mixins/videoBlock

block content 

    .user-profile

        .user-profile__header

            if !user.avatarUrl

                img.u-avatar-default(src="")

            else

                img.u-avatar(src=user.avatarUrl)

            h4.profile__username=user.name

            h4.profile__useremail=user.email

if loggedUser

        if user.id === loggedUser.id

            .user-profile__btns

                a(href=`/users${routes.editProfile}`)

                    button ✏️ Edit Profile

                a(href=`/users${routes.changePassword}`)

                    button 🔒 Change Password

        .uploaded-videos 

            each video in user.videos 

                +videoBlock({

                    id: video.id,

                    title: video.title,

                    views: video.views,

                    videoFile: video.fileUrl

                })

이 부분을 추가해준다.

이 때 중요한 것은 user.videos인 것을 잊지 말도록 한다.

여기서 user는 userDetail 함수에서 찾은 user이고 populate를 통해서 videos의 객체를 모두 가져온 것이다.

나머지는 mixins의 videoBlock과 거의 유사하다.

그리고 로그인 안한 사람도 다른 사람의 프로필을 볼 수 있도록 아까 그 videoDetail처럼 if loggedUser 부분 추가해준다.

728x90
반응형