Day7 - 포스트 페이지

RINM·2023년 1월 5일
0

NextJS - Reddit Clone

목록 보기
7/9

Side Bar

Font Awesome

Font Awesome에서는 다양한 아이콘을 무료로 제공한다. React 환경에서는 SVG-CORE를 사용하여 아이콘을 이용할 수 있다.

npm i --save @fortawesome/fontawesome-svg-core @fortawesome/free-regular-svg-icons @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome

_app.tsx에서 다음과 같이 설정해주면 typescript 환경에서 FontAwesomeIcon 태그를 활용하여 아이콘을 사용할 수 있다.

import { library } from '@fortawesome/fontawesome-svg-core'
import { fas } from '@fortawesome/free-solid-svg-icons'
import '@fortawesome/fontawesome-svg-core/styles.css'

library.add(fas)

각 아이콘은 패키지 종류(fas/far)와 아이콘 이름을 통하여 지정할 수 있다. style을 활용하여 아이콘의 크기 등을 조절할 수 있고 color를 통하여 색 또한 변경할 수 있다.

<FontAwesomeIcon icon={['fas','users']} style={{marginRight:5, maxWidth:15}}/>

포스트 생성

Frontend

로그인한 사용자만 페이지에 접근할 수 있게 설정. 로그인하지 않은 경우 로그인 페이지로 이동

export const getServerSideProps : GetServerSideProps =async ({req,res}) => {
    try {
        const cookie = req.headers.cookie;

        //cookie missing
        if(!cookie) throw new Error("Missing auth token");

        //user authentication on backend
        await axios.get("/auth/me",{headers:{cookie}})

        return {props: {}}

    } catch (error) {
        //move to login page
        res.writeHead(307,{Location:"/login"}).end()
        
        return {props: {}}
    }
}

사용자가 작성한 title과 body를 axios로 서버에 전송
포스트 성공시 생성된 포스트 페이지로 이동

const submitPost = async (e: FormEvent) => {
	e.preventDefault()

	if(title.trim()===""||!subName) return;

	try {
		const {data:post} = await axios.post<Post>("/posts",{
			title: title.trim(),
			body,
            sub: subName
		})

		router.push(`/r/${subName}/${post.identifier}/${post.slug}`)
            
	} catch (error) {
		console.error(error);
            
	}
        
}

Backend

포스트가 속한 sub을 찾은 뒤 새로운 Post 생성하여 DB에 저장

const createPost = async (req: Request, res: Response) => {
    const {title, body, sub} = req.body;

    //if title is empty
    if(title.trim()===""){
        return res.status(400).json({title: "Title cannot be empty"})
    }

    const user = res.locals.user;

    try {
        const subRecord = await Sub.findOneByOrFail({name: sub});

        const post = new Post();
        post.title=title;
        post.body=body;
        post.user=user;
        post.sub=subRecord;

        await post.save();

        return res.json(post);
    } catch (error) {
        console.error(error);
        return res.status(500).json({error: "Something went wrong"})
    }
}


router.post("/",userMiddleware,authMiddleware,createPost)

sub 정보를 가져올 때 속한 포스트 정보도 같이 전달

포스트 페이지 & 댓글

Frontend

SWRConfig

SWR에서 사용할 fetcher를 모든 페이지에 적용하기 위하여 _app.tsx를 수정

//swr fetcher
  const fetcher =async (url:string) => {
    try {
        const res  = await axios.get(url);
        return res.data
    } catch (error:any) {
        throw error.response.data
    }
  }

  return <SWRConfig
    value={{fetcher}}
  >
    <AuthProvider>
    <Head>
        <title>Reddit_Clone</title>
        <link rel="icon" href="/favicon.ico" />
    </Head>
    {!authRoute && <NavBar/>}
    <div className={authRoute ? "" : "pt-16"}>
      <Component {...pageProps} />
    </div>
  </AuthProvider>
  </SWRConfig>

사용:

const {data: post, error} = useSWR<Post>(identifier&&slug? `/posts/${identifier}/${slug}`:null)

comment 내용을 입력받아 axios로 backend에 전달

const submitComment = async (e:FormEvent) => {
        e.preventDefault();
        if(newComment.trim()==="") return;

        try {
            await axios.post(`/posts/${post?.identifier}/${post?.slug}/comments`,{
                body: newComment
            })
            setnewComment("")
			mutate()
        } catch (error) {
            console.error(error);
            
        }
    }

useSWR mutate

mutate(): SWR의 캐시된 데이터를 갱신하는 함수
댓글을 새로 등록하면 바로 밑에 출력되도록 설정하기 위해서는 useSWR에서 mutate 매서드를 받아온 후, 댓글 등록이 완료된 후 mutate를 실행해 주면 된다.

Backend

새로운 comment 생성

const createPostComment = async (req: Request, res: Response) => {
    const {identifier,slug} = req.params;
    const body = req.body.body;

    try {
        const post = await Post.findOneByOrFail({identifier,slug})
        const comment = new Comment()
        comment.body=body;
        comment.user = res.locals.user;
        comment.post=post;

        if(res.locals.user){
            post.setUserVote(res.locals.user);
        }

        await comment.save();

        return res.json(comment)

    } catch (error) {
        console.error(error);
        return res.status(404).json({error:"Post not found"})
    }
}

포스트에 속한 모든 댓글 찾아서 제공

const getPostComment =async (req: Request, res: Response) => {
    const {identifier,slug} = req.params;
    try {
        const post =await Post.findOneByOrFail({identifier,slug})
        const comments = await Comment.find({
            where: {postId: post.id},
            order: {createdAt:"DESC"},
            relations: ["votes"]
        })

    if(res.locals.user){
        comments.forEach((c)=>c.setUserVote(res.locals.user))
    }

    return res.json(comments);

    } catch (error) {
        console.error(error);
        return res.status(500).json({error: "Something went wrong"})
    }
}

0개의 댓글