Showing
29 changed files
with
651 additions
and
160 deletions
assets/scss/config/reset.scss
0 → 100644
1 | +html, | ||
2 | +body, | ||
3 | +div, | ||
4 | +span, | ||
5 | +applet, | ||
6 | +object, | ||
7 | +iframe, | ||
8 | +h1, | ||
9 | +h2, | ||
10 | +h3, | ||
11 | +h4, | ||
12 | +h5, | ||
13 | +h6, | ||
14 | +p, | ||
15 | +blockquote, | ||
16 | +pre, | ||
17 | +a, | ||
18 | +abbr, | ||
19 | +acronym, | ||
20 | +address, | ||
21 | +big, | ||
22 | +cite, | ||
23 | +code, | ||
24 | +del, | ||
25 | +dfn, | ||
26 | +em, | ||
27 | +img, | ||
28 | +ins, | ||
29 | +kbd, | ||
30 | +q, | ||
31 | +s, | ||
32 | +samp, | ||
33 | +small, | ||
34 | +strike, | ||
35 | +strong, | ||
36 | +sub, | ||
37 | +sup, | ||
38 | +tt, | ||
39 | +var, | ||
40 | +b, | ||
41 | +u, | ||
42 | +i, | ||
43 | +center, | ||
44 | +dl, | ||
45 | +dt, | ||
46 | +dd, | ||
47 | +ol, | ||
48 | +ul, | ||
49 | +li, | ||
50 | +fieldset, | ||
51 | +form, | ||
52 | +label, | ||
53 | +legend, | ||
54 | +table, | ||
55 | +caption, | ||
56 | +tbody, | ||
57 | +tfoot, | ||
58 | +thead, | ||
59 | +tr, | ||
60 | +th, | ||
61 | +td, | ||
62 | +article, | ||
63 | +aside, | ||
64 | +canvas, | ||
65 | +details, | ||
66 | +embed, | ||
67 | +figure, | ||
68 | +figcaption, | ||
69 | +footer, | ||
70 | +header, | ||
71 | +hgroup, | ||
72 | +menu, | ||
73 | +nav, | ||
74 | +output, | ||
75 | +ruby, | ||
76 | +section, | ||
77 | +summary, | ||
78 | +time, | ||
79 | +mark, | ||
80 | +audio, | ||
81 | +video { | ||
82 | + margin: 0; | ||
83 | + padding: 0; | ||
84 | + border: 0; | ||
85 | + font-size: 100%; | ||
86 | + font: inherit; | ||
87 | + vertical-align: baseline; | ||
88 | +} | ||
89 | +/* HTML5 display-role reset for older browsers */ | ||
90 | +article, | ||
91 | +aside, | ||
92 | +details, | ||
93 | +figcaption, | ||
94 | +figure, | ||
95 | +footer, | ||
96 | +header, | ||
97 | +hgroup, | ||
98 | +menu, | ||
99 | +nav, | ||
100 | +section { | ||
101 | + display: block; | ||
102 | +} | ||
103 | +body { | ||
104 | + line-height: 1; | ||
105 | +} | ||
106 | +ol, | ||
107 | +ul { | ||
108 | + list-style: none; | ||
109 | +} | ||
110 | +blockquote, | ||
111 | +q { | ||
112 | + quotes: none; | ||
113 | +} | ||
114 | +blockquote:before, | ||
115 | +blockquote:after, | ||
116 | +q:before, | ||
117 | +q:after { | ||
118 | + content: ""; | ||
119 | + content: none; | ||
120 | +} | ||
121 | +table { | ||
122 | + border-collapse: collapse; | ||
123 | + border-spacing: 0; | ||
124 | +} | ||
125 | +a { | ||
126 | + all: unset; | ||
127 | + cursor: pointer; | ||
128 | +} | ||
129 | + | ||
130 | +*, | ||
131 | +input { | ||
132 | + box-sizing: border-box; | ||
133 | +} | ||
134 | + | ||
135 | +input { | ||
136 | + border: none; | ||
137 | + box-sizing: border-box; | ||
138 | + &:focus, | ||
139 | + &:active { | ||
140 | + outline: none; | ||
141 | + } | ||
142 | +} |
assets/scss/main.scss
0 → 100644
1 | +html, | ||
2 | +body { | ||
3 | + height: 100%; | ||
4 | +} | ||
5 | +body { | ||
6 | + background-color: #f5f5f5; | ||
7 | + color: $black; | ||
8 | + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, | ||
9 | + Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; | ||
10 | + font-size: 14px; | ||
11 | +} | ||
12 | + | ||
13 | +main { | ||
14 | + width: 100%; | ||
15 | + max-width: 1200px; | ||
16 | + margin: 0 auto; | ||
17 | + min-height: 70vh; | ||
18 | +} | ||
19 | + | ||
20 | +button, | ||
21 | +input:not([type="file"]), | ||
22 | +textarea, | ||
23 | +.fileUpload { | ||
24 | + padding: 7px 10px; | ||
25 | + width: 100%; | ||
26 | + border: none; | ||
27 | + border-radius: 5px; | ||
28 | + font-size: 14px; | ||
29 | + color: $black; | ||
30 | + font-weight: 600; | ||
31 | + background-color: white; | ||
32 | + max-width: 320px; | ||
33 | + resize: none; | ||
34 | + &::placeholder { | ||
35 | + font-weight: 300; | ||
36 | + color: rgba(0, 0, 0, 0.7); | ||
37 | + } | ||
38 | +} | ||
39 | + | ||
40 | +button { | ||
41 | + border: none; | ||
42 | + background-color: #3498db; | ||
43 | + color: white; | ||
44 | +} | ||
45 | + | ||
46 | +button.delete { | ||
47 | + background-color: $dark-red; | ||
48 | +} | ||
49 | + | ||
50 | +button, | ||
51 | +input[type="submit"] { | ||
52 | + cursor: pointer; | ||
53 | +} |
assets/scss/pages/home.scss
0 → 100644
1 | +.home-videos { | ||
2 | + display: grid; | ||
3 | + grid-template-columns: repeat(6, minmax(150px, 1fr)); | ||
4 | + grid-gap: 30px; | ||
5 | + .videoBlock:first-child, | ||
6 | + .videoBlock:nth-child(2) { | ||
7 | + grid-column: span 3; | ||
8 | + } | ||
9 | + .videoBlock:nth-child(3), | ||
10 | + .videoBlock:nth-child(4), | ||
11 | + .videoBlock:nth-child(5) { | ||
12 | + grid-column: span 2; | ||
13 | + } | ||
14 | +} |
assets/scss/pages/videoDetail.scss
0 → 100644
1 | +.video-detail__container { | ||
2 | + display: flex; | ||
3 | + flex-direction: column; | ||
4 | + align-items: center; | ||
5 | + .video__info { | ||
6 | + width: 100%; | ||
7 | + max-width: 850px; | ||
8 | + margin-top: 25px; | ||
9 | + button { | ||
10 | + width: 100px; | ||
11 | + margin-bottom: 25px; | ||
12 | + } | ||
13 | + .video__title, | ||
14 | + .video__views, | ||
15 | + .video__description { | ||
16 | + display: block; | ||
17 | + margin-bottom: 15px; | ||
18 | + } | ||
19 | + .video__title { | ||
20 | + font-size: 22px; | ||
21 | + font-weight: 300; | ||
22 | + } | ||
23 | + .video__views { | ||
24 | + font-size: 14px; | ||
25 | + } | ||
26 | + .video__description { | ||
27 | + font-size: 16px; | ||
28 | + } | ||
29 | + } | ||
30 | + .video__comments { | ||
31 | + margin-top: 25px; | ||
32 | + .video__comment-number { | ||
33 | + font-size: 18px; | ||
34 | + } | ||
35 | + } | ||
36 | +} |
assets/scss/partials/footer.scss
0 → 100644
1 | +.footer { | ||
2 | + margin: 50px 0; | ||
3 | + padding-top: 50px; | ||
4 | + border-top: 3px solid rgba(0, 0, 0, 0.1); | ||
5 | + width: 100%; | ||
6 | + display: flex; | ||
7 | + flex-direction: column; | ||
8 | + align-items: center; | ||
9 | + .footer__icon { | ||
10 | + color: rgba(0, 0, 0, 0.2); | ||
11 | + font-size: 40px; | ||
12 | + margin-bottom: 20px; | ||
13 | + } | ||
14 | + .footer__text { | ||
15 | + color: rgba(0, 0, 0, 0.2); | ||
16 | + font-weight: 800; | ||
17 | + text-transform: uppercase; | ||
18 | + } | ||
19 | +} |
assets/scss/partials/form.scss
0 → 100644
1 | +.form-container { | ||
2 | + width: 100%; | ||
3 | + display: flex; | ||
4 | + flex-direction: column; | ||
5 | + align-items: center; | ||
6 | + form { | ||
7 | + width: 100%; | ||
8 | + max-width: 320px; | ||
9 | + margin-bottom: 50px; | ||
10 | + input:not([type="file"]), | ||
11 | + .fileUpload { | ||
12 | + display: block; | ||
13 | + width: 100%; | ||
14 | + padding-top: 10px; | ||
15 | + padding-bottom: 10px; | ||
16 | + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); | ||
17 | + &:not(:last-child) { | ||
18 | + margin-bottom: 25px; | ||
19 | + } | ||
20 | + } | ||
21 | + textarea { | ||
22 | + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); | ||
23 | + margin-bottom: 25px; | ||
24 | + } | ||
25 | + input[type="submit"] { | ||
26 | + background-color: #3498db; | ||
27 | + color: white; | ||
28 | + } | ||
29 | + } | ||
30 | + a { | ||
31 | + max-width: 320px; | ||
32 | + width: 100%; | ||
33 | + } | ||
34 | + .fileUpload { | ||
35 | + label { | ||
36 | + font-weight: 300; | ||
37 | + margin-right: 10px; | ||
38 | + } | ||
39 | + } | ||
40 | +} |
assets/scss/partials/header.scss
0 → 100644
1 | +.header { | ||
2 | + background-color: $red; | ||
3 | + margin-bottom: 50px; | ||
4 | + .header__wrapper { | ||
5 | + padding: 5px 0px; | ||
6 | + width: 100%; | ||
7 | + margin: 0 auto; | ||
8 | + max-width: 1200px; | ||
9 | + display: grid; | ||
10 | + grid-template-columns: repeat(3, 1fr); | ||
11 | + align-items: center; | ||
12 | + .header__column { | ||
13 | + i { | ||
14 | + color: white; | ||
15 | + font-size: 30px; | ||
16 | + } | ||
17 | + &:nth-child(2) { | ||
18 | + width: 100%; | ||
19 | + justify-self: center; | ||
20 | + } | ||
21 | + &:last-child { | ||
22 | + justify-self: end; | ||
23 | + } | ||
24 | + ul { | ||
25 | + display: flex; | ||
26 | + color: white; | ||
27 | + font-weight: 600; | ||
28 | + text-transform: uppercase; | ||
29 | + a { | ||
30 | + color: inherit; | ||
31 | + text-decoration: none; | ||
32 | + } | ||
33 | + li:not(:last-child) { | ||
34 | + margin-right: 15px; | ||
35 | + } | ||
36 | + } | ||
37 | + form { | ||
38 | + width: 100%; | ||
39 | + input { | ||
40 | + padding: 7px 10px; | ||
41 | + width: 100%; | ||
42 | + border-radius: 5px; | ||
43 | + font-size: 14px; | ||
44 | + color: $black; | ||
45 | + font-weight: 600; | ||
46 | + max-width: none; | ||
47 | + &::placeholder { | ||
48 | + font-weight: 300; | ||
49 | + color: rgba(0, 0, 0, 0.7); | ||
50 | + } | ||
51 | + } | ||
52 | + } | ||
53 | + } | ||
54 | + } | ||
55 | +} |
assets/scss/partials/socialLogin.scss
0 → 100644
1 | +.social-login { | ||
2 | + width: 100%; | ||
3 | + display: flex; | ||
4 | + flex-direction: column; | ||
5 | + align-items: center; | ||
6 | + button { | ||
7 | + width: 100%; | ||
8 | + max-width: 320px; | ||
9 | + display: flex; | ||
10 | + justify-content: center; | ||
11 | + align-items: center; | ||
12 | + &:not(:last-child) { | ||
13 | + margin-bottom: 15px; | ||
14 | + } | ||
15 | + span { | ||
16 | + margin-right: 10px; | ||
17 | + font-size: 20px; | ||
18 | + } | ||
19 | + } | ||
20 | + .social-login--github { | ||
21 | + background-color: $black; | ||
22 | + color: white; | ||
23 | + } | ||
24 | + .social-login--facebook { | ||
25 | + background-color: #3a5998; | ||
26 | + color: white; | ||
27 | + } | ||
28 | +} |
assets/scss/partials/videoBlock.scss
0 → 100644
1 | -@import "config/variables"; | 1 | +@import "config/_variables.scss"; |
2 | -body { | 2 | +@import "config/reset.scss"; |
3 | - background-color: $bgColor; | 3 | +@import "main.scss"; |
4 | -} | 4 | + |
5 | +@import "partials/header.scss"; | ||
6 | +@import "partials/footer.scss"; | ||
7 | +@import "partials/form.scss"; | ||
8 | +@import "partials/socialLogin.scss"; | ||
9 | +@import "partials/videoBlock.scss"; | ||
10 | + | ||
11 | +@import "pages/home.scss"; | ||
12 | +@import "pages/videoDetail.scss"; | ... | ... |
... | @@ -2,41 +2,40 @@ import routes from "../routes"; | ... | @@ -2,41 +2,40 @@ import routes from "../routes"; |
2 | 2 | ||
3 | // 회원가입 -> 완료 -> 홈화면으로 Redirect | 3 | // 회원가입 -> 완료 -> 홈화면으로 Redirect |
4 | export const getJoin = (req, res) => { | 4 | export const getJoin = (req, res) => { |
5 | - res.render("join", { pageTitle: "Join" }); | 5 | + res.render("join", { pageTitle: "Join" }); |
6 | }; | 6 | }; |
7 | export const postJoin = (req, res) => { | 7 | export const postJoin = (req, res) => { |
8 | - const { | 8 | + const { |
9 | - body: { name, email, password, password2 }, | 9 | + body: { name, email, password, password2 }, |
10 | - } = req; | 10 | + } = req; |
11 | - if (password !== password2) { | 11 | + if (password !== password2) { |
12 | - res.status(400); | 12 | + res.status(400); |
13 | - res.render("join", { pageTitle: "Join" }); | 13 | + res.render("join", { pageTitle: "Join" }); |
14 | - } else { | 14 | + } else { |
15 | - // To Do: Register User | 15 | + // To Do: Register User |
16 | - // To Do: Log user in | 16 | + // To Do: Log user in |
17 | - res.redirect(routes.home); | 17 | + res.redirect(routes.home); |
18 | - } | 18 | + } |
19 | }; | 19 | }; |
20 | 20 | ||
21 | export const getLogin = (req, res) => | 21 | export const getLogin = (req, res) => |
22 | - res.render("login", { pageTitle: "Login" }); | 22 | + res.render("login", { pageTitle: "Login" }); |
23 | export const postLogin = (req, res) => { | 23 | export const postLogin = (req, res) => { |
24 | - res.redirect(routes.home); | 24 | + res.redirect(routes.home); |
25 | }; | 25 | }; |
26 | 26 | ||
27 | -//로그아웃을 클릭하면 LogOut페이지로 가는 것 대신에, 로그아웃을 처리한 후 | 27 | +// 로그아웃을 클릭하면 LogOut페이지로 가는 것 대신에, 로그아웃을 처리한 후 |
28 | // home 페이지로 Redirect로 표현할 것이다. | 28 | // home 페이지로 Redirect로 표현할 것이다. |
29 | -//즉, 초반에 만들어둔 logout.pug는 삭제해도 좋다. | 29 | +// 즉, 초반에 만들어둔 logout.pug는 삭제해도 좋다. |
30 | export const logout = (req, res) => { | 30 | export const logout = (req, res) => { |
31 | - //res.render("logout", { pageTitle: "Logout" }); | 31 | + // res.render("logout", { pageTitle: "Logout" }); |
32 | - res.redirect(routes.home); | 32 | + res.redirect(routes.home); |
33 | }; | 33 | }; |
34 | 34 | ||
35 | - | ||
36 | export const users = (req, res) => res.render("users", { pageTitle: "Users" }); | 35 | export const users = (req, res) => res.render("users", { pageTitle: "Users" }); |
37 | export const userDetail = (req, res) => | 36 | export const userDetail = (req, res) => |
38 | - res.render("userDetail", { pageTitle: "User Detail" }); | 37 | + res.render("userDetail", { pageTitle: "User Detail" }); |
39 | export const editProfile = (req, res) => | 38 | export const editProfile = (req, res) => |
40 | - res.render("editProfile", { pageTitle: "Edit Profile" }); | 39 | + res.render("editProfile", { pageTitle: "Edit Profile" }); |
41 | export const changePassword = (req, res) => | 40 | export const changePassword = (req, res) => |
42 | - res.render("changePassword", { pageTitle: "Change Password" }); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
41 | + res.render("changePassword", { pageTitle: "Change Password" }); | ... | ... |
... | @@ -86,7 +86,7 @@ export const postEditVideo = async (req, res) => { | ... | @@ -86,7 +86,7 @@ export const postEditVideo = async (req, res) => { |
86 | try { | 86 | try { |
87 | // id를 찾아서 body를 얻어와야 한다. 비디오 수정에서 제목과 설명을 가져와야 하기 때문이다. | 87 | // id를 찾아서 body를 얻어와야 한다. 비디오 수정에서 제목과 설명을 가져와야 하기 때문이다. |
88 | // mongoose엔 우리의 id가 없어서 _id : id로 찾아줘야 한다. | 88 | // mongoose엔 우리의 id가 없어서 _id : id로 찾아줘야 한다. |
89 | - await Video.findOneAndUpdate({ _id: id }, { title, description }); //title:title == title | 89 | + await Video.findOneAndUpdate({ _id: id }, { title, description }); // title:title == title |
90 | // 이렇게 하면 default로 얻어온 제목 및 내용을 수정하여 form을 전송하면 해당 내용으로 업데이트 된다. | 90 | // 이렇게 하면 default로 얻어온 제목 및 내용을 수정하여 form을 전송하면 해당 내용으로 업데이트 된다. |
91 | res.redirect(routes.videoDetail(id)); | 91 | res.redirect(routes.videoDetail(id)); |
92 | } catch (error) { | 92 | } catch (error) { | ... | ... |
1 | +import dotenv from "dotenv"; | ||
1 | import app from "./app"; // app.js에서 export default app했기 때문에 불러올 수 있다. | 2 | import app from "./app"; // app.js에서 export default app했기 때문에 불러올 수 있다. |
2 | import "./db"; | 3 | import "./db"; |
3 | -import dotenv from "dotenv"; | ||
4 | -dotenv.config(); | ||
5 | import "./models/Video"; | 4 | import "./models/Video"; |
6 | import "./models/Comment"; | 5 | import "./models/Comment"; |
7 | 6 | ||
7 | +dotenv.config(); | ||
8 | + | ||
8 | const PORT = process.env.PORT || 80; | 9 | const PORT = process.env.PORT || 80; |
9 | 10 | ||
10 | const handleListening = () => { | 11 | const handleListening = () => { |
11 | - console.log(`✅ Listening on: http://localhost:${PORT}`); | 12 | + console.log(`✅ Listening on: http://localhost:${PORT}`); |
12 | - //call-back함수. | 13 | + // call-back함수. |
13 | - //PORT를 listen하기 시작할 때 함수를 호출해준다. | 14 | + // PORT를 listen하기 시작할 때 함수를 호출해준다. |
14 | }; | 15 | }; |
15 | 16 | ||
16 | -app.listen(PORT, handleListening); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
17 | +app.listen(PORT, handleListening); | ... | ... |
... | @@ -4,14 +4,14 @@ import routes from "./routes"; | ... | @@ -4,14 +4,14 @@ import routes from "./routes"; |
4 | const multerVideo = multer({ dest: "uploads/videos/" }); | 4 | const multerVideo = multer({ dest: "uploads/videos/" }); |
5 | 5 | ||
6 | export const localsMiddleware = (req, res, next) => { | 6 | export const localsMiddleware = (req, res, next) => { |
7 | - res.locals.siteName = "my Youtube"; | 7 | + res.locals.siteName = "my Youtube"; |
8 | - res.locals.routes = routes; | 8 | + res.locals.routes = routes; |
9 | - res.locals.user = { | 9 | + res.locals.user = { |
10 | - isAuthenticated: true, | 10 | + isAuthenticated: false, |
11 | - id: 1, | 11 | + id: 1, |
12 | - }; | 12 | + }; |
13 | - next(); | 13 | + next(); |
14 | }; | 14 | }; |
15 | 15 | ||
16 | export const uploadVideo = multerVideo.single("videoFile"); | 16 | export const uploadVideo = multerVideo.single("videoFile"); |
17 | -//single에 들어간 videoFile은 upload.pug의 file 부분 input name | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
17 | +// single에 들어간 videoFile은 upload.pug의 file 부분 input name | ... | ... |
1 | import mongoose from "mongoose"; | 1 | import mongoose from "mongoose"; |
2 | -//video 댓글에 대한 Database | 2 | +// video 댓글에 대한 Database |
3 | 3 | ||
4 | const CommentSchema = new mongoose.Schema({ | 4 | const CommentSchema = new mongoose.Schema({ |
5 | - text: { | 5 | + text: { |
6 | - type: String, | 6 | + type: String, |
7 | - required: "Text is required" | 7 | + required: "Text is required", |
8 | - }, //이러한 형태를 configuration object라 한다. | 8 | + }, // 이러한 형태를 configuration object라 한다. |
9 | - createdAt: { | 9 | + createdAt: { |
10 | - type: Date, | 10 | + type: Date, |
11 | - default: Date.now | 11 | + default: Date.now, |
12 | - } | 12 | + }, |
13 | - /* | 13 | + /* |
14 | - , | 14 | + |
15 | video: { //video와 comment를 연결하는 방법 #2 | 15 | video: { //video와 comment를 연결하는 방법 #2 |
16 | type: mongoose.Schema.Types.ObjectId, //그 다음 어느 model에서 온 id인지 알려줘야 한다. | 16 | type: mongoose.Schema.Types.ObjectId, //그 다음 어느 model에서 온 id인지 알려줘야 한다. |
17 | ref: "Video" | 17 | ref: "Video" |
18 | - }*/ | 18 | + } |
19 | + */ | ||
19 | }); | 20 | }); |
20 | 21 | ||
21 | const model = mongoose.model("Comment", CommentSchema); | 22 | const model = mongoose.model("Comment", CommentSchema); |
22 | -export default model; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
23 | +export default model; | ... | ... |
1 | -//DB 모델을 작성한다. | 1 | +// DB 모델을 작성한다. |
2 | -//Video 자체를 DB에 저장하진 않을 것이다. 즉, byte를 저장하는 것이 아니라 video의 link를 저장한다. | 2 | +// Video 자체를 DB에 저장하진 않을 것이다. 즉, byte를 저장하는 것이 아니라 video의 link를 저장한다. |
3 | import mongoose from "mongoose"; | 3 | import mongoose from "mongoose"; |
4 | 4 | ||
5 | const VideoSchema = new mongoose.Schema({ | 5 | const VideoSchema = new mongoose.Schema({ |
6 | - fileUrl: { | 6 | + fileUrl: { |
7 | - type: String, | 7 | + type: String, |
8 | - required: "File URL is required" //url이 없으면 오류메시지 출력 | 8 | + required: "File URL is required", // url이 없으면 오류메시지 출력 |
9 | + }, | ||
10 | + title: { | ||
11 | + type: String, | ||
12 | + required: "Title is required", | ||
13 | + }, | ||
14 | + description: String, | ||
15 | + views: { | ||
16 | + type: Number, | ||
17 | + default: 0, // 비디오를 처음 생성하면 views를 0으로.. | ||
18 | + }, | ||
19 | + createdAt: { | ||
20 | + type: Date, | ||
21 | + default: Date.now, // 현재 날짜를 반환하는 function | ||
22 | + }, | ||
23 | + // video와 comment를 연결하는 방법 #1 | ||
24 | + comments: [ | ||
25 | + { | ||
26 | + type: mongoose.Schema.Types.ObjectId, // 그 다음 어느 model에서 온 id인지 알려줘야 한다. | ||
27 | + ref: "Comment", | ||
9 | }, | 28 | }, |
10 | - title: { | 29 | + ], |
11 | - type: String, | ||
12 | - required: "Title is required" | ||
13 | - }, | ||
14 | - description: String, | ||
15 | - views: { | ||
16 | - type: Number, | ||
17 | - default: 0 //비디오를 처음 생성하면 views를 0으로.. | ||
18 | - }, | ||
19 | - createdAt: { | ||
20 | - type: Date, | ||
21 | - default: Date.now //현재 날짜를 반환하는 function | ||
22 | - }, | ||
23 | - //video와 comment를 연결하는 방법 #1 | ||
24 | - comments: [{ | ||
25 | - type: mongoose.Schema.Types.ObjectId, //그 다음 어느 model에서 온 id인지 알려줘야 한다. | ||
26 | - ref: "Comment" | ||
27 | - }] | ||
28 | }); | 30 | }); |
29 | // 이제 이 스키마를 이용하여 model을 만들어준다. | 31 | // 이제 이 스키마를 이용하여 model을 만들어준다. |
30 | -//모델의 이름은 "Video" | 32 | +// 모델의 이름은 "Video" |
31 | const model = mongoose.model("Video", VideoSchema); | 33 | const model = mongoose.model("Video", VideoSchema); |
32 | export default model; | 34 | export default model; |
33 | -//모델이 만들어짐을 알리기 위해 init.js에 import해준다. | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
35 | +// 모델이 만들어짐을 알리기 위해 init.js에 import해준다. | ... | ... |
... | @@ -4,7 +4,7 @@ | ... | @@ -4,7 +4,7 @@ |
4 | "description": "make Youtube Website", | 4 | "description": "make Youtube Website", |
5 | "main": "app.js", | 5 | "main": "app.js", |
6 | "scripts": { | 6 | "scripts": { |
7 | - "dev:server": "nodemon --exec babel-node init.js --delay 2", | 7 | + "dev:server": "nodemon --exec babel-node init.js --delay 2 --ignore 'scss'", |
8 | "dev:assets": "WEBPACK_ENV=development webpack -w", | 8 | "dev:assets": "WEBPACK_ENV=development webpack -w", |
9 | "build:assets": "WEBPACK_ENV=production webpack" | 9 | "build:assets": "WEBPACK_ENV=production webpack" |
10 | }, | 10 | }, | ... | ... |
1 | import express from "express"; | 1 | import express from "express"; |
2 | import routes from "../routes"; | 2 | import routes from "../routes"; |
3 | import { | 3 | import { |
4 | - getUpload, | 4 | + getUpload, |
5 | - postUpload, | 5 | + postUpload, |
6 | - videoDetail, | 6 | + videoDetail, |
7 | - getEditVideo, | 7 | + getEditVideo, |
8 | - postEditVideo, | 8 | + postEditVideo, |
9 | - deleteVideo, | 9 | + deleteVideo, |
10 | } from "../controllers/videoController"; | 10 | } from "../controllers/videoController"; |
11 | import { uploadVideo } from "../middlewares"; | 11 | import { uploadVideo } from "../middlewares"; |
12 | -//export const videoRouter = express.Router(); 이렇게하면 이 변수만 export하게 된다. | 12 | +// export const videoRouter = express.Router(); 이렇게하면 이 변수만 export하게 된다. |
13 | const videoRouter = express.Router(); | 13 | const videoRouter = express.Router(); |
14 | 14 | ||
15 | -//Upload | 15 | +// Upload |
16 | videoRouter.get(routes.upload, getUpload); | 16 | videoRouter.get(routes.upload, getUpload); |
17 | videoRouter.post(routes.upload, uploadVideo, postUpload); | 17 | videoRouter.post(routes.upload, uploadVideo, postUpload); |
18 | 18 | ||
... | @@ -26,4 +26,4 @@ videoRouter.post(routes.editVideo(), postEditVideo); | ... | @@ -26,4 +26,4 @@ videoRouter.post(routes.editVideo(), postEditVideo); |
26 | // Video Delete | 26 | // Video Delete |
27 | videoRouter.get(routes.deleteVideo(), deleteVideo); | 27 | videoRouter.get(routes.deleteVideo(), deleteVideo); |
28 | 28 | ||
29 | -export default videoRouter; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
29 | +export default videoRouter; | ... | ... |
... | @@ -19,44 +19,44 @@ const EDIT_VIDEO = "/:id/edit"; | ... | @@ -19,44 +19,44 @@ const EDIT_VIDEO = "/:id/edit"; |
19 | const DELETE_VIDEO = "/:id/delete"; | 19 | const DELETE_VIDEO = "/:id/delete"; |
20 | 20 | ||
21 | const routes = { | 21 | const routes = { |
22 | - home: HOME, | 22 | + home: HOME, |
23 | - join: JOIN, | 23 | + join: JOIN, |
24 | - login: LOGIN, | 24 | + login: LOGIN, |
25 | - logout: LOGOUT, | 25 | + logout: LOGOUT, |
26 | - search: SEARCH, | 26 | + search: SEARCH, |
27 | - users: USERS, | 27 | + users: USERS, |
28 | - userDetail: (id) => { | 28 | + userDetail: (id) => { |
29 | - if (id) { | 29 | + if (id) { |
30 | - return `/users/${id}`; | 30 | + return `/users/${id}`; |
31 | - } else { | 31 | + } else { |
32 | - return USER_DETAIL; | 32 | + return USER_DETAIL; |
33 | - } | 33 | + } |
34 | - }, | 34 | + }, |
35 | - editProfile: EDIT_PROFILE, | 35 | + editProfile: EDIT_PROFILE, |
36 | - changePassword: CHANGE_PASSWORD, | 36 | + changePassword: CHANGE_PASSWORD, |
37 | - videos: VIDEOS, | 37 | + videos: VIDEOS, |
38 | - upload: UPLOAD, | 38 | + upload: UPLOAD, |
39 | - videoDetail: (id) => { | 39 | + videoDetail: (id) => { |
40 | - if (id) { | 40 | + if (id) { |
41 | - return `/videos/${id}`; | 41 | + return `/videos/${id}`; |
42 | - } else { | 42 | + } else { |
43 | - return VIDEO_DETAIL; | 43 | + return VIDEO_DETAIL; |
44 | - } | 44 | + } |
45 | - }, | 45 | + }, |
46 | - editVideo: (id) => { | 46 | + editVideo: (id) => { |
47 | - if (id) { | 47 | + if (id) { |
48 | - return `/videos/${id}/edit`; | 48 | + return `/videos/${id}/edit`; |
49 | - } else { | 49 | + } else { |
50 | - return EDIT_VIDEO; | 50 | + return EDIT_VIDEO; |
51 | - } | 51 | + } |
52 | - }, | 52 | + }, |
53 | - deleteVideo: (id) => { | 53 | + deleteVideo: (id) => { |
54 | - if (id) { | 54 | + if (id) { |
55 | - return `/videos/${id}/delete`; | 55 | + return `/videos/${id}/delete`; |
56 | - } else { | 56 | + } else { |
57 | - return DELETE_VIDEO; | 57 | + return DELETE_VIDEO; |
58 | - } | 58 | + } |
59 | - }, | 59 | + }, |
60 | }; | 60 | }; |
61 | 61 | ||
62 | -export default routes; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
62 | +export default routes; | ... | ... |
This diff is collapsed. Click to expand it.
... | @@ -3,9 +3,11 @@ extends layouts/main | ... | @@ -3,9 +3,11 @@ extends layouts/main |
3 | block content | 3 | block content |
4 | .form-container | 4 | .form-container |
5 | form(action=routes.editProfile, method="post") | 5 | form(action=routes.editProfile, method="post") |
6 | - label(for="avatar") Avatar | 6 | + .fileUpload |
7 | - input(type="file", id="avatar", name="avatar") | 7 | + label(for="avatar") Avatar |
8 | + input(type="file", id="avatar", name="avatar") | ||
8 | input(type="text", placeholder="Name", name="name") | 9 | input(type="text", placeholder="Name", name="name") |
9 | input(type="email", placeholder="Email", name="email") | 10 | input(type="email", placeholder="Email", name="email") |
10 | input(type="submit", value="Update Profile") | 11 | input(type="submit", value="Update Profile") |
11 | - a.form-container__link(href=`/users${routes.changePassword}`) Change Password | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
12 | + a.form-container__link(href=`/users${routes.changePassword}`) | ||
13 | + button Change Password | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -6,4 +6,5 @@ block content | ... | @@ -6,4 +6,5 @@ block content |
6 | input(type="text", placeholder="Title", name="title", value=video.title) | 6 | input(type="text", placeholder="Title", name="title", value=video.title) |
7 | textarea(name="description", placeholder="Description")=video.description | 7 | textarea(name="description", placeholder="Description")=video.description |
8 | input(type="submit", value="Update Video") | 8 | input(type="submit", value="Update Video") |
9 | - a.form-container__link.form-container__link--delete(href=routes.deleteVideo(video.id)) Delete Video | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
9 | + a.form-container__link(href=routes.deleteVideo(video.id)) | ||
10 | + button.delete Delete Video | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -2,11 +2,77 @@ extends layouts/main | ... | @@ -2,11 +2,77 @@ extends layouts/main |
2 | include mixins/videoBlock | 2 | include mixins/videoBlock |
3 | 3 | ||
4 | block content | 4 | block content |
5 | - .videos | 5 | + .home-videos |
6 | each item in videos | 6 | each item in videos |
7 | +videoBlock({ | 7 | +videoBlock({ |
8 | id: item.id, | 8 | id: item.id, |
9 | title : item.title, | 9 | title : item.title, |
10 | views: item.views, | 10 | views: item.views, |
11 | videoFile:item.fileUrl | 11 | videoFile:item.fileUrl |
12 | - }) | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
12 | + }) | ||
13 | + +videoBlock({ | ||
14 | + id:item.id, | ||
15 | + title:item.title, | ||
16 | + views:item.views, | ||
17 | + videoFile:item.fileUrl | ||
18 | + }) | ||
19 | + +videoBlock({ | ||
20 | + id:item.id, | ||
21 | + title:item.title, | ||
22 | + views:item.views, | ||
23 | + videoFile:item.fileUrl | ||
24 | + }) | ||
25 | + +videoBlock({ | ||
26 | + id:item.id, | ||
27 | + title:item.title, | ||
28 | + views:item.views, | ||
29 | + videoFile:item.fileUrl | ||
30 | + }) | ||
31 | + +videoBlock({ | ||
32 | + id:item.id, | ||
33 | + title:item.title, | ||
34 | + views:item.views, | ||
35 | + videoFile:item.fileUrl | ||
36 | + }) | ||
37 | + +videoBlock({ | ||
38 | + id:item.id, | ||
39 | + title:item.title, | ||
40 | + views:item.views, | ||
41 | + videoFile:item.fileUrl | ||
42 | + }) | ||
43 | + +videoBlock({ | ||
44 | + id:item.id, | ||
45 | + title:item.title, | ||
46 | + views:item.views, | ||
47 | + videoFile:item.fileUrl | ||
48 | + }) | ||
49 | + +videoBlock({ | ||
50 | + id:item.id, | ||
51 | + title:item.title, | ||
52 | + views:item.views, | ||
53 | + videoFile:item.fileUrl | ||
54 | + }) | ||
55 | + +videoBlock({ | ||
56 | + id:item.id, | ||
57 | + title:item.title, | ||
58 | + views:item.views, | ||
59 | + videoFile:item.fileUrl | ||
60 | + }) | ||
61 | + +videoBlock({ | ||
62 | + id:item.id, | ||
63 | + title:item.title, | ||
64 | + views:item.views, | ||
65 | + videoFile:item.fileUrl | ||
66 | + }) | ||
67 | + +videoBlock({ | ||
68 | + id:item.id, | ||
69 | + title:item.title, | ||
70 | + views:item.views, | ||
71 | + videoFile:item.fileUrl | ||
72 | + }) | ||
73 | + +videoBlock({ | ||
74 | + id:item.id, | ||
75 | + title:item.title, | ||
76 | + views:item.views, | ||
77 | + videoFile:item.fileUrl | ||
78 | + }) | ... | ... |
... | @@ -3,5 +3,8 @@ mixin videoBlock(video = {}) | ... | @@ -3,5 +3,8 @@ mixin videoBlock(video = {}) |
3 | a(href=routes.videoDetail(video.id)) | 3 | a(href=routes.videoDetail(video.id)) |
4 | video.videoBlock__thumbnail(src=video.videoFile, controls=true) | 4 | video.videoBlock__thumbnail(src=video.videoFile, controls=true) |
5 | h4.videoBlock__title=video.title | 5 | h4.videoBlock__title=video.title |
6 | - h6.videoBlock__views=video.views | 6 | + if video.views === 1 |
7 | + h6.videoBlock__views 1 view | ||
8 | + else | ||
9 | + h6.videoBlock__views #{video.views} views | ||
7 | 10 | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | header.header | 1 | header.header |
2 | - .header__column | 2 | + .header__wrapper |
3 | - a(href=routes.home) | 3 | + .header__column |
4 | - i.fab.fa-youtube | 4 | + a(href=routes.home) |
5 | - .header__column | 5 | + i.fab.fa-youtube |
6 | - form(action=routes.search, method="get") | 6 | + .header__column |
7 | - input(type="text", placeholder="Search by term...", name="term") | 7 | + form(action=routes.search, method="get") |
8 | - .header__column | 8 | + input(type="text", placeholder="Search by term...", name="term") |
9 | - ul | 9 | + .header__column |
10 | - if !user.isAuthenticated | 10 | + ul |
11 | - li | 11 | + if !user.isAuthenticated |
12 | - a(href=routes.join) Join | 12 | + li |
13 | - li | 13 | + a(href=routes.join) Join |
14 | - a(href=routes.login) Log In | 14 | + li |
15 | - else | 15 | + a(href=routes.login) Log In |
16 | - li | 16 | + else |
17 | - a(href=`/videos${routes.upload}`) Upload | 17 | + li |
18 | - li | 18 | + a(href=`/videos${routes.upload}`) Upload |
19 | - a(href=routes.userDetail(user.id)) Profile | 19 | + li |
20 | - li | 20 | + a(href=routes.userDetail(user.id)) Profile |
21 | - a(href=routes.logout) Log out | 21 | + li |
22 | + a(href=routes.logout) Log out | ... | ... |
... | @@ -11,7 +11,6 @@ block content | ... | @@ -11,7 +11,6 @@ block content |
11 | +videoBlock({ | 11 | +videoBlock({ |
12 | title : item.title, | 12 | title : item.title, |
13 | views: item.views, | 13 | views: item.views, |
14 | - videoFile:item.videoFile | ||
15 | videoFile:item.videoFile, | 14 | videoFile:item.videoFile, |
16 | id: item.id | 15 | id: item.id |
17 | }) | 16 | }) |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -3,8 +3,9 @@ extends layouts/main | ... | @@ -3,8 +3,9 @@ extends layouts/main |
3 | block content | 3 | block content |
4 | .form-container | 4 | .form-container |
5 | form(action=`/videos${routes.upload}`, method="post", enctype="multipart/form-data") | 5 | form(action=`/videos${routes.upload}`, method="post", enctype="multipart/form-data") |
6 | - label(for="file") Video File | 6 | + div.fileUpload |
7 | - input(type="file", id="file", name="videoFile", required=true, accept = "video/*") | 7 | + label(for="file") Video File |
8 | + input(type="file", id="file", name="videoFile", required=true, accept = "video/*") | ||
8 | input(type="text", placeholder="Title", name="title", required=true) | 9 | input(type="text", placeholder="Title", name="title", required=true) |
9 | textarea(name="description", placeholder="Description", required=true) | 10 | textarea(name="description", placeholder="Description", required=true) |
10 | input(type="submit", value="Upload Video") | 11 | input(type="submit", value="Upload Video") |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -4,10 +4,15 @@ block content | ... | @@ -4,10 +4,15 @@ block content |
4 | .video__player | 4 | .video__player |
5 | video(src=`/${video.fileUrl}`) | 5 | video(src=`/${video.fileUrl}`) |
6 | .video__info | 6 | .video__info |
7 | - a(href=routes.editVideo(video.id)) Edit video | 7 | + a(href=routes.editVideo(video.id)) |
8 | + button Edit video | ||
8 | h5.video__title=video.title | 9 | h5.video__title=video.title |
9 | span.video__views=video.views | 10 | span.video__views=video.views |
10 | p.video__description=video.description | 11 | p.video__description=video.description |
12 | + if video.views === 1 | ||
13 | + span.video__views 1 view | ||
14 | + else | ||
15 | + span.video__views #{video.views} views | ||
11 | .video__comment | 16 | .video__comment |
12 | if video.comments.length === 1 | 17 | if video.comments.length === 1 |
13 | span.video__comment-number 1 comment | 18 | span.video__comment-number 1 comment | ... | ... |
-
Please register or login to post a comment