Ctrl에 유저 데이터 로직을 사용하다 보니 너무 가시성이 떨어진다
ctrl에서 user.login()만 해도 실행되게 하고 싶다
⇒ 모듈화를 하자
그러기 위해선 User를 이용해 ⇒ user라는 인스턴스를 만들고
사용자가 넘긴 body 데이터를 기본적으로 가지고 있게 만들것이다
즉, 이 User에는 사용자의 특성을 갖게 만들것이다
User.js
"use strict"
const UserStorage = require("./UserStorage");
class User {
constructor(body) {
this.body = body; // home.ctrl 에서 new User(req.body) // 이렇게 보내면 User에 body변수에 req.body가 바인딩됨
}
login() {
//
}
}
module.exports = User;
이게 뭔말이냐면 로그인할때 생성되는 데이터는 User의 인스턴스가 갖게 되야한다(로직을 해야하니깐)
그렇게 되면 User의 인스턴스가 응답을 반환하고, ctrl은 그 응답을 json으로 다시 보내주면된다
id, password만 인증 받고 싶지만
데이터가 리스트 통채로 날라오기 때문에 조건으로 처리할수가 없다
=> 요청한 데이터들만 가져오는 메서드를 만들자(UserStorage에서)
User.js
"use strict"
const UserStorage = require("./UserStorage");
class User {
constructor(body) {
this.body = body; // home.ctrl 에서 new User(req.body) // 이렇게 보내면 User에 body변수에 req.body가 바인딩됨
}
login() {
const body = this.body // 겹치니깐 묶자
const { id, password } = UserStorage.getUserInfo(body.id);
if (id) {
if (id === body.id && password === body.password) {
return { success: true };
}
return { success: false, msg: "비밀번호가 틀렸습니다" }
}
return { success: false, msg: "존재하지 않는 아이디 입니다." }
}
}
module.exports = User;
UserStorage.js
"use strict";
class UserStorage {
static #users = {
id: ["양상우", "김개발", "박부장"],
password: ["1234", "1234", "123456"],
name: ['양땅우', '땡땡이', '곱창먹고싶다'] /
}
static getUsers(...fields) {
const users = this.#users;
const newUsers = fields.reduce((newUsers, field) => {
if (users.hasOwnProperty(field)) {
newUsers[field] = users[field]
}
return newUsers
}, {});
return newUsers
}
static getUserInfo(id) {
const users = this.#users;
const idx = users.id.indexOf(id); // id의 인덱스를 뽑음
const usersKeys = Object.keys(users); // user를 받아오고 그 key 값 들만 배열로 만들거임 => [id, password, name]
const userInfo = usersKeys.reduce((newUser, info) => {
newUser[info] = users[info][idx];
return newUser;
}, {});
return userInfo;
//
}
}
module.exports = UserStorage;
UserStorage.getUserInfo("id") 라 하면 그에 해당하는 데이터만 보내게 하자
userkeys 가 순차적으로 들어감 id이면 ["양상우", "김개발", "박부장"] 이렇게 그렇고 그 id (양상우)에서 idx(0), 즉 해당 하는 (0) 의 user 키의 값들 id[0], password[0], name[o]을 newUser에 순차적으로 넣음
home.ctrl
const procces = {
login: (req, res) => {
const user = new User(req.body)
const response = user.login();
return res.json(response);
},
}
이렇게 하면 UserStorage에서 정제한 데이터를 ⇒ User에서 인증을 하고 인증 결과를 ⇒ ctrl에서 json으로 응답 ⇒ login 패치로 응답을 받아서 ⇒ 표시한다
템플릿을 제공해준다
를 변경해보자
register.ejs 와 register를 라우트 해주자(라우트 + 컨트롤)
<!DOCTYPE html>
<html lang="ko">
<!--브라우저가 언어 인식-->
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/home/login.css">
<script src="/js/home/login.js" defer></script>
<title>Document</title>
</head>
<body>
<div class="login-page">
<div class="form">
<form class="login-form">
<input id="id" type="text" placeholder="아이디" />
<input id="name" type="text" placeholder="이름" />
<input id="password" type="password" placeholder="비밀번호" />
<input id="confirm-password" type="password" placeholder="비밀번호 확인" />
<button>SIGN UP</button>
<p class="message">Already registered? <a href="/login">login</a></p>
</form>
</div>
</div>
</body>
</html>
기존 login이랑 다를게 없다
하지만 몇개만 수정하자
form 으로 감싸져 있음 그렇다, buttom을 p 태그로 바꾸고, id에 buttom을 주자
<p id="button">SIGN UP</p>
이렇게 하면 css도 변경해줘야한다
.form #button {
font-family: "Roboto", sans-serif;
text-transform: uppercase;
outline: 0;
background: #4CAF50;
width: 80%; 이것도 바꾸자
border: 0;
/* 가운데 정렬 0 auto */
margin: 0 auto;
padding: 15px;
color: #FFFFFF;
font-size: 14px;
-webkit-transition: all 0.3 ease;
transition: all 0.3 ease;
cursor: pointer;
}
"use strict";
const id = document.querySelector("#id"),
name = document.querySelector("#name"),
password = document.querySelector("#password"),
confirmPassword = document.querySelector("#confirm-password"),
registerBtn = document.querySelector("#button");
registerBtn.addEventListener("click", register);
if (!id.value) return alert("아이디를 입력해주십시오")
if (password.value !== confirmPassword.value) {
return alert("비밀번호가 일치하지 않습니다.")
}
function register() {
const req = {
id: id.value,
name: name.value,
password: password.value,
};
console.log(req)
// 지금은 register API가 없다, 다음 강의에 만든다
fetch("/register", {
method: "POST",
headers: {
"Content-Type": "application/json" //
},
body: JSON.stringify(req)
})
.then((res) => res.json())
.then((res) => {
if (res.success) {
location.href = "/login";
} else {
alert(res.msg)
}
})
.catch((err) => {
console.error(new Error("회원가입중 에러 발생"))
})
}
로그인과 비슷하지만, 변수할당 제대로 해주자. 이 다음 API를 만들자
get은 저번에 했으니 post을 구현할것 index.js에 라우트 설정 → home.ctrl에서 post설정 → User에서 데이터 저장 요청 → UserStorage에서 데이터 저장하는 로직 설정
//index.js
router.post("/register", ctrl.procces.register); // 레지스터 post 추가
//home.ctrl.js
const procces = {
login: (req, res) => {
const user = new User(req.body)
const response = user.login();
return res.json(response);
},
// 추가
register: (req, res) => {
const user = new User(req.body)
const response = user.register();
return res.json(response);
},
}
//User.js
register() { // 단순하게 데이터 저장되게
const client = this.body
const response = UserStorage.save(client)
return response; // body를 좀더 직관적이게 client로 변경
}
//UserStorage.js
static save(userInfo) {
const users = this.#users
users.id.push(userInfo.id);
users.name.push(userInfo.name);
users.password.push(userInfo.password);
return { success: true }
}
= 단순하게 UserStorage에 있는 user 객체에 push를 해봤자, 서버가 꺼지면 사라지는 데이터가 된다, 진짜 파일에 저장할수있게 해야한다
src/databases/users.json
으로 DB를 만들자
{
"id": [
"양상우",
"김개발",
"박부장"
],
"password": [
"1234",
"1234",
"123456"
],
"name": [
"양땅우",
"땡땡이",
"곱창먹고싶다"
]
}
UserStorage에 있던 users를 여기로 옮겨 분리하자
사용 방법은 fs를 읽어오면된다, fs는 node.js의 모듈인데, 파일 시스템 처리를 담당한다
const fs = require("fs")
현재 기존 users 데이터를 사용하는 UserStorage에서 fs로 user을 가져오 변수 user에 넣으면 기존에 사용하던 코드들을 사용할수 있다
"use strict";
// 파일을 접근하기위해 파일 가져옴
const fs = require("fs").promises // 이렇게 하면 fs도 프로미스로 반환 하ㅔ 된다
class UserStorage {
static getUsers(...fields) {
// const users = this.#users;
const newUsers = fields.reduce((newUsers, field) => {
if (users.hasOwnProperty(field)) {
newUsers[field] = users[field]
}
return newUsers
}, {});
return newUsers
}
static getUserInfo(id) {
// 현재 경로 루트는 app.js이다
// 리드파일도 프로미스를 반환한다.
fs.readFile("./src/databases/users.json", (err, data) => { // 첫번쨰 인자에 경로, 두번째 인자에 에러 처리와 data를 선언
if (err) throw err;
//console.log(JSON.parse(data)) // 이런식으로 파싱 안하면 16진수로 나온다
const users = JSON.parse(data) // data는 fs.readFile의 콜백함수의 지역변수 이기 때문에 사용할려면, 스코프 범위를 잘맞춰야 한다
const idx = users.id.indexOf(id); // 하지만 맞춰도, userInfo가 리턴하는것을 전역으로도 가지고 있어야 User가 사용할수 있다. -> 프로미스의 async await를 사용하자
const usersKeys = Object.keys(users);
const userInfo = usersKeys.reduce((newUser, info) => {
newUser[info] = users[info][idx];
return newUser;
}, {});
return userInfo;
})
}
// 단순하게 여기서 push 로 users에 넣어도 당장은 되지만 서버가 꺼지면 해당 데이터가 사라져 버린다.
static save(userInfo) {
// const users = this.#users
users.id.push(userInfo.id);
users.name.push(userInfo.name);
users.password.push(userInfo.password);
return { success: true }
}
}
module.exports = UserStorage;
readFile함수가 파일을 읽어오기 전에 return되기 때문! ⇒ 프로미스, async await를 사용하자
const fs = require("fs").promises
// 이렇게 하면 fs도 프로미스로 반환 하게 된다
이렇게 된다면 then, catch로 후속처리가 가능하다
추가로 안에 로직은 은닉화해서 만들자
.. 생략...
static getUserInfo(id) {
// 프로미스르 반환하는 fs
return fs.readFile("./src/databases/users.json")
.then((data) => {
return this.#getUserInfo(data, id) // UserStorage에 this 바인딩
})
.catch(console.error);
... 생략 ...
"use strict";
// 파일을 접근하기위해 파일 가져옴
const fs = require("fs").promises // 이렇게 하면 fs도 프로미스로 반환 하ㅔ 된다
class UserStorage {
// 가독성을 위해서 분리함, 암묵적인 값은 되도록이면 최상단으로 가자
static #getUserInfo(data, id) {
const users = JSON.parse(data)
const idx = users.id.indexOf(id);
const usersKeys = Object.keys(users);
const userInfo = usersKeys.reduce((newUser, info) => {
newUser[info] = users[info][idx];
return newUser;
}, {});
// 프로미스의 pending은 아직 데이터를 다 읽어오지 못했다는 뜻 => fs가 못읽음 => userInfo는 잘못된 값이 들어감 => await를 이용하자
return userInfo;
}
static getUsers(...fields) {
// const users = this.#users;
const newUsers = fields.reduce((newUsers, field) => {
if (users.hasOwnProperty(field)) {
newUsers[field] = users[field]
}
return newUsers
}, {});
return newUsers
}
static getUserInfo(id) {
// 프로미스르 반환하는 fs
return fs.readFile("./src/databases/users.json")
.then((data) => {
return this.#getUserInfo(data, id) // UserStorage에 this 바인딩
})
.catch(console.error);
}
static save(userInfo) {
// const users = this.#users
users.id.push(userInfo.id);
users.name.push(userInfo.name);
users.password.push(userInfo.password);
return { success: true }
}
}
module.exports = UserStorage;
하지만 이렇게 해도 undefind
가 계속 생긴다 왤까 ⇒ getUserInfo를 호출 하는 User.js에 login 함수는 기다리지 않고 바로 반환 받기 때문이다
⇒ login함수 를 asycn로 만들어 호출할때 await를 사용할수 있게 만들자
User.js
.. 생략
async login() {
const client = this.body
const { id, password } = await UserStorage.getUserInfo(client.id); // fs를 읽어서 userInfo를 생성하는데. userInfo값이 제대로 올떄까지 기다려
// 하지만 ctrl에서 기다려 해줘야함
if (id) {
if (id === client.id && password === client.password) {
return { success: true };
}
return { success: false, msg: "비밀번호가 틀렸습니다" }
}
return { success: false, msg: "존재하지 않는 아이디 입니다." }
}
.. 생략
마찬가지로 User.js에 login 값을 받아 오는 home.ctrl의 process또한 마찬가지로..
home.ctrl
.. 생략
const procces = {
login: async (req, res) => {
const user = new User(req.body)
const response = await user.login();
return res.json(response);
},
register: (req, res) => {
const user = new User(req.body)
const response = user.register();
return res.json(response);
},
}
.. 생략
이제 잘된다
POST로 오는 과정을 봐보자
register.js 에서 post → index.js 에서 라우트 → home.ctrl에서 procces 를 통해 reponse를 받아옴 → 이 respones는 user.register 함수를 이용해 받아오는데 → User.js 클래스를 통해 받아오고, 해당 로직은 UserStorage.js에 명시되어있다
UserStorage.js
.. 생략
//로직 분리
static #getUsers(data, isAll, fields) {
const users = JSON.parse(data)
// 싹다
if (isAll) return users; // isAll이 true이면 user를 반환한다
// fields 해당하는것만
const newUsers = fields.reduce((newUsers, field) => {
if (users.hasOwnProperty(field)) {
newUsers[field] = users[field]
}
return newUsers
}, {});
return newUsers
}
// 파일 시스템 형태로 바꿔주자
static getUsers(isAll, ...fields) { // isall그후 id, password.... 등등ㄷ으
return fs.
readFile("./src/databases/users.json")
.then((data) => {
return this.#getUsers(data, isAll, fields)
})
.catch(console.error);
}
.. 생략
static async save(userInfo) {
const users = await this.getUsers(true) // DB에 있는 사용자 데이터를 가져와서 users에 가지고 있음
//const data = "a"; // 덮어 씌우짐, 그래서 읽어온 다음에 우리가 추가하고 싶은 데이터를 넣어야한다\
if (users.id.includes(userInfo.id)) { // 등록되어있지 않는 id 만 가입할수 있게
throw "이미 존재하는 아이디 입니다." // 하지만 이 에러는 reponse 가게 된다, 이 함수의 호출자가 reponse에 있기에.. 그래서 return => throw 해줘야함 //Error 형태로 보내면 object로 가니 문자열만 보내자
}
users.id.push(userInfo.id);
users.name.push(userInfo.name);
users.password.push(userInfo.password)
// 이렇게 임시적으로 추가된 데이터를 -> 찐으로 넣음
fs.writeFile("./src/databases/users.json", JSON.stringify(users)) // 문자열 타입으로 data를 다시 보내야하기때문에..
return { success: true }
}
그냥 fs.writeFile을 이용하면 파이썬 처럼 추가 모드가 없기때문에, 기존 데이터를 덮어씌우는 형태가 된다
그래서 기존 파일에 있는데이터를 저장한뒤에, 사용자가 입력한 데이터를 push하고 해당 데이터를 넣으면 된다
기존 파일에 있는 user는 getusers를 통해 가져오는데, isAll을 추가해서, true일 경우 filed로 하나하나 넣을 필요 없이 전체를 다 가져올수있게 해주자
그리고 id가 이미 있으면 문자열을 리턴해주자
⇒ 에러 메세지로 보내면 object로 보내지기 때문에 문자열로 주자,
⇒ object가 err인데 err로 받으면 좋겠지만 해당 에러 객체는 respones로 반환이 되기 때문이다.
User.js
.. 생략
async register() {
try {
const client = this.body
const response = await UserStorage.save(client)
return response;
} catch (err) {
return { success: false, msg: err } // 이렇게 msg로 보내야하는데, err가 object로 넘어오기때문에 응답을 alert로 보여줘야하는 JS에서는 걍 object만 나온다 그래서 그냥 던지지 말고. UserStoage에서 thorw로만 보내자
}
}
이제 마찬가지로 ctrl까지 await를 잘 걸어주면 된다