본문 바로가기

프로젝트/웹페이지

웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 3일차

728x90
반응형

썸네일 - 뤼튼 AI 생성 이미지

 

 

3일차에는 2일차에 스포한 노션 API로 데이터를 받아 오고 가공하는 법까지 알아보겠습니다.

오늘 참고한 자료입니다.

https://recodelog.com/blog/notion/notion-api#2-nextjs-%EC%84%A4%EC%A0%95-%EB%B0%8F-notion-sdk-%EC%84%A4%EC%B9%98

 

Notion API를 사용한 Next.js 페이지 만드는 방법 feat. react-notion-x

Notion API 사용법을 알아보고 Notion 데이터베이스 연동, react-notion-x를 사용해 Next.js 페이지에 적용하는 방법을 알아봅니다.

recodelog.com

 

💾 노션 API로 데이터 받아오기

 

1. 노션 SDK 설치

 

명령 프롬프트에서 next.js를 설치한 위치에 노션 SDK를 설치합니다.

npm install notion-client
npm install @notionhq/client

 

 

처음에 설치 실패했는데 다시 하니까 됐음. 왜지?

 

2. page.js에 코드 작성하기

 

기존에 작성되어 있는 코드를 모두 지우고 새로 작성합니다.

다음 코드는 노션 API로 post 요청을 하는 경우 받아 오는 데이터 쿼리를 JSON 형식으로 출력한 코드입니다.

참고로 코드는 챗 GPT의 도움을 받았습니다. 챗 GPT 최고👍

import { Client } from '@notionhq/client';

export default async function Home() {
  // Notion 클라이언트 설정
  const notion = new Client({
    auth: '프라이빗 API 통합 시크릿',
  });

  // 데이터 가져오기
  let response;
  try {
    const databaseId = 'DB ID';
    response = await notion.databases.query({
      database_id: databaseId,
    });
  } catch (error) {
    console.error('Failed to fetch Notion data:', error);
  }

  return (
    <div className="min-h-screen p-4">
      <h1 className="absolute top-4 left-4 text-xl font-bold">
        노션 API 데이터 받아 오기
      </h1>
      <pre>{JSON.stringify(response, null, 2)}</pre>
    </div>
  );
}

 

클라이언트 설정을 할 때 auth에 만들었던 노션 API의 프라이빗 API 통합 시크릿키를 넣어야 합니다.

 

https://www.notion.so/profile/integrations

 

Your connected workspace for wiki, docs & projects | Notion

A new tool that blends your everyday work apps into one. It's the all-in-one workspace for you and your team.

www.notion.com

 

 

표시를 누르면 복사 할 수 있습니다~

DB ID 얻는 법은 1일차 포스팅을 참고해주세요.

2024.12.21 - [프로젝트/웹페이지] - 웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 1일차

 

웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 1일차

웹 개발 1도 모르는 사람입니다. (당당) 💡 왜 웹 개발 1도 모르는데 웹페이지 만들기 프로젝트를 시작하게 되었는가? 요즘 포트폴리오 작업을 다시 하고 있는데 저를 소개하는 부분이 빈약하

jjrdd.tistory.com

 

이 코드를 실행하고 노션 API에서 데이터를 잘 받아 왔다면 홈페이지에 다음과 같이 나타납니다.

 

 

이 과정에서 몇 시간 애를 먹어서 이 화면 보고 소리를 질렀습니다 ... ㅎㅎ

 

💾 데이터 가공하기

 

노션 API를 사용하여 데이터를 받아 오면 다음과 같은 형태일겁니다.

{
  "results": [
    {
      "id": "some-page-id",
      "properties": {
        "Name": {
          "title": [
            {
              "text": { "content": "Example Page" }
            }
          ]
        },
        "Status": {
          "select": { "name": "Completed" }
        },
        "한글": {
          "number" : 0
        }
      }
    }
  ]
}

 

원하는 필드를 사용하고자 하면 이렇게 접근하면 됩니다.

 

  • 페이지 이름: response.results[0].properties.Name.title[0].text.content
  • 상태: response.results[0].properties.Status.select.name
  • 숫자: response.results[0].properties["한글"].number

 

그러면 제가 가져온 데이터에서 각각의 활동 회차와 시간을 추출하여 홈페이지에 나타내보겠습니다.

import { Client } from '@notionhq/client';

export default async function Home() {
  const notion = new Client({
    auth: '프라이빗 API 통합 시크릿',
  });

  let pages = [];
  try {
    const databaseId = 'DB ID';
    const response = await notion.databases.query({
      database_id: databaseId,
    });

    // 시간 값 추출
    pages = response.results.map((page) => ({
      id: page.id,
      title: page.properties["활동 회차"].title[0]?.text?.content || "Untitled",
      time: page.properties["시간"].number || 0, // 기본값 0
    }));
  } catch (error) {
    console.error('Failed to fetch Notion data:', error);
  }

  return (
    <div className="min-h-screen p-4">
      <h1 className="absolute top-4 left-4 text-xl font-bold">활동 데이터</h1>
      <ul className="mt-8">
        {pages.map((page) => (
          <li key={page.id} className="mb-4">
            <h2 className="text-lg font-semibold">{page.title}</h2>
            <p className="text-sm text-gray-500">시간: {page.time}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

 

 

이 코드는 가져온 데이터에서 원하는 속성만 추출한 뒤 pages에 저장합니다.

그리고 화면에 각 페이지의 이름과 시간을 렌더링합니다.

 

 

원하는대로 잘 나왔습니다!

하지만 노션 API는 한 번에 100개의 데이터밖에 가져오지 못 합니다.

따라서 모든 데이터를 다 가져오려면 pagination이 필요합니다.

 

import { Client } from '@notionhq/client';

export default async function Home() {
  const notion = new Client({
    auth: 'API 프라이빗 통합 시크릿',
  });

  let allPages = [];
  try {
    const databaseId = 'DB ID';
    let hasMore = true;
    let nextCursor = null;

    while (hasMore) {
      const response = await notion.databases.query({
        database_id: databaseId,
        start_cursor: nextCursor || undefined, // 다음 페이지의 커서 사용
      });

      // 현재 페이지의 데이터를 저장
      allPages = [
        ...allPages,
        ...response.results.map((page) => ({
          id: page.id,
          title: page.properties["활동 회차"].title[0]?.text?.content || "Untitled",
          time: page.properties["시간"].number || 0, // 기본값 0
        })),
      ];

      // 페이지네이션 설정
      hasMore = response.has_more;
      nextCursor = response.next_cursor;
    }
  } catch (error) {
    console.error('Failed to fetch Notion data:', error);
  }

  return (
    <div className="min-h-screen p-4">
      <h1 className="absolute top-4 left-4 text-xl font-bold">활동 데이터</h1>
      <ul className="mt-8">
        {allPages.map((page) => (
          <li key={page.id} className="mb-4">
            <h2 className="text-lg font-semibold">{page.title}</h2>
            <p className="text-sm text-gray-500">시간: {page.time}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

 

 

1회차의 활동까지 나타나있는걸 확인할 수 있습니다!

다만, 앞으로도 DB는 계속 늘어날텐데 이 모든 정보를 다 가져오는건 비효율적이라는 생각이 들었습니다.

이건 추후에 페이지 기획이 완료되면 수정해야겠습니다.

 

그럼, 오늘의 목표로 각 멤버마다의 총 시간을 구해서 나타내보겠습니다.

안타깝게도 각 멤버들을 구분해놓은 관계형을 사용할 수 없어서 타이틀을 이용했습니다.

000회차 이름 활동 👈 타이틀이 이런 규칙을 지키고 있었기 때문에 여기서 이름을 추출합니다.

같은 이름의 경우 시간을 더하여 memberTimeMap에 따로 저장합니다.

또한 추출할 때 노션 페이지의 아이콘도 가져올 수 있더군요? 그것도 추가로 렌더링해보겠습니다.

갑자기 진도 빠름 주의 ..... ㅋㅋㅋ

import Image from "next/image";

export default async function Home() {
  const { Client } = require('@notionhq/client');

const notion = new Client({  auth: '시크릿 키~' });

let allPages = [];
let nextCursor = null;
let hasMore = true;

const memberTimeMap = new Map();

try{
  const databaseId = 'DB ID';

  while(hasMore){
    const response = await notion.databases.query({
      database_id: databaseId,
      start_cursor: nextCursor || undefined,
    });

    allPages = [
      ...allPages,
      ...response.results.map((page) => {
        const iconUrl = page.icon?.external?.url || null;
        const title = page.properties["활동 회차"].title[0]?.text?.content || "Untitled";
        const memberName = title.split(" ")[1] || "Unknown";
        const time = page.properties["시간"].number || 0;

        return {iconUrl, memberName, time };
      }),
    ];

    hasMore = response.has_more;
    nextCursor = response.next_cursor;
  }

  allPages.forEach((page) => {
    const { iconUrl, memberName, time } = page;

    if (!memberTimeMap.has(memberName)) {
      memberTimeMap.set(memberName, { totalTime: 0, iconUrl });
    }

    const currentData = memberTimeMap.get(memberName);
    memberTimeMap.set(memberName, {
      ...currentData,
      totalTime: currentData.totalTime + time,
    });
  });
} catch (error){
  console.error('Failed to fetch Notion data:', error);
}

  return (
    <div className="min-h-screen p-4">
      <h1 className="absolute top-4 left-4 text-xl font-bold">
        스터디 멤버들의 총 활동 시간은
      </h1>
      <ul className="mt-8">
        {Array.from(memberTimeMap.entries()).map(([member, { totalTime, iconUrl }]) => (
          <li key={member} className="mb-4 flex items-center">
            {iconUrl && (
              <img
                src={iconUrl}
                alt={`${member}의 아이콘`}
                className="w-8 h-8 mr-4 rounded-full"
              />
            )}
            <div>
              <h2 className="text-lg font-semibold">{member}</h2>
              <p className="text-sm text-gray-500">총 {Math.floor(totalTime)} 시간</p>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}

 

짜잔! 오늘의 목표까지 완성된 모습입니다.

 

 

소소하지만 오늘의 목표를 완성하고 나니 뿌듯하군요😊

 

사실 javascript는 전혀 모르기 때문에 챗 GPT가 대부분 작성을 해주었습니다.

제가 원하는 요구 사항을 물어보면 코드로 답해주고 약간의 오류만 고쳐가며 오늘의 목표를 완수할 수 있었습니다.

장황하게 코드 설명을 하고 싶지만 나도 모르는 이슈로 인해... 아쉽습니다 ㅋㅋ

하지만 누구라도 이 과정과 코드를 보며 조금이나마 도움이 되길 바라며 글을 작성하고는 있습니다.

 

728x90
반응형