인스타그램 클론코딩 2 - 4 USER MODULE

cslee·2022년 1월 13일
0

노마드코더

목록 보기
1/1

#프로젝트를 처음부터 다시 시작

본격적인 인스타그램 클론코딩 시작이다.
시작에 앞서 전에 만들었던 prisma폴더와 movies폴더와 db를 삭제해준다. ==> 이렇게 하면 기존의 설치했던 것들을 그대로 가져올 수 있으나 나는 프로젝트를 새로 생성해 보겠습니다.

  1. 프로젝트 폴더를 생성한다.

    git init
    npm init -y

package.json 생성된다.

  1. VSC에서 해당 폴더를 열고 package.json에서 main은 사용하지 않으므로 지워주고 script에서 test도 지워주고 dev만 빈칸으로 추가해준다.

  2. apollo와 graphql을 설치해준다.

    npm i apollo-server graphql

js가 변화하고 저장됨에 따라 자동으로 서버를 재시작하게 해주는 nodemon을 설치할 것이다.

  1. 명령어로 개발자 라이브러리에 설치해준다.

    npm i nodemon --save-dev

package.json의 script의 dev에 nodemon의 명령어를 추가해준다.

"scripts": {
    "dev": "nodemon --exec node server.js"
  },
  1. babel을 설치한다.
    최신 자바스크립트 문법을 사용하기 위해 babel(javascript compiler)을 설치한다.
    nodejs 버젼에 관계없이 코딩 할 수 있게 도와준다.

    npm install --save-dev @babel/core
    npm install @babel/preset-env --save-dev
    npm install @babel/node --save-dev

babel.config.json 파일을 만들고 아래처럼 추가한다.

// babel.config.json
{
    "presets": [
        "@babel/preset-env"
    ]
}

package.json의 script의 dev를 아래와 같이 수정한다.

// package.json
"scripts": {
    "dev": "nodemon --exec babel-node server.js"
  },
  1. prisma Setup
    Prisma는 ORM이다.
    즉 SQL코드를 쓸 필요 없이 자바스크립트 코드를 작성하면 Priisma가 데이터베이스와 대신 소통해준다.
    Prisma가 migration을 적용하고 DB를 동기화해준다. 그 이후 Prisma는 Client를 생성할 것이다. 그리고 이 client를 통해 자바스크립트로 DB와 대화할 수 있게 된다.

터미널에서 prisma를 설치한다.

npm install prisma --save-dev
npx prisma init
npm install @prisma/client

새로운 파일들이 생긴다. prisma는 default로 postgresql과 대화하게 되어있다.
이 작업을 마치면 prisma/schema.prisma파일과 .env파일이 생성됨

package.json에 migrate와 prisma studio 관련 세팅을 추가.

// package.json
"scripts": {
    ~~~
    "migrate": "npx prisma migrate dev",
    "studio": "npx prisma studio"
  },
  1. Prisma studio 설치
    prisma studio는 visual editor이다.

    npx prisma studio

  2. graphql-tools 설치

**graphql-tools 설치는 제 강의 기준(2022-01-12)에는 업데이트가 되면서 설치 방식이 변경되었다.

npm i graphql-tools <= 설치해도 되나 아래세개를 따로 설치해줘야 동작함

npm i @graphql-tools/schema
npm i @graphql-tools/load-files
npm i @graphql-tools/merge

  1. Dotenv

    npm install dotenv

.env를 읽기 위해서는 dotenv설치가 필요하다.


#기존 프로젝트를 사용할때는 여기부터~

#4.0 Create Account

  1. InstaClone을 위한 prisma를 다시 생성

    npx prisma init

  2. User 모델 생성

//schema.prisma
model User{
   id Int @id @default(autoincrement())
   firstName String
   lastName String? (선택사항)
   username String @unique
   email String @unique
   password String
   createdAt DateTime @default(now())
   updatedAt DateTime @updatedAt
}
  1. users 폴더를 생성하고 하위에 users.mutations.js, users.queries.js, users.typeDefs.js 파일을 생성
  2. users.typeDefs.js파일에 type동기화한다.
  3. query와 mutation을 작성한다.
    이 때 schema.prisma에서 @unique 속성을 부여한 것은 중복될 경우 DB에서 에러가 나므로 사전에 방지해줘야한다.
  4. 비밀번호는 rainbow해킹에 대해 보호하기 위해 hash값을 salt해서 DB에 저장해줘야 한다.
  5. 마지막에 DB에 create해준다.

username과 email은 중복되서는 안되기 때문에 @unique 사용
User 관련 typeDefs와 resolvers 구현

// users.typeDefs.js
import { gql } from "apollo-server";

export default gql`
   type User {
	  id: String!
	  firstName: String!
	  lastName: String
	  username: String!
	  email: String!
	  createdAt: String!
      updatedAt: String!
}
    
   type Mutation{
      createAccount(
         firstName: String!
         lastName: String
         username: String!
         email: String!
         password: String!
       ): User
}

   type Query{
      seeProfile(username: String!): User
}

// users.mutations.js
import bycrypt from "bcrypt";
import client from "../client";

export default {
    Mutation: {
        createAccount: async (_, {
            firstName,
            lastName,
            username,
            email,
            password,
        }) => {//prisma 는 promise를 리턴 하므로 async, await 사용해야한다.
            try {
                const existingUser = await client.user.findFirst({
                    where: {
                        OR: [//fillter는 대문자를 사용햐여한다.
                            { username },
                            { email },
                        ]
                    }
                });
                if (existingUser) {// 이미 있는 user이면 error
                    throw new Error("This username/email is already taken.");
                }
                const uglyPassword = await bycrypt.hash(password, 10);
                return client.user.create({
                    data: {
                        username,
                        firstName,
                        lastName,
                        email,
                        password: uglyPassword,
                    }
                });
            } catch (err) {
                return err;
            }
        }
    }
}

위 users.mutations.js 정리

Prisma를 이용한 작업은 Promise를 리턴하기 때문에 코드에서 즉시 이뤄지지 않는 것을 뜻한다.
성공한다면 성공했을때의 data를 실패한다면 실패했을때의 data를 리턴
따라서 코드가 실행되면 정보를 확인하러 DB로 가고 다시 돌아와야 하기 때문에 기다려야 한다.
이를 위해 async await를 사용
단 return할때는 prisma가 끝날때까지 자동으로 기다려주기 때문에 await를 사용할 필요가 없다.

OR : 이는 조건들의 Array을 필요로한다.
위 코드에서는 OR에 username=username이란 조건과 email=email이란 조건이 존재하는 Array를 매칭해줬다.
따라서 Prisma는 username이 존재하거나 email이 존재 할때 그 user를 가져온다.
이를 이용해 Error작업을 해줬다.

Hash Password

npm i bcrypt

findFirst와 findUnique의 차이

findFirst는 모든 필드를 이용해서 필터링 가능함
findUnique는 unique필드만 이용해서 필터링 가능하다. ex) id,@unique로 지정한 필드 ...


#4.3 seeProfile

DB에서 User를 Read한다.
여기서 findUnique는 unique한 필드에만 적용할 수 있다.

//users.queries.js
import client from "../client";

export default {
    Query: {
        seeProfile: (_, { username }) => client.user.findUnique({
            where: {
                username,
            }
        })
    }
};

#4.x apollographql Studio 사용

http://localhost:4000 접근하면 사용할 수 있음.

http://localhost:5555 => Prisma Studio


#4.4 login

token은 서버가 프론트엔드에 연결되어 있지 않을 때나 다른 장소에 있을 때 사용한다.
여기서는 JWT를 이용한다.
누구나 token안을 볼 수 있다. 따라서 token에는 비밀정보를 담으면 안된다.
token의 목적은 토큰안에 정보를 넣고, 그 토큰이 우리가 싸인했던 토큰인지 확인하는 것이다.
sign을 할 때 private_key와 함께 싸인을 하게되는데 이 key는 절대 노출되면 안된다. 노출되면 그 key 주인의 토큰을 함부로 만들 수 있다.

구현

  1. 가져온 username과 일치하는 user를 DB에서 찾는다. (없다면 error를 리턴)
  2. user가 존재한다면 password를 비교한다. await bcrypt.compare(password, user.password);
  3. password까지 일치한다면 로그인이 성공할 경우 -> 토큰 발급 await jwt.sign({ id: user.id }, process.env.SECRET_KEY);

.env 에 Secret key 추가 (https://randomkeygen.com => CodeIgniter Encryption Keys에서 하나 선택)
SECRET_KEY=m66GLSw301mqFwwEb5yo2Y7uDSsAC9va

//users.typeDefs.js ==> 코드 추가
type LoginResult {
    ok: Boolean!
    token: String
    error: String
  }

type Mutation {
  	~~~~
    login(username: String!, password: String!): LoginResult!
  }

//users.mutations.js
login: async (_, { username, password }) => {
    const user = await client.user.findFirst({ where: { username } });
    if (!user) {
        return {
            ok: false,
            error: "User not found"
        }
    }

    const passwordOk = await bcrypt.compare(password, user.password);
    if (!passwordOk) {
        return {
            ok: false,
            error: "Incorrect password"
        }
    }

    const token = jwt.sign({ id: user.id }, process.env.SECRET_KEY);
    return {
        ok: true,
        token,
    };
}

Secret Key 발급 사이트 -> https://randomkeygen.com
JWT 해석 : https://jwt.io


#4.5 Edit Profile

//users.typeDefs.js ==> 코드 추가
type EditProfileResult {
    ok: Boolean!
    error: String
  }

type Mutation {
  	~~~~
    editProfile(
      firstName: String
      lastName: String
      username: String
      email: String
      password: String
    ): EditProfileResult!
  }

//users.mutations.js
login: async (_, { username, password }) => {
    const user = await client.user.findFirst({ where: { username } });
    if (!user) {
        return {
            ok: false,
            error: "User not found"
        }
    }

    const passwordOk = await bcrypt.compare(password, user.password);
    if (!passwordOk) {
        return {
            ok: false,
            error: "Incorrect password"
        }
    }

    const token = jwt.sign({ id: user.id }, process.env.SECRET_KEY);
    return {
        ok: true,
        token,
    };
}

위에는 한 파일에 다 적어서 동작시키고 아래는 코드를 분리해서 동작시킴
users 폴더 아래에 각 동작에 맞는 세부폴더를 생성한다.
users > editProfile > editProfile.mutations.js, editProfile.typeDefs.js
생성하고 users.mutations.js, users.typeDefs.js에 있는 코드를 나눈다.

//editProfile.typeDefs.js ==> 코드 추가
import { gql } from "apollo-server";

export default gql`
    type EditProfileResult {
        ok: Boolean!
        error: String
    }

    type Mutation {
        editProfile(
            firstName: String
            lastName: String
            username: String
            email: String
            password: String
        ): EditProfileResult!
    }
`;

//editProfile.mutations.js ==> 일단 되는지 확인하는 코드를 작성
export default {
    Mutation: {
        editProfile: () => console.log("hi"),
    }
}

#4.6 Divide and Conquer

이제는 xxx.queries.js, xxx.mutations.js로 나눌 필요가 없다.
합쳐서 xxx.resolvers.js로 한다.

editProfile.mutations.js ==> editProfile.resolvers.js 로 변경한다.

schema.js에서 **/*.{queries,mutations}.js => **/*.resolvers.js 로 변경한다.

users 폴더 아래에 createAccount, editProfile, login, seeProfile 폴더를 생성한다.
createAccount.resolvers.js, createAccount.typeDefs.js 파일 생성
editProfile.resolvers.js, editProfile.typeDefs.js 파일 생성
login.resolvers.js, login.typeDefs.js 파일 생성
seeProfile.resolvers.js, seeProfile.typeDefs.js 파일 생성

이제 users.typeDefs.js와 users.mutations.js에 있던 코드를 각 파일로 분리한다.


#4.7 updateProfile

profile
초심을 잃지 말자!!

0개의 댓글