【Next.js】Prismaの使い方(findUnique編)

# 記事作成の背景
ORMのPrismaでデータベースから一意のレコードを<b>findUnique</b>で取得する際に、沼ってしまったので備忘録として記載します。

# 沼った原因は何だったのか？
結論は、スキーマ定義で対象となるカラムに対してUnique属性を付与していなかったことが原因でした。
今回は、<b>email</b>と<b>password</b>の2つで絞り込んで一意のレコードが取得できるようにします。

```scema.prisma(修正前)
model Users{
  id Int @id @default(autoincrement())
  name String
  email String 
  password String 
  created_at String
  updated_at String
}

```

↑のスキーマ定義だとエラーが生じてしまいます。
コンソールには、「IDとは、一意となるカラムを少なくとも1つは指定してね」という表示が出ます。

なので、下記のように修正します。

```scema.prisma(修正後)
model Users{
  id Int @id @default(autoincrement())
  name String
  email String @unique
  password String @unique
  created_at String
  updated_at String
}

```

これで一意のレコードを取得するためのスキーマ定義完成です。

今回のサンプルはこちら↓↓
emailとpasswordでデータベースからレコードが取得出来たら、toastでメッセージを表示する機能です。

```LoginForm.js
'use client';

import {signIn} from 'next-auth/react';
import { useRouter } from 'next/router';
import toast from 'react-hot-toast';
import { useState } from 'react';
import { getLoginUser } from '../lib/actions/user.action';
import { redirect } from 'next/navigation';
import { revalidatePath } from 'next/cache';
import nextSession from "next-session";


export default function LoginForm({callbackUrl}){
    
    //const router = useRouter();
    const [email,setEmail] = useState('');
    const [password,setPassword] = useState('');

    return (
        <form onSubmit={handleSubmit} className='flex-1 space-y-6'>
            <div className='space-y-2'>
                <label className='label' htmlFor='email'>
                    email
                </label>
                <input
                    id='email'
                    className='input input-bordered w-full'
                    value={email}
                    onChange={e=>setEmail(e.target.value)}
                    type='email'
                    placeholder='Enter your email address'
                    required
                />
            </div>
            <div className='space-y-2'>
                <label className='label' htmlFor='password'>
                    Password
                </label>
                <input
                    id='password'
                    className='input input-bordered w-full'
                    value={password}
                    onChange={e=>setPassword(e.target.value)}
                    type='password'
                    placeholder='Enter password'
                    required
                    minLength={6}
                />
            </div>
            <button className='btn btn-primary btn-block mt-4' id='loginBtn'>Log In</button>
            <button className='btn btn-secondary btn-block mt-4' id='redirectToHome' disabled>
                <a href='http://localhost:3000/'>
                    ホーム画面へ
                </a>              
            </button>
        </form>
    )
}

const handleSubmit = async event=>{
    event.preventDefault();
    //指定したのページへ線維させるか判定するためのフラグ
    let redirectFlg = false;
    
    let email = document.getElementById('email').value;
    let password = document.getElementById('password').value;
    
    const res = await signIn('credentials',{
        email,
        password,
        redirect:false
    });
    //非同期通信
    var selectResult = await getLoginUser(email,password);
    
    sessionStorage.key='名前';
    sessionStorage.value = selectResult;
    sessionStorage.setItem('name',selectResult);
    const sessionData = sessionStorage.getItem('name');
    console.log('セッションに保存されている名前は、'+sessionStorage.getItem('name'));
    console.log('セッションに保存されている名前は、'+sessionData);
    
    
    
    if(res.status === 200){
        toast.success('Login Successful');
        //redirectFlg = true;
        redirectFlg = true;
        
    }else{
        if(res.error === 'CredentialSignin'){
            res.error = 'Wrong Password';
        }
        toast.error(`Login Failed:${res.error}`);
    }
    
    if(redirectFlg){
        //Loginボタンのオブジェクト
        let loginBtn = document.getElementById('loginBtn');
        let redirectToHome = document.getElementById('redirectToHome');
        loginBtn.disabled = true;
        redirectToHome.disabled = false;
    }else{
        //Loginボタンのオブジェクト
        let loginBtn = document.getElementById('loginBtn');
        let redirectToHome = document.getElementById('redirectToHome');
        loginBtn.disabled = false;
        redirectToHome.disabled = true;
    }
    
}



```

```user.action.js
'use server';
import prisma from '../prisma';
import bcrypt from 'bcrypt';

export async function createUser(user){
    //パスワードをハッシュ化する
    const passwordHash = await bcrypt.hash(user.password,10);

    //作成日時を取得する
    let createdAtDate = getCreatedAtDate();

    //更新日時を取得する
    let updatedAtDate = getUpdatedAtDate();

    //登録処理を実行する
    const newUser = await prisma.users.create({data:{
        name:user.name,
        email:user.email,
        password:passwordHash,
        created_at:createdAtDate,
        updated_at:updatedAtDate,
        
    }});
    return newUser;
    /*
    const passwordHash = await bcrypt.hash(user.password,10);
    const newuser = await User.create({
        ...user,
        password:passwordHash
    });
    return JSON.parse(JSON.stringify(newuser));
    */
}

export async function getUserByEmail(email){
    const user = await prisma.users.findUnique({
        where:{
            email
        }
    });

    if(!user){
        throw new Error('User Not Found');
    }

    return JSON.parse(JSON.stringify(user));
}

/**
 * 作成日時を取得する<BR/>
 * 作成日時を取得する処理
 * @returns {String} 作成日時 getCreatedAtDate
 */
function getCreatedAtDate(){
    //作成日時を取得する
    const createdAt = new Date();
    createdAt.setTime(createdAt.getTime()-createdAt.getTimezoneOffset()*60*1000);
    const getCreatedAtDate = createdAt.toISOString().replace('T',' ').substring(0,19);
    //日付型を文字列に型変換して返却
    return getCreatedAtDate.toString();
}

/**
 * 更新日時を取得する<BR/>
 * 更新日時を取得する処理
 * @returns {String} 更新日時 getUpdatedAtDate
 */
function getUpdatedAtDate(){
    //更新日時を取得する
    const updatedAt = new Date();
    updatedAt.setTime(updatedAt.getTime()-updatedAt.getTimezoneOffset()*60*1000);
    const getUpdatedAtDate = updatedAt.toISOString().replace('T',' ').substring(0,19);
    //日付型を文字列の型変換して返却する
    return getUpdatedAtDate.toString();
}

export async function getLoginUser(email,password){
    var userInfo='';
    //パスワードをハッシュ化
    const passwordHash = await bcrypt.hash(password,10);
    console.log(passwordHash);
    userInfo = await prisma.users.findUnique({
        where:({
            email:email,
            password:'$2b$10$/qDzMuB0dQxMSabo3leDE.HH63JS2nbXwTb/POKs9digm4pP4DQD2',//password
        })
    });
    console.log('ユーザーの検索結果は：'+userInfo.name);
    //userInfo = JSON.parse(userInfo);
    return userInfo.name; //userInfo
}
```

以上です。

## 参考サイト
■[Prisma Client API reference](https://www.prisma.io/docs/orm/reference/prisma-client-reference)