갖추어지면 우울하지 않고~Firebase로 Qiita 기사의 자동 백업 환경을 정비한다

Qiita에 의해 지난 3년 이상 전부터 투고를 계속 누계 기사수도 100을 넘었습니다.

그런 가운데, 최근 문득 「Qiita의 서비스 정지 또는 데이터 소실로 나의 투고 기사 모두 사라지는 것은?」라고 불안해졌습니다.
이렇게. Qiita에 대한 게시물 기사는 엄격하게 자신의 것이 아니라 당연하지만 Qiita 플랫폼에 의존합니다.

그래서 좋은가? 아니, 좋지 않다.
그래서 Firebase cloud functions와 Firestore를 사용하여 자신의 Qiita 기사를 정기적으로 백업하는 환경을 만들었습니다 💪
그 소개입니다.

※ 주 Qiita 기사가 갑자기 사라지는 것은 없다는 것은 중대 인식입니다.

TL;DR



이하 Github 리포지토리의 README대로 Firebase를 세우면 소멸에 대비할 수 있습니다.

절차



다음과 같은 흐름으로 백업을 수행합니다.

  • Qiita API v2에서 내 모든 게시물 가져 오기
  • Firestore에 기사 데이터 저장
  • 1,2 Firebase cloud functions를 사용하여 정기 실행

  • 궁극적으로 Firestore에 백업되었을 때의 데이터 구조는 다음과 같습니다.
    qiita-backups
      └── 2020-01-07 00:00 # (バックアップ日時のドキュメント)
           └── items # (記事ごとのサブコレクション)
                 └── fdsadfsafeweafe # (記事データのドキュメント)
    



    이번은 Qiita에서 기사가 소멸한다는 있을 수 없는 시나리오에 대비한 것입니다만, 「Firebase Cloud Functions로 정기적으로 API를 두드려 정보 취득, 데이터를 Firestore에 보존한다」라고 하는 일련의 흐름은 그 밖에도 범용적 사용할 수 있다고 생각합니다.

    Qiita 기사 목록 얻기



    우선, 자신의 투고 기사 일람의 취득입니다.functions.config().qiita.key에서 Qiita 액세스 키를 설정 한 다음 axios에서 Qiita API v2의 각 API를 두드리고 있습니다.
    Firebase cloud functions에서 외부 API를 두드리기 위해 Firebase 프로젝트의 계획은 Blaze 계획(종료 요금)이어야 합니다.

    또, 기사 취득 API에는 페이징의 개념이 있으므로, 자신의 유저 정보로부터 투고 기사수를 취득해,
    페이지장에 루프로 기사 취득 API를 두드려, 자신의 투고 기사 전건을 취득하고 있습니다.

    functions/src/lib/client.ts
    import axios, { AxiosResponse } from "axios";
    import * as functions from "firebase-functions";
    import { Item, User } from "../@types/qiita-type";
    
    axios.defaults.baseURL = "https://qiita.com/api/v2";
    axios.defaults.headers.common["Authorization"] = `Bearer ${
      functions.config().qiita.key
    }`
    
    const MAX_PER_PAGE = 100;
    
    export const fetchItems = async (
      page: number,
      perPage: number
    ): Promise<AxiosResponse<Item[]>> => {
      return await axios.get<Item[]>(
        `/authenticated_user/items?page=${page}&per_page=${perPage}`
      );
    };
    
    export const fetchCurrentUser = async (): Promise<AxiosResponse<User>> => {
      return await axios.get<User>("/authenticated_user");
    };
    
    export const fetchCurrentUserAllItems = async (): Promise<Item[]> => {
      // Get Qiita items count from current user data
      const currentUserData = await fetchCurrentUser();
      const itemsCount = currentUserData.data.items_count;
    
      // Fetch all items;
      let items: Item[] = [];
      await Promise.all(
        [...Array(Math.ceil(itemsCount / MAX_PER_PAGE)).keys()].map(async i => {
          const res = await fetchItems(i + 1, MAX_PER_PAGE);
          items = [...items, ...res.data];
        })
      );
      return items;
    };
    

    Firestore에 저장



    계속해서 취득한 기사 데이터를 Firestore에 저장하는 부분입니다.
    여기는 `qiita-backups'라는 컬렉션의 백업 날짜와 시간마다 문서의 하위 컬렉션에 게시물을 한 건씩 저장하고 있습니다.

    functions/src/lib/firestore.ts
    import { Item } from "../@types/qiita-type";
    import * as admin from "firebase-admin";
    import * as dayjs from "dayjs";
    
    admin.initializeApp();
    
    export const addItemsToFirestore = async (items: Item[]): Promise<void> => {
      // Backup Qiita items to firestore;
      const itemsRef = admin
        .firestore()
        .collection("qiita-backups")
        .doc(dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss"))
        .collection("items");
    
      await Promise.all(
        items.map(async item => {
          await itemsRef.add(item);
        })
      );
    };
    

    Firebase functions 및 cloud schedule로 정기 실행



    마지막으로 지정 일시에 정기 실행의 부분입니다.functions.pubsub.schedule는 Cloud Schedule을 사용하여 지정된 주기로 실행 함수를 정의합니다.BACKUP_SCHEDULE = "0 9 * * 1"와 같이 매주 월요일 9시에이 함수가 실행됩니다.
    Firebase 프로젝트에서 Cloud Schedule을 사용하려면 위치를 us-central1로 설정해야 합니다.
    (GAE를 내부적으로 사용하기 때문에 같다.자세한 것은 문서에)
    (그런 일도 없는 것 같습니다)

    functions/src/index.ts
    import * as functions from "firebase-functions";
    import { fetchCurrentUserAllItems } from "./lib/client";
    import { addItemsToFirestore } from "./lib/firestore";
    
    const BACKUP_SCHEDULE = "0 9 * * 1";
    
    export const scheduledBackupQiitaItems = functions.pubsub
      .schedule(BACKUP_SCHEDULE)
      .onRun(async _context => {
        try {
          const items = await fetchCurrentUserAllItems();
          await addItemsToFirestore(items);
          console.log(`Successfully saved ${items.length} items.`);
        } catch (e) {
          console.log(`A error has occurred.${e}`);
        }
      });
    

    끝에



    이상, 「Firebase로 Qiita 기사의 자동 백업 환경을 정비한다」였습니다.
    Firebase 리소스를 사용하면 이러한 것도 저비용으로 구현할 수 있기 때문에 좋네요.
    이 기사가 누군가의 참고가 되면 다행입니다.

    ※ 엄밀하게 백업을 한다면 기사 중에 포함된 이미지를 Firebase Storage에 저장하는 것이 좋을지도 모른다.

    좋은 웹페이지 즐겨찾기