Showing
13 changed files
with
178 additions
and
15 deletions
... | @@ -11,6 +11,7 @@ | ... | @@ -11,6 +11,7 @@ |
11 | 📺 [KhuTube](http://www.khutube.tk:4000/) | 11 | 📺 [KhuTube](http://www.khutube.tk:4000/) |
12 | 12 | ||
13 | 위 주소는 실제 EC2 서버에 배포한 주소입니다. (불필요한 업로드는 삼가 주시기 바랍니다.) | 13 | 위 주소는 실제 EC2 서버에 배포한 주소입니다. (불필요한 업로드는 삼가 주시기 바랍니다.) |
14 | +현재는 사용하지 않는 주소입니다.(2020.08.29 기준) | ||
14 | 15 | ||
15 | This is the address that deployed to the actual EC2 server. (Please refrain from uploading unnecessary files.) | 16 | This is the address that deployed to the actual EC2 server. (Please refrain from uploading unnecessary files.) |
16 | 17 | ||
... | @@ -108,4 +109,4 @@ I just recommend run both at the same time. | ... | @@ -108,4 +109,4 @@ I just recommend run both at the same time. |
108 | ## Contact | 109 | ## Contact |
109 | 110 | ||
110 | 이용하시다가 궁금한 점이 있으시면 이쪽으로 연락 부탁드립니다. | 111 | 이용하시다가 궁금한 점이 있으시면 이쪽으로 연락 부탁드립니다. |
111 | -강연욱 - rokkyw@khu.ac.kr | 112 | +강연욱 - rokkyw@khu.ac.kr / rokkyw@naver.com | ... | ... |
... | @@ -8,6 +8,7 @@ import bodyParser from "body-parser"; | ... | @@ -8,6 +8,7 @@ import bodyParser from "body-parser"; |
8 | import passport from "passport"; | 8 | import passport from "passport"; |
9 | import mongoose from "mongoose"; | 9 | import mongoose from "mongoose"; |
10 | import session from "express-session"; | 10 | import session from "express-session"; |
11 | +import flash from "express-flash"; | ||
11 | import MongoStore from "connect-mongo"; | 12 | import MongoStore from "connect-mongo"; |
12 | import { localsMiddleware } from "./middlewares"; | 13 | import { localsMiddleware } from "./middlewares"; |
13 | import routes from "./routes"; | 14 | import routes from "./routes"; |
... | @@ -39,7 +40,7 @@ app.use( | ... | @@ -39,7 +40,7 @@ app.use( |
39 | store: new CokieStore({ mongooseConnection: mongoose.connection }), | 40 | store: new CokieStore({ mongooseConnection: mongoose.connection }), |
40 | }) | 41 | }) |
41 | ); | 42 | ); |
42 | - | 43 | +app.use(flash()); |
43 | app.use(passport.initialize()); | 44 | app.use(passport.initialize()); |
44 | app.use(passport.session()); | 45 | app.use(passport.session()); |
45 | 46 | ... | ... |
assets/scss/partials/messages.scss
0 → 100644
1 | +@keyframes flashAnimation { | ||
2 | + 0% { | ||
3 | + transform: translateY(-70px); | ||
4 | + } | ||
5 | + 5% { | ||
6 | + transform: translateY(0px); | ||
7 | + } | ||
8 | + 95% { | ||
9 | + transform: translateY(0px); | ||
10 | + } | ||
11 | + 100% { | ||
12 | + transform: translateY(-70px); | ||
13 | + } | ||
14 | +} | ||
15 | + | ||
16 | +.flash-message__container { | ||
17 | + position: fixed; | ||
18 | + top: 0; | ||
19 | + left: 0; | ||
20 | + width: 100%; | ||
21 | + padding: 20px; | ||
22 | + text-align: center; | ||
23 | + animation: flashAnimation 5s ease-in-out forwards; | ||
24 | + &.error { | ||
25 | + background-color: #e74c3c; | ||
26 | + } | ||
27 | + &.success { | ||
28 | + background-color: #2ecc71; | ||
29 | + } | ||
30 | + &.info { | ||
31 | + background-color: #f1c40f; | ||
32 | + } | ||
33 | + .flash-message__text { | ||
34 | + color: white; | ||
35 | + font-size: 14px; | ||
36 | + } | ||
37 | +} |
... | @@ -10,6 +10,7 @@ | ... | @@ -10,6 +10,7 @@ |
10 | @import "partials/videoBlock.scss"; | 10 | @import "partials/videoBlock.scss"; |
11 | @import "partials/videoPlayer.scss"; | 11 | @import "partials/videoPlayer.scss"; |
12 | @import "partials/videoRecorder.scss"; | 12 | @import "partials/videoRecorder.scss"; |
13 | +@import "partials/messages.scss"; | ||
13 | 14 | ||
14 | @import "pages/home.scss"; | 15 | @import "pages/home.scss"; |
15 | @import "pages/videoDetail.scss"; | 16 | @import "pages/videoDetail.scss"; | ... | ... |
... | @@ -11,6 +11,7 @@ export const postJoin = async (req, res, next) => { | ... | @@ -11,6 +11,7 @@ export const postJoin = async (req, res, next) => { |
11 | body: { name, email, password, password2 }, | 11 | body: { name, email, password, password2 }, |
12 | } = req; | 12 | } = req; |
13 | if (password !== password2) { | 13 | if (password !== password2) { |
14 | + req.flash("error", "Passwords don't match"); | ||
14 | res.status(400); | 15 | res.status(400); |
15 | res.render("join", { pageTitle: "Join" }); | 16 | res.render("join", { pageTitle: "Join" }); |
16 | } else { | 17 | } else { |
... | @@ -37,10 +38,15 @@ export const getLogin = (req, res) => | ... | @@ -37,10 +38,15 @@ export const getLogin = (req, res) => |
37 | export const postLogin = passport.authenticate("local", { | 38 | export const postLogin = passport.authenticate("local", { |
38 | failureRedirect: routes.login, | 39 | failureRedirect: routes.login, |
39 | successRedirect: routes.home, | 40 | successRedirect: routes.home, |
41 | + successFlash: "Welcome", | ||
42 | + failureFlash: "Can't log in. Check email and/or password", | ||
40 | }); | 43 | }); |
41 | 44 | ||
42 | // 깃허브 로그인 | 45 | // 깃허브 로그인 |
43 | -export const githubLogin = passport.authenticate("github"); | 46 | +export const githubLogin = passport.authenticate("github", { |
47 | + successFlash: "Welcome", | ||
48 | + failureFlash: "Can't log in. Check email and/or password", | ||
49 | +}); | ||
44 | // call back 받아오는 함수 | 50 | // call back 받아오는 함수 |
45 | export const githubLoginCallback = async ( | 51 | export const githubLoginCallback = async ( |
46 | accessToken, | 52 | accessToken, |
... | @@ -78,7 +84,10 @@ export const postGithubLogin = (req, res) => { | ... | @@ -78,7 +84,10 @@ export const postGithubLogin = (req, res) => { |
78 | res.redirect(routes.home); | 84 | res.redirect(routes.home); |
79 | }; | 85 | }; |
80 | 86 | ||
81 | -export const facebookLogin = passport.authenticate("facebook"); | 87 | +export const facebookLogin = passport.authenticate("facebook", { |
88 | + successFlash: "Welcome", | ||
89 | + failureFlash: "Can't log in. Check email and/or password", | ||
90 | +}); | ||
82 | 91 | ||
83 | // http://www.passportjs.org/packages/passport-facebook/ 참고하였습니다. | 92 | // http://www.passportjs.org/packages/passport-facebook/ 참고하였습니다. |
84 | export const facebookLoginCallback = async (_, __, profile, cb) => { | 93 | export const facebookLoginCallback = async (_, __, profile, cb) => { |
... | @@ -92,7 +101,7 @@ export const facebookLoginCallback = async (_, __, profile, cb) => { | ... | @@ -92,7 +101,7 @@ export const facebookLoginCallback = async (_, __, profile, cb) => { |
92 | user.avatarUrl = `https://graph.facebook.com/${id}/picture?type=large`; | 101 | user.avatarUrl = `https://graph.facebook.com/${id}/picture?type=large`; |
93 | user.save(); | 102 | user.save(); |
94 | return cb(null, user); | 103 | return cb(null, user); |
95 | - } | 104 | + } // graph API 참조. |
96 | const newUser = await User.create({ | 105 | const newUser = await User.create({ |
97 | email, | 106 | email, |
98 | name, | 107 | name, |
... | @@ -114,10 +123,11 @@ export const postFacebookLogin = (req, res) => { | ... | @@ -114,10 +123,11 @@ export const postFacebookLogin = (req, res) => { |
114 | // 즉, 초반에 만들어둔 logout.pug는 삭제해도 좋다. | 123 | // 즉, 초반에 만들어둔 logout.pug는 삭제해도 좋다. |
115 | export const logout = (req, res) => { | 124 | export const logout = (req, res) => { |
116 | // res.render("logout", { pageTitle: "Logout" }); | 125 | // res.render("logout", { pageTitle: "Logout" }); |
126 | + req.flash("info", "Logged out, see you later"); | ||
117 | req.logout(); | 127 | req.logout(); |
118 | res.redirect(routes.home); | 128 | res.redirect(routes.home); |
119 | }; | 129 | }; |
120 | - | 130 | +// 미들웨어로 부터 로그인한 user를 받는다. |
121 | export const getMe = async (req, res) => { | 131 | export const getMe = async (req, res) => { |
122 | try { | 132 | try { |
123 | const user = await User.findById(req.user.id).populate("videos"); | 133 | const user = await User.findById(req.user.id).populate("videos"); |
... | @@ -137,6 +147,7 @@ export const userDetail = async (req, res) => { | ... | @@ -137,6 +147,7 @@ export const userDetail = async (req, res) => { |
137 | const user = await User.findById(id).populate("videos"); | 147 | const user = await User.findById(id).populate("videos"); |
138 | res.render("userDetail", { pageTitle: "User Detail", user }); | 148 | res.render("userDetail", { pageTitle: "User Detail", user }); |
139 | } catch (error) { | 149 | } catch (error) { |
150 | + req.flash("error", "User not found"); | ||
140 | res.redirect(routes.home); | 151 | res.redirect(routes.home); |
141 | } | 152 | } |
142 | }; | 153 | }; |
... | @@ -155,8 +166,10 @@ export const postEditProfile = async (req, res) => { | ... | @@ -155,8 +166,10 @@ export const postEditProfile = async (req, res) => { |
155 | email, | 166 | email, |
156 | avatarUrl: file ? file.location : req.user.avatarUrl, // S3 적용때메 file.path -> file.location 변경(06/24) | 167 | avatarUrl: file ? file.location : req.user.avatarUrl, // S3 적용때메 file.path -> file.location 변경(06/24) |
157 | }); | 168 | }); |
169 | + req.flash("success", "Profile updated"); | ||
158 | res.redirect(routes.me); | 170 | res.redirect(routes.me); |
159 | } catch (error) { | 171 | } catch (error) { |
172 | + req.flash("error", "Can't update profile"); | ||
160 | res.redirect(`/users${routes.editProfile}`); | 173 | res.redirect(`/users${routes.editProfile}`); |
161 | } | 174 | } |
162 | }; | 175 | }; |
... | @@ -170,6 +183,7 @@ export const postChangePassword = async (req, res) => { | ... | @@ -170,6 +183,7 @@ export const postChangePassword = async (req, res) => { |
170 | } = req; | 183 | } = req; |
171 | try { | 184 | try { |
172 | if (newPassword !== newPassword1) { | 185 | if (newPassword !== newPassword1) { |
186 | + req.flash("error", "Passwords don't match"); | ||
173 | res.status(400); | 187 | res.status(400); |
174 | res.redirect(`/users${routes.changePassword}`); | 188 | res.redirect(`/users${routes.changePassword}`); |
175 | return; | 189 | return; |
... | @@ -177,6 +191,7 @@ export const postChangePassword = async (req, res) => { | ... | @@ -177,6 +191,7 @@ export const postChangePassword = async (req, res) => { |
177 | await req.user.changePassword(oldPassword, newPassword); | 191 | await req.user.changePassword(oldPassword, newPassword); |
178 | res.redirect(routes.me); | 192 | res.redirect(routes.me); |
179 | } catch (error) { | 193 | } catch (error) { |
194 | + req.flash("error", "Can't change password"); | ||
180 | res.status(400); | 195 | res.status(400); |
181 | res.redirect(`/users${routes.changePassword}`); | 196 | res.redirect(`/users${routes.changePassword}`); |
182 | } | 197 | } | ... | ... |
... | @@ -40,11 +40,11 @@ export const postUpload = async (req, res) => { | ... | @@ -40,11 +40,11 @@ export const postUpload = async (req, res) => { |
40 | // 이는 pug와 db.js를 확인해야하는 듯 하다. | 40 | // 이는 pug와 db.js를 확인해야하는 듯 하다. |
41 | const { | 41 | const { |
42 | body: { title, description }, | 42 | body: { title, description }, |
43 | - file: { location }, // path로 할때는 로컬의 경로. S3는 location | 43 | + file: { path }, // path로 할때는 로컬의 경로. S3는 location |
44 | } = req; // file에 path라는 요소가 있다. | 44 | } = req; // file에 path라는 요소가 있다. |
45 | 45 | ||
46 | const newVideo = await Video.create({ | 46 | const newVideo = await Video.create({ |
47 | - fileUrl: location, | 47 | + fileUrl: path, |
48 | title, | 48 | title, |
49 | description, | 49 | description, |
50 | creator: req.user.id, | 50 | creator: req.user.id, |
... | @@ -66,6 +66,7 @@ export const videoDetail = async (req, res) => { | ... | @@ -66,6 +66,7 @@ export const videoDetail = async (req, res) => { |
66 | const video = await Video.findById(id) | 66 | const video = await Video.findById(id) |
67 | .populate("creator") | 67 | .populate("creator") |
68 | .populate("comments"); | 68 | .populate("comments"); |
69 | + console.log(video); | ||
69 | res.render("videoDetail", { pageTitle: video.title, video }); | 70 | res.render("videoDetail", { pageTitle: video.title, video }); |
70 | } catch (error) { | 71 | } catch (error) { |
71 | res.redirect(routes.home); | 72 | res.redirect(routes.home); | ... | ... |
... | @@ -4,7 +4,7 @@ import dotenv from "dotenv"; | ... | @@ -4,7 +4,7 @@ import dotenv from "dotenv"; |
4 | 4 | ||
5 | dotenv.config(); | 5 | dotenv.config(); |
6 | 6 | ||
7 | -mongoose.connect(process.env.MONGO_URL_PROD, { | 7 | +mongoose.connect(process.env.MONGO_URL, { |
8 | useNewUrlParser: true, | 8 | useNewUrlParser: true, |
9 | useFindAndModify: false, | 9 | useFindAndModify: false, |
10 | }); | 10 | }); | ... | ... |
... | @@ -12,15 +12,17 @@ const s3 = new aws.S3({ | ... | @@ -12,15 +12,17 @@ const s3 = new aws.S3({ |
12 | region: "ap-northeast-2", | 12 | region: "ap-northeast-2", |
13 | }); | 13 | }); |
14 | 14 | ||
15 | -// const multerVideo = multer({ dest: "uploads/videos/" }); | 15 | +const multerVideo = multer({ dest: "uploads/videos/" }); |
16 | +/* | ||
16 | const multerVideo = multer({ | 17 | const multerVideo = multer({ |
17 | storage: multerS3({ | 18 | storage: multerS3({ |
18 | s3, | 19 | s3, |
19 | acl: "public-read", | 20 | acl: "public-read", |
20 | bucket: "khutube/video", | 21 | bucket: "khutube/video", |
21 | }), | 22 | }), |
22 | -}); | 23 | +});*/ |
23 | -// const multerAvatar = multer({ dest: "uploads/avatars/" }); | 24 | +const multerAvatar = multer({ dest: "uploads/avatars/" }); |
25 | +/* | ||
24 | const multerAvatar = multer({ | 26 | const multerAvatar = multer({ |
25 | storage: multerS3({ | 27 | storage: multerS3({ |
26 | s3, | 28 | s3, |
... | @@ -28,11 +30,12 @@ const multerAvatar = multer({ | ... | @@ -28,11 +30,12 @@ const multerAvatar = multer({ |
28 | bucket: "khutube/avatars", | 30 | bucket: "khutube/avatars", |
29 | }), | 31 | }), |
30 | }); | 32 | }); |
31 | - | 33 | +*/ |
32 | export const localsMiddleware = (req, res, next) => { | 34 | export const localsMiddleware = (req, res, next) => { |
33 | res.locals.siteName = "my Youtube"; | 35 | res.locals.siteName = "my Youtube"; |
34 | res.locals.routes = routes; | 36 | res.locals.routes = routes; |
35 | res.locals.loggedUser = req.user || null; | 37 | res.locals.loggedUser = req.user || null; |
38 | + // console.log(req); | ||
36 | next(); | 39 | next(); |
37 | }; | 40 | }; |
38 | 41 | ... | ... |
... | @@ -31,6 +31,7 @@ | ... | @@ -31,6 +31,7 @@ |
31 | "css-loader": "^3.5.3", | 31 | "css-loader": "^3.5.3", |
32 | "dotenv": "^8.2.0", | 32 | "dotenv": "^8.2.0", |
33 | "express": "^4.17.1", | 33 | "express": "^4.17.1", |
34 | + "express-flash": "0.0.2", | ||
34 | "express-session": "^1.17.1", | 35 | "express-session": "^1.17.1", |
35 | "extract-text-webpack-plugin": "^4.0.0-beta.0", | 36 | "extract-text-webpack-plugin": "^4.0.0-beta.0", |
36 | "get-blob-duration": "^1.1.1", | 37 | "get-blob-duration": "^1.1.1", | ... | ... |
... | @@ -501,6 +501,91 @@ input[type="submit"] { | ... | @@ -501,6 +501,91 @@ input[type="submit"] { |
501 | width: 100%; | 501 | width: 100%; |
502 | margin-bottom: 20px; } | 502 | margin-bottom: 20px; } |
503 | 503 | ||
504 | +@-webkit-keyframes flashAnimation { | ||
505 | + 0% { | ||
506 | + -webkit-transform: translateY(-70px); | ||
507 | + transform: translateY(-70px); } | ||
508 | + 5% { | ||
509 | + -webkit-transform: translateY(0px); | ||
510 | + transform: translateY(0px); } | ||
511 | + 95% { | ||
512 | + -webkit-transform: translateY(0px); | ||
513 | + transform: translateY(0px); } | ||
514 | + 100% { | ||
515 | + -webkit-transform: translateY(-70px); | ||
516 | + transform: translateY(-70px); } } | ||
517 | + | ||
518 | +@-moz-keyframes flashAnimation { | ||
519 | + 0% { | ||
520 | + -moz-transform: translateY(-70px); | ||
521 | + transform: translateY(-70px); } | ||
522 | + 5% { | ||
523 | + -moz-transform: translateY(0px); | ||
524 | + transform: translateY(0px); } | ||
525 | + 95% { | ||
526 | + -moz-transform: translateY(0px); | ||
527 | + transform: translateY(0px); } | ||
528 | + 100% { | ||
529 | + -moz-transform: translateY(-70px); | ||
530 | + transform: translateY(-70px); } } | ||
531 | + | ||
532 | +@-o-keyframes flashAnimation { | ||
533 | + 0% { | ||
534 | + -o-transform: translateY(-70px); | ||
535 | + transform: translateY(-70px); } | ||
536 | + 5% { | ||
537 | + -o-transform: translateY(0px); | ||
538 | + transform: translateY(0px); } | ||
539 | + 95% { | ||
540 | + -o-transform: translateY(0px); | ||
541 | + transform: translateY(0px); } | ||
542 | + 100% { | ||
543 | + -o-transform: translateY(-70px); | ||
544 | + transform: translateY(-70px); } } | ||
545 | + | ||
546 | +@keyframes flashAnimation { | ||
547 | + 0% { | ||
548 | + -webkit-transform: translateY(-70px); | ||
549 | + -moz-transform: translateY(-70px); | ||
550 | + -o-transform: translateY(-70px); | ||
551 | + transform: translateY(-70px); } | ||
552 | + 5% { | ||
553 | + -webkit-transform: translateY(0px); | ||
554 | + -moz-transform: translateY(0px); | ||
555 | + -o-transform: translateY(0px); | ||
556 | + transform: translateY(0px); } | ||
557 | + 95% { | ||
558 | + -webkit-transform: translateY(0px); | ||
559 | + -moz-transform: translateY(0px); | ||
560 | + -o-transform: translateY(0px); | ||
561 | + transform: translateY(0px); } | ||
562 | + 100% { | ||
563 | + -webkit-transform: translateY(-70px); | ||
564 | + -moz-transform: translateY(-70px); | ||
565 | + -o-transform: translateY(-70px); | ||
566 | + transform: translateY(-70px); } } | ||
567 | + | ||
568 | +.flash-message__container { | ||
569 | + position: fixed; | ||
570 | + top: 0; | ||
571 | + left: 0; | ||
572 | + width: 100%; | ||
573 | + padding: 20px; | ||
574 | + text-align: center; | ||
575 | + -webkit-animation: flashAnimation 5s ease-in-out forwards; | ||
576 | + -moz-animation: flashAnimation 5s ease-in-out forwards; | ||
577 | + -o-animation: flashAnimation 5s ease-in-out forwards; | ||
578 | + animation: flashAnimation 5s ease-in-out forwards; } | ||
579 | + .flash-message__container.error { | ||
580 | + background-color: #e74c3c; } | ||
581 | + .flash-message__container.success { | ||
582 | + background-color: #2ecc71; } | ||
583 | + .flash-message__container.info { | ||
584 | + background-color: #f1c40f; } | ||
585 | + .flash-message__container .flash-message__text { | ||
586 | + color: white; | ||
587 | + font-size: 14px; } | ||
588 | + | ||
504 | .home-videos { | 589 | .home-videos { |
505 | display: grid; | 590 | display: grid; |
506 | grid-template-columns: repeat(6, minmax(150px, 1fr)); | 591 | grid-template-columns: repeat(6, minmax(150px, 1fr)); | ... | ... |
1 | //공통되는 코드가 필요한 경우div | 1 | //공통되는 코드가 필요한 경우div |
2 | - | 2 | +include ../mixins/message |
3 | doctype html | 3 | doctype html |
4 | html | 4 | html |
5 | head | 5 | head |
... | @@ -7,6 +7,21 @@ html | ... | @@ -7,6 +7,21 @@ html |
7 | title #{pageTitle} | #{siteName} | 7 | title #{pageTitle} | #{siteName} |
8 | link(rel="stylesheet", href="/static/style.css") | 8 | link(rel="stylesheet", href="/static/style.css") |
9 | body | 9 | body |
10 | + if messages.error | ||
11 | + +message({ | ||
12 | + type:'error', | ||
13 | + text:messages.error | ||
14 | + }) | ||
15 | + else if messages.info | ||
16 | + +message({ | ||
17 | + type:'info', | ||
18 | + text:messages.info | ||
19 | + }) | ||
20 | + else if messages.success | ||
21 | + +message({ | ||
22 | + type:'success', | ||
23 | + text:messages.success | ||
24 | + }) | ||
10 | include ../partials/header | 25 | include ../partials/header |
11 | main | 26 | main |
12 | block content | 27 | block content | ... | ... |
views/mixins/message.pug
0 → 100644
1 | mixin videoPlayer(video={}) | 1 | mixin videoPlayer(video={}) |
2 | .videoPlayer#jsVideoPlayer | 2 | .videoPlayer#jsVideoPlayer |
3 | - video(src=video.src) | 3 | + video(src=`/${video.src}`) |
4 | .videoPlayer__controls | 4 | .videoPlayer__controls |
5 | .videoPlayer__column | 5 | .videoPlayer__column |
6 | span#jsVolumeBtn | 6 | span#jsVolumeBtn | ... | ... |
-
Please register or login to post a comment