사용자 인증: Prisma Client API 로 user db find/create & 토큰 생성

summereuna🐥·2023년 10월 24일

Prisma Client API 사용하여 User DB에서 email/phone으로 user 찾거나 생성하고, 인증 토큰을 생성해보자.


유저가 enter 페이지에서 이메일 혹은 폰 번호를 올바르게 입력하면 useMutation의 mutation()함수로 data인 validForm이 전달되는데 그 data를 가지고"/api/users/enter" api에 접근한다.

현재 사용자가 "/api/users/enter" api에 접근하면, NextJS는 api Route를 작동시키는데, handler함수를 withHandler 함수에 인자로 보내어 실행되게 해 두었다.

이제 handler 함수에서 email 혹은 phone을 사용하여 DB에서 user를 찾거나 생성해보자.

  • 먼저 콘솔에서 psacle connect <DB이름> 으로 시크릿 터널 연결 해주자.

📍 /pages/api/users/enter.tsx

import client from "@/libs/client/client";
import withHandler from "@/libs/server/withHandler";
import { NextApiRequest, NextApiResponse } from "next";

async function handler(req: NextApiRequest, res: NextApiResponse) {
  
  //api route가 받은 req.body에서 폰이나 이메일 둘 중 하나 가져와야 한다.
  const { email, phone } = req.body;

  const payload = email ? { email } : { phone: +phone };
  // email 있으면 { email: email } 리턴
  // 아니면 { phone: +phone } 리턴
  //form에서 넘긴 phone값은 string이라서 + 붙여서 number로 바꿔주기

  //DB에 user 생성하거나 수정할 때 upsert() 사용
  const user = await client.user.upsert({
    //user의 email에 req.body로 넘겨준 email 있는지
    where: { ...payload }, //조건 따라 이메일할지 폰할지
    //없으면 새로 만들고 user 반환 받음
    create: {
      name: "익명",
      ...payload,
    },
    //있으면 유저 업데이트: 안할거니까 비워두기
    update: {},
  });
  
  //백엔드에서 유저 정보 콘솔에 찍어보기
  console.log(user);
  return res.status(200).end();
}

export default withHandler("POST", handler);

🚌 과정


1. 유저가 있는지 확인 후 없으면 유저 생성

const { email, phone } = req.body;

let user;

//이메일인 경우
if (email) {
  user = await client.user.findUnique({
    where: { email }, //req.body.email이 user DB에 있는지
  });
  
  //user가 있다면
  if (user) {
    console.log("✅ 유저가 있습니다.");
  }
  
  //user가 없다면
  if (!user) {
    console.log("❌ 유저가 없습니다. ✅ 유저를 생성합니다.");
    user = await client.user.create({
      data: {
        name: "익명",
        email,
      },
    });
  }
  console.log(user);
}

//폰인 경우
if (phone) {
  user = await client.user.findUnique({
    where: { phone: +phone },
    // req.body.phone은 string 이다.
    // + 붙여서 number로 바꿀 수 있다.
  });
  
  if (user) {
    console.log("✅ 유저가 있습니다.");
  }
  
  if (!user) {
    console.log("❌ 유저가 없습니다. ✅ 유저를 생성합니다.");
    user = await client.user.create({
      data: {
        name: "익명",
        phone: +phone, 
      },
    });
  }
  
  //백엔드 콘솔에서 유저 있는지 확인
  console.log(user);
}

📚 Prisma Client API

1. findUnique
findUnique 쿼리를 사용하면 단일 데이터베이스 레코드를 검색할 수 있다.

  • where 속성에서 id 혹은 유니크한 속성을 선택하여 DB를 검색할 수 있다.

2. create
create는 새 데이터베이스 레코드를 생성한다.

  • data 옵션을 필수로 가져야 한다.

2. upsert로 더 줄여서 쓸 수 있음

const { email, phone } = req.body;

let user;

//이메일인 경우
if (email) {
  user = await client.user.upsert({
    where: { email },
    //client.user.email에 req.body.email과 같은 email있는지
    
    //없으면 새로 만들고 user 반환 받음
    create: {
      name: "익명",
      email,
    },
    
    //있으면 유저 업데이트: 안할거니까 비워두기
    update: {},
  });
}

//폰인 경우
if (phone) {
  user = await client.user.upsert({
    where: { phone: +phone },
    //client.user.phone에 req.body.phone과 같은 phone있는지
    
    //없으면 새로 만들고 user 반환 받음
    create: {
      name: "익명",
      phone: +phone,
    },
    
    //있으면 유저 업데이트: 안할거니까 비워두기
    update: {},
  });
}

//백엔드 콘솔에서 유저 있는지 확인
console.log(user);

📚 Prisma Client API

3. upsert

  • 기존 데이터베이스 레코드가 where 조건을 만족하면 해당 레코드를 update한다.
  • where 조건을 만족하는 데이터베이스 레코드가 없으면 create으로 새 데이터베이스 레코드가 생성된다.

3. 이 부분을 es6를 사용하여 이렇게 적을 수도 있음

const user = await client.user.upsert({
  where: {
    //es6: 객체 안에서 if else 같은 기능 사용하기
    ...(email && { email }), // email 있으면 { email: email } 리턴
    ...(phone && { phone: +phone }), // phone 있으면 { phone: +phone } 리턴
    //form에서 넘긴 phone값은 string이라서 + 붙여서 number로 바꿔주기
  }, //없으면 새로 만들고 user 반환 받음
  create: {
    name: "익명",
    ...(email && { email }), // email 있으면 { email: email } 리턴
    ...(phone && { phone: +phone }), // phone 있으면 { phone: +phone } 리턴
  },
  //있으면 유저 업데이트: 안할거니까 비워두기
  update: {},
});

4. 더 깔끔하게 하려면 이렇게

//DB에 user 생성하거나 수정할 때 upsert() 사용
const user = await client.user.upsert({
  //user의 email에 req.body로 넘겨준 email 있는지
  where: { ...payload }, //조건 따라 이메일할지 폰할지
  //없으면 새로 만들고 user 반환 받음
  create: {
    name: "익명",
    ...payload,
  },
  //있으면 유저 업데이트: 안할거니까 비워두기
  update: {},
});

✅ 토큰 생성 및 리팩토링


1. 토큰 모델 생성

📍 /prisma/schema.prisma

// 유저 모델
model User {
  id      Int      @id @default(autoincrement())
  name    String
  phone   Int?     @unique
  email   String?  @unique
  avatar  String?
  created DateTime @default(now())
  updated DateTime @updatedAt
  tokens  Token[] //토큰과 연결할 수 있도록 추가
}

// 토큰 모델
model Token {
  id      Int      @id @default(autoincrement())
  payload String   @unique
  user    User     @relation(fields: [userId], references: [id])
  userId  Int
  created DateTime @default(now())
  updated DateTime @updatedAt

  @@index([userId])
}

//payload에 랜덤하게 생성된 6자리 토큰값 넣어 줄 것임
//db에 실제 user 전체 데이터가 들어가지는 않기 때문에 user, userId가 있음 
//user 필드는 디비에 들어가지 않고 대신 userId가 디비에 들어감

2. ResponseType 인터페이스 생성

📍 /libs/server/withHandler.tsx

//...

export interface ResponseType {
  ok: boolean;
  [key: string]: any;
}

//...
  • 유저에게 토큰할당 한 후 응답을 true/false로 받을 예정이므로 res의 타입을 위와 같이 설정한다.
  • withHandler()와 함께 쓰이므로 withHandler.ts에 작성해둔다.

3. 유저 찾기/생성 & 토큰 할당

📍 /pages/api/users/enter.tsx

import client from "@/libs/client/client";
import withHandler, { ResponseType } from "@/libs/server/withHandler";
import { NextApiRequest, NextApiResponse } from "next";

async function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseType> //res 타입 지정해주기
) {
  //api route가 받은 req.body에서 폰이나 이메일 둘 중 하나 가져오기
  const { email, phone } = req.body;

  const userEnterInfo = email ? { email } : phone ? { phone: +phone } : null;

  //유저 입력정보 null이면 배드리퀘스트 보내기
  if (!userEnterInfo) return res.status(400).json({ ok: false });

  //토큰 값 무작위 랜덤 생성
  const payload = Math.floor(100000 + Math.random() * 900000) + "";

  //✅ 토큰 생성 및 유저에게 할당하기 위해 유저 찾거나 생성하기
  const token = await client.token.create({
    data: {
      payload,
      user: {
        //connect는 새로운 토큰을 이미 존재하는 유저와 연결
        //create는 새로운 토큰과 새로운 유저 생성
        //connectOrCreate 는 유저를 찾아서 토큰 연결하므로 위의 코드 없어도 됨
        connectOrCreate: {
          //유저가 있으면 페이로드(이메일/폰넘버) 넣어주기
          where: { ...userEnterInfo },
          //유저가 없으면 유저 새로 만들고, 유저에 토큰 연결
          create: {
            name: "익명",
            ...userEnterInfo,
          },
        },
      },
    },
  });

  return res.json({ ok: true }); // 문제 없으면 응답 ok 주기
}

export default withHandler("POST", handler);
//withHandler(HTTP 메소드, handler 함수)
//외부에서 핸들러 함수를 고차 함수에 인자로 전달하여 더 유연하게 사용 가능

📚 Prisma Client API

client.token.create({
  data: {
    토큰,
    user: {
      //connect();
      //create();
      //connectOrCreate();
    }
  }
})
  • create() 사용하여 토큰을 생성한다.
    • command키를 누른 채 data를 클릭해 보면 필요한 속성이 뭔지 알 수 있다.

    • data에 필요한 필수 속성으로 payload, user가 있다.
      • user에서 필요한 작업은 다음과 같다.
      • connect는 새로운 토큰을 이미 존재하는 유저와 연결
      • create는 새로운 토큰과 새로운 유저 생성
      • connectOrCreate는 유저를 찾아서 유저가 있으면 토큰을 연결하고, 유저가 없으면 유저를 생성한 후 토큰을 연결할 수 있기 때문에 일석이조!
        • where, create 필요
profile
Always have hope🍀 & constant passion🔥

0개의 댓글