ABOUT ME

우주속 작은 존재로, 각종 인생 실험을 수행합니다.

Today
Yesterday
Total
  • #1. Nodebird
    카테고리 없음 2019. 12. 21. 22:06

    (zerocho 님의 강좌를 바탕으로 복습)

     

     

    도화지 가져오자 

    mkdir nodebird
    cd nodebird
    npm init

    각 명령에 대한 현상은 ? 

     

    @ package.json 

    "start" : "nodemon app"

    수정 

     

     

    db는 여기서 mysql + sequelize를 쓰겠다. 

     

    npm i -g sequelize-cli 

     

     

    패키지 설치 

    npm i sequelize mysql2

     

    이제, 설치했던 sequelize-cli 로 인해 다음의 명령어를 프롬프트에서 사용할 수 잇겠지 ? 

    sequelize init

     

    config , migration, model, seeders 폴더 들이 생긴다 . 

     

     

     

    mysql 에서는

    데이터베이스- 테이블- 로우 의 계층이 있다.

    즉 db를 만들어 줘야겠지 ?

     

    config.json 에서 설정을 해준다. 

     

    db를 켜둔 상태에서 

    sequelize db:create

    이때 ! 

    1. db가 켜진 상태가 아니거나 

    2.password가 다를때 (로컬로) 에러메세지가 뜰것이다 

    다음과같이, 에러 발생함. 주의

     

    ERROR: Access denied for user 'root'@'localhost' (using password: YES)

     

    자 일단 db도 연결해놨고,, 

     

    개발환경을 좀 더 구성하기위해서 

    npm i -D nodemon

     

     

    그리고 드뎌 express 에서 주요하게 쓰이는 미들웨어 친구들을 설치하자 

     

    npm i express cookie-parser express-session morgan connect-flash pug

    (express-generator를 사용하지않고 직접 폴더 구조를 잡는다)

     

     

    app.js 파일 생성 

    const express = require('express');
    
    const cookieparser = require('cookie-parser');
    const morgan = require('morgan');
    const path = require('path');
    const session = require('express-session');
    const flash = require('flash');
    
    
    
    const app = express();
    
    
    // app.set한것은 추후 app.get으로 가져올수있다. 
    
    app.set('view engine', 'pug'); 
    app.set('views', path.join(__name, 'views')); //이건 그 흔한 뷰폴더. views폴더 만들어 줘야겠지 ?  알아서 폴더찾아서 간다 
    app.set('port', process.env.PORT || 8001); 
    
    
    
    app.use(morgan('dev'));
    app.use(express.static(path.join(__dirname, 'public')));//public 폴더도 만들어줘야겠지 ? 
    app.use(express.json());
    app.use(express.urlencoded({ extended: false}));
    app.use(cookieparser('nodebirdsecret'));
    app.use(session({
        resave: false,
        saveUninitialized: false,
        secret : 'nodebirdsecret',
        cookie:{
            httpOnly: true,
            secure: false,
        },
    }))
    
    app.use(flash());
    
    //app.set으로 해둔것들 get으로 가져오자 
    app.listen(app.get('port'), ()=>{
        console.log(`${app.get('port')} 번 포트에서 서버 실행중이얌~`)
    })
    
    

     

     

    기계적으로 미들웨어를 작성. 미들웨어 부분은 추후 자세히 분석 

    포인트는 각 미들웨어에서 필요한 미들웨어들을 알고있으면 된다 

     

    이떄 잠깐 ! 

    보안을 위해서 다음을 추가한다 

     

    npm i dotenv

    .env 파일 생성 

     

    그안에

    COOKIE_SECRET=nodebirdsecret
    PORT=8001

     

    당연히 app.js 에 반영하고 미들웨어 작성해줘야됨. (해당 미들웨어 추후 확인) 

     

    require('dotenv').config();

    또한 COKKIE_SECRET으로 대체 해줌 

    app.use(morgan('dev'));
    app.use(express.static(path.join(__dirname, 'public')));//public 폴더도 만들어줘야겠지 ? 
    app.use(express.json());
    app.use(express.urlencoded({ extended: false}));
    app.use(cookieParser(process.env.COOKIE_SECRET));
    app.use(session({
        resave: false,
        saveUninitialized: false,
        secret : process.env.COOKIE_SECRET,
        cookie:{
            httpOnly: true,
            secure: false,
        },
    }))
    

     

     

    이러한 행위가 왜 보안에 강해지는가 ? 

     

     

     

    *무조건 각 백서 원문을 읽어봐야된다.

    nodejs, express, passport 등..

    왜냐면 어떠한 것을 만들때 이미 재료들은 생각보다(?) 충분히 주어져있다는것이다.

     

    동작원리와 개념을 알고있거나, 통상적인 개발자들의 관리비법 혹은 개발동향 방법들을 알고있다면, 

    새로운것을 만들기위해 기존의 것들을 이용해서(백서를 본다거나 등등) 개발 할 수있다는 것인데. 

    그렇다면 이 능력/쎈스를 배워야한다 

    그러니,

    지금 진행하고있는 것들을 일일이 따라해보되, 

    다시 복습시에는 내가 하나도 모른다고 생각하고 그 기능들을 검색하여 백서를 보고 적용을 해보는 것들도 함께 

    비교해가면서 학습해야 될 것 같다. 

     

     

     

    기본라우터 pug파일 세팅

    routes 폴더 생성 

    하위 아래 page.js user.js 생성 

     

    routes/page.js

    routes/user.js

     

    express의 route 기능을 쓰기때문에

    객체화 하는 것들은 자동으로 한다고 생각하면 된다. 기계적으로 .

     

    const express = require('express');
    const router = express.Router();
    
    router.get('/', (req,res,next)=>{
    
    });
    
    module.exports = router;
    
    

     

    page.js  

    에서 (사실 index.js)로 하는게 더 나을듯. 

    무튼! 

     

    page.js에서 가지는 페이지 의 갯수는 총 3개 

    - 프로필 페이지 

    - 회원가입 페이지 

    - 메인 페이지 

     

    그러므로. . . . 

    const express = require('express');
    const router = express.Router();
    
    //프로필 페이지 
    
    router.get('/profile', (req,res)=>{
        res.render('profile', { title: '내 정보 -Node Bird ', user: null })
    })
    
    //회원가입 페이지 
    router.get('/join', (req, res)=>{
        res.render('join', {
            title: '회원가입 - 뷁세NodeBird ',
            user : null,
            joinError: req.flash('joinError'),
        })
    })
    
    router.get('/', (req,res,next)=>{
        res.render('main', {
            title: 'NodeBird_Baik',
            twits: [],
            user: null,
            loginError: req.flash('loginError'),
        });
    });
    
    module.exports = router;
    
    

     

     

    각 페이지에 맞게 랜더링 해줘야 되는 파일들. 

    그리고 flash를 사용하여 추후 에러시 메세지 요청예정 

    그리고 항상 개념적으로 생각해줘야되는게 app.js 에서 라우터가 선언되어있는지 보는것 ! 

     

    app.js 

     

    1) 내가 만든 라우터를 선언해주는것.

    2) 그 선언한 것을 app.use 로 미들웨어 밑에 연결지어주는것. 

     

    const indexRouter = require('./routes/page');
    
    .
    .
    .
    .
    
    
    app.use('/', indexRouter);
    

     

     

    자 . 그러면 라우터에서 만들었던....3가지 페이지! 

    내가 어떤것들 랜더(render)해줄께 ~ ~ 했던 파일들을 실제로 만들어줘야겠지 ? 

     

     

    views/error.pug

    views/join.pug

    views/layout.pug

    views/main.pug

    views/profile.pug

     

    (이 코드는 일단 긁어옴)

     

    이때, 에러 페이지도 만들었으니 미들웨어 연결 해줘야된다 ( 추후 자세히 보기 ) 

     

    app.js

    app.use((req, res, next) => {
        const err = new Error('Not Found');
        err.status = 404;
        next(err);
      })
    
    
      app.use((err, req, res, next) => {
        res.locals.message = err.message;
        res.locals.error = req.app.get('env') === 'development' ? err : {};
        res.status(err.status || 500);
        res.render('error');
      });

     

    이렇게 일단 기본적인 뼈대를 만들었다.

     

    여기까지 미비한건  front부분에서 데이터를 받아오는 post 방식의 라우터들과 모델 및 테이블등은 아직 만들지않았다 

    진짜 딱 와꾸만 페이지 렌더링 라우터 정도만 한것. (물론 에러처리 및 env 처리까지) 

    갈길이 좀 남았다 ~ 

     

    (logging 191222sun)

    모델/테이블 만들기

     

    mysql sequelize 를 사용할때, 보통 sequelize-cli 를 이용해서 sequelize init 을 하게된다. 

    그떄 생성되는 폴더/파일 중 가장 중요한것은 

    models/ index.js 이다 . 

     

    models / index.js 

    const Sequelize = requrie('sequelize');
    const env = process.env.NODE_ENV || 'development';
    const config = require('../config/config')[env];
    const db = {};
    
    const sequelize = new Sequelize(
      config.database, config.username, config.password, config,
    );
    
    db.sequelize = sequelize;
    db.Sequelize = Sequelize;
    
    module.exports = db;

    와 같이 코드를 수정 

     

    우리에게 필요한 테이블은 다음과 같다. 

    - 해시태그 테이블 : hashtag.js 

    - 사용자 테이블 : user.js

    - 게시글 테이블 : post.js

     

    이렇게 models 폴더 내에 파일을 생성하면 되겠지 ? 

     

    1. 먼저 user 테이블을 만들어 보자

     

    models / user.js

     

    module.exports = ((sequelize,DataTypes)=> (
        sequelize.define('user', {
            email: {
                type: DataTypes.STRING(40),
                allowNull : false,
                unique: true,
            },
            nick : {
                type: DataTypes.STRING(15),
                allowNull : false,
    
            },
            password : {
                type: DataTypes.STRING(100),
                allowNull : true,
            },
            provider :{
                type : DataTypes.STRING(10),
                allowNull:false,
                defaultValue: 'local',
            },  
            snsId :{ //카카오 예정 
                type: DataTypes.STRING(30),
                allowNull: true,
            },
            
        }, {
            timestamps: true, //생성일, 수정일
            paranoid:true, //삭제일(복구용)
        })  
    
    ));

     

    시퀄라이즈를 통해서 user.js 테이블을 위와 같이 만들었다. 

    (왜 저러한 약속으로 쓸수있는지는 추후 백서 참고예정)

    Q1. 어떠한 레벨/기준으로 테이블을 나눈것일까? 

     

    post.js 도 비슷하게 테이블을 작성하자

     

    models / post.js 

     

    module.exports = ((sequelize,DataTypes)=> (
        sequelize.define('post', {
            content:{
                type: DataTypes.STRING(140),
                allowNull : false,
            },
            img:{
                type: DataTypes.STRING(200),
                allowNull: true,
            }
            
        }, {
            timestamps: true, //생성일, 수정일
            paranoid:true, //삭제일(복구용)
        })  
    
    ));

     

    그리고 hashtag.js 테이블도 작성 한다 

     

    module.exports = ((sequelize,DataTypes)=> (
        sequelize.define('hashtag', {
            title:{
                type: DataTypes.STRING(15),
                allowNull : false,
                unique: true,
            },
            
        }, {
            timestamps: true, //생성일, 수정일
            paranoid:true, //삭제일(복구용)
        })  
    
    ));

     

     

    이렇게 총 3가지의 테이블을 만들었다. 

    models / post.js  

    models / hashtag.js  

    models / user.js

     

    그러면 당연히 이들의 관계를 표현해주고 연결해주는 index.js에서 뭔가 요리를 해줘야 될것같은 느낌이 드는가.? 

    이들은 그냥 파일로서 존재하면 안되고, 누군가 어디선가 저들을 호출하고 끌어와야 되기 때문이다.

     

     

    다대다 관계 이해하기 

     

    models / index.js 에서 이 관계를 정의한다. 

     

    먼저,

    1. 저위에 3가지 파일들을 db에 선언하고  

    2. 그들의 관계를 정의할 것이다 

     

    const Sequelize = requrie('sequelize');
    const env = process.env.NODE_ENV || 'development';
    const config = require('../config/config')[env];
    const db = {};
    
    const sequelize = new Sequelize(
      config.database, config.username, config.password, config,
    );
    
    db.User = require('./user')(sequelize, Sequelize);
    db.User = require('./post')(sequelize, Sequelize);
    db.User = require('./hashtag')(sequelize, Sequelize);
    
    //
    db.User.hasMany(db.Post);
    db.Post.belongsTo(db.User);
    
    //다대다 관계에서 필요함 
    db.Post.belongsToMany(db.Hashtag, { trough: ' PostHashtag'});
    db.Hashtag.belongsToMany(db.Post, { trough: ' PostHashtag'});
    
    //
    db.User.belongsToMany(db.User, { through : 'Follow', as: 'Followers', foreignKey: 'followingId'});
    db.User.belongsToMany(db.User, { through : 'Follow', as:'Following', foreignKey: 'follwerId'});
    
    //
    db.User.belongsToMany(db.Post, { through: 'Like'});
    db.Post.belongsToMany(db.Post, { through: 'Like'});
    
    
    db.sequelize = sequelize;
    db.Sequelize = Sequelize;
    
    module.exports = db;

     

    다대다 관계 혹은 일대다 관계는 

    하나하나 예시를 들면서 설계자에 맞게 가야된다. 

     

    1. 관계에 따른 코드 사용법

    2. 관계를 어떻게 정의하는지는

     

    (추후 구글링과 백서를 참고예정)

     

    그렇다면 이제 models 폴더에 필요한 테이블들 (3가지) 도 작성하였고

    그 파일들을 models / index.js 에서 관계와 db설정을 선언하였다.

     

    그러면 당연히 이제는 app.js에 가서 이들을 또 연결해줘야 된다.

     

    const { sequelize } = require('./models');
    
    
    
    sequelize.sync();

    를 app.js 내에 미들웨어 전에 적절하게(?) 배치한다. 

     

    그리고 드뎌 ! run 을 해보자 !! 

    당연히 바로 안되며, 여러 짜잘한 에러들 잡아주면 

     

    과 같이 실행되며 실제로 코드작성한 것처럼 테이블을 생성한다 !!! 

     

    워크벤치에서도 확인 ! 

    워크벤치에서도 동일하게 테이블이 생성된것을 확인할 수 있다. ! ! wow !!! 

     

    정말 시퀄라이즈만 사용해서 javascript로 만 ! 테이블과 디비를 생성하다. 

     


    그러면 지금까지 한것을 정리해보자

     

    • 세팅작업 
      • npm init 부터 sequelize init 까지 . node와 sequelize만 사용해서 db생성부터 테이블 까지. 만들기위한 환경작업
      • express middleware 직접 작성 
    • 외형 작업
      • view 작업 (using express) --> index.js 에서 관리 --> app.js 에 선언 
      • router 작업 (using express) --> index.js --> app.js 
      • model 작업 (using sequelize-clie) 

     

     

    딱 이정도 대강 한것 같다. 

    자세한 내용들은 이 부분을 다룬 백서과 구글링을 조금 더 해서 정확한 사실관계와 저렇게 사람들이 쓴 이유들, 그리고 왜 저렇게 할 수 밖에 없었는지를 알고싶다. 

    예를들어, 

    1. sequelize-cli를 이용해서 sequelize를 사용했다. 

    그리고 테이블을 생성하고 그들의 관계를 hasmany, belongsTo라던지의 메서드를 사용하는데 다른 메서드 들은 없는지.

    다른 프레임워크에서는 어떻게 저런것을 구현하는지 등 . 

    조금 더 면밀하게 관찰해볼 필요가 있는 듯 하다. 

     

     

    또한, 초심자 입장에서 세팅작업부터 한다고 볼때, 

    단순히 명령어를 쓰고 파일을 작성하는게 아니라, 어느정도의 이해를 하고 넘어가기에는 사실역부족이라고 생각한다. 

     

    그래서 내가 생각하는 좋은 방법은 3cycle이다. 

     

    1cycle 은 그냥 무작정 뇌없이 생각없이 손가락과 눈만 가지고 보고 쓰고 돌려보는것이다. 

    2cycle 은 내가 적었던 코드들에대해서 구글링을 하는것. 아무거나 생각나는거 전부다. 

    3cycle은  알아낸 것들을 하나씩 정리하고 다시 코드를 작성해보는것. 

     

    그래서 저 3 싸이클을 계속 반복하다 보면 어느새 개념이 나의 것이 되지 않을까? 

    하는 생각을 하게된다. 

     

    그래서 내가 해보고 있다.


    이제 계속해서 

     

    • 회원가입 using passport 
    • 카카오 로그인 
    • 게시글 CRUD 
    • 해시태그 / 팔로잉 구현 

    으로 진행을 할 예정이다. 

    19.12.24.화 6:45am

    passport 세팅과 passport-local 전략 

    먼저 패키지 install 부터 한다. 

     

    npm i passport passport-local passport-kakao bcrypt

    이렇게 레고블럭 조립하듯이 패키치를 깔았으니, 

    당연히 app.js가서 또 선언을 해줘야 된다. 

     

    app.js 

     

    const passport = require('passport');

    그리고 passport 폴더를 하나 생성 후 index.js localstrategy.js kakaoStrategy.js를 생성한다. 

    (이건 원리를 알고있어야 한다. 백서를 당연히 훑어 봐야됨) 

     

    passport / index.js 

    passport / localStrategy.js

    passport / kakaoStrategy.js

     

    이렇게 localStrategy 와 kakaoStrategy 파일을 만들었으니, 

    index.js에 연결을 해주고 그 부분을 index.js에서 선언해주고 작동을 시켜줘야된다. 

     

    그리고 난 뒤 당연히 app.js에서 passport/index.js를 구동해줘야되고 미들웨어가 필요하다면 middleware도 작성해줘야된다. 

     

    계속 반복하겠지만 늘 이런 방식(패턴)을 가지고 있다. 

     

    npm install --> folder or file create --> index.js 선언 및 연결 --> app.js 연결 및 middleware 연결 

     

     

    app.js 에서 passport 구동을 위한코드 추가 

    const passport = require('passport');
    .
    .
    .
    
    const passportConfig = require('./passport');
    .
    .
    .
    
    passportConfig(passport);
    

     

    app.js 에서 passport 미들웨어 추가 

    app.use(passport.initialize());
    app.use(passport.session());

    주의사항은, expresss session을 사용해서 passport session을 쓰기 때문에 이후에 선언해줘야된다. 

     

    그러면 이제 각 전략(strategy를 써보자) 

     

    local부터 하자.

    localStrategy에서는 

    epsress의 미들웨어 인 urlendcoded 가 해석한 req.body의 값들을 --> username과 password에 연결한다 

    저 흐름을 이해하는게 중요 

    module.exports =(passport)=>{
        passport.use(new localStorage({
            usernameField: 'email', //req.body.email
            passwordFiled: 'password', //req.body.password
        }, async (email, password, done)=>{
    
        
        }))
    }

     

    계속해서 코드를 써 나갈텐데, 구현 알고리즘은  이렇다.

     

    1. 웹 페이지에서 username 과 password를 받는다 
    2. 1번의 과정이 가능하게 하는건 express 의 미들웨어 인 app.use(express.urlendcoded)가 해석한 req.body의 값들이다. 
    3. 실제로 사용자(user)가 입력한 아뒤와 비번 과 db에 있는 아뒤 비번을 비교한다. 
    4. 비교를 위해서 먼저 db를 불러오고 찾는 선언이 필요
    5. 비교작업은 bcrpt 모듈에서 compare 메서드를 사용해서 보안강화
    6. 입력한 아뒤비번 vs db에 있는 아뒤비번 비교결과 true면 성공
    7. false이면 비번 일치 X  메세지 반환 
    8. 가입되지않은 경우도 메세지 반환 

     

    대략 이렇다. 

    저 과정을 코드로 마저 옮겨보자 

     

    passport/localStrategy.js

    const localStorage = require('passport-local').Strategy;
    const bcrypt = require('bcrypt');
    
    const { User } = require('../models');
    
    
    module.exports =(passport)=>{
        passport.use(new localStorage({
            usernameField: 'email', //req.body.email
            passwordFiled: 'password', //req.body.password
        }, async (email, password, done)=>{ //done(에러, 성공, 실패)
            try {
                const exUser = await User.findOne({where:{email}}); //db에 있는 비번 찾기 
                if(exUser){
                    //비번검사
                    const result = await bcrypt.compare(password, exUser.password); //사용자가 입력한 비번 vs db에 있는 비번 비교 
                    if(result){ //result는 true or false 로 반환 
                        done(null, exUser);
                    }else{
                        done(null, false, {message: '비번 불일치 '});
                    }
                }else {
                    done(null, false, {message: '미가입 회원 '});
                }
            } catch (error) {
                console.error(error);
                done(error);
            }
        
        }))
    }

     

    기본적인 세팅은 끝났고, 이제는 실제 회원가입을 하기 위해서 routes를 하나더 생성하고 passport와 연결하는 작업을 하자 

     

    회원가입

    routes 폴더에서 auth.js 파일생성 

    라우트 파일을 생성했으면 다음과 같은 코드는 기계적으로 작성해야된다. 

    이유는  알지 ?

     

    const express = require('express');
    const router = express.Router();
    
    router.post('/join', (req,res,next)=>{
    
    })
    
    router.post('./login', (req,res,next)=>{
    
    })
    
    module.exports = router;

    특이점은 post방식을 썼다는 것. 이유는 추후 더 자세히 작성하겠지만, Front에서 Form태그의 메소드와 연관이있다. 

    즉 프론트와도 기능이 연관성이 있다는것.

     

    회원가입 의 프론트 코드를 잠깐 보면 , 

    form#join-form(action='/auth/join' method='post')
          .input-group
            label(for='join-email') 이메일
            input#join-email(type='email' name='email')
          .input-group
            label(for='join-nick') 닉네임
            input#join-nick(type='text' name='nick')

    이때

    1. form태그의 method가 post방식 

    2. action은 /auth/join 

    으로 되어있다. 

     

    이 관계를 잘 봐야 추후 에러가 적다 

     

    계속해서 진행을 해보자.

    가입에 대한 부분(/join)은 다음과 같은 구조로 코드 작성을 해본다, 

     

    기본적으로 async await을 사용해서 분기를 명확화 한다. 또한 try catch 를 사용해서 코드를 명확하게 작성. 

     

    1. req.body로 부터 받은 값 객체화 

    2. 받아온 값 과 db에 있는 값이 있는지 확인 

    3. 있으면 일시적 메세지(flash)와 함께 메세지 처리 --> 그리고 다시회원가입(/join)으로 redirect 

    4. 없으면 bcrpyt를 통해 hash암호화

    5. db에 새로 생성 (create) --> 매인화면으로 redirct (/) 

     

     

    routes/auth.js

    const express = require('express');
    const bcrypt = require('bcrypt');
    const { User } = require('../models');
    
    const router = express.Router();
    
    
    router.post('/join', async(req,res,next)=>{
        const { email, nick, password} = req.body;   //받아온 값 
        try {
            const exUser = await User.findOne({where:{email}}) //기존에 있는지 인스턴스화 
            if(exUser){ // true 이면 
                req.flash('joinError','이미있어');
                return res.redirect('/join');
            }// 없으면 바로 내려간다, 
            console.time('암호화시간');
            const hash = await bcrypt.hash(password,12);
            console.timeEnd('암호하시간')
    
            await User.create({
                email,
                nick,
                password: hash,
            });
    
            return res.redirect('/')
        } catch (error) {
            console.error(error);
            next(error);
        }
        
    })
    
    router.post('./login', (req,res,next)=>{
    
    })
    
    module.exports = router;

     

    일단 구동해보자. 여기까지 에러가 없는지 ! 

    아니네, 아직 passport 폴더 쪽 index 및 카카오 쪽이 부족해서 안돌아갈듯 

    19.12.26. am 7:02

     

    로그인 로그아웃 구현

     

    정리를 좀 하고 로그인과 로그아웃을 구현해보자. 

     

    먼저 로그인을 위한 연관있는 폴더구조는 이렇다. 

     

    1번. /routes/auth.js 

     

    2번. /passport/index.js

    3번. /passport/localStrategy.js

    4번. /passport/kakaoStrategy.js

     

    우리는 먼저 local 기능을 수행한다. 

    그래서 3번 localStrategy.js 를 작성해왔다. 

     

    local에서 비밀번호를 비교하고 판단하는 것은 3번파일에서 수행 

     

    "그러면 결국 라우트인 1번에서 passport가 있어야 되고, 4번에서 수행한 결과를 매개변수로 받아온다."

     

    이것이 중요한 개념이다. 

     

    이를 수행하기위해서 

     

    /routes/auth.js

    const passport = require('pasport');

    를 추가한다. 

    왜냐면 passport의 메서드를 사용해서 로그인 여부를 요청하고 --> 이를 3번에서 수행할 것이고 --> 이를 argument로 받아와서 다시 라우트인 /routes/auth.js에서 작업을 하는 방식이기 때문이다. 

     

    이를 다음과 같이 작성을 한다.

     

    /routes/auth.js 

    router.post('/login',isNotLoggedIn, (req,res,next)=>{ //req.body.email, req.body.password
        passport.authenticate('local', (authError, user, info)=>{
            if(authError){
                console.error(authError);
                return next(authError);
            }
            if(!user){
                req.flash('loginError', info.message);
                return res.redirect('/');
            }
            return req.login(user, (loginError)=>{ //req.user 
                if(loginError){
                    console.error(loginError);
                    return next(loginError);
                }
                res.redirect('/')
            } )
        })(req,res,next)
    
    })
    
    

    Q. passport.authenticate('local',  ~ ~ ~    -->  how they know local is localStategy ?

    아 이해함...exports를 그렇게했네 

    결국 /routes/auth.js 에서 보면 passport를 이용해서 authenticate을 하는데 우리의 경우 

    local 함수를 불러와서 호출결과를 authError, user , infro에 담는다고 생각하면 되는거네 

     

    "또한 로그인 된(isLoggedIn), 로그인이 안된(isNotLoggedIn) 을 구분하여 애초에 라우트에서 

    저 두경우 로직에 맞지 않는 경우를 없애도록 한다."

     

    이것도 중요 개념이다. 이는 auth 폴더 내에 미들웨어를 추가하는 방식으로 구현을 해본다. 

     

    routes/auth.js
    routes/middlewares.js

     

    exports.isLoggedIn = (req,res,next)=>{
        if(req.isAuthenticated()) {
            next();
        } else {
            res.status(403).send('로그인 필요');
        }
    };
    
    exports.isNotLoggedInt = (req,res,next)=>{
        if(!req.isAuthenticated()){
            next();
        } else {
            res.redirect('/')
        }
    }

    (상세한 메서드는 백서를 찾아본다) 

     

    이렇게 구현을 했다. 

    그리고 로그아웃 까지 구현을 하면 다음과같다. 

     

    routes/auth.js

    router.get('/logout', isLoggedIn, (req,res)=>{
        req.logout();
        req.session.destroy(); //req.user
    	res.redirect('/');
    });

     

    아참. 이렇게 구현이 가능한 코드구조는 

    router.get(미들웨어1, 미들웨어2, 미들웨어3 ) 식으로 코드를 읽어 나가기 때문

    그러니깐, 라우터에서 get을 할때 먼저 미들웨어1을 찾아서 매개변수 반환 해주고 그 다음 미들웨어2 읽고 반환하는 식이라는 것이다. 

     

    *참고로 라우터를 추가했다는것 (폴더추가) 하면 각 폴더에 또 여러가지 get post 등을 이용해서 rendering할 page들이 있겠지? 

    그렇기때문에 app.js 에 가서 그 라우터 path 에 맞게 선언하고 미들웨어를 추가하는것은 기계적으로 할 줄 알아야한다는것 ! 

     

    Like this > 

    const indexRouter = require('./routes/page');
    const authRouter = require('./routes/auth');
    -
    -
    -
    -
    app.use('/', indexRouter);
    app.use('/auth', authRouter);

     

    passport serializeUser/ desrializeUser

    isLoggedIn , isNotLoggedIn 과 같은 미들웨어를 

    routes/page

    routes/user 

    에도 동일하게 적용을 하자. 그말인 즉슨 'frontend'를 너무 믿지 말라는 것이다. 

    백엔드에서 한번더 확인을 하자는 것이죠 

     

    그렇게

    routes/page

    routes/user도 코드를 추가한다. (자세한 코드는 추후 전체코드 삽입예정orgithub code )

     

    그나저나, 

    계속 진행하고있는것은 passport 모듈을 이용해서 로그인과 로그아웃을 구현하는것이다. 

    그렇기때문에 로그인 페이지가 필요하고 routes가 필요하고 미들웨어(isLoggedIn, isNotLoggedIn)을 사용해서 제한조건도 걸었다. 

    그렇다면 여기서 가장 중요한 부분은 무얼까? 

    바로 

    passport/index.js  파일이다. 

     

    로그인시 세션에 저장했던 user의 정보가 

    {id:1, name:Jimmy, age:11} 이런식일텐데, 

     

    이게 양이 많아지면 객체수가 너무많으니깐, 

    id만 알아도 되지않냐 라는 것이다. 

     

    또한, deserialize할때는 id만 찾아서 

    나머지 정보들을 찾아서 user를 복구시킨다 

     

    이 관계를 정확하게 이해하려면 app.js에서 부터 전체흐름을 기억해야된다. ! 매우중요 !! 

     

    -매 요청시마다 passport.session() 여기서 desrializeUser 가 실행 . 

    user.id를 DB조회 후 req.user 로 

    이거 좀더 자세히 흐름을 구조를 이해를 하자 

     

     

     

    구동 해보자 ! 

     

    카카오 로그인구현하기 

    sns로그인 중 카카오 로그인도 추가해보자 . 

     

    로그인 인증개념은 대략 이렇네. 

     

    즉 view단에서 로그인 요청을 함 

    - 로컬 로그인/회원가입

    - sns 로그인/ 회원가입 

     

    에 따라 방식이 조금 다르지만 거의 비슷하다. 

    핵심은 라우터에서는 랜더링할 페이지와 콜백함수를 가지고 있다. 

    그 콜백함수는 passport.authenticate을 이용해서 passport 폴더 내에 있는 애들을 가지고 로그인 및 회원가입을 DB와 일처리를 한다. 

    그렇게 한뒤 로그인이 인증되었든 안되었든. 회원가입이 잘 됬든 안됬든 매개변수 혹은 에러를 router에 반환 한다. 

    그 값에 따라 router에서 결과값을 화면에 뿌린다.(렌더링한다) 

     

    말이 조금 어려울 수 있는데 코드를 하나씩 따라가다 보면 이 개념이 잡힐 것이다. 

     

    *물론 app.js에서의 serialize 및 deserialize도 함께 개념을 잡아야한다. 

     

    *추후 > passport공식 문서와 내 코드를 비교분석 한다. 

     

    무튼 그러면 passport/kakaoStrategy를 작성하자 

     

     

    passport/kakaoStrategy

     

    const KakaoStrategy = require('passport-kakao').Strategy;
    
    const { User } = require('../models');
    
    //(2)
    module.exports = (passport) => {
      passport.use(new KakaoStrategy({
        clientID: process.env.KAKAO_ID, //.env는 process.env에 들어간다. 
        callbackURL: '/auth/kakao/callback',
      }, async (accessToken, refreshToken, profile, done) => {
        try {
          const exUser = await User.find({ where: { snsId: profile.id, provider: 'kakao' } });
          if (exUser) {
            done(null, exUser);
          } else {
            const newUser = await User.create({
              email: profile._json && profile._json.kaccount_email,
              nick: profile.displayName,
              snsId: profile.id,
              provider: 'kakao',
            });
            done(null, newUser);
          }
        } catch (error) {
          console.error(error);
          done(error);
        }
      }));
    };

    먼저는, 카카오전략을 선언하고, db와 통신하는 부분도 있으니 db를 가져와야된다

    (왜냐면, 카카오로 회원가입/로그인을 하지만 그 정보를 나의 db에도 저장을 할 것이기때문) 

     

    passport나 kakaoStategy부분은 각자의 백서를 보면서 자세히 지그~시 한번은 읽어보는게 도움이 될듯하다. 

    다만 중요한 점은 이렇다. 

     

    첫째, 

    module.exports = (passport)=>{

     

     

    }

    로 파일밖으로 추출(export)하는 부분 안에 원하는 코드를 passport 와 kakaoStrategy에서 지원하는 함수로 잘~ 구성하면 된다는것. 

     

    둘쨰, try ~ catch 패턴과 동시에 asnyc ~ await을 사용하여 비동기적으로 처리하면서 동시에 일어날 수 있는 에러에 대해서 처리를 해주는것. 이 틀이 중요하다고 생각한다. 

     

    셋째, 

    env 파일내에 KAKAO developer사이트에서 발행된 토큰 값을 저장하고, 이것을 우리는 process.env. 를 사용하여 보안에 유의하자는 것. 

     

     

    넷쨰, callbackURL 에서 나타내는 url주소가 router에서도 표현이 동일하게 이루어 져야된다는것이다. 

     

     

    이렇게 4가지가 중요한 점인듯하다. 

     

    잠깐 정리 ! 

     

     

    웹이 처음에 어렵다고 느끼는 이유는 

    javascript 언어를 사용하면서 처음보는 method들 ( db, 각종 패키지들) 이 섞여있고 또 요청은 http프로토콜 기반으로 명령을 때리니, 

    또한 코드 구조는 콜백을 쓰면서 동시에 매개변수까지 파악을 해야되는 점. 

    이런 부분들이 뒤죽박죽 섞여있어서 처음에 이해하는데 시간이 좀 걸리는듯하다. 

     

     

    카카오 앱 등록& 실행 & 디버깅 

     

    kakao developer에서 설정하는 것은 생략 

     

    에러발견

    Data too long for column 'nick' at row 1

    SequelizeDatabaseError: Data too long for column 'nick' at row 1 at Query.formatError (/Users/seyoungbaik/Documents/inf_Nodejs/npmtest/nodebird/node_modules/sequelize/lib/dialects/mysql/query.js:244:16) at Execute.handler [as onResult] (/Users/seyoungbaik/Documents/inf_Nodejs/npmtest/nodebird/node_modules/sequelize/lib/dialects/mysql/query.js:51:23) at Execute.execute (/Users/seyoungbaik/Documents/inf_Nodejs/npmtest/nodebird/node_modules/mysql2/lib/commands/command.js:30:14) at Connection.handlePacket (/Users/seyoungbaik/Documents/inf_Nodejs/npmtest/nodebird/node_modules/mysql2/lib/connection.js:412:32) at PacketParser.Connection.packetParser.p [as onPacket] (/Users/seyoungbaik/Documents/inf_Nodejs/npmtest/nodebird/node_modules/mysql2/lib/connection.js:70:12) at PacketParser.executeStart (/Users/seyoungbaik/Documents/inf_Nodejs/npmtest/nodebird/node_modules/mysql2/lib/packet_parser.js:75:16) at Socket.Connection.stream.on.data (/Users/seyoungbaik/Documents/inf_Nodejs/npmtest/nodebird/node_modules/mysql2/lib/connection.js:77:25) at Socket.emit (events.js:193:13) at addChunk (_stream_readable.js:295:12) at readableAddChunk (_stream_readable.js:276:11) at Socket.Readable.push (_stream_readable.js:231:10) at TCP.onStreamRead (internal/stream_base_commons.js:154:17)

     

     

    해결방법은 나와있는데 bash shell 하나 쉽게 다루지 못해서 당황스럽네 . . . 

    mysql 에서 데이터 엄격성 옵션을 바꿔주면 되는 문제인듯하다. 

     

    일단 스킵 

     

    추후 에러잡는다. 

     

    191230 6:30 am

     

    Multer로 이미지 업로드 

    npm i multer

     

    routes/post.js 생성 

     

    라우터 파일생성 후 습관적으로 작성 해버리기 

    const express = require('express');
    const multer = require('multer');
    const router = express.Router();
    
    
    router.post('/img');
    
    router.post('/')
    
    
    module.exports = router;
    

     

     

    이미지를 올리려고 하는데 그 프론트쪽 을 보면 

    form 에서 method 는 post 이고 enctype 이 multipart/form-data 이면 multer 가 필요하다. 

    body-parser같은 거로는 해석이 안된다 는것 

     

    그래서 multer를 설치한거였음. 

    multer백서를 훑어볼것 .

     

     

    storage : 어디에 저장할지 

    limit : 사이즈 제한 

     

    routes/post.js 

    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) + new Date.now() + ext);
        },
      }),
      limits: { fileSize: 5 * 1024 * 1024 },
    });
    
    
    router.post('/img', isLoggedIn, upload.single('img'), (req, res) => {
      console.log(req.file);
      res.json({ url: `/img/${req.file.filename}` });
    });

     

    게시글 업로드 구현하기 

    계속해서 routes에서 컨트롤러의 내용까지 다 작성하고 있다는 것. 추후에 controller단을 정리하여 코드 및 폴더를 정리하자. 

    무튼, 이제 실제 라우팅작업을 해주자. 

     

     

    routes/post 

     

    const upload2 = multer();
    router.post('/', isLoggedIn, upload2.none(), async (req, res, next) => {
      try {
        const post = await Post.create({
          content: req.body.content,
          img: req.body.url,
          userId: req.user.id, //post 와 user model의 관계때문에
        });
        const hashtags = req.body.content.match(/#[^\s#]*/g); //해쉬태그는 정규표현식으로 표현 
        if (hashtags) {
            // 안녕하세요 #노드 #익스프레스 
            // hashtag = ['#노드', '#익스프레스' ]
            //게시글과 헤쉬태그의 관계 
          const result = await Promise.all(hashtags.map(tag => Hashtag.findOrCreate({ //findOrCreate : 있으면 찾고 없으면 생성하라 
            where: { title: tag.slice(1).toLowerCase() }, //wow 
          })));
          await post.addHashtags(result.map(r => r[0])); //wow 
        }
        res.redirect('/');
    
      } catch (error) {
        console.error(error);
        next(error);
      }
    });
    
    module.exports = router; //이한줄을 안적어서 안되었네 

     

     

    당연히 잊으면 안되고 실수하면 안되는것. 계속해서 연상해라 

    회로가 마치 open 되면 전류가 흐르지 않듯, 

    모든 폴더끼리 파일끼리 연관성이 있는 녀석들은 다 연결기 되있어야된다. 임피던스 매칭없이 그냥 선언해주고 exports해주고 미들웨어만 작성하면 되니깐 얼마나 쉽고 편한가. 그런데도 실수를 하고 그 연결성을 까먹고 습관적으로 작성할때가 많다는 것이다. 

     

    app.js에 반영 

    const postRouter = require('./routes/post')
    -
    -
    -
    -
    app.use('/post', postRouter);

     

    이렇게하면 게시글이 저장된다

    그러나 화면에는 보이지않는다. 

    따라서 page 수정 

     

     

    routes/page  수정 .

     

    router.get('/', (req, res, next) => {
        Post.findAll({
          include: {
            model: User,
            attributes: ['id', 'nick'],
          },
          order: [['createdAt', 'DESC']],
        })
          .then((posts) => {
            res.render('main', {
              title: 'NodeBird',
              twits: posts,
              user: req.user,
              loginError: req.flash('loginError'),
            });
          })
          .catch((error) => {
            console.error(error);
            next(error);
          });
      });

    갠적으로 이런코드가 너무좋다. 캬 

     

     

    해시태그 검색&팔로잉 구현& 마무리 

    이제 해시태그 검색하고 가져오는 기능을 추가해보자. 

     

     

     

    routes/post.js 

    router.get('/hashtag', async (req, res, next) => {
      const query = req.query.hashtag;
      if (!query) {
        return res.redirect('/');
      }
      try {
        const hashtag = await Hashtag.findOne({ where: { title: query } });
        let posts = [];
        if (hashtag) {
          posts = await hashtag.getPosts({ include: [{ model: User }] });
        }
        return res.render('main', {
          title: `${query} | NodeBird`,
          user: req.user,
          twits: posts,
        });
      } catch (error) {
        console.error(error);
        return next(error);
      }
    });

    추가. 

     

    여기서 중요한점은 

    - hashtag.getPosts 로 User를 포함하여 콜한다는 부분. 

    -req.query 부분 

    -검색결과는 twits 로 반환 

     

    팔로잉&팔로워 구현 ! 

    routes/user.js 로 가보자 

    const express = require('express');
    
    const { isLoggedIn } = require('./middlewares');
    const { User } = require('../models');
    
    const router = express.Router();
    
    router.post('/:id/follow', isLoggedIn, async (req, res, next) => {
      try {
        const user = await User.findOne({ where: { id: req.user.id } }); //현재 나를 찾자(로그인한 사람을 찾자.) 
        await user.addFollowing(parseInt(req.params.id, 10)); //addFollowing? 
        res.send('success');
      } catch (error) {
        console.error(error);
        next(error);
      }
    });
    
    module.exports = router;

     

     

    찾으면서도 동시에 passport에서도 작업을 해준다. 

     

    passport/index.js 

     

    const local = require('./localStrategy');
    const kakao = require('./kakaoStrategy');
    const { User } = require('../models');
    
    module.exports = (passport) => {
      passport.serializeUser((user, done) => {
        done(null, user.id);
      });
    
      passport.deserializeUser((id, done) => {
        User.findOne({
          where: { id },
          include: [{                        //추가 
            model: User,
            attributes: ['id', 'nick'],
            as: 'Followers',
          }, {
            model: User,
            attributes: ['id', 'nick'],
            as: 'Followings',
          }],
        })
          .then(user => done(null, user))
          .catch(err => done(err));
      });
    
      local(passport);
      kakao(passport);
    };

     

     

     

     

    **모델관계이해하고 모델관계설정--> 컨트롤러 설계부분 

     

    모델설정이 중요하네..

     

     

     

    추가 !! 

     

     

    닉네임 변경 프로필 변경 기능추가 

    닉네임을 프로필에서 바꾸고싶다.

     

    1. 프론트에서 닉네임 수정하는 폼과 버튼을 추가 method ='post'

    2. 라우트 를 새로 하나 만들어준다 

    3. db의 update기능으로 id를 찾는다

    4. 새로 req된 닉네임을 추가한다.

     

    이것을 코드로 옮겨보자 

     

    1. 

    views/profile.pug 

    form#profile-form(method='post', action='/user/profile')
          input(name='nick')
          button 닉네임 수정 

     

    2. 경로는 /user/profile 로 되어있네 ? 

     

    routes/user.js 

    router.post('/profile', async(req,res,next)=>{
        try {
            await User.update({nick: req.body.nick}, {
                where: {id: req.user.id}
            });
         res.redirect('/profile')
            
        } catch (error) {
            console.error(error);
            next(error)
        }
        
    })

     

    3. id찾기 

    4. 업데이트 해주기 

     

     

     

    게시글 좋아요 기능 

    1. 모델에서, 게시글과 사용자와의 관계가 다대다 관계다 

    -한사람이 좋아요를 여러번 할수있고 

     

    즉 새로운 테이블이 필요하다. 

     

    2. 프론트에서 좋아요 를 생성해준다. 

    if user
                button Like

     

    3. routes/post.js에서 라우팅을 해준다. 

     

     

    팔로잉 안끊기고, 

    좋아요 좋아요 취소가 안됨.....

    일단 넘어간ㄷ, 

     

    댓글

실험중인 삶, Life is not a race