Flare-k

[Add] videoDetail, css

import "../scss/style.scss";
const something = async () => {
console.log("something");
};
import "../scss/styles.scss";
import "./videoPlayer";
......
File mode changed
.u-avatar {
height: 80px;
background-color: $red;
border-radius: 50%;
}
.user-profile {
width: 100%;
display: flex;
align-items: center;
flex-direction: column;
.user-profile__header {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 18px;
.profile__username {
font-size: 18px;
margin-top: 15px;
}
}
.user-profile__btns {
display: flex;
flex-direction: column;
align-items: center;
a:not(:last-child) {
margin-bottom: 20px;
}
}
}
......@@ -10,6 +10,11 @@
width: 100px;
margin-bottom: 25px;
}
.video__author {
a {
color: #3498db;
}
}
.video__title,
.video__views,
.video__description {
......
.videoPlayer {
position: relative;
&:hover {
.videoPlayer__controls {
opacity: 1;
}
}
video {
max-width: 100%;
}
.videoPlayer__controls {
opacity: 0;
transition: opacity 0.4s linear;
color: white;
position: absolute;
z-index: 9;
bottom: 5px;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
padding: 10px;
display: grid;
grid-template-columns: repeat(3, 1fr);
font-size: 16px;
.videoPlayer__column:first-child {
display: flex;
align-items: center;
span:first-child {
margin-right: 10px;
}
}
.videoPlayer__column:last-child {
justify-self: end;
}
.videoPlayer__column:nth-child(2) {
justify-self: center;
}
i {
font-size: 25px;
cursor: pointer;
}
}
}
@import "config/_variables.scss";
@import "config/reset.scss";
@import "config/utils.scss";
@import "main.scss";
@import "partials/header.scss";
......@@ -7,6 +8,8 @@
@import "partials/form.scss";
@import "partials/socialLogin.scss";
@import "partials/videoBlock.scss";
@import "partials/videoPlayer.scss";
@import "pages/home.scss";
@import "pages/videoDetail.scss";
@import "pages/userProfile.scss";
......
......@@ -120,7 +120,7 @@ export const logout = (req, res) => {
export const getMe = (req, res) =>
res.render("userDetail", { pageTitle: "User Detail", user: req.user });
// req.user -> 로그인된 유저
// export const users = (req, res) => res.render("users", { pageTitle: "Users" });
export const userDetail = async (req, res) => {
......@@ -128,13 +128,50 @@ export const userDetail = async (req, res) => {
params: { id },
} = req; // req로 부터 params의 id가져오기
try {
const user = await User.findById(id);
const user = await User.findById(id).populate("videos");
res.render("userDetail", { pageTitle: "User Detail", user });
} catch (error) {
res.redirect(routes.home);
}
};
export const editProfile = (req, res) =>
export const getEditProfile = (req, res) =>
res.render("editProfile", { pageTitle: "Edit Profile" });
export const changePassword = (req, res) =>
export const postEditProfile = async (req, res) => {
const {
body: { name, email },
file,
} = req;
try {
// 로그인된 user의 id 가져오면 됨
await User.findByIdAndUpdate(req.user.id, {
name,
email,
avatarUrl: file ? file.path : req.user.avatarUrl,
});
res.redirect(routes.me);
} catch (error) {
res.redirect(`/users${routes.editProfile}`);
}
};
export const getChangePassword = (req, res) =>
res.render("changePassword", { pageTitle: "Change Password" });
export const postChangePassword = async (req, res) => {
const {
body: { oldPassword, newPassword, newPassword1 },
} = req;
try {
if (newPassword !== newPassword1) {
res.status(400);
res.redirect(`/users${routes.changePassword}`);
return;
}
await req.user.changePassword(oldPassword, newPassword);
res.redirect(routes.me);
} catch (error) {
res.status(400);
res.redirect(`/users${routes.changePassword}`);
}
};
......
......@@ -46,9 +46,12 @@ export const postUpload = async (req, res) => {
fileUrl: path,
title,
description,
creator: req.user.id,
// 여기있는 fileUrl, title, description은 videoDB의 속성이다.
});
console.log(newVideo);
// console.log(newVideo);
req.user.videos.push(newVideo.id); // user DB의 video atribute에 추가
req.user.save();
res.redirect(routes.videoDetail(newVideo.id)); // id는 DB의 id
// id는 mongoDB가 랜덤하게 만들어준다.
};
......@@ -59,7 +62,7 @@ export const videoDetail = async (req, res) => {
params: { id },
} = req;
try {
const video = await Video.findById(id);
const video = await Video.findById(id).populate("creator");
res.render("videoDetail", { pageTitle: video.title, video });
} catch (error) {
res.redirect(routes.home);
......@@ -72,7 +75,11 @@ export const getEditVideo = async (req, res) => {
try {
const video = await Video.findById(id);
// video를 받아서 render로 통해 템플릿으로 던져준다,
res.render("editVideo", { pageTitle: `Edit ${video.title}`, video });
if (String(video.creator) !== req.user.id) {
throw Error();
} else {
res.render("editVideo", { pageTitle: `Edit ${video.title}`, video });
}
// rendering하는 순간 템플릿에선 video의 title과 description을 던져준다.
} catch (error) {
res.redirect(routes.home);
......@@ -99,7 +106,13 @@ export const deleteVideo = async (req, res) => {
params: { id },
} = req;
try {
await Video.findOneAndRemove({ _id: id });
const video = await Video.findById(id);
// video를 받아서 render로 통해 템플릿으로 던져준다,
if (String(video.creator) !== req.user.id) {
throw Error();
} else {
await Video.findOneAndRemove({ _id: id });
}
} catch (error) {
console.log(error);
}
......
......@@ -2,6 +2,7 @@ import multer from "multer";
import routes from "./routes";
const multerVideo = multer({ dest: "uploads/videos/" });
const multerAvatar = multer({ dest: "uploads/avatars/" });
export const localsMiddleware = (req, res, next) => {
res.locals.siteName = "my Youtube";
......@@ -26,3 +27,4 @@ export const onlyPrivate = (req, res, next) => {
};
export const uploadVideo = multerVideo.single("videoFile");
// single에 들어간 videoFile은 upload.pug의 file 부분 input name
export const uploadAvatar = multerAvatar.single("avatar");
......
......@@ -10,6 +10,10 @@ const CommentSchema = new mongoose.Schema({
type: Date,
default: Date.now,
},
creator: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
/*
video: { //video와 comment를 연결하는 방법 #2
......
......@@ -7,6 +7,18 @@ const UserSchema = new mongoose.Schema({
avatarUrl: String,
facebookId: Number,
githubId: Number,
comments: [
{
type: mongoose.Schema.Types.ObjectId, // 그 다음 어느 model에서 온 id인지 알려줘야 한다.
ref: "Comment",
},
],
videos: [
{
type: mongoose.Schema.Types.ObjectId, // 그 다음 어느 model에서 온 id인지 알려줘야 한다.
ref: "Video",
},
],
});
// 이 상태에서 새로운 스키마를 추가한다.
// passportLocalMongoose는 configuration object가 필요하다.
......
......@@ -27,6 +27,10 @@ const VideoSchema = new mongoose.Schema({
ref: "Comment",
},
],
creator: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
});
// 이제 이 스키마를 이용하여 model을 만들어준다.
// 모델의 이름은 "Video"
......
......@@ -4,7 +4,7 @@
"description": "make Youtube Website",
"main": "app.js",
"scripts": {
"dev:server": "nodemon --exec babel-node init.js --delay 2 --ignore 'scss'",
"dev:server": "nodemon --exec babel-node init.js --delay 2 --ignore '.scss' --ignore 'static'",
"dev:assets": "WEBPACK_ENV=development webpack -w",
"build:assets": "WEBPACK_ENV=production webpack",
"tunnel": "ngrok http 80"
......
......@@ -2,15 +2,20 @@ import express from "express";
import routes from "../routes";
import {
userDetail,
editProfile,
changePassword,
getEditProfile,
postEditProfile,
getChangePassword,
postChangePassword,
} from "../controllers/userController";
import { onlyPrivate } from "../middlewares";
import { onlyPrivate, uploadAvatar } from "../middlewares";
const userRouter = express.Router();
userRouter.get(routes.editProfile, onlyPrivate, editProfile);
userRouter.get(routes.changePassword, onlyPrivate, changePassword);
userRouter.get(routes.editProfile, onlyPrivate, getEditProfile);
userRouter.post(routes.editProfile, onlyPrivate, uploadAvatar, postEditProfile);
userRouter.get(routes.changePassword, onlyPrivate, getChangePassword);
userRouter.post(routes.changePassword, onlyPrivate, postChangePassword);
userRouter.get(routes.userDetail(), userDetail);
export default userRouter;
......
......@@ -4,9 +4,9 @@ import {
getUpload,
postUpload,
videoDetail,
deleteVideo,
getEditVideo,
postEditVideo,
deleteVideo,
} from "../controllers/videoController";
import { uploadVideo, onlyPrivate } from "../middlewares";
// export const videoRouter = express.Router(); 이렇게하면 이 변수만 export하게 된다.
......
......@@ -94,18 +94,29 @@
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _scss_style_scss__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../scss/style.scss */ \"./assets/scss/style.scss\");\n/* harmony import */ var _scss_style_scss__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_scss_style_scss__WEBPACK_IMPORTED_MODULE_0__);\n\n\nconst something = async () => {\n console.log(\"something\");\n};\n\n\n//# sourceURL=webpack:///./assets/js/main.js?");
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _scss_styles_scss__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../scss/styles.scss */ \"./assets/scss/styles.scss\");\n/* harmony import */ var _scss_styles_scss__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_scss_styles_scss__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _videoPlayer__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./videoPlayer */ \"./assets/js/videoPlayer.js\");\n/* harmony import */ var _videoPlayer__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_videoPlayer__WEBPACK_IMPORTED_MODULE_1__);\n\n\n\n\n//# sourceURL=webpack:///./assets/js/main.js?");
/***/ }),
/***/ "./assets/scss/style.scss":
/*!********************************!*\
!*** ./assets/scss/style.scss ***!
\********************************/
/***/ "./assets/js/videoPlayer.js":
/*!**********************************!*\
!*** ./assets/js/videoPlayer.js ***!
\**********************************/
/*! no static exports found */
/***/ (function(module, exports) {
eval("// removed by extract-text-webpack-plugin\n\n//# sourceURL=webpack:///./assets/scss/style.scss?");
eval("\n\n//# sourceURL=webpack:///./assets/js/videoPlayer.js?");
/***/ }),
/***/ "./assets/scss/styles.scss":
/*!*********************************!*\
!*** ./assets/scss/styles.scss ***!
\*********************************/
/*! no static exports found */
/***/ (function(module, exports) {
eval("// removed by extract-text-webpack-plugin\n\n//# sourceURL=webpack:///./assets/scss/styles.scss?");
/***/ }),
......
......@@ -140,6 +140,13 @@ input {
input:focus, input:active {
outline: none; }
.u-avatar {
height: 80px;
background-color: #ea232c;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
border-radius: 50%; }
html,
body {
height: 100%; }
......@@ -413,6 +420,49 @@ input[type="submit"] {
font-weight: 300;
margin-bottom: 10px; }
.videoPlayer {
position: relative; }
.videoPlayer:hover .videoPlayer__controls {
opacity: 1; }
.videoPlayer video {
max-width: 100%; }
.videoPlayer .videoPlayer__controls {
opacity: 0;
-webkit-transition: opacity 0.4s linear;
-o-transition: opacity 0.4s linear;
-moz-transition: opacity 0.4s linear;
transition: opacity 0.4s linear;
color: white;
position: absolute;
z-index: 9;
bottom: 5px;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
padding: 10px;
display: grid;
grid-template-columns: repeat(3, 1fr);
font-size: 16px; }
.videoPlayer .videoPlayer__controls .videoPlayer__column:first-child {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-moz-box-align: center;
-ms-flex-align: center;
align-items: center; }
.videoPlayer .videoPlayer__controls .videoPlayer__column:first-child span:first-child {
margin-right: 10px; }
.videoPlayer .videoPlayer__controls .videoPlayer__column:last-child {
justify-self: end; }
.videoPlayer .videoPlayer__controls .videoPlayer__column:nth-child(2) {
justify-self: center; }
.videoPlayer .videoPlayer__controls i {
font-size: 25px;
cursor: pointer; }
.home-videos {
display: grid;
grid-template-columns: repeat(6, minmax(150px, 1fr));
......@@ -450,6 +500,8 @@ input[type="submit"] {
.video-detail__container .video__info button {
width: 100px;
margin-bottom: 25px; }
.video-detail__container .video__info .video__author a {
color: #3498db; }
.video-detail__container .video__info .video__title,
.video-detail__container .video__info .video__views,
.video-detail__container .video__info .video__description {
......@@ -466,3 +518,65 @@ input[type="submit"] {
margin-top: 25px; }
.video-detail__container .video__comments .video__comment-number {
font-size: 18px; }
.user-profile {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-moz-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-moz-box-orient: vertical;
-moz-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column; }
.user-profile .user-profile__header {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-moz-box-orient: vertical;
-moz-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-align: center;
-webkit-align-items: center;
-moz-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 18px; }
.user-profile .user-profile__header .profile__username {
font-size: 18px;
margin-top: 15px; }
.user-profile .user-profile__btns {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-moz-box-orient: vertical;
-moz-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-align: center;
-webkit-align-items: center;
-moz-box-align: center;
-ms-flex-align: center;
align-items: center; }
.user-profile .user-profile__btns a:not(:last-child) {
margin-bottom: 20px; }
......
......@@ -3,7 +3,7 @@ extends layouts/main
block content
.form-container
form(action=`/users${routes.changePassword}`, method="post")
input(type="password", name="oldPasswod", placeholder="Current Password")
input(type="password", name="oldPassword", placeholder="Current Password")
input(type="password", name="newPassword", placeholder="New Password")
input(type="password", name="newPassword1", placeholder="Verify New Password")
input(type="submit", value="Change Password")
\ No newline at end of file
......
......@@ -2,12 +2,11 @@ extends layouts/main
block content
.form-container
form(action=routes.editProfile, method="post")
form(action=`/users${routes.editProfile}`, method="post", enctype="multipart/form-data")
.fileUpload
label(for="avatar") Avatar
input(type="file", id="avatar", name="avatar")
input(type="text", placeholder="Name", name="name")
input(type="email", placeholder="Email", name="email")
input(type="file", id="avatar", name="avatar", accept="image/*")
input(type="text", placeholder="Name", name="name", value=loggedUser.name)
input(type="email", placeholder="Email", name="email", value=loggedUser.email)
input(type="submit", value="Update Profile")
a.form-container__link(href=`/users${routes.changePassword}`)
button Change Password
\ No newline at end of file
\ No newline at end of file
......
......@@ -9,70 +9,4 @@ block content
title : item.title,
views: item.views,
videoFile:item.fileUrl
})
+videoBlock({
id:item.id,
title:item.title,
views:item.views,
videoFile:item.fileUrl
})
+videoBlock({
id:item.id,
title:item.title,
views:item.views,
videoFile:item.fileUrl
})
+videoBlock({
id:item.id,
title:item.title,
views:item.views,
videoFile:item.fileUrl
})
+videoBlock({
id:item.id,
title:item.title,
views:item.views,
videoFile:item.fileUrl
})
+videoBlock({
id:item.id,
title:item.title,
views:item.views,
videoFile:item.fileUrl
})
+videoBlock({
id:item.id,
title:item.title,
views:item.views,
videoFile:item.fileUrl
})
+videoBlock({
id:item.id,
title:item.title,
views:item.views,
videoFile:item.fileUrl
})
+videoBlock({
id:item.id,
title:item.title,
views:item.views,
videoFile:item.fileUrl
})
+videoBlock({
id:item.id,
title:item.title,
views:item.views,
videoFile:item.fileUrl
})
+videoBlock({
id:item.id,
title:item.title,
views:item.views,
videoFile:item.fileUrl
})
+videoBlock({
id:item.id,
title:item.title,
views:item.views,
videoFile:item.fileUrl
})
})
\ No newline at end of file
......
mixin videoPlayer(video={})
.videoPlayer
video(src=`/${video.src}`)
.videoPlayer__controls
.videoPlayer__column
span
i.fas.fa-volume-up
span
|00:00 / 10:00
.videoPlayer__column
span
i.fas.fa-play
.videoPlayer__column
span
i.fas.fa-expand
\ No newline at end of file
extends layouts/main
include mixins/videoBlock
block content
.user-profile
.user-profile__header
img.avatar(src=user.avatarUrl)
h4.profile__username=user.name
\ No newline at end of file
img.u-avatar(src=user.avatarUrl)
h4.profile__username=user.name
if user.id === loggedUser.id
.user-profile__btns
a(href=`/users${routes.editProfile}`)
button 🖌 Edit Profile
a(href=`/users${routes.changePassword}`)
button 🔒 Change Password
.home-videos
each item in user.videos
+videoBlock({
id: item.id,
title : item.title,
views: item.views,
videoFile:item.fileUrl
})
\ No newline at end of file
......
extends layouts/main
include mixins/videoPlayer
block content
.video__player
video(src=`/${video.fileUrl}`)
.video__info
a(href=routes.editVideo(video.id))
button Edit video
h5.video__title=video.title
span.video__views=video.views
p.video__description=video.description
if video.views === 1
.video-detail__container
+videoPlayer({
src:video.fileUrl
})
.video__info
if loggedUser && 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__comment
if video.comments.length === 1
span.video__comment-number 1 comment
else
span.video__comment-number #{video.comments.length} comments
\ No newline at end of file
else
span.video__views #{video.views} views
.video__author
|Uploaded by
a(href=routes.userDetail(video.creator.id))=video.creator.name
.video__comment
if video.comments.length === 1
span.video__comment-number 1 comment
else
span.video__comment-number #{video.comments.length} comments
\ No newline at end of file
......