Open6

nextauth mongodb

TsuboiTsuboi

mongodb 接続

  • mongodb atlas→New Project→Cluster(connect useing MongoDB Compass)→Copy the connection string, then open MongoDB Compass.→パスワードは DatabaseAccessで取得

MongoDB Compass

  • connect → disconnect → edit → confirm → url取得
  • vscode で .env.local ファイル作成(url貼り付け)
TsuboiTsuboi

VSCode で編集したものをMongoDB compass でデータを取得

utils/database.ts
import { MongoClient, Db } from 'mongodb'

interface ConnectType {
  db: Db
  client: MongoClient
}

const client = new MongoClient(process.env.DATABASE_URL, {
  useNewUrlParser: true,
  useUnifiedTopology: true
})

export default async function connect(): Promise<ConnectType> {
  if (!client.isConnected()) await client.connect()

  const db = client.db('Database Name')
  return { db, client }
}
api/user.tsx
import { NextApiRequest, NextApiResponse } from 'next'
import connect from '../../utils/database'

interface ResponseType {
  message: string
}

export default async (
  req: NextApiRequest,
  res: NextApiResponse<ResponseType>
): Promise<void> => {
  if (req.method === 'GET') {
    const { db } = await connect()
    const response = await db.collection('Collection Name').insertOne({
      name: 'tsuboi'
    })
    res.status(200).json(response.ops[0])
  } else {
    res.status(400).json({ message: 'Wrong request method' })
  }
}
TsuboiTsuboi

Postman や Insomnia で編集したものを MongoDB compass でデータを取得

pages/api/user
import { NextApiRequest, NextApiResponse } from 'next'
import connect from '../../utils/database'

interface SuccessResponseType {
  name: string
}
interface ErrorResponseType {
  error: string
}

export default async (
  req: NextApiRequest,
  res: NextApiResponse<SuccessResponseType | ErrorResponseType>
): Promise<void> => {
  if (req.method === 'POST') {
    const { name } = req.body
    if (!name) {
      res.status(400).json({ error: 'not name' })
      return
    }
    const { db } = await connect()
    const response = await db.collection('user').insertOne({
      name
    })
    res.status(200).json(response.ops[0])
  } else {
    res.status(400).json({ error: 'Wrong request method' })
  }
}

より詳細に

  1. 条件分岐
pages/api/user
import { NextApiRequest, NextApiResponse } from 'next'
import connect from '../../utils/database'

interface SuccessResponseType {
  name: string
  email: string
  teacher: boolean
  courses: string[]
}
interface ErrorResponseType {
  error: string
}

export default async (
  req: NextApiRequest,
  res: NextApiResponse<SuccessResponseType | ErrorResponseType>
): Promise<void> => {
  if (req.method === 'POST') {
    const { name, email, teacher, courses } = req.body
    if (!teacher) {
      if (!name || !email) {
        res.status(400).json({ error: 'not current' })
        return
      }
    } else if (teacher) {
      if (!name || !email || !courses) {
        res.status(400).json({ error: 'not current' })
        return
      }
    }
    const { db } = await connect()
    const response = await db.collection('user').insertOne({
      name,
      email,
      teacher,
      courses: courses || []
    })
    res.status(200).json(response.ops[0])
  } else {
    res.status(400).json({ error: 'Wrong request method' })
  }
}

2.1.1 MongoDB で登録したデータを Postman や Insomnia で email を指定することで確認

pages/api/user
else if (req.method === 'GET') {
    const { email } = req.body
    if (!email) {
      res
        .status(400)
        .json({ error: 'email というプロパティを指定してください。' })
      return
    }
    const { db } = await connect()
    const response = await db.collection('user').findOne({ email })
    if (!response) {
      res
        .status(400)
        .json({ error: 'そのようなアドレスは登録されていません。' })
      return
    }
    res.status(200).json(response)
  } 

2.1.2 MongoDB で登録したデータを Postman や Insomnia で url に email を指定することで確認

pages/api/user/[email].tsx
import { NextApiRequest, NextApiResponse } from 'next'
import connect from '../../../utils/database'

interface SuccessResponseType {
  name: string
  email: string
  teacher: boolean
  courses: string[]
  appointments: Record<string, unknown>[]
}
interface ErrorResponseType {
  error: string
}

export default async (
  req: NextApiRequest,
  res: NextApiResponse<SuccessResponseType | ErrorResponseType>
): Promise<void> => {
  if (req.method === 'GET') {
    const { email } = req.query
    if (!email) {
      res
        .status(400)
        .json({ error: 'email というプロパティを指定してください。' })
      return
    }
    const { db } = await connect()
    const response = await db.collection('user').findOne({ email })
    if (!response) {
      res
        .status(400)
        .json({ error: `${email} のようなアドレスは登録されていません。` })
      return
    }
    res.status(200).json(response)
  } else {
    res.status(400).json({ error: 'Wrong request method' })
  }
}

2.2 MongoDB で登録したデータを Postman や Insomnia で id を指定することで確認

pages/api/teacher.tsx
import { ObjectId } from 'mongodb'
import { NextApiRequest, NextApiResponse } from 'next'
import connect from '../../utils/database'

interface SuccessResponseType {
  name: string
  email: string
  teacher: boolean
  courses: string[]
}
interface ErrorResponseType {
  error: string
}

export default async (
  req: NextApiRequest,
  res: NextApiResponse<SuccessResponseType | ErrorResponseType>
): Promise<void> => {
  if (req.method === 'GET') {
    const { id } = req.body
    if (!id) {
      res.status(400).json({ error: 'id というプロパティを指定してください。' })
      return
    }
    const { db } = await connect()
    const response = await db
      .collection('user')
      .findOne({ _id: new ObjectId(id) })
    if (!response) {
      res.status(400).json({ error: 'そのような ID は登録されていません。' })
      return
    }
    res.status(200).json(response)
  } else {
    res.status(400).json({ error: 'Wrong request method' })
  }
}

2.3 MongoDB で登録したデータを Postman や Insomnia で Course を指定することで(配列で)取得

pages/api/search.tsx
import { NextApiRequest, NextApiResponse } from 'next'
import connect from '../../utils/database'

interface SuccessResponseType {
  name: string
  email: string
  teacher: boolean
  courses: string[]
}
interface ErrorResponseType {
  error: string
}

export default async (
  req: NextApiRequest,
  res: NextApiResponse<SuccessResponseType[] | ErrorResponseType>
): Promise<void> => {
  if (req.method === 'GET') {
    const { courses } = req.body
    if (!courses) {
      res
        .status(400)
        .json({ error: 'courses というプロパティを指定してください。' })
      return
    }
    const { db } = await connect()
    const response = await db.collection('user').find({ courses }).toArray()
    if (response.length === 0) {
      res
        .status(400)
        .json({ error: 'そのような Course は登録されていません。' })
      return
    }
    res.status(200).json(response)
  } else {
    res.status(400).json({ error: 'Wrong request method' })
  }
}

2.3 Postman や Insomnia で 編集後 MongoDB で登録した2つのデータをつなげる。

pages/api/appointment.tsx
import { ObjectId } from 'mongodb'
import { NextApiRequest, NextApiResponse } from 'next'
import { getSession } from 'next-auth/client'
import connect from '../../utils/database'

interface SuccessResponseType {
  date: string
  teacherName: string
  teacherId: string
  studentName: string
  studentId: string
  course: string
  location: string
  appointmentLink: string
}
interface ErrorResponseType {
  error: string
}

export default async (
  req: NextApiRequest,
  res: NextApiResponse<SuccessResponseType | ErrorResponseType>
): Promise<void> => {
  if (req.method === 'POST') {
    // const session = await getSession({ req })
    // if (!session) {
    //   res.status(400).json({ error: '先にログインしてください ' })
    //   return
    // }
    const {
      date,
      teacherName,
      teacherId,
      studentName,
      studentId,
      course,
      location,
      appointmentLink
    } = req.body
    if (
      !date ||
      !teacherName ||
      !teacherId ||
      !studentName ||
      !studentId ||
      !course ||
      !location
    ) {
      res.status(400).json({ error: 'パラメータが不足しています。 ' })
      return
    }
    const { db } = await connect()
    const teacherExists = await db
      .collection('user')
      .findOne({ _id: new ObjectId(teacherId) })
    if (!teacherExists) {
      res
        .status(400)
        .json({ error: `${teacherId}${teacherName}は紐付いていません。` })
      return
    }
    const studentExists = await db
      .collection('user')
      .findOne({ _id: new ObjectId(studentId) })
    if (!studentExists) {
      res
        .status(400)
        .json({ error: `${studentId}${studentName}は紐付いていません。` })
      return
    }

    const appointment = {
      date,
      teacherName,
      teacherId,
      studentName,
      studentId,
      course,
      location,
      appointmentLink: appointmentLink || ' '
    }
    await db
      .collection('user')
      .updateOne(
        { _id: new ObjectId(teacherId) },
        { $push: { appointments: appointment } }
      )
    await db
      .collection('user')
      .updateOne(
        { _id: new ObjectId(studentId) },
        { $push: { appointments: appointment } }
      )
    res.status(200).json(appointment)
  } else {
    res.status(400).json({ error: 'Wrong request method' })
  }
}

TsuboiTsuboi

next-auth との接続

https://next-auth.js.org/providers/auth0

指示に従ってコードの編集。

pages/api/auth/[...nextauth].tsx
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
import { NextApiRequest, NextApiResponse } from 'next'

const options = {
  // Configure one or more authentication providers
  providers: [
    Providers.Auth0({
      clientId: process.env.AUTH0_CLIENT_ID,
      clientSecret: process.env.AUTH0_CLIENT_SECRET,
      domain: process.env.AUTH0_DOMAIN
    })
  ]
}

export default (
  req: NextApiRequest,
  res: NextApiResponse
): void | Promise<void> => NextAuth(req, res, options)

ページの設定

pages/index.tsx
import React from 'react'
import Head from 'next/head'
import { Container } from '../styles/pages/Home'
import { signIn, signOut, useSession } from 'next-auth/client'
import { NextPage } from 'next'

const Home: NextPage = () => {
  const [session, loading] = useSession()
  return (
    <Container>
      <Head>
        <title>Homepage</title>
      </Head>

      {!session && (
        <>
          Not signed in <br />
          <button onClick={(): Promise<void> => signIn('auth0')}>
            Sign in
          </button>
        </>
      )}
      {session && (
        <>
          Signed in as {session.user.email} <br />
          <button onClick={(): Promise<void> => signOut()}>Sign out</button>
        </>
      )}
      {loading && <h1>Loading...</h1>}
    </Container>
  )
}

export default Home

セッション状態をページ間で共有できるようにするために、NextAuth.js の Provider を使用

pages/_app.tsx
import React from 'react'
import { AppProps } from 'next/app'
import { ThemeProvider } from 'styled-components'

import GlobalStyle from '../styles/global'
import theme from '../styles/theme'
import { Provider } from 'next-auth/client'

function MyApp({ Component, pageProps }: AppProps): JSX.Element {
  return (
    <ThemeProvider theme={theme}>
      <Provider session={pageProps.session}>
        <Component {...pageProps} />
        <GlobalStyle />
      </Provider>
    </ThemeProvider>
  )
}

export default MyApp
TsuboiTsuboi

MongoDB と NextAuth とのデータを紐付ける(SWR)

pages/profile.tsx
import React from 'react'
import Navbar from '../components/NavBar/NavBar'
import Head from 'next/head'
import { Container } from '../styles/pages/Home'
import { signIn, signOut, useSession } from 'next-auth/client'
import { NextPage } from 'next'
import useSWR from 'swr'
import api from '../utils/api'

const Profile: NextPage = () => {
  const [session, loading] = useSession()

  const { data, error } = useSWR(`/api/user/${session?.user.email} `, api) //第一引数にfetchするURL文字列,第二引数に、第一引数で渡したURLを引数に取りデータを返却するfetch関数
//返り値のオブジェクトの中のdataがfetch関数で返却したデータ
  return (
    <Container>
      <Head>
        <title>Homepage</title>
      </Head>
      <Navbar />
      {!session && (
        <>
          Not signed in <br />
          <button onClick={(): Promise<void> => signIn('auth0')}>
            Sign in
          </button>
        </>
      )}
      {session && data && (
        <>
          <h1>Profile Page</h1>
          Signed in as {session.user.email} <br />
          <p>{data.data.name}</p>
          <p>{data.data.email}</p>
          <button onClick={(): Promise<void> => signOut()}>Sign out</button>
        </>
      )}
      {error && <h1>dont exisit {session?.user.email} </h1>}
      {loading && <h1>Loading...</h1>}
    </Container>
  )
}

export default Profile

TsuboiTsuboi

条件にあったユーザーをセレクト

バックエンド側で courses を取り出す

import { NextApiRequest, NextApiResponse } from 'next'
import connect from '../../../utils/database'

interface SuccessResponseType {
  name: string
  email: string
  teacher: boolean
  courses: string[]
}
interface ErrorResponseType {
  error: string
}

export default async (
  req: NextApiRequest,
  res: NextApiResponse<SuccessResponseType[] | ErrorResponseType>
): Promise<void> => {
  if (req.method === 'GET') {
    const courses = req.query.courses as string
    if (!courses) {
      res
        .status(400)
        .json({ error: 'courses というプロパティを指定してください。' })
      return
    }
    const { db } = await connect()
    const response = await db
      .collection('user')
      .find({ courses: { $in: [new RegExp(`^${courses}`, 'i')] } })
      .toArray()
    if (response.length === 0) {
      res
        .status(400)
        .json({ error: 'そのような Course は登録されていません。' })
      return
    }
    res.status(200).json(response)
  } else {
    res.status(400).json({ error: 'Wrong request method' })
  }
}

フロントエンド側でコースを抽出

pages/search/index.tsx
import React, { useState, useCallback } from 'react'
import Navbar from '../../components/NavBar/NavBar'
import Head from 'next/head'
import { Container } from '../../styles/pages/Home'
import { signIn, signOut, useSession } from 'next-auth/client'
import { NextPage } from 'next'
import api from '../../utils/api'
import Link from 'next/link'

interface Teacher {
  name: string
  email: string
  teacher: boolean
  courses: string[]
  _id: string
  appointments: Record<string, unknown>[]
}
const Search: NextPage = () => {
  const [textInput, setTextInput] = useState('')
  const [data, setData] = useState<Teacher[]>([])
  const [session, loading] = useSession()

  const handleSearch = useCallback(() => {
    api(`/api/search/${textInput}`).then(response => {
      const teacher: Teacher[] = response.data

      setData(teacher)
    })
  }, [textInput])
  return (
    <Container>
      <Head>
        <title>Homepage</title>
      </Head>
      <Navbar />
      <h1>Search</h1>
      {!session && (
        <>
          Not signed in <br />
          <button onClick={(): Promise<void> => signIn('auth0')}>
            Sign in
          </button>
        </>
      )}
      {session && (
        <>
          <h1>言語を選択</h1>
          <input
            value={textInput}
            onChange={e => setTextInput(e.target.value)}
            type="text"
            placeholder="Nome da matéria"
            className="bg-pink-200"
          />
          <button type="submit" onClick={handleSearch}>
            Submit
          </button>
          <Container>
            Signed in as {session.user.email} <br />
            <button onClick={(): Promise<void> => signOut()}>Sign out</button>
          </Container>
          {data.length !== 0 &&
            data.map(teacher => (
              <Link href={`/search/${teacher._id}`} key={teacher._id}>
                <a style={{ textDecoration: 'none' }}>
                  <h1>{teacher.name}</h1>
                </a>
              </Link>
            ))}
        </>
      )}
      {loading && <h1>Loading...</h1>}
    </Container>
  )
}

export default Search

axios でパスを取得

utils/api
import axios, { AxiosResponse } from 'axios'

export default function api(path: string): Promise<AxiosResponse<any>> {
  return axios.get(path)
}

個人データを取得

pages/search/[_id].tsx
import React from 'react'
import axios from 'axios'
import { GetServerSideProps, GetServerSidePropsContext } from 'next'

interface Teacher {
  name: string
  email: string
  teacher: boolean
  courses: string[]
  _id: string
  appointments: Record<string, unknown>[]
}

export default function teacherProfilePage({
  name,
  email,
  _id
}: Teacher): JSX.Element {
  return (
    <>
      <h1>Name: {name}</h1>
      <h1>E-mail: {email}</h1>
      <h1>ID: {_id}</h1>
    </>
  )
}

export const getServerSideProps: GetServerSideProps = async (
  context: GetServerSidePropsContext
) => {
  const _id = context.query._id as string

  const response = await axios.get<Teacher>(
    `http://localhost:3000/api/teacher/${_id}`
  )

  const teacher = response.data

  return {
    props: teacher
  }
}