728x90

nodejs 는 

chrome v8 javascrpit엔진으로 빌드된 javascript런타임이다.

 

( "자바스크립트 런타임"이란 "자바스크립트 코드를 실행하는 환경"을 말한다. 

자바스크립트는 일반적으로 브라우저에서 실행된다. 

다른 환경에서도 실행될 수 있지만 이를 위해서는 자바스크립트 엔진을 다른 프로그램에 내장하여 자바스크립트 코드를 실행할 수 있는 환경을 만들어야한다. 자바스크립트 엔진과 함께 필요한 라이브러리, 모듈, api 등을 포함하는 것이 자바스크립트 런타임이다. 

)

 

브라우저에서 실행되는 자바스크립트와 달리 nodejs 는 파일 시스템,네트워크 등과 같은

서버 사이드 기능을 제공하여, 서버 사이드 웹 개발을 위한 환경을 제공한다. 

이를 통해 *비동기식 I/O, 이벤트 기반 처리 모델 등을 활용해 높은 성능의 웹 애플리케이션을 만들 수 있다.

 

또한 노드js는 npm이라는 패키지 매니저를 제공한다. node package manager

이를 통해 라이브러리, 모듀르 프레임워크 등의 다양한 패키지를 쉽게 설치하고 관리할 수 있다.

 

 

(I/O(Input/Output)는 컴퓨터에서 데이터를 읽고 쓰는 작업을 말합니다. 이때, 데이터를 읽고 쓰는 과정에서 CPU는 다른 작업을 수행할 수 없는 차단(blocking) 상태에 빠지게 됩니다. 이러한 문제를 해결하기 위해 비동기식 I/O(Asynchronous I/O)가 등장했습니다.)

 

+

express는 

노드Js를 위한 웹 프레임워크이다. 간결하고 유연한 구조를 가지고 있는 것이 특징

노드js의 http 모듈을 기반으로하며, 

http 요청

라우팅 및 미들웨어 처리를 쉽게 할 수 있도록 한다.

 

다음과 같은 기능을 제공한다. 

  1. 라우팅: HTTP 요청의 URL을 해당하는 처리기(콜백 함수)에 매핑합니다. 이를 통해 사용자는 특정 URL에 대한 요청에 대한 적절한 응답을 반환할 수 있습니다.
  2. 미들웨어: 요청을 처리하기 전에 실행되는 작업을 구성할 수 있습니다. 예를 들어 요청 헤더의 유효성을 검사하거나 로깅을 수행하는 등의 작업을 수행할 수 있습니다. 또한 미들웨어를 사용하여 사용자 정의 라우팅 및 오류 처리 논리를 구현할 수도 있습니다.
  3. HTTP 요청 및 응답: Express는 HTTP 요청 및 응답을 쉽게 처리할 수 있는 기능을 제공합니다. 예를 들어 요청 본문을 구문 분석하거나 특정 상태 코드로 응답을 반환할 수 있습니다.
  4. 뷰 엔진: Express는 다양한 뷰 엔진을 지원합니다. 이를 통해 사용자는 HTML 및 CSS와 같은 마크업 언어로 페이지를 렌더링할 수 있습니다. 대표적인 뷰 엔진으로는 Pug, EJS, Handlebars 등이 있습니다.

 

728x90
728x90

지난시간,,

https://stepby-yun.tistory.com/180

 

[mySQL 이미지 저장] BLOB말고 문자열로 하는 이유 (+string문자열 VAR / CHAR 차이)

지난 시간,,, https://stepby-yun.tistory.com/179 formData 이미지데이터 보내고 받기 (react,nodejs,header) 회원가입시 아이디 비번 프로필을 등록하려했다. id password는 넘겨주고 req.body.id로 받았는데 프..

stepby-yun.tistory.com

db에 이미지 저장까지 마쳤다.

formData로 이미지를 받았다.

이미지를 불러오는걸 하기 전에

id와 password도 함께 보내는 걸 하고 싶다.

 

formData를 쓰면 req.body를 쓸 수 없고id, password는 req.body로 받아와야하니 분리해야하나 싶었는데 역시나 방법이 있었어

 

 

 참조

<form> 파일, 텍스트 </form> 다 가져오고싶은데

  • 파일업로드 미들웨어의 필수조건인 <form enctype="multipart/form-data">을 쓰면 req.body를 undefined로 받고
  • 그렇다고 req.body를 받기위해 <form enctype="multipart/form-data">를 안쓰면 express-fileuplode나 multer를 못쓴다.

 

bodyParser를 쓰면 된다고 함 (팀플할때 무지성으로 썼는데 혼자하니 이렇게 깨닫는구나 역시 왜 쓰는지 생각해봐야함)

 

 

 

파싱이란 HTML 형식으로 사용자가 제출한 데이터에 접근하는 것을 의미합니다. 'GET' 방식으로 폼을 제출하면 '쿼리 문자열'에 데이터가 추가되어 쉽게 접근할 수 있지만 'POST' 방식으로 폼을 제출하면 데이터에 접근하기가 다소 어렵습니다. 보안을 위해 인코딩되었습니다. 데이터를 매우 쉽게 파싱할 수 있는 바디 파서 NPM 방법이 있지만 이 파싱 방법을 처음부터 구현하려고 하면 몇 가지 단계가 필요합니다.

 

Parsing means accessing the data submitted by user with HTML form. When the form is submitted with the ‘GET’ method, data are appended in ‘query string’ and can access easily but when a form is submitted using the ‘POST’ method, it is somewhat difficult to get access over those data since they are encoded because of security purpose. 
There exist a body-parser NPM method that makes it very easy to parse the data but if we try to implement this parsing method from scratch, there’s involve some steps.

 

 

근데 드는 의문이

bodyParser쓰면 multer지금 쓰고 있는거 필요없는거 아닌가?

걔도 미들웨어... 둘다 쓰는건가..

 

어라라 찾아보니까

하지만, express 버전 4.16이상 부터는 'express bodyparser deprecated ( bodyParser는 더이상 지원되지 않습니다.)' 와 같은 문제가 발생한다.

그 이유는, 4.16버전 이상 부터는 express 내부에 bodyParser가 포함되기 때문이다.

라고 한다.

 

 

- app.use(express.bodyParser())
+ app.use(express.json())
+ app.use(express.urlencoded())

이렇게 바디파서 대신 밑에 두줄을 쓴다는 건데...뭔지 잘모르겠는데요

 

 

 

 

일단 코드 추가하고 실행해보니 

일단 body-parser 모듈을 사용할 때 아무 옵션을 주지 않는 다면
body-parser deprecated undefined extended: provide extended option 같은 문구가 뜬다.

 

bodyParser 미들웨어의 여러 옵션 중에 하나로 false 값일 시 node.js에 기본으로 내장된 queryString, true 값일 시 따로 설치가 필요한 npm qs 라이브러리를 사용한다.

queryString 과 qs 라이브러리 둘 다 url 쿼리 스트링을 파싱해주는 같은 맥락에 있으나 qs가 추가적인 보안이 가능한 말 그대로 extended 확장된 형태이다.
기본이 true 값이니 qs 모듈을 설치하지 않는다면 아래와 같이 false 값으로 따로 설정을 해주어야 한다.

 

오 그렇군요 감사합니다

그래도 아직 req.body는 아무것도 안들어온다

이미지만 들어오고 흠흠

 

 

 

보낼때 id랑 password추가하면 body값만 찍히고 

이미지는 안되고...

 

 

라는데 왜...안되지..

https://blog.naver.com/bunggl/221699257359

 

 

이미지 넣은 것처럼 id랑 password도 formdata에 넣어봄

 

'id,password'이렇게 한 배열로 들어가서 안됨

 

 

 

https://hyc7575.github.io/2017/05/24/2017-05-24-node-js-bodyparserAndMulter/

 

Node.js(express) - body-parser와 multer

Node.js에서 form양식을 submit을 하기위해 사용되는 body-parser와 multer 미들웨어에 대해서 간단하게 알아보려 합니다. 각 미들웨어의 용도를 짧게 소개하면 body-parser는 라우터와 미들웨어 예제때 언급

hyc7575.github.io

 

https://kirkim.github.io/javascript/2021/10/16/body_parser.html

 

[NodeJs] express.json()과 express.urlencoded()의 차이점 알아보기

1️⃣ 사용이유 (1) .json()과 .urlencoded()를 사용하지 않을 때

kirkim.github.io

 

 

 

 

 

 

 

res.json/res.send차이https://haeguri.github.io/2018/12/30/compare-response-json-send-func/

출처

res.json

https://jin2rang.tistory.com/entry/express-bodyparser-deprecated-bodyparser%EB%8A%94-%EB%8D%94%EC%9D%B4%EC%83%81-%EC%82%AC%EC%9A%A9%EB%90%98%EC%A7%80-%EC%95%8A%EC%8A%B5%EB%8B%88%EB%8B%A4-%EB%AC%B8%EC%A0%9C%ED%95%B4%EA%B2%B0

 

https://velog.io/@hyunju-song/body-parser%EC%9D%98-urlencoded%EB%8A%94-%EB%8F%84%EB%8C%80%EC%B2%B4-%EC%96%B4%EB%96%A4-%EC%97%AD%ED%95%A0%EC%9D%84-%ED%95%98%EB%8A%94-%EA%B1%B8%EA%B9%8C

++

바디파서써서 req.body랑 이미지 파일 전송하기 

https://velog.io/@yuna_song/enctypemultipartform-data-%EC%82%AC%EC%9A%A9-%EC%8B%9C-req.body-%EC%82%AC%EC%9A%A9%EB%B2%95

formData 객체전송

https://melius.tistory.com/51

728x90
728x90

지난 시간,,,

https://stepby-yun.tistory.com/179

 

formData 이미지데이터 보내고 받기 (react,nodejs,header)

회원가입시 아이디 비번 프로필을 등록하려했다. id password는 넘겨주고 req.body.id로 받았는데 프로필은 formData 객체로 묶어서 그자체로 넘겨줬는데 자꾸 undefined 떴다. formData는 req.body가 안된다길

stepby-yun.tistory.com

formData 로 이미지 받는 걸 Header를 뒤늦게 추가해서 넣었다.

이제 mySQL 데이터베이스에 넣으려는데

아무것도 없고  BLOB만 달랑 저장됨

이미지 저장은 보통 BLOB로 한다고 해서 타입을 이렇게 넣었는데 

저렇게만 보이니 당황,, 

 

 

BLOB은 뭘까?

A Binary Large Object (BLOB) is a MySQL data type that can store binary data such as images, multimedia, and PDF files.

바이너리 데이터를 DB외부에 저장하기 위한 타입이다

"blob의 경우 4GB의 이진 데이터를 저장할 수 있다고 합니다. 하지만 이건 DB에 직접 저장하는 것이 아니라 DB에는 Large Object의 위치 포인터만 저장하게 됩니다."

그말은 즉, 컴퓨터가 인식하는 모든 파일(이진 데이터)를 저장하는 타입이라고 한다. 오 찾아보니까 볼 수 있긴하네

이렇게 확인할 수는 있구만 

 

 

하지만 BLOB보다는 URL자체로 저장을 선호한다고 한다.

그 이유는데이터베이스 서버는 애플리케이션이 확장될 때 종종 성능병목 현상이 일어나는데 이미지와 함께 로드하면 더 큰 병목현상이 발생한다고 함.

Many web app designers don't store images in database BLOBS, but rather store them in a file system, and store their URLs in database strings. Why? Database servers often become a performance bottleneck when an application scales up. If you load them with images, they'll become even bigger bottlenecks.

 

그럼 string문자열로 바꿔줘야지~

하고 string찾는데 없어 아 맞다 VAR이지하고 보다가

 

VARCHAR과 CHAR의 차이가 뭔가 궁금해졌다

VARCHAR은 '가변길이'

실질적인 데이터와 길이 정보도 같이 저장된다.

CHAR은 길이가 고정되어있어야한다. 남는 공간은 공백으로 채운다 공간낭비 발생!

VARCHAR is variable length, while CHAR is fixed length

https://petri.com/sql-server-string-data-types/

 

 

 

경로저장함

 

 

 

이제 id랑 password 도 함께 저장해봐야지 

 

 

 

 

 

 

출처

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=magnking&logNo=220950061851

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=rlasksdud53&logNo=220595010315

 

 

728x90
728x90

회원가입시

아이디 비번

프로필을 등록하려했다.

 

id password는 

넘겨주고 req.body.id로 받았는데 

프로필은 formData 객체로 묶어서 

그자체로 넘겨줬는데 자꾸 undefined 떴다. 

 

formData는 req.body가 안된다길래 

 

 

 

multer을 써봤는데도 안됨

(req.body대신 req.file 또는 files를 찍어봤지만 안됐다. )

 

formData쓸때 헤더에 타입을 넣어줘야한대서 

 

뒤늦게 

  {
        headers: {
          "Content-Type": "multipart/form-data",
        },
      }

를 추가했다. 위치는 보내는 formdata 다음에 넣어줬다.

 

이걸로도 해결이 안됐는데 신기하게 

같이보낸 id, password를 지우니 

undefined가 안뜨고 이미지가 전송돼서 images폴더에 이미지도 담겼다!

 

req.file에 이미지url이 찍혔다.

 

import React, { useState } from "react";
import Axios from "axios";
import "./signup.css";
import FormData from "form-data";

function Signup() {
  //회원가입 값
  const [id, setId] = useState("");
  const [password, setPassword] = useState("");

  // const [imageUpload, setImageUpload] = useState(null);

  const [image, setImage] = useState({
    preview:
      "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAM1BMVEXk5ueutLfn6eqrsbTp6+zg4uOwtrnJzc/j5earsbW0uby4vcDQ09XGyszU19jd3+G/xMamCvwDAAAFLklEQVR4nO2d2bLbIAxAbYE3sDH//7WFbPfexG4MiCAcnWmnrzkjIRaD2jQMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMw5wQkHJczewxZh2lhNK/CBOQo1n0JIT74/H/qMV0Z7GU3aCcVPuEE1XDCtVLAhgtpme7H0s1N1U7QjO0L8F7llzGeh1hEG/8Lo7TUmmuSrOfns9xnGXpXxsONPpA/B6OqqstjC6Ax/0ujkNdYQQbKNi2k64qiiEZ+ohi35X+2YcZw/WujmslYewiAliVYrxgJYrdwUmwXsU+RdApUi83oNIE27YvrfB/ZPg8+BJETXnqh9CVzBbTQHgojgiCvtqU9thFJg/CKz3VIMKMEkIXxIWqIpIg2SkjYj+xC816mrJae2aiWGykxRNsW0UwiJghJDljYI5CD8GRiCtIsJxizYUPQ2pzItZy5pcisTRdk/a9m4amtNNfBuQkdVhSaYqfpNTSFGfb9GRIakrE2Pm+GFLaCQPqiu0OpWP+HMPQQcgQMiQprWXNmsVwIjQjYi/ZrhAqNTCgr2gu0Jnz85RSSjso0HkMFZ0YZjKkc26a/jlmh9JiDyDxi9oeorTYAzZkwwoMz19pzj9bnH/GP/+qbchjSGflneWYhtTuKdMOmNKZcJ5TjInQKcYXnESd/jQxy0ENpULTNGOGgxpap/oyw9pbUAqhfx2Dbkhovvfgz4iUzoM9+GlK6/Mh4q29hyC1mwro30hpVVLPF9wYQr71RazOeM5/cw81iBRD+A03aM9/C/obbrKjbYSpCmIVG3qT/Q8oeUo3Rz0IL7vI1tEbCB9pSiu8I/aV8x3Kg/BGWrWp4ZVs0nZfmAoEG4h/61yHYIJiFSl6Q0Vk6tTW1N8kYp8hdOkfHYYMXd2Qft+8CYwqYDSKvqIh+MCF8Wgca2u/cwdgeW3TtuVn6+1oBs3yLo5C2JpK6CvQzGpfUkz9UG/87gCsi5o2LIXolxN0FbwAsjOLEr+YJmXn7iR6N0BCt5p5cMxm7eAsfS+/CACQf4CTpKjzgkvr2cVarVTf96372yut7XLJ1sa7lv6VcfgYrWaxqr3Wlo1S6pvStr22sxOtTNPLzdY3nj20bPP+ejFdJYkLsjGLdtPBEbe/mr2bQKiXWJDroA+vtzc0p9aahuwqHMDYrQEXHEw9jwQl3drMpts9JBU1SdktPe5FBRdJQ6bwXBpa57ib2A8kukQDzMjh++Uo7Fo6Wd02Pkf4fknqoo4HtvAIjsqUcjx6DIPgWCaOML9rKI/oqD9/lgNrn+eF+p7j8tnzHBiR7+kdUGw/+V1Kzkc75mMy6U+FMaxjPibiM1U1uGM+puInHpmALZCgP4pt7i840MV8+0R1zPsRB6UTcqpizncYwZ89syDydfyWCwXB1l8/zRNGWbTG/GHKUm9AkxHMc/EGSk3z2+ArEhPEV5TUBLEvUGFcjEUH80J/jveTGOAJEljJbILWGQT3zRYiwuKsUXN1EEJAzBhRJFll7mBUG7KD8EqPkKekBREaL8hMDZLQSG6AQjtHPYmvTQnX0TtpC1SYCe2YdkkyLP3jj5BSbKiuR585eQhTgoje6yIb0Yb0C+mV6EYvebqw5SDy2WmubogZiF2AVxPC2FpDf8H2Q9QWo6IkjUxTWVEI3WY/wrCeSuqJ+eRWzXR/JXwgVjUMozbCOfoEZiSiKVGepqv5CJ8RyR4D7xBeamqa7z3BJ/z17JxuBPdv93d/a2Ki878MMAzDMAzDMAzDMAzDMF/KP09VUmxBAiI3AAAAAElFTkSuQmCC",
    data: "",
    // Headers: " 'Content-Type': 'multipart/form-data'",
  });

  //다 userlist에 담아서 db저장하기
  // const [userList, setUserList] = useState([]);

  const onChangeId = (event) => {
    setId(event.target.value);
  };

  const onChagePassword = (event) => {
    setPassword(event.target.value);
  };

  const Submit = (e) => {
    e.preventDefault();

    let formData = new FormData();
    formData.append("file", image.data);

    console.log(image.data, "선택한이미지! ");
    console.log(formData, "들어가기전폼데이터");

    Axios.post(
      "http://localhost:3001/submit",

      // {
      //   id: id,
      //   password: password,
      //   formData,
      //   headers: { "Content-Type": "application/x-www-form-urlencoded" },
      // }
      // {
      //   id: id,
      //   password: password,
      // },

      formData,

      {
        headers: {
          "Content-Type": "multipart/form-data",
        },
      }
    ).then(() =>
      console.log(
        formData,
        "then이후 폼데이터",
        image,
        "이건뭐",
        image.data,
        "이미지데이터"
      )
    );
  };

  const handleFileChange = (e) => {
    const img = {
      preview: URL.createObjectURL(e.target.files[0]),
      data: e.target.files[0],
    };

    setImage(img);
  };

  return (
    <div className="formBox">
      <div className="signup_id">
        <input placeholder="아이디" onChange={onChangeId}></input>
      </div>
      <div className="signup_password">
        <input placeholder="비밀번호" onChange={onChagePassword}></input>
      </div>
      <div>
        <input placeholder="비밀번호 확인"></input>
      </div>
      {image.preview && <img src={image.preview} width="100" height="100" />}
      <input
        type="file"
        onChange={handleFileChange}
        name="Images"
        accept="Images/*"
      />

      <button onClick={Submit}>제출</button>
    </div>
  );
}

export default Signup;

 

 

 

const express = require("express");
const app = express();

const mysql = require("mysql");
const cors = require("cors");
const multer = require("multer");
const path = require("path");
app.use(cors());

app.use(express.json());
//이거 필요한거임?
app.use(express.static("Images"));

const db = mysql.createConnection({
  user: "*",
  host: "*",
  password: "*",
  database: "*",
});

const upload = multer({
  storage: multer.diskStorage({
    destination: function (req, file, cb) {
      cb(null, "Images/");
    },
    filename: function (req, file, cb) {
      cb(null, new Date().valueOf() + path.extname(file.originalname));
    },
  }),
});
// const upload = multer({
//   dest: "Images/",
//   limits: { fileSize: 5 * 1024 * 1024 },
// });

// app.post("/up", upload.array("img"), (req, res) => {
//   console.log(req.files);
// });

app.post("/submit", upload.single("file"), (req, res) => {
  console.log(req.files, "레큐파일ㄴ");
  console.log(req.body, "레큐바디");
  // console.log(req.body, "레큐바디");
  // const id = req.body.id;
  // const password = req.body.password;
  const profile = req.file;

  console.log(req.file, "파일");
  // res.json({ url: `/img/${req.file.filename}` });
  // FormData의 경우 req로 부터 데이터를 얻을수 없다.
  // upload 핸들러(multer)를 통해서 데이터를 읽을 수 있다

  //   function insertRecord(req, res) {
  // req.files.forEach((e) => {
  // // console.log(e.filename);
  // // });
  // console.log(req.file, "파일");
  // console.log(profile, "이미지");

  db.query(
    "INSERT INTO userlist (userProfile) VALUES (?) ",
    [profile],
    (err, result) => {
      if (err) {
        console.log(err);
      } else {
        res.send("userlist values inserted");
      }
    }
  );
});

app.listen(3001, () => {
  console.log("your server is running on 3001~! yeah");
});

 

문제는 id랑 password를 빼고 보내서 그런지 

 

Error: ER_WRONG_VALUE_COUNT_ON_ROW: Column count doesn't match value count at row 1

오류: ER_WRONG_VALUE_COUNT_ON_ROW: 열 개수가 행 1의 값 개수와 일치하지 않습니다.

이게 뜬다

 

아직 db저장도 안됐고 해결안된게 많지만 이미지를 받아서 행복함...

id랑 password를 같이 받으면 왜 undefined로 뜨는걸까?

따로 해야되는것인가..

 

 

 

아무튼 

header의 역할 ,context Type에 대해 공부하고

https://jw910911.tistory.com/117

formData(json으로 변환하라는 얘기도 있던데...나는 그거 안되던데..왜지?), multer

에 대해 더 공부하고 정리해봐야겠다.

+

app/router차이도 헷갈리니까 확인해놓자.

http://expressjs.com/ko/guide/routing.html

express가져와서 app으로 선언해서 썼는데 검색하다가 코드보면 express.router로 쓰는 게 무슨 차인가 싶어서..

728x90
728x90

 

상황: 

우분투 연결안된 비주얼코드창에서 마리아db연결하겠다고 삽질하고 있었던 것을 깨닫

 

마리아db 이미 깔려있고 리눅스창에서 작업해야되는거니까 mysql 워크벤치 연동은 의미없다는 것을 깨닫

 

비주얼코드 우분트연결 창 켜서 

폴더만들어서 안에 로그인되는 코드넣어놓고 작업시작

npm i

디비 생성

 

역시 같은 에러.

이 오류에 경우

 

1. 비번 틀렷을 경우

2. 권한설정이 안됐을 경우.

3. 기타 등등 있대서

 

비번은 바꿔주고 확인해봤으니 아니고

 

 

 

비번바꾸기

use mysql;

사용자와 비밀번호 출력하기

select user, password from user;

abc 사용자의 비밀번호를 1234로 설정

update user set password=password('1234') where user='abc';

변경 사항 적용

flush privileges;

 

 

 

 

 

 

권한설정이 문제인가 해서

 

mariadb 계정 권한설정

MariaDB [mysql]> grant all privileges on *.* to 'test'@'localhost' with grant option; Query OK, 0 rows affected (0.002 sec)

https://94mogi.tistory.com/7

바꿔봤는데도 안됐음 

 

 

우분투 파일 권한 설정

이 문젠가 싶어서 그것도 해봄 

하위폴더까지 다 권한줌

[root@~/]# chmod 755 -R /폴더명

777도 해봄(이거는 보안상 위험할 수 있대서 다시 755로 바꿨다, 물론 에러 원인은 아니었지만)

 

(이거 개념 나중에 참고하삼 https://withcoding.com/103)

연산자 설명 배리 굿https://nachwon.github.io/shell-chmod/

 

[Shell] chmod - 파일 및 폴더의 권한 설정

chmod 셸 명령어는 파일 또는 폴더의 권한을 변경할 때 사용한다.

nachwon.github.io

 

 

여전히 같은 에러 뜸

 

이번에는 계정이 문젠가 싶어서 

기존계정 삭제하고 다시 만들어봄

 

 

 

mariadb 계정 삭제

 

use mysql

DROP USER '계정'@'접속대역';

 

새로 만들고

권한설정도 하고

 

플러그인이 문제인가 싶어서 해봄

MariaDB [mysql]>  update user set plugin='mysql_native_password' where user='mariatest';

확인

MariaDB [mysql]> select user,host,plugin from mysql.user;

 

 

이걸로 해봤는데도 안됨

 

 

 

 

 

혹시나해서 비밀번호 초기화 해봄

(및 보안강화를 위한 설정명령어, 라고 함.)

 

sudo mysql_secure_installation

이후에 우분투 켜서 다시 접속하려는데 안됨

mysql -u root -p

sudo mysql

이거 둘다 안됨

 

 

 

 

mariadb들어갈때 

sudo mysql해서 들어가는데 

에러뜸

 

 

새로운 에러 등장~~

에러 

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

 

 

 

service mysql start 

sudo su-로 들어가서 하니까 시작되고

나와서

sudo mysql하니까 접속됨

 

 

 

들어와서 db생성 실행해보니

 

역시 안되고

 

 

엇 생각해보니까

이 mariadb 포트가 3307인데 

config에 포트 추가로 넣은거 걍 아무거나 넣어본거였거든

 

똑같은 포트번호 3307으로 해봄

 

 

 

 

와우 

생성잘됨

 

 

삽질2 포트번호가 달라서 그랬던거임

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

생각해보니까 이 에러도

 

전에 mariadb깔고 실행할때 떴던 에러인데

기존에 깔려있는 mysql이 3306포트를 써서 mariadb량 겹쳐서 위에 에러가 떴거든

그때 mariadb포트를 3307으로 바꿔줌

(mariadb설치시 위 에러해결 이거 참고하슈 : https://codecoco.tistory.com/58?category=524414

 

 

 

db안에 회원가입한거 잘들어왔나 확인

 

응잘들어옴 로그인도 잘됨

 

 

 

mariadb

로그인하기 db생성하기 

 

끝~~

 

 

(아근데 로그인하는 코드자체 제로초보고 다 따라한거라 나중에 해봐야겠다...

혼자 해보려다가 잘안됐음.. 여기서 시간 많이 먹긴함ㅋ)

 

 

728x90
728x90

계속 헷갈려서 애먹는..더 공부해야될 거 

  • 코드 어떤 순서로 읽혀지는지
  • 라우트

어제 오늘 해본거

1. 회원가입할 때 프로필 이미지로 넣을 사진 업로드 

사진경로가 로컬에 잘 들어가는지 확인

이제, 사진이 화면에 뜨는지 확인

 

2. 마이페이지에 이미지가 들어와져서 보이는지

 

3. 마이페이지에서 닉네임과 비밀번호 변경

 

 

일단 코드다 적어놓고 어디서 왜 막혔는지 볼까유

 

 

 

1. 회원가입할 때 프로필 이미지로 넣을 사진 업로드 

app.js 에 사용할 라우터 넣어놓고 ()

 

const signupRouter = require("./routes/login/signup");

정해놓고

 

 

app.use("/signup", signupRouter);

/이거 부를때, 이 라우터불러서 쓰겠다..

 

 

signup 라우터

//모듈
const express = require("express");
const path = require("path");
const multer = require("multer");
const fs = require("fs");
const { User } = require("../../models");
const bcrypt = require("bcrypt");
const passport = require("passport");
const { isNotLoggedIn } = require("../middlewares");

//라우터
const router = express.Router();

//회원가입 창띄우기(get)
router.get("/", (req, res, next) => {
  res.render("login/signup", { title: "회원가입" });
});

// 회원가입 데이터 입력한거 전송하는 코드
// 여기로 이동! signUp 들어왔을때 뒤에 실행
// post 데이터들 있어서(post데이터 전송)
router.post("/",isNotLoggedIn, async (req, res, next) => {
  const { email, nick, password, img } = req.body;
  //User는 데이터베이스!거기에서 email 가져옴(기존 email확인하려고! 그전 유저exUser)
  try {
    const exUser = await User.findOne({ where: { email } });
    if (exUser) {
      return res.redirect("/signUp?error=exist");
    }
    const hash = await bcrypt.hash(password, 12);
    User.create({
      email,
      nick,
      password: hash,
      img: req.body.url,
    });
    //로그인 완료 메인으로~
    return res.redirect("/");
  } catch (error) {
    //여기 안에서 에러나면 catch로
    console.error(error);
    //app.js로 되돌림 얘를 호출한 데로 부르는거. return 되돌려~
    return next(error);
  }
});

// uploads 폴더
try {
  fs.readdirSync("uploads");
} catch (error) {
  console.error("uploads 폴더가 없어 폴더를 생성합니다.");
  fs.mkdirSync("uploads");
}

/* multer 기본 설정 */
const upload = multer({
  storage: multer.diskStorage({
    destination(req, file, cb) {
      cb(null, "uploads/");
    },
    filename(req, file, cb) {
      const ext = path.extname(file.originalname);
      cb(null, path.basename(file.originalname, ext) + Date.now() + ext);
    },
  }),
  limits: { fieldSize: 5 * 1024 * 1024 },
});

/* 게시글 IMG CREATE */
router.post("/img", upload.single("img"), (req, res) => {
  console.log(req.file);

  res.json({ url: `/img/${req.file.filename}` });
});

/* 게시글 TEXT CREATE */
router.post("/", upload.none(), async (req, res, next) => {
  try {
    await User.create({
      // nick: req.body.nick,
      img: req.body.url,
    });
    res.redirect("/signup");
  } catch (error) {
    console.error(error);
    next(error);
  }
});

//모듈 전체 라우터 사용하려고
module.exports = router;

 

signup.html

{% extends "layout.html" %} {% block css %} {% endblock %} {% block content %}
<div id="join-form-body">
  <div class="join-form-title">회원가입</div>
  <!-- action에 페이지 주소입력 -->
  <form
    method="POST"
    class="join-form-container"
    id="user-id"
    onsubmit="return true"
    action="signup"
  >
    <fieldset class="join-form-field">
      <div class="input-container">
        <label for="email">Email<small style="color: red">*</small></label
        ><br />
        <input
          class="input-box"
          type="email"
          id="user-email"
          name="email"
          placeholder="Enter Your Email"
          onchange="checkMail()"
        />
        <!--유효성 검사 후 잘못되었을때 경고 문자-->
        <text
          id="usereMail"
          class="wrong-message"
          style="color: red; font-size: small"
        ></text>
      </div>
      <div class="input-container">
        <label for="password">Password<small style="color: red">*</small></label
        ><br />
        <input
          class="input-box"
          type="password"
          id="user-pw"
          name="password"
          placeholder="Enter Your Password"
          onchange="checkPw()"
          required
        />
        <!--유효성 검사 후 잘못되었을때 경고 문자-->
        <text
          id="wrongPw"
          class="wrong-message"
          style="color: red; font-size: small"
        ></text>
      </div>
      <div class="input-container">
        <input
          class="input-box"
          type="password"
          id="user-pw-confirm"
          name="reUserpw"
          placeholder="Confirm Password"
          onchange="reCheckPw()"
          required
        /><br />
        <!--유효성 검사 후 잘못되었을때 경고 문자-->
        <text
          id="wrongRePw"
          class="wrong-message"
          style="color: red; font-size: small"
        ></text>
      </div>
      <div class="input-container">
        <label for="nick">Nickname<small style="color: red">*</small></label
        ><br />
        <input
          class="input-box"
          type="text"
          id="user-nickname"
          name="nick"
          placeholder="Enter Your Nickname"
          onchange="checkId()"
          required
        />
        <!--유효성 검사 후 잘못되었을때 경고 문자-->
        <text
          id="wrongid"
          class="wrong-message"
          style="color: red; font-size: small"
        ></text>
      </div>
      <div class="input-container">
        <label for="birthdate">Date of Birth</label><br />
        <input
          class="input-box"
          type="date"
          id="user-birth"
          name="birth"
        /><br />
      </div>
      <div class="profile-upload-content upload-img">
        <div class="img-preview">
          <img
            id="img-preview"
            src=""
            style="display: none"
            width="250"
            alt="미리보기"
          />
          <input id="img-url" type="hidden" name="url" />
        </div>
      </div>
      <div class="profile-img">
        <label class="img-btn img-file" id="img-label" for="img"
          >첨부 파일</label
        >
        <input id="img" type="file" accept="image/*" style="display: none" />
      </div>
    </fieldset>
    <input type="submit" value="Sign Up" id="signUp" onclick="checkForm()" />
  </form>
  <!-- 이미지테스트 -->
</div>
{% endblock %} {% block script %}
<script src="/login/javascripts/signupcheck.js"></script>
<script src="/login/javascripts/signup-myimg.js"></script>
{% endblock %}

//회원가입 창띄우기(get)

router.get("/", (req, res, next) => {

  res.render("login/signup", { title: "회원가입" });

});

 

이걸로 signup.html 띄워져

 

 

signup.html에서 이미지 삽입 부분

 

<div class="profile-upload-content upload-img">

        <div class="img-preview">

          <img

            id="img-preview"

            src=""

            style="display: none"  

            width="250"

            alt="미리보기"

          />

<inputid="img-url"type="hidden"name="url"/>//안보이다가 이미지 넣으면 보이게   

        </div>

      </div>

      <div class="profile-img">

        <label class="img-btn img-file" id="img-label" for="img"

          >첨부 파일</label

        >

        <input id="img" type="file" accept="image/*" style="display: none" />

//여기display:none은 파일추가버튼안보이게한거

      </div>

 

 

 

 

 input type="submit" 하면 폼 전송됨

 

 

폼 전송 받음

// 회원가입 데이터 입력한거 전송하는 코드

// 여기로 이동! signUp 들어왔을때 뒤에 실행

// post 데이터들 있어서(post데이터 전송)

router.post("/",

~~~~~~잘되면~~

 //로그인 완료 메인으로~

    return res.redirect("/");

 

multer 이미지 업로드 위해씀

 

 

 

 

 

 

/* 게시글 IMG CREATE */

router.post("/img", upload.single("img"), (req, res) => {

  console.log(req.file);

 

  res.json({ url: `/img/${req.file.filename}` });

});

 

/* 게시글 TEXT CREATE */

router.post("/", upload.none(), async (req, res, next) => {

  try {

    await User.create({

      // nick: req.body.nick,

      img: req.body.url,

    });

    res.redirect("/signup");

  } catch (error) {

    console.error(error);

    next(error);

  }

});

 

근데 이부분은 게시글 참고해서 쓴건데 병합하면서 좀 달라져서 헷갈린다..

업로드할 이미지를 경로로 부르고 User에서 찾아서 띄운다는 거같은데

밑에 코드를 게시글에서는 text띄울때썼나?싶어서

 

2. 마이페이지에 이미지가 들어와져서 보이는지

 

layout.html 에

<a href="/mypage">{{'안녕하세요! ' + user.nick + '님'}}</a>

누르면 

 

 

mypage.js

const express = require("express");
const { User, Club } = require("../../models");
const { isLoggedIn, isNotLoggedIn } = require("../middlewares");

const router = express.Router();

router.use((req, res, next) => {
  res.locals.user = req.user;
  res.locals.followerCount = req.user ? req.user.Followers.length : 0;
  res.locals.followingCount = req.user ? req.user.Followings.length : 0;
  res.locals.followerIdList = req.user
    ? req.user.Followings.map((f) => f.id)
    : [];
  next();
});

//프로필 사진 읽기
router.get("/", async (req, res, next) => {
  try {
    const getImage = await User.findOne({
      // where: { id: req.user.id }, // 여기서 id 에러창뜨고 로그인됨
      where: { id: `${req.user.id}` }, // 문자로 바꿈. 아마도. 암튼 개선함
    });
    console.log(getImage);
    res.render("mypage/mypage", {
      title: "mountain 커뮤니티",
      signupImages: getImage,
    });
  } catch (error) {
    console.error(error);
    next(error);
  }
});

module.exports = router;

가져올 팔로우 정보들 넣어주고

get으로 mypage.html 띄워줘

 

mypage.html 

{% extends "layout.html" %} {% block css %} {% endblock %} {% block content %}

<div class="timeline">
  <div class="user-name">{{'안녕하세요! ' + user.nick + '님'}}</div>
  <div class="half">
    <div>팔로잉</div>
    <div class="count following-count">{{followingCount}}</div>
  </div>
  <div class="half">
    <div>팔로워</div>
    <div class="count follower-count">{{followerCount}}</div>
  </div>

  <div class="followings half">
    <h2>팔로잉 목록</h2>
    {% if user.Followings %} {% for following in user.Followings %}
    <div>{{following.nick}}</div>
    {% endfor %} {% endif %}
  </div>
  <div class="followers half">
    <h2>팔로워 목록</h2>
    {% if user.Followers %} {% for follower in user.Followers %}
    <div>{{follower.nick}}</div>
    {% endfor %} {% endif %}
  </div>
  <a id="modify"  href="/modify" class="btn">정보 수정</a>
  <body>
  <div>
    <p><div class="mypage-img">{{signupImages.content}}</div></p>
    <img style="width: 200px;" src="{{signupImages.img}}" alt="섬네일">
</div>
</body>
</div>
{% endblock %} {% block script %} {% endblock %}

 try {

    const getImage = await User.findOne({

      // where: { id: req.user.id }, // 여기서 id 에러창뜨고 로그인됨

      where: { id: `${req.user.id}` }, // 문자로 바꿈. 아마도. 암튼 개선함

    });

    console.log(getImage);

    res.render("mypage/mypage", {

      title: "mountain 커뮤니티",

      signupImages: getImage,

    });

  }

 

 

 

models에서 user.js 로

데이터베이스 설정해준거 User에서 찾아 id값을 . 그걸 getImage로 선언하고

마이페이지 띄우고

getImage를 signupImages로 다시 이름 정해주고 마이페이지에서 써먹기

 

signupImages의 이미지

 

    <p><div class="mypage-img">{{signupImages.content}}</div></p>

    <img style="width: 200px;" src="{{signupImages.img}}" alt="섬네일">

 

 

3. 마이페이지에서 닉네임과 비밀번호 변경

 

다 마찬가지로 라우트 만들고

 

  <a id="modify"  href="/modify" class="btn">정보 수정</a>

마이페이지에서 이거 누르면 라우트로 넘어가

 

 

modify.js

const express = require("express");
const passport = require("passport");
const bcrypt = require("bcrypt");
const { isLoggedIn, isNotLoggedIn } = require("../middlewares");

const { User } = require("../../models");

const router = express.Router();

router.get("/", (req, res, next) => {
  console.log("너니?");
  res.render("mypage/modify");
});

router.post("/", isLoggedIn, async (req, res, next) => {
  console.log("로그인됐을때수정되려나");
  const { email, nick, password } = req.body;
  try {
    if (nick && password) {
      const hash = await bcrypt.hash(password, 12);
      await User.update(
        {
          nick,
          password: hash,
        },
        {
          where: { email: email },
        }
      );
    } else if (nick) {
      await User.update(
        {
          nick,
        },
        {
          where: { email: email },
        }
      );
    } else if (password) {
      const hash = await bcrypt.hash(password, 12);
      await User.update(
        {
          password: hash,
        },
        {
          where: { email: email },
        }
      );
    }
    return res.redirect("/");
  } catch (err) {
    console.error(err);
    return next(err);
  }
});

module.exports = router;

router.get("/", (req, res, next) => {

  console.log("너니?");

  res.render("mypage/modify");

});

 

get으로

html의 마이페이지 폴더 안에 있는 modify.html 띄워줌

 

바꾼 닉넴 아이디를 

폼안에 써주고

 

 <form id="modify-form" action="modify" method="POST">

action이 경로, 여기로 보내는거 의미하는 듯함..

 

그리고 버튼 눌러 전송하면

 <button id="modify-form" type="submit" class="btn">변경</button>

 

 

 

router.post("/", isLoggedIn, async (req, res, next) => {

  console.log("로그인됐을때수정되려나");

  const { email, nick, password } = req.body;

  try {

    if (nick && password) {

      const hash = await bcrypt.hash(password, 12);

      await User.up~~~~~~

modify.js 라우터로 넘어와서 post에서 받아서 어쩌고 수정~

 

내가 에러났던게 

 

바꾸고나서 리턴을

  return res.redirect("/");

이렇게 해야되는데 /mypage/modify로 하고 있었음.. 그래서 변경버튼 눌렀을때 이 라우트 없다고 알람뜨고 돌아가보면 변경은 잘 되어있는..

 

 

 

좀 두서없이 써놓긴 했는데

 

내가 관계설정이랑 이동하는걸 아직 정확하게 이해한 게 아닌거같음

팀플하면서 맡은부분 먼저 결과물 나타나는거에 집중 중! 그리고 책은 이미 읽었지만 다 이해하지 못하는 부분도 많아서 해보면서 알아야할 거같아

점점 하면서 감잡아간다고 생각해...ㅎㅎ 콘솔찍어보면서 얘가 어디로 가는지 찾아보는중이야

 

 

 

 

//+

컴퓨터는 계속 전체코드 읽다가

사용자가 눌렀을때 거기서 멈춘대 (코드 읽는 순서 더 공부해봐야겠다. 아직 많이 헷갈려)

 

 

지금은 다른 형식으로 하지만

아까는 id값을 위에 붙여서 프로필 이미지 가져오는거 봤었음

 

 

mypage.js

 

const express = require("express");
const { User, Club } = require("../../models");
const { isLoggedIn, isNotLoggedIn } = require("../middlewares");

const router = express.Router();

router.use((req, res, next) => {
  res.locals.user = req.user;
  res.locals.followerCount = req.user ? req.user.Followers.length : 0;
  res.locals.followingCount = req.user ? req.user.Followings.length : 0;
  res.locals.followerIdList = req.user
    ? req.user.Followings.map((f) => f.id)
    : [];
  next();
});

//프로필 사진 읽기
router.get("/:id", async (req, res, next) => {
  console.log("mypage의get"); //콘솔에 안찍힘
  try {
    const getImage = await User.findOne({
      where: { nick: `${req.params.id}` },
    });
    console.log(getImage);
    res.render("mypage/mypage", {
      title: "mountain 커뮤니티",
      signupImages: getImage,
    });
  } catch (error) {
    console.error(error);
    next(error);
  }
});

module.exports = router;

 

router.get("/:id"async (reqresnext=> {

  console.log("mypage의get"); //콘솔에 안찍힘

  try {

    const getImage = await User.findOne({

      where: { nick: `${req.params.id}` },

    });

    console.log(getImage);

    res.render("mypage/mypage", {

      title: "mountain 커뮤니티",

      signupImages: getImage,

    });

  } catch (error) {

    console.error(error);

    next(error);

  }

});

 

 

//콘솔에 안찍힘 -> 아까 안찍혔음 지금은 돼

안됐던 이유가 /mypage 뒤에 아이디값을 붙일거를 계속 찾고 있어서...

 

나는 mypage.js랑

mypage.html만 보고 있었음

 

 

 

 

근데 마이페이지로 들어가는

메인 layout.html을 봤어야했어..

 

<a href="/mypage/{{user.nick}}"
              >{{'안녕하세요! ' + user.nick + '님'}}</a
            >

 

전에는

a href="/mypage"

 

 

mypage에 id값을 붙이고 mypage.html 부르려고하니까 안됐어

 

아예 mypage 페이지 안불러와짐

 

a href="/mypage/{{user.nick}}"

이렇게 해주니까 

라우트에서 아이디값붙이고 콘솔찍히고

User에서 nick 찾고 

mypage.html 렌더해줌

 

 

 

 

////근데 이렇게 하면 주소창에

/mypage/User.nick 이 찍히니까

개선한 지금이 나아~

 

 

 

 

이해하려고 이것도 읽어봤음..

https://fierycoding.tistory.com/20

 

 

 

728x90
728x90

https://velog.io/@pkbird/Nunjucks-basic

 

팔로우하기

팔로우끊기

 

버튼교체!!!!

 

해결함 

넌적스 제대로 안됐던 이유는 club.html코드보면서 라우터 mypage.js 걸로 왜 안되냐 하고 있었던 것~~

 

 (mypage에서 팔로우 정보 불러올때 썼던 것처럼)

 

club.html에서 쓸

followerIdList를 club.js에서 쓸거라고 넣어줘야 됐음

 


목표:

(나자신)   - 아무것도 안뜨게

(팔로잉한 사람followerIdlist에 포함되어있음)  - 팔로우끊기

(팔로우 안함사람) – 팔로우하기

 

///넌적스 if절은//

만약에 트윗유저가 followerIdlist에 포함되어있지 않고 나자신이 아니면

팔로우하기

그게 아니면(트윗유저가 followerIdlist에 포함되어있으면 그리고 나자신 아니면)

팔로우끊기

 


 

 

 

 

(좋아요는 아직 기능안해서 주석처리)

 

club.html

{% extends "layout.html" %} {% block css %}
<link rel="stylesheet" href="/club/stylesheets/club.css" />
{% endblock %} {% block content %}
<div class="search_field">
  <div class="fake_field">
    <input
      class="picture-search"
      type="text"
      name="search"
      placeholder="사진 검색"
    />
    <button class="picture-btn btn">검색</button>
  </div>
</div>

<div class="feed-container">
  <h3>인기 피드</h3>

  <!-- <form id="myFeed">
    <button id="feed-btn" type="submit" class="black btn">
      <a class="picture-uploads" href="/clubupload">업로드</a>
    </button>
    {% for twit in twits %}
    <tr class="clubMain">
      <td>
        <a href="/">
          <img style="width: 100px" src="{{twit.img}}" alt="섬네일"/>
        </a>
      </td>
    </tr>
    {% endfor %}
  </form> -->
  {% if user and user.id %}
  <button id="feed-btn" type="submit" class="black btn">
    <a class="picture-uploads" href="/clubupload">업로드</a>
  </button>
  {% endif %} {% for twit in twits %}
  <div class="twit">
    <input type="hidden" value="{{twit.User.id}}" class="twit-user-id" />
    <input type="hidden" value="{{twit.id}}" class="twit-id" />
    <div class="twit-author">{{twit.User.nick}}</div>

    {% if not followerIdList.includes(twit.User.id) and twit.User.id !== user.id
    %}
    <button class="twit-follow btn">팔로우하기</button>
    {% elif twit.User.id !== user.id%}
    <button class="twit-unfollow btn">팔로우끊기</button>
    {% endif %}

    <div class="twit-img">
      <a href="/clubdetail/:id"
        ><img style="width: 100px" src="{{twit.img}}" alt="섬네일"
      /></a>
    </div>

    <button class="like btn">좋아요</button>

    <button class="unlike btn">좋아요 취소</button>

    <div class="twit-content"></div>
  </div>
  {% endfor %}
</div>
{% endblock %} {% block script %}
<script>
  //팔로우
  document.querySelectorAll(".twit-follow").forEach(function (tag) {
    console.log("팔로이있는창");
    tag.addEventListener("click", function () {
      console.log("팔로우클릭");
      const myId = document.querySelector("#my-id");
      if (myId) {
        console.log("myid가져와지니?");
        const userId = tag.parentNode.querySelector(".twit-user-id").value;
        console.log(userId);
        //if (userId !== myId.value)원래 이거였음 근데 바꿔도 콘솔에는 찍히네

        if (userId !== myId.value) {
          if (confirm("팔로잉하시겠습니까?")) {
            console.log("팔로요청되나?");
            axios
              .post(`/user/${userId}/follow`)
              .then(() => {
                console.log("팔로됐나");
                location.reload();
              })
              .catch((err) => {
                console.log("에러");
                console.error(nperr);
              });
          }
        }
      }
    });
  });
  //팔로우끊기
  document.querySelectorAll(".twit-unfollow").forEach(function (tag) {
    tag.addEventListener("click", function () {
      const myId = document.querySelector("#my-id");
      console.log(myId);
      if (myId) {
        const userId = tag.parentNode.querySelector(".twit-user-id").value;

        if (userId !== myId.value) {
          if (confirm("팔로잉끊으시겠습니까?")) {
            console.log("팔로요청되나?");
            axios
              .post(`/user/${userId}/unfollow`)
              .then(() => {
                console.log("팔로됐나");
                location.reload();
              })
              .catch((err) => {
                console.log("에러");
                console.error(err);
              });
          }
        }
      }
    });
  });

  //   forEach.call(document.querySelectorAll(".like"), function (tag) {
  //   tag.addEventListener("click", function () {
  //     var isLoggedIn = document.querySelector("#my-id");
  //     var twitId = tag.parentNode.querySelector("#twit-id").value;
  //     if (idLoggedIn) {
  //       var xhr = new XMLHttpRequest();
  //       xhr.onload = function () {
  //         if (xhr.status === 200) {
  //           location.reload();
  //         } else {
  //           console.error(xhr.responseText);
  //         }
  //       };
  //       xhr.open("POST", "/club/" + twitId + "/like");
  //       xhr.send();
  //     }
  //   });
  // });
  // forEach.call(document.querySelectorAll(".unlike"), function (tag) {
  //   tag.addEventListener("click", function () {
  //     var isLoggedIn = document.querySelector("#my-id");
  //     var twitId = tag.parentNode.querySelector("#twit-id").value;
  //     if (idLoggedIn) {
  //       var xhr = new XMLHttpRequest();
  //       xhr.onload = function () {
  //         if (xhr.status === 200) {
  //           location.reload();
  //         } else {
  //           console.error(xhr.responseText);
  //         }
  //       };
  //       xhr.open("DELETE", "/club/" + twitId + "/like");
  //       xhr.send();
  //     }
  //   });
  // });
</script>

{% endblock %}

 location.reload(); //이거 새로고침!

<button class="twit-unfollow btn">팔로우끊기</button//띄어쓰기하면 클래스 하나 더 들어가는거

 

 

팔로잉한 데이터 가져오려면

라우터에서 만들어줘야했음!

 

 

경로는 라우터에서

get으로 club.html불러주고

use로 사용하겠다.

어떻게 사용할지~

 

club.js

const express = require("express");
const { isLoggedIn, isNotLoggedIn } = require("../middlewares");
const { Club, User } = require("../../models");
const router = express.Router();

router.use((req, res, next) => {
  res.locals.user = req.user;
  // res.locals.followerCount = 0;
  // res.locals.followingCount = 0;
  // res.locals.followerIdList = [];
  res.locals.followerCount = req.user ? req.user.Followers.length : 0;
  res.locals.followingCount = req.user ? req.user.Followings.length : 0;
  res.locals.followerIdList = req.user
    ? req.user.Followings.map((f) => f.id)
    : [];
  next();
});


router.get("/", async (req, res, next) => {
  try {
    const clubs = await Club.findAll({
      include: {
        model: User,
        attribute: ["id", "nick"],
      },
      order: [["createdAt", "DESC"]],
    });
    res.render("club/club", {
      title: "mountain feed",
      twits: clubs,
    });
  } catch (error) {
    console.error(error);
    next(error);
  }
});

module.exports = router;

참고로 라우트 쓸려면

 

app.js

에 라우트 선언해야함

const clubRouter = require("./routes/club/club");

미들웨어도 추가

app.use("/club"clubRouter);

 

 

 

 

결과물~!!

마이페이지~

 

 

 

 

 

https://design-system.service.gov.uk/components/tabs/

 

Tabs – GOV.UK Design System

Components Tabs Experimental This component is currently experimental because more research is needed to validate it. The tabs component lets users navigate between related sections of content, displaying one section at a time. Contents Past day Past week

design-system.service.gov.uk

이건 넌적스 유용하게 쓸 수 있을 거 같은 사이트

728x90
728x90

클라이언트가 보내는 요청의 단점, 누가 보낸 건지 몰라. 

대신 로그인 구현하면 되는데 로그인 한후에 새로고침해도 로그아웃 안되도록 

클라이언트가 서버에 누구인지 지속적으로 알려줘야함

쿠키

클라이언트 로컬에 저장되는

유효기간 있고 name=zerocho 처럼 단순한 Key-Value쌍의 작은 데이터 파일

 

쿠키의 구성요소

1. 이름 : 각각의 쿠키를 구별하는데 사용함

2. 유효시간: 쿠키의 유지시간

3. 도메인: 쿠키를 전송할 도메인

4. 경로: 쿠키를 전송할 요청 경로

5. 값: 쿠키와 이름과 관련된 값

 

 

 

동작방식

1. 클라이언트 -> 페이지 request 요청

2. 서버에서 쿠키를 생성

3. http헤더에 쿠키를 포함해서 respond 응답

4. 브라우저가 종료되어도 쿠키의 기한이 정해져 있지 않고 명시적으로 지우지 않는다면 반 영구적으로 쿠키가 남아있게됨

클라이언트 요청(쿠키가지지 않은 상태)
------------->
쿠키와 함께 응답
<-------------
쿠키와 함께 요청
--------------->
응답(쿠키 가진 상태)
<---------------
서버

 

 

 

세션 

브라우저가 종료되기 전까지 클라이언트의 요청을 유지하게 해주는 기술

 

세션은 어쨌든 쿠키를 기반으로 하고 있음

서버측에서 관리하긴함

 

 

 

사용자 많아질수록 서버 메모리 많이 먹어

클라이언트한테 아이디부여하는데 그게 세션아이디임

 

동작방식

  1. 클라이언트가 서버에 로그인 요청
  2. 서버는 클라이언트의 로그인 요청의 유효성을 확인하고(아이디와 비밀번호 검사) unique한 id를 sessionid라는 이름으로 저장
  3. 서버가 응답할 때 응답헤더에 set-cookie: sessionid:a1x2fjz를 추가하여 응답

   4 . 클라이언트는 이후 서버에 요청할 때 전달받은 sessionid:a1x2fjz쿠키를 자동으로 요청헤더에 추가하여 요청

   5. 서버에서는 요청헤더의 sessionid 값을 저장된 세션저장소에서 찾아보고 유효한지 확인후 요청을 처리하고 응답

 

세션의 내용은 서버에 저장되기 때문에 계속하여 늘어날 경우 서버에 부하가 발생

 

 

쿠키와 세션의 차이점

가장 큰 차이점 정보가 저장되는 위치

 

저장위치

쿠키는 서버에 저장되지 않고 클라이언트 로컬에 저장됨

세션은 서버의 자원을 써서 로컬과 서버에 저장됨

 

장단점

보안면에서는 세션이 좋고

속도면에서는 쿠키가 빠름

 

세션은 쿠키를 이용해 id만 저장하고 서버에 저장해

세션도 만료시간 정할 수 있지만 브라우저가 종료되면 만료시간 상관없이 날라감

쿠키는 브라우저 종료해도 파일로 남아있음

 

 

 

얘를 보완해서 JWT가 나옴 책에도 나와~

 

 

 

 

 

책보면서 직접 코드 따라해보기

 

cookie.js  

const http = require("http");
http
  .createServer((req, res) => {
    console.log(req.url, req.headers.cookie);
    res.writeHead(200, { "Set-Cookie": "mycookie=test" });
    res.end("Hello Cookie");
  })
  .listen(8083, () => {
    console.log("8083번 포트에서 서버 대기 중입니다");
  });

 

터미널

 경로 입력하고 서버대기 중 확인

 

 

createServer 메서드의 콜백에서는 req 객체에 담겨있는 쿠키 가져옴

쿠키는

req.headers.cookie 에 들어있음

req.headers는 요청의 헤더 의미 (쿠키는 요청과 응답의 헤더를 통해 오감)

응답의 헤더에 쿠키를 기록해야 돼서

res.writeHead 메서드를 사용함 

"Set-Cookie": "mycookie=test" 는 "다음에 오는 쿠키를 저장해라 : 다음"

 

 

 

 

로컬주소에 접속

req.url과 req.headers.cookies에 대한 정보를 로깅하도록 함

req.url은 주소의 path와 search부분을 알림 (사실 이부분 이해안돼서 찾아봤는데 이해안됨 그래도 읽어보자)

아 이건인가봐 (뒤늦게 발견)

 

 

 

 

위 코드까지는 쿠키 심기만 한거고

그 쿠키가 나인지를 식별 못하고 있음

이제는 사용자를 식별하는 방법을 알아보자

 

cookie2.html

<!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" />
    <title>쿠키&세션 이해하기</title>
  </head>
  <body>
    <form action="/login">
      <input id="name" name="name" placeholder="이름을 입력하세요" />
      <button id="login">로그인</button>
    </form>
  </body>
</html>

cookie2.js

const http = require("http");
const fs = require("fs").promises;
const url = require("url");
const qs = require("querystring");
//쿠키는 문자열임 이를 쉽게 사용하기 위해 js객체 형식으로 바꾸는  parseCookies함수
const parseCookies = (cookie = "") =>
  cookie
    .split(";")
    .map((v) => v.split("="))
    .reduce((acc, [k, v]) => {
      acc[k.trim()] = decodeURIComponent(v);
      return acc;
    }, {});
http
  .createServer(async (req, res) => {
    const cookies = parseCookies(req.headers.cookie);

    //주소가 /login으로 시작하는 경우,url과 querystring모듈로 각각 주소와 주소에 딸려오는 query분석한다
    if (req.url.startsWith("/login")) {
      const { query } = url.parse(req.url);
      const { name } = qs.parse(query);
      const expires = new Date();
      //쿠키 유효시간을 현재시간 +5분으로 설정
      expires.setMinutes(expires.getMinutes() + 5);
      //헤더에 302응답코드,리다이렉트 주소와 함께 쿠키를 넣어
      //브라우저는 이 응답코드를 보고 페이지를 해당주소로 리다이렉트함
      //헤더는 한글을 설정할 수 없어서 name변수를encodeURIComponent메서드로 인코딩함
      res.writeHead(302, {
        Location: "/",
        "Set-Cookie": `name=${encodeURIComponent(
          name
        )}; Expires=${expires.toGMTString()};HttpOnly;path=/`,
      });
      res.end();

      //그 외의 경우(/로 접속했을때 등),먼저 쿠키있나없나확인. 없으면 로그인할 수 있는페이지보냄
      //처음 방문한경우 쿠키없으므로 cookie2.html 전송됨 쿠키있다면 로그인한상태로 간주해인사말보냄
      //name이라는 쿠키가 있는 경우
    } else if (cookies.name) {
      res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8" });
      res.end(`${cookies.name}님 안녕하세요`);
    } else {
      try {
        const data = await fs.readFile("./cookie2.html");
        res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
        res.end(data);
      } catch (err) {
        res.writeHead(500, { "Content-Type": "text/plain;charset=utf-8" });
        res.end(err.message);
      }
    }
  })
  .listen(8084, () => {
    console.log("8084번 포트에서 서버 대기중입니다!");
  });

set-Cookie로 쿠키 설정할 때 

만료시간 expires 과 HttpOnly, Path같은 옵션 부여함

쿠키 설정할 때 각종 옵션 넣을 수 있고 옵션 사이 세미콜론(;) 써서 구분

쿠키에는 한글 쓰기, 줄바꿈 하면 안됨

  • 쿠키명=쿠키값  기본적 쿠키값 예) mycookie=test
  • Expires=날짜: 만료기한. 기본값은 클라이언트가 종료될 때까지
  • Domain= 도메인명:  쿠키가 전송될 도메인 특정할 수 있음. 기본값은 현재 도메인
  • Path=url : 쿠키가 전송될 url을 특정할 수 있음 기본값은'/'이고 이경우 모든 url에서 쿠키 전송할 수 있음
  • secure: HTTPS 일 경우에만 쿠키가 전송됨
  • HttpOnly: 설정 시 자바스크립트에서 쿠키에 접근할 수 없음 쿠키 조작을 방지하기 위해 설정하는 것이 좋음

 

 

 

 

 

이제 이름 노출안되는 버전

세션 방식.

const http = require("http");
const fs = require("fs").promises;
const url = require("url");
const qs = require("querystring");

const parseCookies = (cookie = "") =>
  cookie
    .split(";")
    .map((v) => v.split("="))
    .reduce((acc, [k, v]) => {
      acc[k.trim()] = decodeURIComponent(v);
      return acc;
    }, {});

const session = {};
http
  .createServer(async (req, res) => {
    const cookies = parseCookies(req.headers.cookie);
    if (req.url.startsWith("/login")) {
      const { query } = url.parse(req.url);
      const { name } = qs.parse(query);
      const expires = new Date();
      expires.setMinutes(expires.getMinutes() + 5);
      const uniqueInt = Date.now();
      session[uniqueInt] = {
        name,
        expires,
      };
      res.writeHead(302, {
        Location: "/",
        "Set-Cookie": `session=${uniqueInt}; Expires=${expires.toGMTString()};HttpOnly;Path=/`,
      });
      res.end();
      //세션 쿠키가 존재하고 만료기간이 지나지 않았다면
    } else if (
      cookies.session &&
      session[cookies.session].expires > new Date()
    ) {
      res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
      res.end(`${session[cookies.session].name}님 안녕하세요`);
    } else {
      try {
        const data = await fs.readFile("./cookie2.html");
        res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
        res.end(data);
      } catch (err) {
        res.writeHead(500, { "Content-Type": "text/plain;charset=utf-8" });
        res.end(err.message);
      }
    }
  })
  .listen(8085, () => {
    console.log("8085번 포트에서 서버 대기 중입니다");
  });

 

코드 따라썼는데 자꾸 안된다고 에러뜸 

바로 이 에러~!!

NodeJS address already in use 

찾아서 따라해서 이전 포트? 종료하고 다시 함

로컬주소 들어가서 정보 입력하니까!

 

 아까 넣었던 쿠키 위에 쌓였다.

이 방식이 세션임

 

 

아까랑 다른 건 쿠키에 이름을 담아서 보내는 대신, uniqueInt라는 숫자값을 보냄. 사용자의 이름과 만료시간은 uniqueInt 속성명 아래에 있는 session이라는 객체에 대신 저장함

이제 cookie.session이 있고 만료기한 넘기지 않았으면 session 변수에서 사용자 정보를 가져와 사용한다.

 

 

안전하게 사용하기 위해서는 다른 사람들이 만든 검증된 코드를 사용하는게 좋대

그건 책 5장에서~~~

 

 

 

 

 

 

 

 

그.런.데.

내가 코드를 다 이해했다? 전혀 아니지요.

 

map 메서드

배열.map((요소, 인덱스, 배열) => { return 요소 });

const oneTwoThree = [1, 2, 3];
let result = oneTwoThree.map((v) => {
  console.log(v);
  return v;
});
// 콘솔에는 1, 2, 3이 찍힘
oneTwoThree; // [1, 2, 3]
result; // [1, 2, 3]
oneTwoThree === result; // false

reduce 메서드

배열.reduce((누적값, 현잿값, 인덱스, 요소) => { return 결과 }, 초깃값);

result = oneTwoThree.reduce((acc, cur, i) => {
  console.log(acc, cur, i);
  return acc + cur;
}, 0);
// 0 1 0
// 1 2 1
// 3 3 2
result; // 6

acc(누적값)이 초깃값인 0부터 시작해서 return하는대로 누적되는 것을 볼 수 있습니다. 초깃값을 적어주지 않으면 자동으로 초깃값이 0번째 인덱스의 값이 됩니다.

 

그렇다고 함 이것이 바로 설명 링크.. 읽어도 잘모르겠지만 암튼 보자 

 

 

 

 

 

 

 

NodeJS address already in use 문제 해결 - JooTC

NodeJS address already in use 문제 해결 방법 Error: listen EADDRINUSE: address already in use :::5000 현재 다른 프로세스에서 사용 중이라 해당 서비스를 시작할 수 없다는 에러입니다.

jootc.com

 

 

 

 

 

출처

728x90

+ Recent posts