Others/42Seoul

BLIND CLONE CODING 1일차

tonyhan18 2022. 1. 22. 11:51
728x90

BLIND CLONE CODING 1일차

앞으로 3일간 BLIND 웹사이트를 구현할것이다.

 

개힘들겠다.

 

폴더 구성은

BLIND(server)

 

서버부터 구성하자

```

mkdir server

cd server/

npm init

->blind-server 나머지는 엔터

 

npx express-generator BLIND

npm install express cors

npm i -g nodemon

```

cannot use import statement outside a module 가 계속 발생했다.

결론은 module이라는 타입으로 돌리겠다는 것을 넣어주었어야 했던거였다.

 

결국에는 어찌저찌 해결했다. app.js는 공유하겠다.

 

 

// 강의
const express = require("express");
const cors = require("cors");
const app = express();
const PORT = 3000;

app.use(cors());
app.use(express.json()); // POST가 왔을때 req.body에 담개해준다.
app.use(express.urlencoded({ extended: true })); //?
app.get("/", (req, res, next) => {
  res.send("Server is running!");
});

app.listen(PORT, "localhost", () => {
  console.log(`App listening at http://localhost:${PORT}`);
});

 

 

몽구스를 설치하자

 

mongoose/schema/mode.js

 

npm install mongoose

 

user.js -> 사용자 정보

company.js -> 회사 정보

article.js -> 유저가 글쓰기 할 수 있도록

comment.js

index.js -> 전체조율

하면 스키마에서 필요한 부분들은 모두 만든거다

 

const mongoose = require("mongoose");
const Crypto = require("crypto");
let Schema = mongoose.Schema;

const User = new Schema({
  email: { type: String, required: true, unique: true },
  hashedPassword: { type: String, required: true },
  salt: { type: String, required: true },
  createdAt: { type: Date, default: Date.now, required: true },
  nickname: { type: String, required: true, unique: true },
  company: { type: Schema.Types.ObjectId, ref: "Company", required: true },
});

module.exports = User;

user.js

 

const mongoose = require("mongoose");
let Schema = mongoose.Schema;

const Company = new Schema({
  name: { type: String, required: true, unique: true },
  createdAt: { type: Date, default: Date.now, required: true },
  reviewScore: { type: Number, default: 3 },
  imgAddress: { type: String, default: null },
  realtimeScore: { type: Number, default: 0 },
});

module.exports = Company;

 

company.js

 

const mongoose = require("mongoose");
//import AutoIncrement from "mongoose-sequence"; //무한 스크롤
const AutoIncrement = require("mongoose-sequence")(mongoose);
let Schema = mongoose.Schema;

const Article = new Schema({
  author: { type: Schema.Types.ObjectId, ref: "User" },
  title: { type: String, required: true, unique: true },
  content: { type: String, required: true },
  board: { type: Schema.Types.ObjectId, ref: "Board", required: true },
  createdAt: { type: Date, default: Date.now, required: true },

  // 동적으로 변화할 수 있는 데이터
  viewCount: { type: Number, default: 0 },
  thumbupCount: { type: Number, default: 0 },
  thumbdownCount: { type: Number, default: 0 },
  commentCount: { type: Number, default: 0 },

  // (옵션) : 사용자가 게시글에 추가할 수 있는 데이터
  articleImgAddress: { type: String, default: null },
  memtion: { type: Schema.Types.ObjectId, ref: "User" },
});

Article.plugin(AutoIncrement, { inc_field: "id" }); //무한 스크롤

module.exports = Article;

Article.js

이때 실시간 인기 회사를 구현하기 위해서 조절을 해주어야한다. 하나는 Company 안에 데이터를 넣는거다. 위에서는 realtimeScore이다.

 

문제는 몽고DB를 쓸때 자주바뀌는 것과 안바뀌는 것들을 구분해야한다. 자주바뀌는 것들은 따로 빼주어야한다. 계속 IO 가 발생하기 때문이다. 하지만 일단은 배우는 중이니 여기에다가 넣어주자

 

mention 부분은 다른 db를 참조해야하기 때문에 mongoose schema의 ObjectId의 ref:User 테이블을 참조해야한다.

 

comment와 같은 경우 Article 안에 배열로 만들수도 있기는 하다. 하지만 commentCount는 댓글 갯수를 미리 정해놓지 않으면 매번 DB 참조해서 갯수 계산하고 복잡한게 한두가지가 아니다. 그래서 미리 Schema 안에 정의를 하는 것이다.

 

게시판의 종류를 처리해주어야한다. 그래서 게시판도 만들어주자

 

const mongoose = require("mongoose");
let Schema = mongoose.Schema;

// 댓글
const Comment = new Schema({
  author: { type: Schema.Types.ObjectId, ref: "User" },
  article: { type: Schema.Types.ObjectId, ref: "Article" },
  content: { type: String, required: true },
  createdAt: { type: Date, default: Date.now, required: true },

  // 동적으로 변화할 수 있는 데이터
  viewCount: { type: Number, default: 0 },
  thumbupCount: { type: Number, default: 0 },
  thumbdownCount: { type: Number, default: 0 },
  commentCount: { type: Number, default: 0 },

  // (옵션) : 사용자가 게시글에 추가할 수 있는 데이터
  articleImgAddress: { type: String, default: null },
});

module.exports = Comment;

Comment.js

 

문제는 댓글이다. 댓글의 댓글, 대댓글은 굉장히 만들기 어려운 구조이다.

 

일단 여기까지만 만들고 board 부터 작업하자

const mongoose = require("mongoose");
let Schema = mongoose.Schema;

const Board = new Schema({
  title: { type: String, required: true, unique: true },
  url: { type: String, required: true, unique: true },
  createdAt: { type: Date, default: Date.now, required: true },
});

module.exports = Board;

board.js

 

마지막으로 위와같이 Schema로 변경될 수 있도록 만들자

 

 

이제 대댓글 구현인데

1. Comment Schema 안에 대댓글 배열형태로 넣기

2. 스키마 하나더 만들기

 

const mongoose = require("mongoose");
let Schema = mongoose.Schema;

// 대댓글
const Reply = new Schema({
  author: { type: Schema.Types.ObjectId, ref: "User" },
  comment: { type: Schema.Types.ObjectId, ref: "Comment" },
  content: { type: String, required: true },
  createdAt: { type: Date, default: Date.now, required: true },

  // 동적으로 변화할 수 있는 데이터
  viewCount: { type: Number, default: 0 },
  thumbupCount: { type: Number, default: 0 },
  thumbdownCount: { type: Number, default: 0 },

  // (옵션) : 사용자가 게시글에 추가할 수 있는 데이터
  replyImgAddress: { type: String },
});

module.exports = Reply;

Reply.js

 

이렇게 분리를 하는데에는 장/단점이 있다. 데이터 사이즈가 작으면 그냥 리스트로 넣으면 되는데

대댓글이 몇백개이면 이걸 한번에 처리하지 못한다.

 

 

이제 model을 손보자

 

즉시 실행함수로 db 를 실행하게끔 처리했다.

 

위와같이 for문으로 mongoose의 model들을 모두 가지고 왔다.

 

문제는 무한로딩인데 무한로딩을 구현하기 위해서는 게시판의 게시글들 안에 인덱스값이 존재해야한다. 이거를 어디에서 어디까지 찾아내고의 방법이 필요한데 이 방법이 간단하게는 마지막 글의 id를 잡아서 그것보다 생성시간 이전걸 글고오는 방법이 있을 수 있고

 

또는 pagination 기능을 사용하는 방법이 있다.

mongoose pagination하면 mongoose pagination v2라는 기능이 있다.

 

페이지네이션이라는 것은 각각의 페이지를 계산하는데 한 페이지가 10개다라면 10개씩 잘라서 이건 1페이지이다. 또 잘라서 이건 2페이지다 하는 방식이다. 이걸 사용하면 auto increment라는 것이 생긴다.

 

문제는 이걸쓰면 프론트의 페이지와 백의 페이지가 다를 수 있다. 그래서 몽구스 페이지네이션 기능은 페이지의 숫자값으로 되어 있는 경우는 페이지네이션을 써야하고 그게아니면 last index 잡아서 이전거를 가지고 와야한다.

그런데 시간기준으로 searching하면 오래걸린다. 그래서 mongoose에도 auto increment를 가지고 오면된다.

 

mongoose-sequence - npm (npmjs.com)

 

mongoose-sequence

Very generic autoincrement plugin for mongoose

www.npmjs.com

```

npm install --save mongoose-sequence

```

 

const mongoose = require("mongoose");
//import AutoIncrement from "mongoose-sequence"; //무한 스크롤
const AutoIncrement = require("mongoose-sequence")(mongoose);
let Schema = mongoose.Schema;

const Article = new Schema({
  author: { type: Schema.Types.ObjectId, ref: "User" },
  title: { type: String, required: true, unique: true },
  content: { type: String, required: true },
  board: { type: Schema.Types.ObjectId, ref: "Board", required: true },
  createdAt: { type: Date, default: Date.now, required: true },

  // 동적으로 변화할 수 있는 데이터
  viewCount: { type: Number, default: 0 },
  thumbupCount: { type: Number, default: 0 },
  thumbdownCount: { type: Number, default: 0 },
  commentCount: { type: Number, default: 0 },

  // (옵션) : 사용자가 게시글에 추가할 수 있는 데이터
  articleImgAddress: { type: String, default: null },
  memtion: { type: Schema.Types.ObjectId, ref: "User" },
});

Article.plugin(AutoIncrement, { inc_field: "id" }); //무한 스크롤

module.exports = Article;

 

article에 위쪽과 아래쪽에 위와같이 추가해주자

 

이제 준비가 되었으니 DB를 만들어보자

MongoDB Cloud | MongoDB

 

MongoDB Cloud

MongoDB Cloud는 최신 애플리케이션을 위한 통합 데이터 플랫폼으로, 글로벌 클라우드 데이터베이스, 검색, 데이터레이크, 모바일 및 애플리케이션 서비스를 포함하고 있습니다.

www.mongodb.com

 

MongoDB Atlas에서 처리해주자

 

대충 위 사이트가서 가입하고 cloud를 만들면 된다.

Database Access

root

패스워드는 42서울 패스워드

 

커넷트 눌러라

 

Allow Access from Anywhere

 

connection method는 application

 

코드 복사해서 앱으로 넘어가자

 

위와같이 connect 부분에 주소를 넣었고 패스워드는 가렸다.

그리고 옵션들을 넣었는데 ??? 이건 나중에 데이터를 수정할때 에러가 나지 않게끔하기 위해 넣는 부분들이다.

 

 

한번 몽고db가 잘 작동하는지 확인을 해보자

했더니 망할 mongoose-sequence에서 문제가 겁나게 생긴다. 이래서 import from을 안쓰는 건지도 모르겠다. require로 바꾸자

 

지금까지의 과정을 쭉 따라왔다면 수정할 필요가 없다. 이미 미래의 내가 수정된 걸로 블로그에 글을 썼기 때문이다. 그냥 진행하자

 

암튼 결론은 위와같이 Warning을 제외하고 잘 작동한다.

 

가장 지랄 맞은것은 이 놈의 MongoParseError이다. 이게 몽구스 버전 6.0이상부터는 항상 useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false로 설정되어 있다고 한다. 

 

즉 이 지랄난거를

 

그냥 깔끔하게 처내면 된다. 정말 지랄맞긴하다

 

[MongoDB] MongoParseError: options usecreateindex, usefindandmodify are not supported (velog.io)

 

[MongoDB] MongoParseError: options usecreateindex, usefindandmodify are not supported

다들 MongoDB를 사용할 때 연결을 도와주는 몽구스를 사용 할 것이다. 이런 식으로 연결을 할 것이다. 이때 만약 몽구스 버전이 6.0이상이라면 MongoParseError: options usecreateindex, usefindandmodify are not s

velog.io

 

관련공부를 또 해주어야겠다.

 

 

// 강의
const express = require("express");
const cors = require("cors");
const API = require("./api");
const app = express();
const PORT = 3000;

app.use(cors());
app.use(express.json()); // POST가 왔을때 req.body에 담개해준다.
app.use(express.urlencoded({ extended: true })); //?
app.get("/", (req, res, next) => {
  res.send("Server is running!");
});

app.get("/article/:id", (req, res, next) => {
  API.getArticle;
});

app.listen(PORT, "localhost", () => {
  console.log(`App listening at http://localhost:${PORT}`);
});

 

수정의 수정을 거치어 마참내 제대로 실행된다. 망할

 

---

 

이제 기능을 좀 분리해보자

 

api/index.js를 routes/index.js파일 안으로 다 옮기었다.

그걸 또 수정해서 위와같이 router로 받아오게끔 했다.

 

그리고 index.js를 복사해서 article.js로 이름을 바꾸고

index.js를 모두 지우고 위와같이 파일들이 모일 수 있도록 만들자

 

충고하건데 절때 무슨일이 있어도 routes에 있는거 긁어올떄는 {}를 쓰지마자 

 

그리고 위와같이 article을 사용하겠다고 추가해주자

 

그리고 article.js에 Comment도 가져올 수 있도록 추가해주자.

 

사실 다 만들어주어야한다.

 

이제 문제점은 user 의  hashedPassword이다. 원래라면 일반적인 email login을 할텐데 email login 시 패스워드는 일정한 규칙에 따라서 hash로 만들어준다.(단방향 암호화) 그걸 hashedPassword에 넣어준다. 그리고 그건 스키마로 내장 함수로 생성할때 작동되도록 만든다.

 

crypto를 사용해보자

 

패스워드는 항상 SHA256 방식에 따라서 절때 바뀌지 않는 문자열로 바뀐다. 그런데 해커들은 이 문자열중 일부를 가지고 있으니 salt를 암호 뒤에 붙여버리는 것이다.

 

대충이런느낌으로 규칙을 넣는것이다. 중요한건 규칙 한번 넣으면 수정못한다. 바꾸면 ㅈ된다.

 

const mongoose = require("mongoose");
const Crypto = require("crypto");
let Schema = mongoose.Schema;

const User = new Schema({
  email: { type: String, required: true, unique: true },
  hashedPassword: { type: String, required: true },
  salt: { type: String, required: true },
  createdAt: { type: Date, default: Date.now, required: true },
  nickname: { type: String, required: true, unique: true },
});

// password 가상 선택자
User.virtual("password").set((password) => {
  this._password = password;
  this.salt = this.makeSalt();
  this.hashedPassword = this.encryptPassword(password);
});

// Salt 생성 함수
User.method("makesalt", () => {
  return Math.round(new Date().valueOf() * Math.random() + "THISISSECRET");
});

// 해시된 비밀번호 생성 함수
User.method("encryptPassword", (plainPassword) => {
  return crypto
    .createHmac("sha256", this.salt)
    .update(plainPassword)
    .digets("hex");
});

// 사용자 인증 함수
User.method("authenticate", (plainPassword) => {
  const inputPassword = this.encryptPassword(plainPassword);
  return (inputPassword === this.hashedPassword);
});

module.exports = User;

이때 사용되는 virtual을 password 가상 선택자라고 부른다. 몽구스에서 새로운 데이터를 만들기 위해 save를 한다. 이 save 하기 전에 객체 형태로 값을 넣는다. 그때 password라는 값을 받는것이다. 그러면 내부적으로 처리를 돌리는 구조인 것이다.

 

보면 여기에는 해쉬 패스워드를 생성하는 것이 있으니 이걸 인증하는 Authentication 함수도 함께 적어주었다.

 

보면 알겠지만 JWT가 안들어가 있다. 이것도 나중에 추가해주자.

const { User } = require("../mongoose/model.js");
var express = require("express");
var router = express.Router();

// 로그인 요청
router.post("/user/login", async (req, res) => {
  const { email, password } = req.body;
  const loginUser = await User.find({ email: email });
  if (!loginUser._id) {
    return res.send({
      error: true,
      msg: "존재하지 않는 이메일",
    });
  }
  const correctPassword = await loginUser.authenticate(password);
  if (!correctPassword) {
    return res.send({
      error: true,
      msg: "비밀번호 불일치",
    });
  }
  res.send({ email: loginUser.email, nickname: loginUser.nickname });
});

module.exports = router;

 

위쪽 함수를 받아오는 곳에서는 위와같이 처리된다.

 

이번에는 사용자 생성 부분도 만들어보자

위와같이 사용자로부터 데이터를 받아 User을 새롭게 저장을 하는데... 문제는 company이다.

 

그래서 company를 추가로 입력하는 부분을 만들려고 보니까... 이미지를 처리해야한다. 뭔가 겁나 복잡하다.

 

일단 컴퍼니는 위와같이 짜놓자 

 

index.js에 짜놓은 부분들을 가져오고

 

app.js에서 처리하게끔 만들자

 

이제 postmain으로 테스트해보자

 

실재 db에 들어가보니 삼성전자가 들어가 있다.

 

위와같이 해서 user에 쐈는데 서버에서 에러났다.

 

이게 User Schema쪽 문제인데 this가 들어간 것들은 화살표로 처리한 것과 function으로 처리한것이 다르다고 한다.

 

어떻게 다른걸까...

 

암튼 전송 성공해서 어떻게 되었는지 확인하러 가자.

 

흠...  salt가 생성 안되었다

 

코드에 오타가 있었다.

 

makeSalt 함수를 생성시 문자열이 숫자랑 붙어있어서 에러나 났었다

 

암튼 이번에는 아래쪽걸로 잘 들어갔다.

 

오늘은 여기까지만 하고 내일 일찍 일어나서 남은 부분 구현해야 겠다. 그만 자자 오늘 시간 이상한데에 너무 많이 섰다.

728x90