Pub/Sub 클라우드 기능을 사용하는 Hashnode 및 Dev.to에 대한 개념

CodingCat.dev의 목표는 가능한 한 많은 학생에게 다가가는 것입니다. 이를 위해 Dev.to 및 Hashnode를 사용하여 기사를 교차 게시하고 표준 URL을 활용하여 해당 기사를 원본으로 되돌립니다. 우리에게는 더 많은 시선이 콘텐츠와 비디오에 도달할 수 있습니다.

노션 데이터 검색



개념적으로 처리가 필요한 데이터를 계속 찾기 위해 가장 간단한 방법은 새 블로그가 보관될 URL의 레코드를 유지하는 것입니다. 예를 들어 추가해야 할 레코드를 찾을 때 Dev.to 추가합니다. devto라는 열입니다. 그런 다음 Notion의 API 및 필터링된 필드를 사용하여 해당 필드가 비어 있는 기준에 맞는 레코드를 데이터베이스에서 검색할 수 있습니다.

아래 예에서는 database_id의 데이터베이스에서 CodingCat.dev의 데이터베이스를 검색합니다. 우리는 또한 이 필드에 1을 전달하여 게시물이 순서대로 처리되고 순서를 벗어나지 않도록 보장할 수 있도록 이러한 항목이 순서대로 나가기를 원하기 때문에 start_cursor를 추가합니다. 또한 slug , Released , Episode 가 지정되고 start 날짜가 오늘 또는 그 이후인 팟캐스트도 찾습니다.

export const queryPurrfectStreamDevTo = async (
  page_size?: number,
  start_cursor?: string | null
) => {
  const raw = await notionClient.databases.query({
    database_id: notionConfig.purrfectStreamsDb,
    start_cursor: start_cursor ? start_cursor : undefined,
    page_size,
    filter: {
      and: [
        {
          property: 'slug',
          url: {
            is_not_empty: true,
          },
        },
        {
          property: 'Status',
          select: {
            equals: 'Released',
          },
        },
        {
          property: 'Episode',
          number: {
            is_not_empty: true,
          },
        },
        {
          property: 'start',
          date: {
            on_or_before: new Date().toISOString(),
          },
        },
        {
          property: 'devto',
          url: {
            is_empty: true,
          },
        },
        {
          property: 'youtube',
          url: {
            is_not_empty: true,
          },
        },
        {
          property: 'spotify',
          url: {
            is_not_empty: true,
          },
        },
      ],
    },
    sorts: [
      {
        property: 'Season',
        direction: 'ascending',
      },
      {
        property: 'Episode',
        direction: 'ascending',
      },
    ],
  });
  return await formatPosts(raw, 'podcast');
};


예약된 클라우드 기능



이제 지정된 간격으로 실행할 수 있는 Google Cloud - Cloud 스케줄러 작업을 생성하는 Firebase 함수를 생성할 수 있습니다. 이것은 pubsub 기능을 사용하는 convenience method입니다. 아래에서 이 기능을 실행하고every 5 minutes 기준과 일치하는 새 기사가 있는지 확인합니다. 이를 1시간마다 되돌릴 수도 있지만 올바른 기준이 있는 경우 비교적 빠르게 이 트리거를 볼 수 있어 좋습니다.

const topicId = 'devtoCreateFromNotion';

export const scheduledNotionToDevto = functions.pubsub
  .schedule('every 5 minutes')
  .onRun(async () => {
    // Check to see if ther are scheduled pods
    console.log('Checking for scheduled pods');
    const scheduledRes = await queryPurrfectStreamDevTo(1);
    console.log('Scheduled Result:', JSON.stringify(scheduledRes));

    if (scheduledRes?.results) {
      const needCloudinaryPods = scheduledRes?.results;
      console.log('Pods to add to pub/sub', JSON.stringify(needCloudinaryPods));

      for (const pod of needCloudinaryPods) {
        await sendTopic(topicId, pod);
      }
    }

    console.log('Checking for devto missing');
    const posts = await queryByDevto('post', 1);
    console.log('Posts:', JSON.stringify(posts));

    if (posts?.results) {
      const needposts = posts?.results;
      console.log('Posts to add to pub/sub', JSON.stringify(needposts));

      for (const p of needposts) {
        await sendTopic(topicId, p);
      }
    }

    return true;
  });


이제 queryPurrfectStreamDevTo 함수에서 실제로 발견된 것이 있으면 해당 항목에서 데이터를 가져와 다른 pub/sub 함수로 전달할 수 있습니다. CodingCat.dev의 경우 생성된 순서대로 항목을 게시할 수 있도록 항목 1개만 찾습니다. 이러한 API 중 일부에서 실제 플랫폼에서 원래 게시 날짜를 설정하는 쉬운 방법이 없다는 것을 알았기 때문입니다.

게시물을 게시하는 Pub/Sub 기능



이 pub/sub 기능은 devtoCreateFromNotion의 주제를 보낸 다음 적절한 게시물 정보를 찾는 모든 항목을 찾습니다. 이는 플랫폼에 게시하려는 여러 항목이 있는 경우 대규모 확장이 발생하는 가장 쉬운 방법입니다.

이 예에서는 게시물 유형에 따라 body_markdown를 변경합니다. 여기서 중요한 점은 검색 엔진 봇이 이것이 원본 콘텐츠라고 생각하지 않고 필요한 경우 301을 추가할 위치를 알 수 있도록 canonical_url를 전송한다는 것입니다.

export const devtoToNotionPubSub = functions.pubsub
  .topic(topicId)
  .onPublish(async (message, context) => {
    console.log('The function was triggered at ', context.timestamp);
    console.log('The unique ID for the event is', context.eventId);
    const page = JSON.parse(JSON.stringify(message.json));
    console.log('page', page);

    let data;
    if (page._type === 'podcast') {
      data = {
        article: {
          title: page.title,
          published: true,
          tags: ['podcast', 'webdev', 'javascript', 'beginners'],
          series: `codingcatdev_podcast_${page.properties.Season.number}`,
          main_image: `https://media.codingcat.dev/image/upload/b_rgb:5e1186,c_pad,w_1000,h_420/${page?.coverPhoto?.public_id}`,
          canonical_url: `https://codingcat.dev/${page._type}/${page.slug}`,
          description: page.excerpt,
          organization_id: '1009',
          body_markdown: `Original: https://codingcat.dev/${page._type}/${
            page.slug
          }
          {% youtube ${page.properties.youtube.url} %}
          {% spotify spotify:episode:${page.properties.spotify.url
            .split('/')
            .at(-1)
            .split('?')
            .at(0)} %}

          `,
        },
      };
    } else {
      console.log(
        `Getting ${page._type}: ${page.id} markdown, with slug ${page?.properties?.slug?.url}`
      );
      const post = await getNotionPageMarkdown({
        _type: page._type,
        slug: page?.properties?.slug?.url,
        preview: false,
      });

      console.log('Block Result', post);

      if (post && post?.content) {
        data = {
          article: {
            title: page.title,
            published: true,
            tags: ['podcast', 'webdev', 'javascript', 'beginners'],
            main_image: `https://media.codingcat.dev/image/upload/b_rgb:5e1186,c_pad,w_1000,h_420/${page?.coverPhoto?.public_id}`,
            canonical_url: `https://codingcat.dev/${page._type}/${page.slug}`,
            description: page.excerpt,
            organization_id: '1009',
            body_markdown: post.content,
          },
        };
      }
    }

    if (data) {
      try {
        console.log('addArticle to devto');
        const response = await addArticle(data);
        console.log('addArticle result:', response);

        const devto = response?.data?.url;

        if (!devto) {
          console.log('devto url missing');
          return;
        }

        const update = {
          page_id: page.id,
          properties: {
            devto: {
              id: 'remote',
              type: 'url',
              url: devto,
            },
          },
        };
        console.log('Updating page with: ', JSON.stringify(update));
        const purrfectPagePatchRes = await patchPurrfectPage(update);
        console.log(
          'Page update result:',
          JSON.stringify(purrfectPagePatchRes)
        );

        return purrfectPagePatchRes;
      } catch (error) {
        console.error(error);
      }
    } else {
      console.error('No Data matched for article');
    }
    return;
  });


보너스: 로컬 테스트



이러한 기능을 자체적으로 실행하기 전에 로컬에서 테스트하는 것이 좋습니다. 이를 위해 Firebase 에뮬레이션 제품군을 사용할 수 있습니다. 그런 다음 일정 및 http 버전 모두에 대한 코드를 업데이트하여 아래와 같은 동일한 함수를 호출합니다scheduleCheck().

const scheduleCheck = async () => {
  // Check to see if ther are scheduled pods
  console.log('Checking for scheduled pods');
  const scheduledRes = await queryPurrfectStreamDevTo(1);
  console.log('Scheduled Result:', JSON.stringify(scheduledRes));

  if (scheduledRes?.results) {
    const needCloudinaryPods = scheduledRes?.results;
    console.log('Pods to add to pub/sub', JSON.stringify(needCloudinaryPods));

    for (const pod of needCloudinaryPods) {
      await sendTopic(topicId, pod);
    }
  }

  for (const _type of ['post', 'tutorial']) {
    console.log('Checking for devto missing');
    const posts = await queryByDevto(_type, 1);
    console.log('Posts:', JSON.stringify(posts));

    if (posts?.results) {
      const needposts = posts?.results;
      console.log('Posts to add to pub/sub', JSON.stringify(needposts));

      for (const p of needposts) {
        await sendTopic(topicId, p);
      }
    }
  }
};

export const httpNotionToDevto = functions.https.onRequest(async (req, res) => {
  await scheduleCheck();

  res.send({ msg: 'started' });
});

export const scheduledNotionToDevto = functions.pubsub
  .schedule('every 5 minutes')
  .onRun(async () => {
    await scheduleCheck();
    return true;
  });


전체 소스



전체Dev.to 예제는 다음에서 찾을 수 있습니다.

https://github.com/CodingCatDev/codingcat.dev/blob/dev/backend/firebase/functions/src/devto/scheduledNotionToDevto.ts

전체 해시노드 예시는 다음에서 찾을 수 있습니다.

https://github.com/CodingCatDev/codingcat.dev/blob/dev/backend/firebase/functions/src/hashnode/scheduledNotionToHashNode.ts

이것은 TypeScript이며 모든 함수는 배포할 파일index.ts에 노출되어야 합니다.

https://github.com/CodingCatDev/codingcat.dev/blob/dev/backend/firebase/functions/src/index.ts

좋은 웹페이지 즐겨찾기