본문 바로가기
개발/React

리액트 - Indexed DB로 깃허브 RESTful API PUT 최신 정보 유지하기

by 피로물든딸기 2023. 7. 22.
반응형

리액트 전체 링크

Git / GitHub 전체 링크

 

참고

- 깃허브 RESTful API 한글 깨짐 현상 해결하기

- 두 날짜 사이의 시간 차이 구하기

- Indexed DB로 로컬에 데이터 저장하기

 

깃허브 RESTful API 한글 깨짐 현상 해결하기를 참고해서 아래 코드를 실행해보자.

import React, { useState } from "react";

import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { TextField } from "@mui/material";
import { Textarea } from "@mui/joy";
import { Octokit } from "@octokit/rest";

let myKey = "...";

const App = () => {
  const [repo, setRepo] = useState("auto-test");
  const [path, setPath] = useState("README.md");
  const [contents, setContents] = useState("");

  const getSHA = async (octokit) => {
    const result = await octokit.request(
      `GET /repos/bloodstrawberry/${repo}/contents/${path}`,
      {
        owner: "bloodstrawberry",
        repo: `${repo}`,
        path: `${path}`,
      }
    );

    return result.data.sha;
  };

  const fileWrite = async () => {
    const octokit = new Octokit({
      auth: myKey,
    });

    const currentSHA = await getSHA(octokit);
    const result = await octokit.request(
      `PUT /repos/bloodstrawberry/${repo}/contents/${path}`,
      {
        owner: "bloodstrawberry",
        repo: `${repo}`,
        path: `${path}`,
        message: "commit message!",
        sha: currentSHA,
        committer: {
          name: "bloodstrawberry",
          email: "bloodstrawberry@github.com",
        },
        // content: `${btoa(`${contents}`)}`,
        content: `${btoa(unescape(encodeURIComponent(`${contents}`)))}`,
        headers: {
          "X-GitHub-Api-Version": "2022-11-28",
        },
      }
    );

    console.log(result.status);
  };

  const fileRead = async () => {
    const octokit = new Octokit({
      auth: myKey,
    });

    const result = await octokit.request(
      `GET /repos/bloodstrawberry/${repo}/contents/${path}`,
      {
        owner: "bloodstrawberry",
        repo: `${repo}`,
        path: `${path}`,
      }
    );

    // setContents(atob(result.data.content));
    setContents(decodeURIComponent(escape(window.atob(result.data.content))));
  };

  return (
    <div>
      <h1>깃허브 파일 편집기</h1>
      <Stack sx={{ m: 5 }} spacing={2} direction="row">
        <Button variant="outlined" color="primary" onClick={fileRead}>
          불러오기
        </Button>
        <Button variant="outlined" color="secondary" onClick={fileWrite}>
          저장하기
        </Button>
      </Stack>
      <Stack sx={{ m: 5 }} spacing={2} direction="row">
        <TextField
          id="outlined-required"
          label="repository"
          value={repo}
          onChange={(e) => setRepo(e.target.value)}
        />
        <TextField
          id="outlined-required"
          label="filePath"
          value={path}
          onChange={(e) => setPath(e.target.value)}
        />
      </Stack>
      <Stack sx={{ m: 5 }}>
        <Textarea
          name="Primary"
          placeholder="Type in here…"
          variant="outlined"
          color="primary"
          value={contents}
          onChange={(e) => setContents(e.target.value)}
        />
      </Stack>
    </div>
  );
};

export default App;

 

깃허브에는 내용이 즉시 바뀌지만, 다시 불러오기를 할 경우 이전 정보가 갱신되는 것을 알 수 있다.

 

이 현상은 깃허브 내부 정책에 의해 발생하기 때문에 클라이언트가 수정할 수 없다.

여기서는 IndexedDB를 이용해서 최신 정보를 저장해보자.


만들어둔 IndexedDB 라이브러리를 import 한다.

import * as idblib from "./indexeddblibrary.js";

 

fileWrite에서는 PUT가 성공한 경우, 최신 정보를 DB에 저장한다.

  const fileWrite = async () => {
    
    let date = new Date();
    
    ...
    
    if (result.status === 200) {
      let tempData = {
        id: "temp",
        contents,
        date,
      };

      idblib.addDataToIndexedDB(tempData);
    }
  };

 

fileRead의 경우 불러온 정보의 갱신 날짜(headers의 last-modified)와 비교하여, 

수정된 날짜가 DB의 날짜보다 작은 경우, DB의 정보를 보여주도록 한다.

초 단위까지 비교하면 항상 tempTime이 큰 경우가 발생할 수 있으므로, 100ms * 60s = 6000 만큼 나눠서 비교한다.

  const fileRead = async () => {
    ...

    let content = decodeURIComponent(escape(window.atob(result.data.content)));

    let modifiedDate = new Date(result.headers["last-modified"]);
    let mdTime = parseInt(modifiedDate.getTime() / 6000);

    let tempData = await idblib.getDataFromIndexedDB("temp");
    if (tempData === undefined) {
      setContents(content);
      return;
    }

    let tempTime = parseInt(tempData.date.getTime() / 6000);

    console.log({ mdTime, tempTime });
    if (mdTime < tempTime) {
      console.log(tempData);
      content = tempData.contents;
    }

    // setContents(atob(result.data.content));
    setContents(content);
  };

 

이제 최신 정보를 불러오지 못한다면 IndexedDB가 내용을 대체한다.

 

전체 코드는 다음과 같다.

import React, { useState } from "react";

import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { TextField } from "@mui/material";
import { Textarea } from "@mui/joy";
import { Octokit } from "@octokit/rest";

import * as idblib from "./indexeddblibrary.js";

let myKey = "...";

const App = () => {
  const [repo, setRepo] = useState("auto-test");
  const [path, setPath] = useState("README.md");
  const [contents, setContents] = useState("");

  const getSHA = async (octokit) => {
    const result = await octokit.request(
      `GET /repos/bloodstrawberry/${repo}/contents/${path}`,
      {
        owner: "bloodstrawberry",
        repo: `${repo}`,
        path: `${path}`,
      }
    );

    return result.data.sha;
  };

  const fileWrite = async () => {
    const octokit = new Octokit({
      auth: myKey,
    });

    const currentSHA = await getSHA(octokit);
    let date = new Date();
    const result = await octokit.request(
      `PUT /repos/bloodstrawberry/${repo}/contents/${path}`,
      {
        owner: "bloodstrawberry",
        repo: `${repo}`,
        path: `${path}`,
        message: "commit message!",
        sha: currentSHA,
        committer: {
          name: "bloodstrawberry",
          email: "bloodstrawberry@github.com",
        },
        // content: `${btoa(`${contents}`)}`,
        content: `${btoa(unescape(encodeURIComponent(`${contents}`)))}`,
        headers: {
          "X-GitHub-Api-Version": "2022-11-28",
        },
      }
    );

    console.log(result);

    if (result.status === 200) {
      let tempData = {
        id: "temp",
        contents,
        date,
      };

      idblib.addDataToIndexedDB(tempData);
    }
  };

  const fileRead = async () => {
    const octokit = new Octokit({
      auth: myKey,
    });

    const result = await octokit.request(
      `GET /repos/bloodstrawberry/${repo}/contents/${path}`,
      {
        owner: "bloodstrawberry",
        repo: `${repo}`,
        path: `${path}`,
      }
    );

    let content = decodeURIComponent(escape(window.atob(result.data.content)));

    let modifiedDate = new Date(result.headers["last-modified"]);
    let mdTime = parseInt(modifiedDate.getTime() / 6000);

    let tempData = await idblib.getDataFromIndexedDB("temp");
    if (tempData === undefined) {
      setContents(content);
      return;
    }

    let tempTime = parseInt(tempData.date.getTime() / 6000);

    console.log({ mdTime, tempTime });
    if (mdTime < tempTime) {
      console.log(tempData);
      content = tempData.contents;
    }

    // setContents(atob(result.data.content));
    setContents(content);
  };

  return (
    <div>
      <h1>깃허브 파일 편집기</h1>
      <Stack sx={{ m: 5 }} spacing={2} direction="row">
        <Button variant="outlined" color="primary" onClick={fileRead}>
          불러오기
        </Button>
        <Button variant="outlined" color="secondary" onClick={fileWrite}>
          저장하기
        </Button>
      </Stack>
      <Stack sx={{ m: 5 }} spacing={2} direction="row">
        <TextField
          id="outlined-required"
          label="repository"
          value={repo}
          onChange={(e) => setRepo(e.target.value)}
        />
        <TextField
          id="outlined-required"
          label="filePath"
          value={path}
          onChange={(e) => setPath(e.target.value)}
        />
      </Stack>
      <Stack sx={{ m: 5 }}>
        <Textarea
          name="Primary"
          placeholder="Type in here…"
          variant="outlined"
          color="primary"
          value={contents}
          onChange={(e) => setContents(e.target.value)}
        />
      </Stack>
    </div>
  );
};

export default App;
반응형

댓글