Write Post, All Post (client, Api)

김종민·2022년 8월 6일
0

apple-market

목록 보기
21/37


들어가기
Post를 쓰는 폼(client)와 API
두개를 만들어 봅시다.

1.pages/api/posts/index

client에서 질문(POST)를 작성하면,
그 작성된 질문을 처리하는 API

import withHandler, { ResponseType } from '@libs/server/withHandler'
import { NextApiRequest, NextApiResponse } from 'next'
import client from '@libs/server/client'
import { withApiSession } from '@libs/server/withSession'

async function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseType>
) {
  if (req.method === 'POST') { ///client에서 질문 작성처리 API(POST)
  
    const { question, latitude, longitude } = req.body
    ///front에서 보낸 data인 question, latitude(위도,가로), longitude(경, 세) 를
    ///req.body로 받는다
    
    const { user } = req.session ///loggedInUser 받음.
    
    const post = await client.post.create({
      data: {  ///question, latitude, longitude받고, user와 connect해서
               ///post create함.
        question,
        latitude,
        longitude,
        user: {
          connect: {
            id: user?.id,
          },
        },
      },
    })
    res.json({ ok: true, post })  ///ok:true와 만들어진 Post를 return해줌.
  }
  if (req.method === 'GET') {  ///모든 질문을 나열하는 요청을 받았을떄, 처리API
    const {
      query: { latitude, longitude }, ///req.query로 현재 user의 GPS를 받음.
    } = req
    // const latitudeNumber = parseFloat(latitude?.toString())
    // const longitudeNumber = parseFloat(longitude.toString())
    
    const posts = await client.post.findMany({ 
      include: { ///Post를 찾는데, user의 id, name, avatar포함시킴
        user: {
          select: {
            id: true,
            name: true,
            avatar: true,
          },
        },
        _count: {  ///Post의 wondering숫자와 answer의 숫자를 count해서 같이 보내줌.
          select: {
            wondering: true,
            answers: true,
          },
        },
      },
      where: { ///where는 Post의 찾는 범위를 설정해주는건데,
               ///현재 위도, 경도의 -0.1 ~~~ +0.1 범위에 있는 Post만 나열되게 설정
                ///위도, 경도가 Float(소수)이기때문에
            ///니코샘은const longitudeNumber =ㅔarseFloat(longitude.toString())
            ///이렇게 하라는데, error가 남. 시간 될때, 조금더 연구할 것!!!
            
        latitude: {
          gte: Number(latitude) - 0.01,
          lte: Number(latitude) + 0.01,
        },
        longitude: {
          gte: Number(longitude) - 0.01,
          lte: Number(longitude) + 0.01,
        },
      },
    })
    res.json({ ok: true, posts }) ///찾은 Posts들을 return해줌.
  }
}

export default withApiSession(
  withHandler({
    methods: ['POST', 'GET'],
    handler,
  })
)

pages/api/posts/index.ts 파일은 POST로 받으면, post작성 API, GET으로 받으면, 모든 질문보기임.

2. pages/community/write.tsx

import useCoords from '@libs/client/useCoords'
import useMutation from '@libs/client/useMutation'
import { Post } from '@prisma/client'
import { NextPage } from 'next'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import Button from '../../components/button'
import Layout from '../../components/layout'
import TextArea from '../../components/textarea'

interface WriteForm {
  question: string
}
///question을 적는 Form

interface WriteResponse {
  ok: boolean
  post: Post
}
///post를 작성하고 나서 return받는 data의 종류의 type지정

const Write: NextPage = () => {
  const { latitude, longitude } = useCoords()
  ///useCoords hook을 이용해서 현재 Post 작성자의 위치를 확인 가능,
  ///useCoords hook은 3번에서 다룰 예정.
  
  const router = useRouter()
  const { register, handleSubmit } = useForm<WriteForm>() ///react-hook-form
  const [post, { loading, data }] = useMutation<WriteResponse>('/api/posts')
  ///useMutaion으로 '/api/posts' 에 작성한 Post를 보내줌.
  
  const onValid = (data: WriteForm) => {
    if (loading) return
    post({...data, latitude, longitude})
  }
  ///onSubmit을 하면 data(Writeform)와 위도, 경도를 넣어서 API('/api/posts')로 보내줌
  
  useEffect(() => {
    if (data && data.ok) {
      router.push(`/community/${data.post.id}`)
    }
  }, [data, router])
  ///Post를 작성해서 onSubmit가 성공적으로 이루어져서 return으로 data.ok를 
  ///받았다면, post의 detaile Page로 이동시킴.
  
  return (
    <Layout canGoBack title="Write Post!">
      <form onSubmit={handleSubmit(onValid)} className="p-4 space-y-4">
        <TextArea
        	///TextArea로 register보내줌.
          register={register('question', { required: true, minLength: 5 })}
          required
          placeholder="Ask a question!"
        />
        <Button text={loading ? 'Loading' : 'Submit'} /> 
        ///loading일떄, 반드시 button title을 바꿔주어야함.
      </form>
    </Layout>
  )
}

export default Write

3. pages/post/index.tsx

import useCoords from '@libs/client/useCoords'
import { Post, User } from '@prisma/client'
import type { NextPage } from 'next'
import Link from 'next/link'
import useSWR from 'swr'
import FloatingButton from '../../components/floating-button'
import Layout from '../../components/layout'

interface PostWithUser extends Post {
  user: User
  _count: {
    wondering: number
    answers: number
  }
}
///api/posts로 GET 요청을 해서(모든 Post를 보는) 받는 return Data에
///user가 포함되어 있어 Post라는 type에 user를 넣어줌.

interface PostsResponse {
  ok: boolean
  posts: PostWithUser[]
}
///Post를 확장한 type인 PostWithUser를 배열로 받음.


const Community: NextPage = () => {
  const { latitude, longitude } = useCoords() ///현재 user의 위도, 경도 찾음.
  const { data } = useSWR<PostsResponse>(
    latitude && longitude
      ? `/api/posts?latitude=${latitude}&longitude=${longitude}`
      : null
  )///useSWR을 이용해서 '/api/post' API에 req해서 data를 받는데
  ///요청을 보낼때, req.query에 latitude와 longitude를 넣어서 보냄.
  ///넣어서 보내는 방법은 
  ///API주소 뒤에 '''?latitude=${latitude}&longitude=${longitude}'''
  ///넣어서 보낸다. API에서 받을떄는 const {latitude, longitude} = req.query
  ///위도, 경도가 undefined이면, error가 날 수 있으니, 반드시
  ///앞에 latitude && longitude ?  를 넣어준다
 
  return (
    <Layout hasTabBar title="동네생활">
      <div className="py-16 space-y-8">
        {data?.posts?.map((post: any) => ( ///받은 data를 map으로 뿌려줌.
          <Link key={post.id} href={`/community/${post.id}`}>
          ///나열된 질문을 클릭하면, 상세 질문 페이지로 이동.
          
            <a className="flex cursor-pointer flex-col items-start">
              <span className="flex ml-4 items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
                동네질문
              </span>
              <div className="mt-2 px-4 text-gray-700">
                <span className="text-orange-500 font-medium">Q .</span>
                {post.question} ///질문 뿌리기
              </div>
              <div className="mt-5 px-4 flex items-center justify-between w-full text-gray-500 font-medium text-xs">
                <span>{post.user.name}</span>  ///user.name 뿌리기!!
                <span>{post.createdAt}</span>  ///Post 작성날짜 뿌리기
              </div>
              <div className="flex px-4 space-x-5 mt-3 text-gray-500 py-3 border-t w-full border-b-[2px] shadow-lg">
                <span className="flex space-x-2 items-center text-sm ">
                  <svg
                    className="w-4 h-4"
                    fill="none"
                    stroke="currentColor"
                    viewBox="0 0 24 24"
                    xmlns="http://www.w3.org/2000/svg"
                  >
                    <path
                      strokeLinecap="round"
                      strokeLinejoin="round"
                      strokeWidth="2"
                      d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
                    ></path>
                  </svg>
                  <span>궁금해요 {post._count.wondering}</span>  
                  ///wondering count뿌려주기!!!
                </span>
                <span className="flex space-x-2 items-center text-sm ">
                  <svg
                    className="w-4 h-4"
                    fill="none"
                    stroke="currentColor"
                    viewBox="0 0 24 24"
                    xmlns="http://www.w3.org/2000/svg"
                  >
                    <path
                      strokeLinecap="round"
                      strokeLinejoin="round"
                      strokeWidth="2"
                      d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
                    ></path>
                  </svg>
                  <span>답변 {post._count.answers}</span>
                  ///answers count 뿌려주기!!
                </span>
              </div>
            </a>
          </Link>
        ))}
        <FloatingButton href="/community/write">
          <svg
            className="w-6 h-6"
            fill="none"
            stroke="currentColor"
            viewBox="0 0 24 24"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth="2"
              d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
            ></path>
          </svg>
        </FloatingButton>
      </div>
    </Layout>
  )
}

export default Community

4.libs/client/useCoords.ts

현재 위치(GPS)를 위도, 경도로 찍어주는 Hook
이 훅을 사용하면, 현재 작성자의 위치로 위도, 경도를
찍어줄 수 있음.

import { useEffect, useState } from 'react'

interface UseCoordState {
  latitude: number | null
  longitude: number | null
}

export default function useCoords() {
  const [coords, setCoords] = useState<UseCoordState>({
    latitude: null,
    longitude: null,
  })
  const onSuccess = ({
    coords: { latitude, longitude },
  }: GeolocationPosition) => {
    setCoords({ latitude, longitude })
  }

  useEffect(() => {
    navigator.geolocation.getCurrentPosition(onSuccess)
  }, [])
  return coords
}
profile
코딩하는초딩쌤

0개의 댓글