본문 바로가기
개발/React

리액트 - GitHub OAuth 로그인 정보를 활용하여 Commit Message 남기기

by 피로물든딸기 2023. 8. 19.
반응형

깃허브 데스크탑으로 프로젝트 관리하기 강의 오픈!! (인프런 바로가기)

 

리액트 전체 링크

 

참고

- 깃허브 OAuth Access 토큰 발급 받기

- Toast UI 에디터로 깃허브 마크다운 저장하기

 

GitHub OAuth Project Settings
Material UI로 깃허브 로그인 프로필 만들기
깃허브 OAuth 콜백 처리하기
인증 토큰 획득 서버 구현하기
인증 토큰으로 로그인 상태 관리하기
로그인 정보를 활용하여 Commit Message 남기기
새 창으로 로그인해서 현재 상태 유지하기

 

이제 인증 정보를 이용하여 README.md를 수정한 유저를 커밋 메시지에 추가해보자.


SimpleToastEditor 수정

 

SimpleToastEditor에서 GitHub RESTful POST API를 사용하는 fileWritecommitMsg 파라미터를 추가한다.

  const fileWrite = async (contents, commitMsg) => {
    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: commitMsg,
        ...

 

그리고 githublibrary.js에서는 현재 로그인 상태만 알 수 있는 메서드 getLoginStatus를 추가한다.

export const loginCheck = async (setLoginStatus) => {
  ...
};

export const getLoginStatus = async () => {
  let token = localStorage.getItem("GITHUB_TOKEN");
  try {
    const response = await axios.get("https://api.github.com/user", {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });
    console.log(response);
    return true;
  } catch (error) {
    console.error("Error fetching user data:", error);
    return false;
  }
};

 

이전에 저장 버튼을 누르면 fireWrite가 아래와 같이 호출되었다.

  const handleSave = () => {
    let markDownContent = editorRef.current.getInstance().getMarkdown();
    fileWrite(markDownContent);
  };

 

이제 로그인 된 경우만 write가 가능하도록 아래와 같이 코드를 수정하자. 

await을 사용하기 때문에 handleSaveasync가 되어야 한다.

import * as gh from "../githublibrary.js";

...

  const handleSave = async () => {
    let status = await gh.getLoginStatus();

    if(status) {
      let loginID = localStorage.getItem("LOGIN_ID");
      let markDownContent = editorRef.current.getInstance().getMarkdown();

      fileWrite(markDownContent, `submitted by ${loginID}`);
    } else {
      Swal.fire({
        icon: 'error',
        title: 'Oops...',
        text: '로그인 해주세요!!', 
      })
    }
  };

 

이제 로그인 된 경우만 편집이 가능하고, 누가 편집했는지 알 수 있게 되었다.


전체 코드는 다음과 같다.

 

githublibrary.js

import axios from "axios";

export const loginCheck = async (setLoginStatus) => {
  let token = localStorage.getItem("GITHUB_TOKEN");
  try {
    const response = await axios.get("https://api.github.com/user", {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });
    console.log(response);
    setLoginStatus(true);
  } catch (error) {
    console.error("Error fetching user data:", error);
    setLoginStatus(false);
  }
};

export const getLoginStatus = async () => {
  let token = localStorage.getItem("GITHUB_TOKEN");
  try {
    const response = await axios.get("https://api.github.com/user", {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });
    console.log(response);
    return true;
  } catch (error) {
    console.error("Error fetching user data:", error);
    return false;
  }
};

 

page/SimpleToastEditor.js

import React, { useEffect, useRef, useState } from "react";

import Box from "@mui/material/Box";
import Button from "@mui/material/Button";

import * as gh from "../githublibrary.js";
import Swal from "sweetalert2";

// GitHub RESTful API
import { Octokit } from "@octokit/rest";

// Toast UI Editor
import "@toast-ui/editor/dist/toastui-editor.css";
import "@toast-ui/editor/dist/toastui-editor-viewer.css"; // Viewer css
import { Editor } from "@toast-ui/react-editor";
import Viewer from "@toast-ui/editor/dist/toastui-editor-viewer";

// Dark Theme 적용
// import '@toast-ui/editor/dist/toastui-editor.css';
// import '@toast-ui/editor/dist/theme/toastui-editor-dark.css';

// Color Syntax Plugin
import "tui-color-picker/dist/tui-color-picker.css";
import "@toast-ui/editor-plugin-color-syntax/dist/toastui-editor-plugin-color-syntax.css";
import colorSyntax from "@toast-ui/editor-plugin-color-syntax";

// Table Merged Cell Plugin
import "@toast-ui/editor-plugin-table-merged-cell/dist/toastui-editor-plugin-table-merged-cell.css";
import tableMergedCell from "@toast-ui/editor-plugin-table-merged-cell";

const colorSyntaxOptions = {
  preset: [
    "#333333", "#666666", "#FFFFFF", "#EE2323", "#F89009", "#009A87", "#006DD7", "#8A3DB6",
    "#781B33", "#5733B1", "#953B34", "#FFC1C8", "#FFC9AF", "#9FEEC3", "#99CEFA", "#C1BEF9",
  ],
};

let myKey = process.env.REACT_APP_MY_TOKEN;

const SimpleToastEditor = () => {
  const editorRef = useRef(null);
  const [editMode, setEditMode] = useState(false);
  const repo = "auto-test";
  const path = "README.md";

  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 (contents, commitMsg) => {
    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: commitMsg,
        sha: currentSHA,
        committer: {
          name: "bloodstrawberry",
          email: "bloodstrawberry@github.com",
        },
        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}`,
        encoding: "utf-8",
        decoding: "utf-8",
      }
    );

    return result;
  };

  const handleSave = async () => {
    let status = await gh.getLoginStatus();

    if(status) {
      let loginID = localStorage.getItem("LOGIN_ID");
      let markDownContent = editorRef.current.getInstance().getMarkdown();

      fileWrite(markDownContent, `submitted by ${loginID}`);
    } else {
      Swal.fire({
        icon: 'error',
        title: 'Oops...',
        text: '로그인 해주세요!!', 
      })
    }
  };

  const initReadMe = async () => {
    // let item = localStorage.getItem(CONTENT_KEY);
    let result = await fileRead();
    let contents = decodeURIComponent(escape(window.atob(result.data.content)));
    //console.log(contents);

    if (editMode === false) {
      const viewer = new Viewer({
        el: document.querySelector(".toast-editor-viewer"),
        viewer: true,
        height: "400px",
        usageStatistics: false, // 통계 수집 거부
        plugins: [tableMergedCell],
      });

      viewer.setMarkdown(contents);
    }

    if (editorRef.current)
      editorRef.current.getInstance().setMarkdown(contents);
  };

  useEffect(() => {
    initReadMe();
  }, [editMode]);

  return (
    <div>
      <Box sx={{ m: 2 }}>
        <h1>Toast UI Editor</h1>
        <Button
          variant="outlined"
          color="secondary"
          sx={{ m: 1 }}
          onClick={() => setEditMode(!editMode)}
        >
          {editMode ? "취소하기" : "편집하기"}
        </Button>
        <Button
          variant="outlined"
          color="primary"
          sx={{ m: 1 }}
          onClick={handleSave}
          disabled={editMode === false}
        >
          저장하기
        </Button>

        {editMode === false && <div className="toast-editor-viewer"></div>}

        {editMode === true && (
          <Editor
            ref={editorRef}
            // initialValue={initContents}
            height="400px"
            placeholder="Please Enter Text."
            previewStyle="tab" // or vertical
            initialEditType="wysiwyg" // or markdown
            // hideModeSwitch={true} // 하단 숨기기
            toolbarItems={[
              // 툴바 옵션 설정
              ["heading", "bold", "italic", "strike"],
              ["hr", "quote"],
              ["ul", "ol", "task", "indent", "outdent"],
              ["table", /* "image", */ "link"],
              ["code", "codeblock"],
            ]}
            //theme="dark"
            //useCommandShortcut={false} // 키보드 입력 컨트롤 방지 ex ctrl z 등
            usageStatistics={false} // 통계 수집 거부
            plugins={[[colorSyntax, colorSyntaxOptions], tableMergedCell]}
          />
        )}
      </Box>
    </div>
  );
};

export default SimpleToastEditor;
반응형

댓글