Accounts Logic
이제 본격적으로 로그인 form을 위한 로그인 api를 만들어 보도록 하겠습니다. 앞서 정리한 내용을 한번 서술해 보도록 하겠습니다. enter page에서 저희는 useMutation hook을 작성해서 fetch api로 데이타를 쉽게 보낼 수 있게 포장했습니다. 그리고 useMutation의 반환 값으로는 이를 실행할 수 있는 함수를 주어주었고, 각종 loading, data, error즉 api의 요청의 결과와 작동과정과 관련된 데이터를 반환하게 했습니다.
그리고 저희는 /page/api/enter에서는 withHandler(HOF)를 반환하게 하여, api가 작동하게 했습니다.
자 여기부터 이제는 prisma client를 반환하게 하여서, 유저의 form에 따른 data를 저희의 db에 crud하는 작업을 해보도록 하겠습니다.
/pages/api/enter.tsx
import client from "@libs/client/client";
import type { Prisma } from "@prisma/client";
import withHandler from "@libs/server/withHandler";
import type {
NextApiRequest,
NextApiResponse,
NextApiHandler,
} from "next";
interface reqDataType {
email?: string;
phone?: string;
}
const handler: NextApiHandler = async (
req: NextApiRequest,
res: NextApiResponse
) => {
const { email, phone }: reqDataType = req.body;
// let user;
const user = await client.user.upsert({
where: {
...(phone && { phone: +phone }),
...(email && { email }),
},
create: {
name: "Anonymous",
...(phone && { phone: +phone }),
...(email && { email }),
},
update: {},
});
console.log(user);
// if (email) {
// user = await client.user.findUnique({
// where: { email },
// });
// if (user) console.log("found it.");
// if (!user) {
// console.log("Did not find. Will create.");
// user = await client.user.create({
// data: {
// name: "Anonymous",
// email,
// },
// });
// }
// console.log(user);
// }
// if (phone) {
// user = await client.user.findUnique({
// where: { phone: +phone },
// });
// if (user) console.log("found it.");
// if (!user) {
// console.log("Did not find. Will create.");
// user = await client.user.create({
// data: {
// name: "Anonymous",
// phone: +phone,
// },
// });
// }
// console.log(user);
// }
res.status(200).end();
};
export default withHandler("POST", handler);
저는 기본적으로 req.body로 form data가 들어올 것이라고 가정했습니다. 그리고 홈페이지 logic상, email or phone중 하나만 들어오게 되어있습니다. 그리고 typescript의 기능을 활용하여 주석한 부분을 확인해 보시면, email, phone이 있는 경우에만 prisma client의 findUnique를 사용하여 email, phone을 활용해서 기존 디비에서 사용자를 찾습니다. 만약 유저가 없다면, name은 임의로 "Anonymous"로 주고 phone, email을 주어진 데이터로 하여서 추가해 주었습니다. 그리고 form의 email, phone의 data type은 string이여서 phone number는 +을 붙여서 int형으로 바꾸어 주었습니다. 왜냐하면 저희가 db scheme을 짤 때 phone은 Int형으로 선언해 주었기 때문입니다. ( 이 모든걸 prisma가 다 알려줍니다!! 틀렸으면 지적도 해주고요!~ )
https://www.prisma.io/docs/concepts/components/prisma-client/crud
기본적인 prisma client를 통한 CRUD의 method는 위의 링크를 참고하시면 될 것 같습니다.
하지만 제가 주석한 코드를 저는 사용하지 않았습니다. 왜냐하면, 너무 코드가 장황하고 if문들이 너무 장황했기 때문입니다. 따라서, 저는 prisma에서 제공하는 upsert를 사용했습니다.
공식 문서에서 upsert()는 기존 데이터를 업데이트하거나 새 데이터베이스 레코드를 생성하는데, where에 해당하는 record가 존재하게 되면, update를 하게 되고, 없다면 create를 해주는 아주 저희의 편의를 반영한 util함수 입니다.
이제 enter홈페이지에서 email, phone을 차례대로 쓴다음에 제출하게 되면, 다음과 같이 prisma studio에서 확인하며 데이터가 잘 삽입되어 있는 것을 보실 수 있습니다.
Token Logic
이제 user와 1: many관계로 이루어지는 token model을 생성해 봅시다.
https://www.prisma.io/docs/concepts/components/prisma-schema/relations/one-to-many-relations
공식문서를 참고하면서 관련 메소드들을 참고해 보면 좋을 것 같습니다.
우선 token model을 schema.prisma에 추가해 주고 prisma에 push해 주어야 합니다.
/prisma/schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
referentialIntegrity = "prisma"
}
model User {
id Int @id @default(autoincrement())
phone Int? @unique
email String? @unique
name String
avatar String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tokens Token[]
}
model Token {
id Int @id @default(autoincrement())
payload String @unique
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId Int
}
여기서 Token에 payload필드가 있는데, 이는 token이 들어갈 자리입니다. 그리고 저희의 목표는 user와 token의 관계를 설정해 주는 것입니다. 상식적으로 한 user는 여러개의 token을 가질 수 있습니다. 이러한 관계를 prism에서 설정하는 방법을 알아보도록 하겠습니다.
token model에 user필드를 추가했는데, 이는 실제로 디비에 추가되는 것이 아니고 token에서 user값에 접근할 때 사용됩니다. 예를 들어서 token.user.id === ? 와 같이 값을 확인할 때 말이죠. @relation(fields: [userId], reference: [id])의 의미는 User model의 id를 참고한다는 (references)것이고, 이를 Token의 userId필드에 foreign key로써 저장하겠다는 의미입니다. 그리고 User필드에 tokens 필드를 추가해 주면 끝입니다.
// push prisma schema
$npx prisma db push
// restart server
$npm run dev
다음과 같이 prisma에 추가 해준 schema를 반영해 주도록 합니다.
그리고 간단한 관계 쿼리 하나를 작성해 보도록 하겠습니다.
/pages/api/enter.tsx
import client from "@libs/client/client";
import type { Prisma } from "@prisma/client";
import withHandler from "@libs/server/withHandler";
import type {
NextApiRequest,
NextApiResponse,
NextApiHandler,
} from "next";
interface reqDataType {
email?: string;
phone?: string;
}
const handler: NextApiHandler = async (
req: NextApiRequest,
res: NextApiResponse
) => {
const { email, phone }: reqDataType = req.body;
const payload = phone ? { phone: +phone } : { email };
// const user = await client.user.upsert({
// where: {
// ...payload,
// },
// create: {
// name: "Anonymous",
// ...payload,
// },
// update: {},
// });
// const token = await client.token.create({
// data: {
// payload: "1231234",
// user: {
// connect: {
// id: user.id,
// },
// },
// },
// });
// console.log(token);
const getUser = await client.user.findUnique({
where: {
id: 4,
},
include: {
tokens: {
select: {
payload: true,
},
},
},
});
console.log(getUser);
res.status(200).end();
};
export default withHandler("POST", handler);
일단 임의로 아래와 같이 user에 token을 추가해 두었습니다.
그리고 위 관계쿼리를 사용해 주면 아래와 같이 정상 작동하는 것을 보실 수 있습니다.
그럼 저 위의 데이터는 어떻게 생성했냐?? 의문이 드실 수도 있습니다. 당연히 그냥 제가 임의로 추가한 것은 아니라 이것 또한 관계쿼리로 삽입한 것입니다.
제가 주석처리한 코드를 보시면 create안에 인수로 connect가 있는 것을 보실 수 있습니다. 저 코드의 전체적인 코드는 위에서 해당하는 user의 id를 사용하여 payload가 1231234인 token과 연결해 주는 것입니다. 점차 사용해 보시면 이해가 더 쉬워지실 겁니다.
그리고 connect외에 connectOrCreate를 connect대신에 써줄 수 있습니다. 이는 만약에 해당하는 where에 해당하는 레코드가 있다면 create로 user를 생성해 주고 이에 해당하는 token의 relation을 지정해 줄 수 있는 아주 혁신적인 것입니다.
그럼 위의 코드가 아래와 같이 획기적이게 짧게 변하게 됩니다.
/pages/api/enter.tsx
import client from "@libs/client/client";
import type { Prisma } from "@prisma/client";
import withHandler from "@libs/server/withHandler";
import type {
NextApiRequest,
NextApiResponse,
NextApiHandler,
} from "next";
interface reqDataType {
email?: string;
phone?: string;
}
const handler: NextApiHandler = async (
req: NextApiRequest,
res: NextApiResponse
) => {
const { email, phone }: reqDataType = req.body;
const payload = phone ? { phone: +phone } : { email };
const token = await client.token.create({
data: {
payload: "1231234",
user: {
connectOrCreate: {
where: {
...payload,
},
create: {
name: "Anonymous",
...payload,
},
},
},
},
});
console.log(token);
res.status(200).end();
};
export default withHandler("POST", handler);
이제 잘 작동하는지 확인하기 위해서 unique한 token의 payload를 변경해 주고 다시한번 실행해 보도록 하겠습니다. ( payload값은 1231231231으로 변경 )
다음과 같이 email에 기존에 없던 이메일을 적어주고 버튼을 눌러보겠습니다. 그럼 결과는 아래와 같습니다.
connectOrCreate를 통해 성공적으로 email도 만들어 주고 이에 해당하는 1231231231이라는 token도 만들어 진 것을 확인해 볼 수 있습니다.
'Web > CloneCoding' 카테고리의 다른 글
[Carrot Market] #10 - AUTHORIZATION (0) | 2022.05.13 |
---|---|
[Carrot Market] #9 - Authentication - 2 (0) | 2022.05.12 |
[Carrot Market] #8 REFACTORING Form (0) | 2022.05.04 |
[Carrot Market] #7 React Hook Form (0) | 2022.05.03 |
[Carrot Market] #6 Prisma - PlanetScale (0) | 2022.05.03 |