들어가기
express에서 file upload를 알아본다.
일단은 server에 먼저 올려보고
나중에 aws S3에 저장하고 path를 받아보는걸로 한다.
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'))
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임.
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
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
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')
}
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하는 코딩!!
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
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 →
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')
비디오 시청 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지정해주는 부분을 유의해서 볼것!!!!
참고만!!!!!!!!
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