개발

Notion API로 원하는 날짜에 일정 생성하기

이제곱 2024. 10. 9. 16:10

서론

8~9월동안 원티드 프리온보딩 인턴쉽 챌린지를 진행하며 좋은 인연을 맺게 되었다. 팀으로 활동하는 과정은 끝났지만, 데일리 스크럼을 통해 매일 할 일을 정리하며 취업 활동을 같이 이어나가기로 하였다.

 

기존에는 팀 공용으로 쓰는 노션 페이지의 캘린더 뷰 데이터베이스에 팀원들의 리마인드를 위해 직접 블록을 생성해오고 있었다.

 

이런 식으로.. 다 직접 생성해서 관리했다.

 

과정을 진행하며 공휴일을 제외한 평일 오전 10시 30분에 정기적으로 진행하기로 정해졌으므로 자동화 방법을 찾고있었다. 매번 페이지 생성 하는 것은 너무 번거롭기 때문에...

 

해결 방법들

1. Notion 페이지 반복 기능 이용

 

 

데이터베이스의 템플릿에 ... 버튼을 누르면 Repeat 설정을 할 수 있다.

 

 

 

 

여기서 weekly 를 선택하면 요일 지정은 가능하지만... 더 섬세한 작업은 불가능했다.

내가 원하는 것은 "공휴일과 주말을 제외한 모든 날에 자동생성되는 페이지" 였기 때문에 다른 방법을 찾기로 했다.

 

 

2. Notion API와 공공 API 이용

공공 데이터 포털에서 제공해주는 특일 API를 이용해서 공휴일을 판별하고, Notion API를 이용해서 페이지 생성 요청을 보내면 정확히 내가 원하는 작업을 할 수 있다.

특일 API

https://www.data.go.kr/data/15012690/openapi.do

 

한국천문연구원_특일 정보

(천문우주정보)국경일정보, 공휴일정보, 기념일정보, 24절기정보, 잡절정보를 조회하는 서비스 입니다. 활용시 날짜, 순번, 특일정보의 분류, 공공기관 휴일 여부, 명칭을 확인할 수 있습니다.

www.data.go.kr

 

짧게 찾아본 바로는 공휴일을 제공해주는 API가 이것 뿐이었다.

여러 정보 중 "공휴일 정보"를 받아오면 된다.

 

요청 URL은 다음과 같다.

http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo

 

여기에 필요한 query parameter를 붙여서 요청하면 된다.

참고로 10월의 공휴일 데이터는 다음과 같다.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><response><header><resultCode>00</resultCode><resultMsg>NORMAL SERVICE.</resultMsg></header><body><items><item><dateKind>01</dateKind><dateName>임시공휴일</dateName><isHoliday>Y</isHoliday><locdate>20241001</locdate><seq>2</seq></item><item><dateKind>01</dateKind><dateName>개천절</dateName><isHoliday>Y</isHoliday><locdate>20241003</locdate><seq>1</seq></item><item><dateKind>01</dateKind><dateName>한글날</dateName><isHoliday>Y</isHoliday><locdate>20241009</locdate><seq>1</seq></item></items><numOfRows>10</numOfRows><pageNo>1</pageNo><totalCount>3</totalCount></body></response>

 

Notion API

https://developers.notion.com/reference/post-page

 

Create a page

Creates a new page that is a child of an existing page or database. If the new page is a child of an existing page, title is the only valid property in the properties body param. If the new page is a child of an existing database, the keys of the propertie

developers.notion.com

 

굉장히 다양한 기능을 API로 제공한다.

처음에는 Block을 생성해야 한다고 생각했는데, 캘린더 일정은 Database에 소속된 Page 형태여서 Page를 생성하는 요청을 보내야 한다.

 

 

사용하기 전에 "API Integration"으로 앱과 노션 데이터베이스를 연결하는 작업이 필요하다.

https://developers.notion.com/docs/authorization

 

Authorization

This guide describes the authorization flows for internal and public Notion integrations.

developers.notion.com

 

Integration 설정 페이지로 들어가서 새 Integration (새 API 통합)을 생성하면 된다.

 

 

여기서 생성한 Integration을 사용할 데이터베이스에 꼭 연결해주어야 한다.

 

나는 생성만 하고 연결은 안 한 상태에서 왜 안되지를 외치고 있었다..

 

https://www.notion.so/ko/help/add-and-manage-connections-with-the-api#add-connections-to-pages

 

API 통합 추가와 관리 – Notion (노션) 도움말 센터

다른 소프트웨어의 API를 Notion에 연결하고, 워크스페이스 작업을 자동화하고, 다른 플랫폼의 서비스를 Notion에서 이용하세요 🤖

www.notion.so

 

 

정상적으로 Integration을 생성했다면 점 세개 - connect to를 눌렀을 때 내 Integration이 보인다. 누르고 confirm하면 연결 완료.

 

Notion SDK

https://github.com/makenotion/notion-sdk-js

 

GitHub - makenotion/notion-sdk-js: Official Notion JavaScript Client

Official Notion JavaScript Client. Contribute to makenotion/notion-sdk-js development by creating an account on GitHub.

github.com

 

Notion팀이 동일한 기능을 제공하는 Javascript SDK도 제공하고 있다. (정말 감사합니다)

운이 좋게도 Javascript를 이용해서 개발 중이었기에 바로 가져다 썼다.

 

page POST 요청을 하려면 클라이언트 객체 생성을 한 후, pages.create() 메소드를 호출해주면 된다. 너무 간단해서 감동의 눈물이..

  const notion = new Client({
    auth: process.env.NOTION_SECRET,
  });

  await notion.pages.create(page);

구현

간단 플로우)

공휴일 API 요청 및 응답 데이터 처리 -> 공휴일이 아닌 날짜만 Page 타입 객체로 생성 -> Notion SDK 사용하여 Notion API 요청

1. 공휴일 데이터

공휴일 정보는 xml형태로 오므로 편한 처리를 위해 fast-xml-parser를 이용하여 js object 형태로 변환하여 사용하였다. 많은 패키지 중 fast-xml-parser를 선택한 이유는 최근 다운수가 많았고, 업데이트 날짜가 최근(한 달 전)이었기 때문이다. 패키지 크기도 작았던 것으로 기억한다..

response.body.items.item에 원하는 값의 배열이 들어있다.

{
  item: [
    {
      dateKind: '01',
      dateName: '임시공휴일',
      isHoliday: 'Y',
      locdate: '20241001',
      seq: '2'
    },
    ...
  ]
}

 

locdate를 YYYY-MM-DD 형태로 변형한 후 새 Date 객체를 생성해주었다.

  return items.map((item) => {
    const date = item.locdate;
    return new Date(
      `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}`
    );
  });

2. Page 객체 생성

Notion Client의 page.create()의 인자로는 생성할 Database의 속성에 맞는 객체가 들어가야 한다.

속성 확인을 위해 database.retrive()를 호출해서 상세 속성을 확인하였고, 다음 형태로 객체를 생성해야 했다.

 

{
  parent: { database_id },
  icon: {
    emoji: '💫',
  },
  properties: {
    // database의 속성과 일치
    Name: {
      title: [
        {
          text: {
            content: '데일리 스크럼',
          },
        },
      ],
    },
    Date: {
      date: {
        start: date, // Date type
        time_zone: 'Asia/Seoul',
      },
    },
  },
  children: [
    {
      object: 'block',
      type: 'paragraph',
      paragraph: {
        rich_text: [
          {
            text: {
              content: 'notion API로 만들어진 페이지입니다.',
            },
          },
        ],
      },
    },
  ],
}

 

 

 

PagePageBuilder를 사용하여 쉽게 알맞은 타입의 객체가 생성되도록 했다.

 

 

export class Page {
  constructor(databaseId) {
    this.parent = { database_id: databaseId };
    this.properties = {};
    this.children = [];
    this.icon = null;
  }

  addProperty(name, value) {
    this.properties[name] = value;
    return this;
  }

  addChildBlock(block) {
    this.children.push(block);
    return this;
  }

  setIcon(emoji) {
    this.icon = { emoji };
    return this;
  }
}
import { Page } from './page.js';

export class PageBuilder {
  constructor(databaseId) {
    this.page = new Page(databaseId);
  }

  setTitle(title) {
    this.page.addProperty('Name', {
      title: [{ text: { content: title } }],
    });
    return this;
  }

  setDate(date) {
    this.page.addProperty('Date', {
      date: { start: date.toISOString(), time_zone: 'Asia/Seoul' },
    });
    return this;
  }

  setIcon(emoji) {
    this.page.setIcon(emoji);
    return this;
  }

  addParagraph(content) {
    this.page.addChildBlock({
      object: 'block',
      type: 'paragraph',
      paragraph: {
        rich_text: [{ text: { content } }],
      },
    });
    return this;
  }

  build() {
    return this.page;
  }
}

 

 

사용은 요런식으로..

pageArray.push(
  new PageBuilder(databaseId)
    .setTitle('데일리 스크럼')
    .setDate(date)
    .setIcon('💫')
    .addParagraph('notion API로 만들어진 페이지입니다.')
    .build()
);

 

 

3. Notion API 요청 보내기

내가 다른 방법을 발견하지 못한 걸 수도 있지만.. 안타깝게도 page.create()로는 한 요청에 한 페이지밖에 생성할 수 없었다.

 

그래서 PageBuilderPage 배열을 생성하고 map()을 사용하여 각 페이지를 생성하는 page.create() 요청의 Promise 배열을 만든 다음, Promise.all()로 병렬 처리하였다.

export const createNotionPage = async (holidays) => {
  const databaseId = process.env.NOTION_DATABASE_ID;
  const pageArray = createPageArray(holidays, databaseId);

  const notion = new Client({
    auth: process.env.NOTION_SECRET,
  });

  const promises = pageArray.map((page) => {
    notion.pages.create(page);
    console.log('page created at: ', page.properties.Date.date.start);
  });

  await Promise.all(promises);
};

 

 

배포

간단하게 배포할 수 있는 Railway를 사용하였다.

여러 배포 서비스 중 Railway를 선택한 이유는 무료 플랜이 있고 배포 과정이 간단해 보였기 때문이다. 무료 플랜은 램 500MB, 디스크 1GB, vCPU 2개가 제공된다.

 

github repository로도 할 수 있고, Docker image로도 할 수 있는데 간단한 프로그램이라 repository를 직접 연결해주었다.

 

create 누르고 원하는 github repo 누르면 그대로 package.json 읽어서 실행해준다.

scripts에  "start"가 없으면 실행이 안된다. 간단하게 node src/main.js를 추가해주었다.

 

중간에 railway github app 설치해도 되냐고도 물어보는데 허용해주면 된다.

로그보니까 Docker image 생성해서 하는 것 같던데 repository 연결보단 Docker image파일 이용하는 쪽이 더 빠를 듯 싶다.

 

 

 

연결 후 앱에 맞게 설정할 것 설정하면 된다.

variable과 cron schedule을 설정해주었다.

 

 

 

이런 변경사항이 있으면 꼭.. Apply change 해주어야 한다... variable 설정 했는데도 자꾸 undefined 문제가 발생해서 뭔가 했더니 휴먼에러였던...........

 

 

코드가 실행되면 원하는대로 생성된 페이지를 확인할 수 있다.

 

 

 

 

하루만에 끝낼 줄 알았는데 이틀 걸렸지만... 

뿌듯 ^_^ 

 

Github

https://github.com/jis-kim/make-some-scrum

 

GitHub - jis-kim/make-some-scrum: 스크럼 벗고 소리질러

스크럼 벗고 소리질러. Contribute to jis-kim/make-some-scrum development by creating an account on GitHub.

github.com

 

별 거 없지만 깃헙은 이쪽입니다,,