[nextjs]Gotosocial + CRUD

👉🏻 기존 프로젝트에 글쓰기 기능과 글 수정기능을 추가합니다.
Add post creation and editing features to the existing project.

👉🏻 타임라인을 불러오는 gotosocial.ts파일은 삭제됐습니다.
The gotosocial.ts file that fetches the timeline has been deleted.

📁 프로젝트 구조 / Project Structure

myapp10/
├── app/
│ ├── api/
│ │ ├── auth/ # NextAuth 
│ │ └── gts/
│ │      └── statuses/
│ │       ├── route.ts # POST Create
│ │       └── [id]/
│ │            └── route.ts # DELETE, PUT
│ ├── feed/
│ │    ├── page.tsx # frontend(main page,post read)
│ │    ├── PostCard.tsx # frontend(post content,delete,update)
│ │    └── PostComposer.tsx # frontend(post create)
│ ├── layout.tsx
│ ├── page.tsx # Home
│ └── globals.css
├── auth.ts.   # NextAuth 
└── package.json

📁 Nextjs 프로젝트 생성
Create a Next.js project

npx create-next-app@latest

📁 next-auth 설치
Install next-auth

npm install next-auth@beta

📁 글쓰기 / Writing(Create)

✔️ myapp10/app/feed/PostComposer.tsx

— 글 작성 폼 / Post Creation Form

'use client'
import { useState } from 'react'

export function PostComposer() {
  const [text, setText] = useState('')
  
  const handlePost = async () => {
    await fetch('/api/gts/statuses', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ status: text })
    })
    location.reload()
  }

  return (
    <div className="border p-4 rounded-xl flex gap-2">
      <input value={text} onChange={e => setText(e.target.value)} className="flex-1 border p-2 rounded" placeholder="무슨 일이 일어나고 있나요?" />
      <button onClick={handlePost} className="bg-black text-white px-4 rounded">게시</button>
    </div>
  )
}

1)서버에서 CORS에러를 막기위해서 /api/gts/statuses 라우터로 fetch를 실행합니다.
To prevent CORS errors on the server, a fetch request is made to the /api/gts/statuses route.

✔️ myapp10/app/feed/page.tsx

— 메인페이지,글 작성 폼을 불러옵니다. / Loads the main page and post creation form. (PostComposer.tsx)

import { PostComposer } from './PostComposer' // Create Component
... ...
    return (
      <div className="max-w-xl mx-auto p-4 space-y-4">
        <h1 className="text-xl font-bold border-b pb-2">연합 우주 타임라인</h1>
        <h1 className="text-sm text-gray-500"> Federated Universe Timeline </h1>
        
        {/* Post Composer */}
        <PostComposer token={session.accessToken} />
        
        {/* Post Cards */}
        {posts.length === 0 ? (
          <p className="text-gray-500 py-10 text-center">아직 타임라인에 표시할 글이 없습니다. / No posts to display yet.</p>
        ) : (
          posts.map((p: any) => <PostCard key={p.id} post={p} token={session.accessToken} />)
        )}
      </div>
    )

1)클라이언트와 서버의 기능을 동일한페이지에 사용할 수 없기 때문에 파일을 분리합니다.
Since client-side and server-side functions cannot be used on the same page, the files are separated.

✔️ myapp10/app/api/gts/router.ts

— gotosocial서버에 글을 저장합니다.
Saves the post to the GoToSocial server.

import { auth } from "@/auth"

export async function POST(req: Request) {
  const session = await auth()
  if (!session?.accessToken) return new Response("Unauthorized", { status: 401 })
  
  const { status } = await req.json()
  const baseUrl = process.env.GTS_URL || "https://freelifemakers.com"
  
  const res = await fetch(`${baseUrl}/api/v1/statuses`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ status,visibility : "public" })
  })
  
  const data = await res.json()
  return Response.json(data)
}

📁 글 수정 / Edit Post (Update)

✔️ myapp10/app/feed/PostCard.tsx

— 글 삭제 아래 부분에 글 수정 버튼을 붙입니다.
Place the “Edit Post” button below the “Delete Post” button.

— 정규 표현식은 html을 제거하는 부분입니다.
The regular expression is the part that removes HTML.

        <button 
          onClick={async () => {
            const newText = prompt('수정할 내용수정할 내용 / Edit content:', post.content.replace(/<[^>]*>/g, '')) // HTML 태그 제거
            if(!newText) return
            await fetch(`/api/gts/statuses/${post.id}`, { 
              method: 'PUT', 
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ status: newText }) 
            })
            location.reload()
          }}
          className="text-blue-500 text-sm"
        >
          수정/Edit
        </button>

✔️ myapp10/app/api/gts/statuses/[id]/route.ts

— 글 수정 라우트 입니다.
This is the route for editing a post.(Update)

// 수정 / Update
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const session = await auth()
  const { status } = await req.json()
  const baseUrl = process.env.GTS_URL || "https://freelifemakers.com"
  
  const res = await fetch(`${baseUrl}/api/v1/statuses/${id}`, {
    method: 'PUT',
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ status, visibility : "public" })
  })
  
  return Response.json(await res.json())
}


📁 서버실행 / Start Server

npm run dev

📁 브라우저 접속 / Access Browser

http://localhost:3000/feed
/feed

Leave a Reply