# HTML form 작업
# Join: Log In HTML
# Join & Login 화면 form
// views/join.pug
extends layouts/main
block content
.form-container
form(action=routes.join, method="post")
input(type="text", name="name", placeholder="Full Name")
input(type="email", name="email", placeholder="Email")
input(type="password", name="password", placeholder="Password")
input(type="password", name="password2", placeholder="Verified Password")
input(type="submit", value="Join Now")
include partials/socialLogin
Join 화면은 form 태그를 이용해서 이름, 이메일, 비밀번호, 비밀번호 확인, 가입하기 버튼을 만들었다.
// views/login.pug
extends layouts/main
block content
.form-container
form(action=routes.login, method="post")
input(type="email", name="email", placeholder="Email")
input(type="password", name="password", placeholder="Password")
input(type="submit", value="Log In")
include partials/socialLogin
Login 화면은 form 태그를 이용해서 이메일, 비밀번호, 로그인하기 버튼을 만들었다.
form을 추가할 때 method가 get이면 모든 정보가 url에 보이게 된다.
Join, Login은 정보를 비밀로 유지해야 하기 때문에 method를 post로 한다.
# Social login Parcial
//views/partials/socialLogin.pug
.social-login
// BEM 방법론
button.social-login--github
span
i.fab.fa-github
| Continue with Github
button.social-login--facebook
span
i.fab.fa-facebook
| Continue with Facebook
소셜 로그인 버튼을 Join과 Login 화면에 띄우기 위해서 parcial 하나를 만들었다.
# Change Profile In HTML
Profile을 Updata하는 Edit Profile 페이지의 form을 작성해보자.
// views/editProfile.pug
extends layouts/main
block content
.form-container
form(action=`/users${routes.editProfile}`, method="post")
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="submit", value="Updata Profile")
a.form-container__link(href=`/users${routes.changePassword}`) Change Password
action 경로 지정을 위해서 routes파일에 지정된 경로를 가져다 썼다.
action=`/users${routes.editProfile}`
와 같이 경로를 설정해 줄 수 있다.
# Change Password In HTML
// views/changePassword.pug
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="newPassword", placeholder="New Password")
input(type="password", name="newPassword1", placeholder="Verify New Password")
input(type="submit", value="Change Password")
# Edit Profile In HTML
// views/editProfile.pug
extends layouts/main
block content
.form-container
form(action=`/users${routes.editProfile}`, method="post")
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="submit", value="Updata Profile")
a.form-container__link(href=`/users${routes.changePassword}`) Change Password
# Edit Video In HTML
// views/editVideo.pug
extends layouts/main
block content
.form-container
form(action=`/videos${routes.editVideo}`, method="post")
input(type="text", placeholder="Title", name="title")
textarea(name="description", placeholder="Description")
input(type="submit", value="Update Video")
a.form-container__link.form-container__link--delete(href=`/videos${routes.deleteVideo}`) Delete Video
# Home In HTML
# Fake DB 생성
메인 화면에서는 fake database에 임의의 정보들을 아래와 같이 만들어 두었다.
export const videos = [
{
id: 32345,
title: "Viedo Awesome",
description: "This is somthing I love",
views: 24,
viedoFile: "https://youtu.be/8h3OCbx7ln8",
creater: {
id: 11252,
name: "Junhyuk",
email: "hshine1226@gmail.com",
},
},
{
id: 34533,
title: "Viedo Amazing",
description: "This is somthing I love",
views: 24,
viedoFile: "https://youtu.be/8h3OCbx7ln8",
creater: {
id: 11252,
name: "Junhyuk",
email: "hshine1226@gmail.com",
},
},
{
id: 12351,
title: "Viedo Super",
description: "This is somthing I love",
views: 24,
viedoFile: "https://youtu.be/8h3OCbx7ln8",
creater: {
id: 11252,
name: "Junhyuk",
email: "hshine1226@gmail.com",
},
},
{
id: 98273,
title: "Viedo Cool",
description: "This is somthing I love",
views: 24,
viedoFile: "https://youtu.be/8h3OCbx7ln8",
creater: {
id: 11252,
name: "Junhyuk",
email: "hshine1226@gmail.com",
},
},
{
id: 69828,
title: "Viedo Nice",
description: "This is somthing I love",
views: 24,
viedoFile: "https://youtu.be/8h3OCbx7ln8",
creater: {
id: 11252,
name: "Junhyuk",
email: "hshine1226@gmail.com",
},
},
];
# Fake DB Import
그리고 위의 db를 가져오기 위해서 아래와 같이 import를 해주었다.
// controllers/videoController.js
import { videos } from "../db";
export const home = (req, res) => {
res.render("home", { pageTitle: "Home", videos });
};
# Fake DB 사용
db를 html에서 사용하기 위해서 컨트롤러에서 받아온 template variables를 받아와서 사용한다.
또한 each 문을 이용해서 리스트의 각각의 오브젝트들을 순회할 수 있다.
// views/home.pug
extends layouts/main
block content
.videos
each video in videos
h1= video.title
p= video.description
# Upload Video In HTML
// views/upload.pug
extends layouts/main
block content
.form-container
form(action=`/videos${routes.upload}`, method="post")
label(for="file") Video File
input(type="file", id='file', name='file')
input(type="text", placeholder="Title", name="title")
textarea(name="description", placeholder="Description" cols="30", rows="3")
input(type="submit", value="Upload Video")
// views/partials/header.pug
// ...
.header__column
ul
if !user.isAuthenticated
li
a(href=routes.join) Join
li
a(href=routes.login) Log In
else
li
a(href=`/videos${routes.upload}`) Upload
li
// local variable에서 받아온 user.id를 userDetail로 보내준다.
a(href=routes.userDetail(user.id)) Profile
li
a(href=routes.logout) Log Out
# get, post 분리
// controllers/videoController.js
export const getUpload = (req, res) =>
res.render("upload", { pageTitle: "Upload" });
export const postUpload = (req, res) => {
const {
body: { file, title, description },
} = req;
// To Do: Upload and save video
res.redirect(routes.videoDetail(324393));
};
비디오를 업로드 할 때는 그 비디오의 id가 존재할 것이다. 따라서 비디오 업로드를 할 시에 생성된 비디오 id를 videoDetail 페이지로 redirect해줄 것이다.
일단 지금 postUpload Controller에서 보내주는 값은 가짜 데이터 값이다. 나중에는 진짜 데이터 값을 보낼 것이다.
# Router 연결
// routers/videoRouter.js
videoRouter.get(routes.upload, getUpload);
videoRouter.post(routes.upload, postUpload);
# Search Video In HTML
// controllers/videoController.js
import { videos } from "../db";
export const search = (req, res) => {
const {
query: { term: searchingBy },
} = req;
res.render("search", { pageTitle: "Search", searchingBy, videos });
};
위와 같이 videoController에서 template variable로 fakeDB에 있는 videos를 넘겨준다.
// views/search.pug
extends layouts/main
include mixins/videoBlock
block content
.search__header
h3 Searching for: #{searchingBy}
.search__videos
each video in videos
+videoBlock({
title:video.title,
views: video.views,
videoFile: video.videoFile
})
home화면과 마찬가지로 search.pub 파일에서는 미리 작성해둔 비디오를 보여주는 Mixin에 받아온 videos 데이터베이스의 정보를 넘겨준다.
# Join In HTML
# join controller 분리
join.pug 파일에서는 post 방식으로 form을 전송한다. 따라서 userController에서 기존의 join 컨트롤러를 getJoin, postJoin으로 분리해준다.
import routes from "../routes";
// join화면에 접근하면 render해주는 controller
export const getJoin = (req, res) => {
res.render("join", { pageTitle: "Join" });
};
// join을 post 방식으로 받아올 때의 컨트롤러이다.
export const postJoin = (req, res) => {
const {
body: { name, email, password, password2 },
} = req;
// 첫번째 암호와 두번째 암호가 같지 않다면 잘못된 요청이라는 의미의 400번의 status code를 보내준다.
if (password !== password2) {
res.status(400); // 잘못된 요청
res.render("join", { pageTitle: "Join" });
} else {
// To Do: Register User
// To Do: Log user in
res.redirect(routes.home);
}
};
아래는 join.pug이다. 이 form은 post 방식으로 되어있다.
// views/join.pug
extends layouts/main
block content
.form-container
form(action=routes.join, method="post")
input(type="text", name="name", placeholder="Full Name")
input(type="email", name="email", placeholder="Email")
input(type="password", name="password", placeholder="Password")
input(type="password", name="password2", placeholder="Verified Password")
input(type="submit", value="Join Now")
include partials/socialLogin
# Controller route 연결
분리된 컨트롤러를 연결하기 위해서 globalRouter에서 routes를 연결해준다.
// routers/globalRouter.js
import {
postJoin,
getJoin,
login,
logout,
} from "../controllers/userController";
globalRouter.get(routes.join, getJoin);
globalRouter.post(routes.join, postJoin);
# BodyParser Middleware
만약 postJoin에서 console.log(req.body)
를 통해 request의 body를 확인해보면, 회원가입을 할 때, 아래와 같이 req.body에서 받아온 정보들이 출력되게 된다.
{
name: '최준혁',
email: 'hshine1226@gmail.com',
password: 'Mz49SzK8ccWnyv@',
password2: 'Mz49SzK8ccWnyv@'
}
이 정보들은 bodyparser라는 미들웨어를 통해 받아오는 것이다.
// app.js
import bodyParser from "body-parser";
// ...
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// ...
# Login In HTML
로그인 역시 join과 마찬가지로 컨트롤러를 분리하면 된다.
# login controller 분리
// controllers/userController.js
export const getLogin = (req, res) =>
res.render("login", { pageTitle: "Login" });
export const postLogin = (req, res) => {
const {
body: { email, password },
} = req;
// 로그인이 성공했다면 redirect해준다.
// 나중에 DB가 연결되면 입력 비밀번호와 DB 비밀번호가 같은지 검사할 것이다..
res.redirect(routes.home);
};
# Router 연결
// routers/globalRouter.js
import {
postJoin,
getJoin,
logout,
getLogin,
postLogin,
} from "../controllers/userController";
globalRouter.get(routes.login, getLogin);
globalRouter.post(routes.login, postLogin);
# Header 페이지에서 isAuthenticated 검사
일단 가짜 유저 정보를 만들어보자.
물론 나중에는 실제로 정보를 가져와서 사용할 것이지만, 일단은 미들웨어에 가짜로 변수를 만들어 둘 것이다.
아래와 같이 middleware.js에서 local variable로 가짜 유저 정보를 만든다.
// middlewares.js
import routes from "./routes";
export const localsMiddleware = (req, res, next) => {
// ...
// db 생기기 전까지 쓰일 가짜 정보 Local Variable
res.locals.user = {
isAuthenticated: true,
id: 1,
};
next();
};
그리고 만든 가짜 유저 정보(Local Variable)를 통해서 isAuthenticated가 true일 때와, false일 때, Header에 표시되는 정보를 다르게 할 것이다.
// views/partials/header.pug
.header__column
ul
// isAuthenticated가 false이면 join, login화면
// true이면 upload, profile, logout을 리스트로 출력하게 한다.
// middleware의 Local Variable을 사용해서 임시로 작업한다.
if !user.isAuthenticated
li
a(href=routes.join) Join
li
a(href=routes.login) Log In
else
li
a(href=routes.upload) Upload
li
// 미들웨어에서 설정한 user.id가 url에 들어가도록 한다.
a(href=routes.userDetail(user.id)) Profile
li
a(href=routes.logout) Log Out
아래는 userDetail을 함수 형태로 바꾸는 것이다.
// routes.js
// ...
// Users
// ...
const USER_DETAIL = "/:id";
// Videos
// ...
const VIDEO_DETAIL = "/:id";
// Router Object
const routes = {
// ...
// userDetail: USER_DETAIL
// 아래의 userDetail 함수는 id를 인자로 입력 받는다.
// 그리고 만약 id가 있다면 `/users/${id}`를 반환한다.
// id가 없다면 USER_DETAIL을 반환한다.
userDetail: (id) => {
if (id) {
return `/users/${id}`;
} else {
return USER_DETAIL;
}
},
// videoDetail역시 id와 함께 바뀌어야 하기 때문에 아래와 같이 수정한다.
videoDetail: (id) => {
if (id) {
return `/video/${id}`;
} else {
return VIDEO_DETAIL;
}
},
};
export default routes;
# Video Detail In HTML
/views/mixins/videoBlock.pug
mixin videoBlock(video = {})
.videoBlock
a(href=routes.videoDetail(video.id))
video.videoBlock__thumnail(src=video.videoFile, controls='true')
h4.videoBlock__title=video.title
h6.videoBlock__views=video.views
비디오를 클릭하면 VideoDetail 페이지로 이동하게 하기 위해서 videoBlock mixin에 a 태그를 추가했다. 그리고 videoDetail 함수의 인자로 id를 입력했다.
그리고 videoBlock을 사용하는 곳에서 인자를 입력할 때 id를 추가로 보내준다.
/// views/home.pug
extends layouts/main
include mixins/videoBlock
block content
.videos
each video in videos
+videoBlock({
id: video.id,
title:video.title,
views: video.views,
videoFile: video.videoFile
})
그리고 videoRouter.js에서 videoDetail을 실행해준다. routes.videoDetail()
왜냐하면 videoDetail은 함수이기 때문이다.
import express from "express";
import routes from "../routes";
import {
videoDetail,
// ...
} from "../controllers/videoController";
// ...
// videoDetail은 함수이기 때문에 실행해주어야 한다.
videoRouter.get(routes.videoDetail(), videoDetail);
// ...
export default videoRouter;
# Logout In HTML
로그아웃을 하면 로그아웃 처리를 하고 홈페이지로 Redirect하는 기능이다.
로그아웃 처리는 아직 구현하지 않는다.
// controllers/userController.js
import routes from "../routes";
// ...
export const logout = (req, res) => {
// To do: Process Log Out
res.redirect(routes.home);
};
// ...