인스타그램 클론코딩 3일차 - BE

박병준·2021년 7월 24일
0
post-thumbnail

본격적인 인스타그램 클론코딩 시작이다. 시작에 앞서 전에 만들었던 prisma폴더와 movies폴더와 db를 삭제해준다.

#2.0 Create Account

  1. user model을 만든다.
//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 //prisma에서 지원한다.
}
  1. users.typeDefs.js파일에 type동기화한다.
  2. query와 mutation을 작성한다.
    이 때 schema.prisma에서 @unique 속성을 부여한 것은 중복될 경우 DB에서 에러가 나므로 사전에 방지해줘야한다.
  3. 비밀번호는 rainbow해킹에 대해 보호하기 위해 hash값을 salt해서 DB에 저장해줘야 한다.
  4. 마지막에 DB에 create해준다.
//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;
            }

        }
    }
}

#2.1 seeProfile

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

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

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

#2.2 Login

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

//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,
    };
}

#2.3 updateProfile

update될 때 값을 안바꾸는 undefined된 값은 DB에 반영되지 않는다.
비밀번호는 hash를 다시 해줘야 한다.
context는 모든 resolver에서 접근 가능한 정보를 넣을 수 있는 object이다.
resolver함수를 protectedResolver함수로 감싸서 다른 js파일에서 매번 login한 user인지 확인해주지 않고 함수로 만든다. login한 user이면 해당 resolver를 실행한다.
graphql-resolver가 받는 인자는 root, args, context, info 이다.

//user.utils.js
import jwt from "jsonwebtoken";
import client from "../client";

export const getUser = async (token) => {
    try {
        if (!token) {
            return null;
        }
        const { id } = await jwt.verify(token, process.env.SECRET_KEY);
        const user = await client.user.findUnique({ where: { id } });
        if (user) {
            return user;
        } else {
            return null;
        }
    } catch {
        return null;
    }
};

export const protectedResolver = (ourResolver) => (root, args, context, info) => {
    if (!context.loggedInUser) {
        return {
            ok: false,
            error: "Please log in to perform this action"
        };
    }
    return ourResolver(root, args, context, info);
};

모든 resolver에서 context로 접근 가능하게끔 apollo server에서 context 수정이 가능하다.

//server.js
const server = new ApolloServer({
    schema,
    cnotext: async ({ req }) => {
        return {
            loggedInUser: await getUser(req.headers.token),
        };
    },
});
//updateProfile.resolvers.js
import bcrypt from "bcrypt";
import client from "../../client";
import { protectedResolver } from "../users.utils";

export default {
    Mutation: {
        updateProfile: protectedResolver(
            async (_,
                { firstName, lastName, username, email, password: newPassword },
                { loggedInUser }
            ) => {
                let uglyPassword = null;
                if (newPassword) {
                    uglyPassword = await bcrypt.hash(newPassword, 10);
                }
                const updatedUser = await client.user.update({
                    where: {
                        id: loggedInUser.id,
                    },
                    data: {
                        firstName,
                        lastName,
                        username,
                        email,
                        ...(uglyPassword && { password: uglyPassword }),
                    },
                });
                if (updatedUser.id) {
                    return {
                        ok: true,
                    };
                } else {
                    return {
                        ok: false,
                        error: "Could not update profile.",
                    };
                }
            }
        )
    }
}
profile
뿌셔뿌셔

1개의 댓글

comment-user-thumbnail
2022년 1월 13일

Velog 잘 보고 있습니다.
혹시 create Account에서
const existingUser = await client.user.findFirst({...})을 호출하면 아래와 같이 에러가 발생합니다.
find, create 다 안됩니다.
혹시 어떤 문제인지 알 수있을까요?

meta: {
query_validation_error: 'Field does not exist on enclosing type.',
query_position: 'Query.findFirstUser'
}

답글 달기

관련 채용 정보