File Upload(image, video)

김종민·2022년 11월 1일
0

Youtube

목록 보기
13/23

들어가기
express에서 file upload를 알아본다.
일단은 server에 먼저 올려보고
나중에 aws S3에 저장하고 path를 받아보는걸로 한다.


1.server.js

import 'dotenv/config'
import './db'
import Video from './models/Video'
import User from './models/User'
import express from 'express'
import session from 'express-session'
import MongoStore from 'connect-mongo'
import morgan from 'morgan'
import rootRouter from './routes/rootRouter'
import userRouter from './routes/userRouter'
import videoRouter from './routes/videoRouter'
import { localsMiddleware } from './middlewares'

const PORT = 4000
const app = express()
const logger = morgan('dev')

app.set('view engine', 'pug')
app.set('views', process.cwd() + '/src/views')
app.use(logger)
app.use(express.urlencoded({ extended: true }))

app.use(
  session({
    secret: process.env.COOKIE_SECRET,
    resave: false, //true하면 req요청때마다, cookie만듬, false는 login떄만 만듬
    saveUninitialized: false,
    cookie: {
      maxAge: 200000000000,
    },
    store: MongoStore.create({ mongoUrl: process.env.DB_URL }),
  })
)

// app.use((req, res, next) => {
//   res.locals.sexy = 'you' //pug파일에서 sexy로 접근가능
//   res.locals.siteName = 'jmTube' //pug파일에서 siteName으로 접근가능
//   console.log(res)
//   res.sessionStore.all((error, sessions) => {
//     console.log(sessions)
//     next()
//   })
// })

app.use(localsMiddleware)
app.use('/uploads', express.static('uploads'))
///uploads path에 접속하면, uploads폴더에 접근이 가능하게
///해 주는 것이 static이다. 매우 중요!!!

app.use('/', rootRouter)
app.use('/videos', videoRouter)
app.use('/users', userRouter)

app.listen(PORT, () => console.log('Server start in 4000'))

2. middleware.js

express에서 file Upload를 하기 위해서는
multer를 이용한다.

npm i multer

import multer from 'multer'
///multer를 import한다.

export const localsMiddleware = (req, res, next) => {
  res.locals.loggedIn = Boolean(req.session.loggedIn)
  res.locals.loggedInUser = req.session.user || {}
  res.locals.siteName = 'jmTube'
  next()
}

export const protectorMiddleware = (req, res, next) => {
  if (req.session.loggedIn) {
    return next()
  } else {
    return res.redirect('/login')
  }
}

export const publicOnlyMiddleware = (req, res, next) => {
  if (!req.session.loggedIn) {
    return next()
  } else {
    return res.redirect('/')
  }
}

export const avatarUpload = multer({
  dest: 'uploads/avatars/',
  limits: {
    fileSize: 3000000,
  },
})
///imageFile Upload middleware
///dest는 file이 upload되는 폴더위치,
///limits는 파일크기 제한, 3000000는 3M임.

export const videoUpload = multer({
  dest: 'uploads/videos/',
  limits: {
    fileSize: 25000000,
  },
})
///imageFile Upload middleware
///dest는 file이 upload되는 폴더위치,
///limits는 파일크기 제한, 3000000는 25M임.

3. routes/userRouter.js

import express from 'express'
import {
  finishGithubLogin,
  getChangePassword,
  getUserEdit,
  logout,
  postChangePassword,
  postUserEdit,
  seeUser,
  startGithubLogin,
} from '../controllers/userController'
import {
  avatarUpload,
  protectorMiddleware,
  publicOnlyMiddleware,
} from '../middlewares'

const userRouter = express.Router()

userRouter.get('/:id([0-9a-f]{24})', seeUser)

userRouter
  .route('/edit')
  .get(getUserEdit)
  .all(protectorMiddleware)
  .post(avatarUpload.single('avatar'), postUserEdit)
///postUser Controller를 호출하기 전에 먼저,
///avatarUpload middleware를 먼저 호출한다.
///single은 파일을 하나만 upload한다는 뜻이고,
///('avatar')는 pug파일의 input(type=file)의 name임.
///pug파일의 input name과 일치시켜 주어야 함.

userRouter.get('/logout', logout)
userRouter.get('/github/start', publicOnlyMiddleware, startGithubLogin)
userRouter.get('/github/finish', publicOnlyMiddleware, finishGithubLogin)
userRouter
  .route('/change-password')
  .all(protectorMiddleware)
  .get(getChangePassword)
  .post(postChangePassword)

export default userRouter

4.routes/videoRouter.js

import express from 'express'
import {
  deleteVideo,
  getEdit,
  getUpload,
  postEdit,
  postUpload,
  removeVideo,
  seeVideo,
} from '../controllers/videoController'
import { protectorMiddleware, videoUpload } from '../middlewares'

const videoRouter = express.Router()

videoRouter
  .route('/upload')
  .all(protectorMiddleware)
  .get(getUpload)
  .post(videoUpload.single('video'), postUpload)
/// 'upload' path로 post method에서 postUpload controller
///이전에 videoUpload middleware를 먼저 호출한다.
///single은 파일을 하나만 업로드 한다는 뜻이고, 
///('video')는 pug 파일의 input(file)의 name과 일치시켜주어야함.
  
videoRouter.route('/:id([0-9a-f]{24})').get(seeVideo)
//videoRouter.get('/:id(\\d+)', seeVideo)
videoRouter
  .route('/:id([0-9a-f]{24})/edit')
  .all(protectorMiddleware)
  .get(getEdit)
  .post(postEdit)
// videoRouter.get('/:id(\\d+)/edit', getEdit)
// videoRouter.post('/:id(\\d+)/edit', postEdit)
videoRouter
  .route('/:id([0-9a-f]{24})/delete')
  .all(protectorMiddleware)
  .get(deleteVideo)

export default videoRouter

5. userController.js

export const getUserEdit = (req, res) => {
  res.render('edit-profile', { pageTitle: 'Edit Profile' })
}


export const postUserEdit = async (req, res) => {
  const {
    session: {
      user: { _id, avatarUrl, email: sessionEmail, username: sessionUsername },
    },
    body: { name, email, username, location },
    file,
  } = req
  console.log(file)
///'edit-proflie.pug'파일에서 file을 choice해서 submit을 하면
///req.file로 submit한 file을 받을 수 있음.

  let searchParam = []
  if (sessionEmail !== email) {
    searchParam.push({ email })
  }
  if (sessionUsername !== username) {
    searchParam.push({ username })
  }
  if (searchParam.length > 0) {
    const foundUser = await User.findOne({ $or: searchParam })
    if (foundUser && String(foundUser._id) !== String(_id)) {
      return res.status(400).render('edit-profile', {
        pageTitle: 'Edit Profile',
        errorMessage: 'This username/email is already taken',
      })
    }
  }
  const updateUser = await User.findByIdAndUpdate(
    _id,
    {
      avatarUrl: file ? file.path : avatarUrl,
      ///pug파일에서 file을 submit하면, req.file로
      ///file을 받아서 DB의 avatarUrl에
      ///file.path로 저장해 주면 된다.
      
      name,
      username,
      email,
      location,
    },
    { new: true }
  )
  // req.session.user = {
  //   ...req.session.user,
  //   name,
  //   email,
  //   username,
  //   location,
  // }
  req.session.user = updateUser
  return res.redirect('/users/edit')
}

6. videoController.js

export const getUpload = (req, res) => {
  return res.render('upload', { pageTitle: 'Upload Video' })
}
/// upload path로 접속하면, upload.pug파일을 rendering함.

export const postUpload = async (req, res) => {
  const {
    user: { _id },
  } = req.session
  const file = req.file
  ///req.file로 'upload.pug'에서 submit한 파일을 받음.
  
  const { title, description, hashtags } = req.body
  try {
    const newVideo = await Video.create({
      owner: _id,
      title,
      fileUrl: file.path,
      ///Video DB에 video를 create함.
      ///fileUrl은 file.path임.
      
      description,
      hashtags: Video.formatHashtags(hashtags),
    })
    const user = await User.findById(_id)
    ///session에서 따낸 _id로 loggedIn한 user를 찾음.
    
    user.videos.push(newVideo._id)
    ///찾은 User의 videos안에 create한 newVideo의 id를 push함.
    user.save()
    ///마지막으로 user를 save함.
    
    return res.redirect('/')
    ///home으로 redirrect함.
  } catch (error) {
    console.log(error)
    return res.status(404).render('upload', {
      pageTitle: 'Upload Video',
      errorMessage: error._message,
    })
  }

  // const dbVideo = await video.save()
  //console.log(dbVideo)
}

export const search = async (req, res) => {
  const { keyword } = req.query
  let videos = []
  if (keyword) {
    videos = await Video.find({
      title: {
        $regex: new RegExp(keyword, 'i'),
      },
    })
  }
  res.render('search', { pageTitle: 'Search', videos })
}
///video를 search하는 코딩!!

7. userSchema, videoSchema

const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  avatarUrl: String,
  socialOnly: { type: Boolean, default: false },
  username: { type: String, required: true, unique: true },
  password: { type: String },
  name: { type: String },
  location: String,
  videos: [
    { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Video' },
  ],
})


const videoSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
    upppercase: true,
    trim: true,
    maxLength: 140,
  },
  fileUrl: { type: String, required: true },
  description: String,
  createdAt: { type: Date, required: true, default: Date.now },
  hashtags: [{ type: String, trim: true }],
  meta: {
    views: { type: Number, default: 0, required: true },
    rating: { type: Number, default: 0, required: true },
  },
  owner: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'User' },
})

userSchema와 videoSchema에서 두개가 ref된 부분을 잘 봐두다.
userSchema의 videos, videoSchema의 owner

8. edit-profile.pug

extends base.pug

block content 
    h1 Edit Profile
    if errorMessage 
        h3=errorMessage
    img(src='/'+loggedInUser.avatarUrl, width="100", height="100")
    ///image를 front에 불러들이는 코딩.
    ///src뒤에 '/'를 붙여주는것에 주의한다!!!!
    
    form(method="POST", enctype='multipart/form-data')
    ///file Upload시 반드시
    ///enctype='multupart/form-data'를 적어주어야함.
        label(for='avatar') Avatar
        input(type="file", id='avatar', name="avatar", accept='image/*')
    ///label과 input은 for와 id로 연결됨.
    ///type은 file로 하고, accept='image/*'는
    ///모든 image 파일을 의미함.
        input(name="email", placeholder="Email", type="email",value=loggedInUser.email )
        input(name="username", placeholder="Username", type="text", value=loggedInUser.username)
        input(name="name", placeholder="Name", type="text", value=loggedInUser.name)
        input(name="location", placeholder="Location", type="text",value=loggedInUser.location )
        input( type="submit", value="Update Profile")
        if !loggedInUser.socialOnly
            hr
            a(href="change-password") Change Password →

9. upload.pug(videoUpload)

   extends base.pug

block content 
    h3 hello from upload
    if errorMessage 
        span=errorMessage
    form(method="POST", enctype='multipart/form-data')
    ///file은 무조건 enctype='multipart/form-data'입력할것
   
        label(for='video') Video File 
        input(type='file', accept='video/*', id='video', name='video', required)
 ///label과 input을 연결하고, type='file' name은 video
 ///accept='video/*' 비디오 관련 모든 파일을 의미함.
        
        input(name="title", placeholder='Title',required, type='text', minLength=3)
        input(name="description", placeholder='description',required, type='text')
        input(name="hashtags", placeholder='hashtags',required, type='text')
        input(type='submit', value='Upload Video')
   

10. watch.pug

비디오 시청 Page!!

 extends base.pug

//- block head 
//-     title Watch | JmTube

block content 
  video(src='/'+video.fileUrl, controls, width=600, height=500)    
  ////비디오 파일을 불러들일때는 video를 사용
  ///src에는 '/'를 넣고, video.fileUrl을 호출한다.
  /// controls를 반드시 넣어주고, 비디오 크기를 설정해줌.
  
  h1=video.title
  div
      p=video.description
      small=video.createdAt
  
  div 
      small=video.owner.email 
      br
      small Uploaded by 
              a(href=`/users/${video.owner._id}`) #{video.owner.username}
  ///비디오 업로드한 user의 profile 페이지로 이동
              
  each hashtag in video.hashtags 
          li=hashtag
  h3 #{video.views} #{video.views === 1 ? "view" : "views"}
  if String(video.owner._id) === String(loggedInUser._id)
      a(href=`${video.id}/edit`) Edit Video →
      br
      a(href=`${video.id}/delete`) Delete Video →
///video Upload User와 loggedInUser가 같은면, 
///Edit Video와 delete Video가 가능하게 해줌.
///video.owner._id 와 loggedInUser._id를
///String으로 type지정해주는 부분을 유의해서 볼것!!!!
 

11. mixins/video.pug

참고만!!!!!!!!

mixin video(info)
 div 
     h4
         a(href=`/videos/${info.id}`)=info.title
     video(src='/'+info.fileUrl, controls, width=300, height=200)  
     p=info.description
     each hashtag in info.hashtags 
         li=hashtag
     small=info.createdAt
     hr  
profile
코딩하는초딩쌤

0개의 댓글