From 938020eea834a5a767541e76c562102d67777917 Mon Sep 17 00:00:00 2001 From: Yunu Date: Tue, 4 Apr 2023 02:32:32 +0900 Subject: [PATCH] =?UTF-8?q?[#2]=20feat:=20=EB=A0=88=ED=8F=AC=20=EB=94=94?= =?UTF-8?q?=EB=A0=89=ED=86=A0=EB=A6=AC=20=EA=B5=AC=EC=A1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/app.ts | 2 +- server/src/constant/error/octokit.error.ts | 62 ++++ server/src/constant/error/octokit.message.ts | 13 + server/src/constants/errors/octokit.error.ts | 41 --- server/src/controller/github.controller.ts | 222 ++++++++++++ server/src/controllers/github.controller.ts | 151 --------- server/src/octokit/branch.octokit.ts | 68 ++++ server/src/octokit/commit.octokit.ts | 85 +++-- server/src/octokit/content.octokit.ts | 23 +- server/src/octokit/repo.octokit.ts | 139 ++++---- server/src/route/github.route.ts | 66 ++++ server/src/{routes => route}/index.ts | 0 server/src/routes/github.route.ts | 45 --- server/src/service/github.service.ts | 335 ++++++++++++++++++- server/src/service/user.service.ts | 6 + 15 files changed, 916 insertions(+), 342 deletions(-) create mode 100644 server/src/constant/error/octokit.error.ts create mode 100644 server/src/constant/error/octokit.message.ts delete mode 100644 server/src/constants/errors/octokit.error.ts create mode 100644 server/src/controller/github.controller.ts delete mode 100644 server/src/controllers/github.controller.ts create mode 100644 server/src/octokit/branch.octokit.ts create mode 100644 server/src/route/github.route.ts rename server/src/{routes => route}/index.ts (100%) delete mode 100644 server/src/routes/github.route.ts create mode 100644 server/src/service/user.service.ts diff --git a/server/src/app.ts b/server/src/app.ts index 7428918..5589b68 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -1,6 +1,6 @@ import express, { Request, Response } from 'express'; import logger from 'morgan'; -import githubRouter from './routes/github.route'; +import githubRouter from './route/github.route'; import dotonv from 'dotenv'; dotonv.config(); diff --git a/server/src/constant/error/octokit.error.ts b/server/src/constant/error/octokit.error.ts new file mode 100644 index 0000000..ee50ada --- /dev/null +++ b/server/src/constant/error/octokit.error.ts @@ -0,0 +1,62 @@ +/** + * 찾는 파일이 없는 경우 + */ +export class NotFoundError extends Error { + constructor() { + super('Not Found'); + } +} + +/** + * 같은 이름의 레포지토리가 있는 경우 + */ +export class RepositoryCreationFailedError extends Error { + constructor() { + super('RepositoryCreationFailedError'); + } +} + +/** + * 깃허브 토큰이 입력이 안된 경우 + */ +export class RequiresAuthenticationError extends Error { + constructor() { + super('RequiresAuthenticationError'); + } +} + +/** + * 깃허브 토큰이 인증이 안될 경우 + */ +export class BadCredentialsError extends Error { + constructor() { + super('BadCredentialsError'); + } +} + +/** + * 중복된 브랜치를 만들 경우 + */ +export class ReferenceAlreadyExistsError extends Error { + constructor() { + super('ReferenceAlreadyExistsError'); + } +} + +/** + * 브랜치가 없는 경우 + */ +export class ReferenceDoesNotExistError extends Error { + constructor() { + super('ReferenceDoesNotExistError'); + } +} + +/** + * 레포지토리가 비어있는 경우 (새로 생성된 레포지토리 등) + */ +export class RepositoryIsEmptyError extends Error { + constructor() { + super('RepositoryIsEmptyError'); + } +} diff --git a/server/src/constant/error/octokit.message.ts b/server/src/constant/error/octokit.message.ts new file mode 100644 index 0000000..65580e9 --- /dev/null +++ b/server/src/constant/error/octokit.message.ts @@ -0,0 +1,13 @@ +export const NOT_FOUND = 'Not Found'; + +export const REPOSITORY_CREATION_FAILED = 'Repository creation failed'; + +export const REQUIRES_AUTHENTICATION = 'Requires authentication'; + +export const BAD_CREDENTIALS = 'Bad credentials'; + +export const REFERENCE_ALREADY_EXISTS = 'Reference already exists'; + +export const REFERENCE_DOES_NOT_EXIST = 'Reference does not exist'; + +export const REPOSITORY_IS_EMPTY = 'This repository is empty.'; diff --git a/server/src/constants/errors/octokit.error.ts b/server/src/constants/errors/octokit.error.ts deleted file mode 100644 index b4ddad4..0000000 --- a/server/src/constants/errors/octokit.error.ts +++ /dev/null @@ -1,41 +0,0 @@ -export class NotFoundError extends Error { - constructor() { - super('Not Found'); - this.name = 'Not Found'; - } -} - -export class CreateFileContentsError extends Error { - constructor() { - super('CreateFileContentsError'); - this.name = 'CreateFileContentsError'; - } -} - -export class UpdateFileContentsError extends Error { - constructor() { - super('UpdateFileContentsError'); - this.name = 'UpdateFileContentsError'; - } -} - -export class RepositoryCreationFailedError extends Error { - constructor() { - super('RepositoryCreationFailedError'); - this.name = 'Repository creation failed'; - } -} - -export class RequiresAuthenticationError extends Error { - constructor() { - super('RequiresAuthenticationError'); - this.name = 'Requires authentication'; - } -} - -export class BadCredentialsError extends Error { - constructor() { - super('BadCredentialsError'); - this.name = 'Bad credentials'; - } -} diff --git a/server/src/controller/github.controller.ts b/server/src/controller/github.controller.ts new file mode 100644 index 0000000..249e4c2 --- /dev/null +++ b/server/src/controller/github.controller.ts @@ -0,0 +1,222 @@ +import { Request, Response } from 'express'; +import { Octokit } from '@octokit/rest'; +import { findById } from '../service/user.service'; +import { + addBranch, + addContent, + addRepo, + deleteBranch, + deleteRepo, + findAllRepo, + findContent, + findRepo, + modifyContent, +} from '../service/github.service'; + +/** + * completed + */ +export const githubRepoList = async (req: Request, res: Response) => { + const { token } = findById(1); + + if (!token) return res.status(404).send([]); + + try { + const repos = await findAllRepo(token); + return res.send(repos); + } catch (error) { + return res.status(500).send([]); + } +}; + +export const githubRepo = async (req: Request, res: Response) => { + const { repo: repoName } = req.params; + const { username, token } = findById(1); + + if (!username || !token) return res.status(404).send([]); + + try { + const repoStructure = await findRepo({ + token, + owner: username, + repoName, + }); + return res.send(repoStructure); + } catch (error) { + return res.status(500).send([]); + } +}; + +/** + * completed + */ +export const githubRepoContent = async (req: Request, res: Response) => { + const { repo: repoName } = req.params; + const path = req.params[0]; + const { ref } = req.query as { ref: string }; + + const { username, token } = findById(1); + + if (!username || !token) return res.status(404).send([]); + + try { + /* path에 따라 객체 또는 객체 배열을 반환, content가 없는 경우 undefind */ + const content = await findContent({ + token, + owner: username, + repoName, + path, + ref, + }); + + return res.send(content); + } catch (error) { + return res.status(500).send('fail'); /* todo: error 응답 형태 */ + } +}; + +/** + * completed + */ +export const githubRepoCreate = async (req: Request, res: Response) => { + const { repo: repoName } = req.params; + const { isPrivate } = req.body as { isPrivate: boolean }; + const { token } = findById(1); + + if (!token) return res.status(404).send([]); + + try { + await addRepo({ token, repoName, isPrivate }); + return res.send('success'); + } catch (error) { + return res.status(500).send('fail'); + } +}; + +/** + * completed + */ +export const githubRepoDelete = async (req: Request, res: Response) => { + const { repo: repoName } = req.params; + const { token } = findById(1); + + if (!token) return res.status(404).send([]); + + try { + await deleteRepo({ token, repoName }); + return res.send('success'); + } catch (error) { + return res.status(500).send('fail'); + } +}; + +export const githubFileContentCommit = async (req: Request, res: Response) => { + const { repo: repoName } = req.params; + const path = req.params[0]; + const { ref: branchName } = req.query as { ref: string }; + const { message, content } = req.body as { message: string; content: string }; + const { username, token } = findById(1); + + if (!username || !token) return res.status(404).send([]); + + try { + const oldContent = await findContent({ + token, + owner: username, + repoName, + path, + ref: branchName, + }); + + if (Array.isArray(oldContent)) return res.status(404).send([]); + + /* 기존 content가 없는 경우 -> 새로운 파일 생성 */ + if (!oldContent) { + await addContent({ + token, + owner: username, + repoName, + path, + content, + branchName, + message, + }); + } else { + /* 기존 content가 있는 경우 -> 기존 파일 변경 */ + await modifyContent({ + token, + owner: username, + repoName, + path, + content, + blobSha: oldContent.sha, + }); + } + + return res.send('success'); + } catch (error) { + return res.status(500).send('fail'); + } +}; + +export const githubCommitList = async (req: Request, res: Response) => { + const { repo: repoName } = req.params; + const octokit = new Octokit(); + + try { + const response = await octokit.repos.listCommits({ + owner: process.env.MY_GITHUB_USERNAME!, + repo: repoName, + }); + const commits = response.data.map(commit => ({ + sha: commit.sha, + message: commit.commit.message, + author: commit.commit.author?.name, + date: commit.commit.author?.date, + url: commit.html_url, + })); + return res.send(commits); + } catch (error) { + return res.status(500).send('fail'); + } +}; + +export const githubBranchCreate = async (req: Request, res: Response) => { + const { repo: repoName, branch: branchName } = req.params; + const { ref: commitSha } = req.query as { ref: string }; + const { username, token } = findById(1); + + if (!username || !token) return res.status(404).send([]); + + try { + await addBranch({ + token, + owner: username, + repoName, + branchName, + commitSha, + }); + return res.send('success'); + } catch (error) { + return res.status(500).send('fail'); + } +}; + +export const githubBranchDelete = async (req: Request, res: Response) => { + const { repo: repoName, branch: branchName } = req.params; + const { username, token } = findById(1); + + if (!username || !token) return res.status(404).send([]); + + try { + await deleteBranch({ + token, + owner: username, + repoName, + branchName, + }); + return res.send('success'); + } catch (error) { + return res.status(500).send('fail'); + } +}; diff --git a/server/src/controllers/github.controller.ts b/server/src/controllers/github.controller.ts deleted file mode 100644 index 6387ee3..0000000 --- a/server/src/controllers/github.controller.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { Request, Response } from 'express'; -import { Octokit } from '@octokit/rest'; -import { selectContent } from '../octokit/content.octokit'; -import { - insertFileContents, - updateFileContents, -} from '../octokit/commit.octokit'; -import { - insertRepo, - removeRepo, - selectRepoList, -} from '../octokit/repo.octokit'; - -export const githubRepoList = async (req: Request, res: Response) => { - const octokit = new Octokit({ - auth: process.env.MY_GITHUB_PERSONAL_ACCESS_TOKEN, - }); - try { - const repositores = await selectRepoList({ octokit }); - return res.send(repositores); - } catch (error) { - return res.status(500).send(error); - } -}; - -export const githubRepoContent = async (req: Request, res: Response) => { - const repo = req.params.repo; - const path = req.params[0]; - - const octokit = new Octokit({ - auth: process.env.MY_GITHUB_PERSONAL_ACCESS_TOKEN, - }); - try { - const content = await selectContent({ - octokit, - owner: process.env.MY_GITHUB_USERNAME!, - repo, - path, - }); - - if (!Array.isArray(content)) { - return res.send(content); - } - - const contents = content.map(file => file.name); - return res.send(contents); - } catch (error) { - return res.status(500).send(error); - } -}; - -export const githubRepoCreate = async (req: Request, res: Response) => { - const { repoName, isPrivate } = req.body as { - repoName: string; - isPrivate: boolean; - }; - - const octokit = new Octokit({ - auth: process.env.MY_GITHUB_PERSONAL_ACCESS_TOKEN, - }); - try { - await insertRepo({ octokit, name: repoName, isPrivate }); - return res.send('success'); - } catch (error) { - return res.status(500).send('fail'); - } -}; - -export const githubRepoDelete = async (req: Request, res: Response) => { - const { repoName } = req.body as { repoName: string }; - - const octokit = new Octokit({ - auth: process.env.MY_GITHUB_PERSONAL_ACCESS_TOKEN, - }); - - try { - await removeRepo({ octokit, name: repoName }); - return res.send('success'); - } catch (error) { - return res.status(500).send('fail'); - } -}; - -export const githubRepoCommit = async (req: Request, res: Response) => { - const repo = req.params.repo; - const path = req.params[0]; - const { content } = req.body as { content: string }; - const octokit = new Octokit({ - auth: process.env.MY_GITHUB_PERSONAL_ACCESS_TOKEN, - }); - - try { - const oldContent = await selectContent({ - octokit, - owner: process.env.MY_GITHUB_USERNAME!, - repo, - path, - }); - if (Array.isArray(oldContent)) { - console.log(oldContent.map(content => content.name)); - return res.status(404).send('fail'); - } - // 새로운 파일일 경우 생성 - if (!oldContent) { - await insertFileContents({ - octokit, - owner: process.env.MY_GITHUB_USERNAME!, - repo, - path, - content, - }); - } - // 기존 파일이 있다면 업데이트 - else { - await updateFileContents({ - octokit, - owner: process.env.MY_GITHUB_USERNAME!, - repo, - path, - content, - sha: oldContent.sha, - }); - } - return res.send('success'); - } catch (error) { - return res.status(500).send(error); - } -}; - -export const githubRepoCommitList = async (req: Request, res: Response) => { - console.log('getCommits'); - const repo = req.params.repo; - const octokit = new Octokit(); - console.log(repo); - try { - const response = await octokit.repos.listCommits({ - owner: process.env.MY_GITHUB_USERNAME!, - repo, - }); - const commits = response.data.map(commit => ({ - sha: commit.sha, - message: commit.commit.message, - author: commit.commit.author?.name, - date: commit.commit.author?.date, - url: commit.html_url, - })); - return res.send(commits); - } catch (error) { - return res.status(500).send(error); - } -}; diff --git a/server/src/octokit/branch.octokit.ts b/server/src/octokit/branch.octokit.ts new file mode 100644 index 0000000..6199fd4 --- /dev/null +++ b/server/src/octokit/branch.octokit.ts @@ -0,0 +1,68 @@ +import { RequestError } from '@octokit/request-error'; +import { Octokit } from '@octokit/rest'; + +export const insertBranch = async ({ + octokit, + owner, + repoName, + branchName, + commitSha, +}: { + octokit: Octokit; + owner: string; + repoName: string; + branchName: string; + commitSha: string; +}) => { + await octokit.git.createRef({ + owner, + repo: repoName, + ref: `refs/heads/${branchName}`, + sha: commitSha, + }); +}; + +export const removeBranch = async ({ + octokit, + owner, + repoName, + branchName, +}: { + octokit: Octokit; + owner: string; + repoName: string; + branchName: string; +}) => { + console.log(owner, repoName, branchName); + await octokit.git.deleteRef({ + owner, + repo: repoName, + ref: `heads/${branchName}`, + }); +}; + +// export const selectLastCommitSha = async ({ +// octokit, +// owner, +// repoName, +// branchName, +// }: { +// octokit: Octokit; +// owner: string; +// repoName: string; +// branchName: string; +// }) => { +// try { +// const branch = await octokit.git.getRef({ +// owner, +// repo: repoName, +// ref: `refs/heads/${branchName}`, +// }); + +// return branch.data.object.sha; +// } catch (error) { +// if (error instanceof RequestError) { +// } +// throw new Error(); +// } +// }; diff --git a/server/src/octokit/commit.octokit.ts b/server/src/octokit/commit.octokit.ts index c561690..94f1b61 100644 --- a/server/src/octokit/commit.octokit.ts +++ b/server/src/octokit/commit.octokit.ts @@ -1,79 +1,106 @@ +import { RequestError } from '@octokit/request-error'; import { Octokit } from '@octokit/rest'; +import { + BadCredentialsError, + NotFoundError, + RequiresAuthenticationError, +} from '../constant/error/octokit.error'; +import { + BAD_CREDENTIALS, + REQUIRES_AUTHENTICATION, + NOT_FOUND, +} from '../constant/error/octokit.message'; export const insertFileContents = async ({ octokit, owner, - repo, + repoName, path, - message = new Date().toISOString(), content, + message = new Date().toLocaleString('ko-KR', { timeZone: 'UTC' }), + branchName, }: { octokit: Octokit; owner: string; - repo: string; + repoName: string; path: string; - message?: string; content: string; + message?: string; + branchName?: string; }) => { - try { - await octokit.repos.createOrUpdateFileContents({ - owner, - repo, - path, - message, - content: Buffer.from(content).toString('base64'), - }); - return true; - } catch (error) { - return false; - } + await octokit.repos.createOrUpdateFileContents({ + owner, + repo: repoName, + path, + message, + content: Buffer.from(content).toString('base64'), + branch: branchName, + }); }; export const updateFileContents = async ({ octokit, owner, - repo, + repoName, path, - message = new Date().toISOString(), content, sha, + message = new Date().toLocaleString('ko-KR', { timeZone: 'UTC' }), }: { octokit: Octokit; owner: string; - repo: string; + repoName: string; path: string; - message?: string; content: string; sha: string; + message?: string; +}) => { + await octokit.repos.createOrUpdateFileContents({ + owner, + repo: repoName, + path, + message, + content: Buffer.from(content).toString('base64'), + sha, + }); +}; + +export const selectListCommits = async ({ + octokit, + owner, + repo, +}: { + octokit: Octokit; + owner: string; + repo: string; }) => { try { - await octokit.repos.createOrUpdateFileContents({ + const { data } = await octokit.repos.listCommits({ owner, repo, - path, - message, - content: Buffer.from(content).toString('base64'), - sha, }); - return true; + return data; } catch (error) { - return false; + return undefined; } }; -export const selectListCommits = async ({ +export const selectCommit = async ({ octokit, owner, repo, + ref, }: { octokit: Octokit; owner: string; repo: string; + ref: string; }) => { try { - const { data } = await octokit.repos.listCommits({ + const { data } = await octokit.repos.getCommit({ owner, repo, + ref, }); return data; } catch (error) { diff --git a/server/src/octokit/content.octokit.ts b/server/src/octokit/content.octokit.ts index 71cfca0..dbbe641 100644 --- a/server/src/octokit/content.octokit.ts +++ b/server/src/octokit/content.octokit.ts @@ -7,23 +7,22 @@ import { Octokit } from '@octokit/rest'; export const selectContent = async ({ octokit, owner, - repo, + repoName, path, + ref, }: { octokit: Octokit; owner: string; - repo: string; + repoName: string; path: string; + ref?: string; }) => { - try { - const { data: content } = await octokit.repos.getContent({ - owner, - repo, - path, - }); + const { data: content } = await octokit.repos.getContent({ + owner, + repo: repoName, + path, + ref, + }); - return content; - } catch (error) { - return undefined; - } + return content; }; diff --git a/server/src/octokit/repo.octokit.ts b/server/src/octokit/repo.octokit.ts index 4841bc7..ea7e1b6 100644 --- a/server/src/octokit/repo.octokit.ts +++ b/server/src/octokit/repo.octokit.ts @@ -1,82 +1,97 @@ -import { RequestError } from '@octokit/request-error'; import { Octokit } from '@octokit/rest'; -import { - RepositoryCreationFailedError, - RequiresAuthenticationError, - BadCredentialsError, - NotFoundError, -} from '../constants/errors/octokit.error'; +/** + * 새로운 레포지토리 생성 + */ export const insertRepo = async ({ octokit, - name, + repoName, isPrivate, }: { octokit: Octokit; - name: string; + repoName: string; isPrivate: boolean; }) => { - try { - await octokit.repos.createForAuthenticatedUser({ - name, - private: isPrivate, - }); - } catch (error) { - if (error instanceof RequestError) { - switch (error.message) { - case BadCredentialsError.name: - throw new BadCredentialsError(); - case RequiresAuthenticationError.name: - throw new RequiresAuthenticationError(); - case RepositoryCreationFailedError.name: - throw new RepositoryCreationFailedError(); - } - } - throw new Error(); - } + await octokit.repos.createForAuthenticatedUser({ + name: repoName, + private: isPrivate, + }); }; +/** + * 기존의 레포지토리 삭제 + */ export const removeRepo = async ({ octokit, - name, + repoName, }: { octokit: Octokit; - name: string; + repoName: string; }) => { - try { - await octokit.repos.delete({ - owner: process.env.MY_GITHUB_USERNAME!, - repo: name, - }); - } catch (error) { - if (error instanceof RequestError) { - switch (error.message) { - case BadCredentialsError.name: - throw new BadCredentialsError(); - case RequiresAuthenticationError.name: - throw new RequiresAuthenticationError(); - case NotFoundError.name: - throw new NotFoundError(); - } - } - throw new Error(); - } + await octokit.repos.delete({ + owner: process.env.MY_GITHUB_USERNAME!, + repo: repoName, + }); +}; + +/** + * 전체 레포지토리 목록 조회 + */ +export const selectRepos = async ({ octokit }: { octokit: Octokit }) => { + const { data } = await octokit.repos.listForAuthenticatedUser(); + const repositories = data.map(repo => repo.name); + return repositories; }; -export const selectRepoList = async ({ octokit }: { octokit: Octokit }) => { - try { - const { data } = await octokit.repos.listForAuthenticatedUser(); - const repositories = data.map(repo => repo.name); - return repositories; - } catch (error) { - if (error instanceof RequestError) { - switch (error.message) { - case BadCredentialsError.name: - throw new BadCredentialsError(); - case RequiresAuthenticationError.name: - throw new RequiresAuthenticationError(); - } - } - throw new Error(); +/** + * 레포지토리 조회 + */ +export const selectRepo = async ({ + octokit, + owner, + repoName, + path = '', + ref, +}: { + octokit: Octokit; + owner: string; + repoName: string; + path?: string; + ref?: string; +}) => { + const { data } = await octokit.repos.getContent({ + owner, + repo: repoName, + path, + ref, + }); + + if (Array.isArray(data)) { + const files: any = await Promise.all( + data.map(async sub => { + if (sub.type === 'file') { + return sub.name; + } else if (sub.type === 'dir') { + const subPath = `${path}/${sub.name}`; + const subContents = await selectRepo({ + octokit, + owner, + repoName, + path: subPath, + ref, + }); + return { + [sub.name]: subContents, + }; + } else { + return sub.name; + } + }) + ); + + console.log(files); + return files.filter(Boolean); } + + return data.name; }; diff --git a/server/src/route/github.route.ts b/server/src/route/github.route.ts new file mode 100644 index 0000000..c0c85b5 --- /dev/null +++ b/server/src/route/github.route.ts @@ -0,0 +1,66 @@ +import { Router } from 'express'; +import dotenv from 'dotenv'; +import { + githubRepoContent, + githubFileContentCommit, + githubCommitList, + githubRepoCreate, + githubRepoDelete, + githubRepoList, + githubBranchCreate, + githubBranchDelete, + githubRepo, + // githubRepo, +} from '../controller/github.controller'; + +dotenv.config(); +const router = Router(); + +/** + * 유저 전체 레포지토리 조회 + */ +router.get('/repos', githubRepoList); + +/** + * 레포지토리 생성 + */ +router.post('/repos/:repo', githubRepoCreate); + +/** + * 레포지토리 삭제 (github token 생성시 delete_repo 체크박스 체크해줘야함) + */ +router.delete('/repos/:repo', githubRepoDelete); + +/** + * 레포지토리 커밋 조회 + */ +router.get('/repos/:repo/commits', githubCommitList); + +/** + * 레포지토리 전체 조회 (디렉토리 구조) + */ +router.get('/repos/:repo', githubRepo); + +/** + * 특정 커밋 시점의 레포지토리, 파일 조회 + */ +router.get('/repos/:repo/contents/*', githubRepoContent); + +/** + * 파일 커밋 + * 어떤 브랜치의 마지막 커밋에서 커밋할 경우 그대로 커밋 + * 어떤 브랜치의 중간 커밋에서 커밋할 경우 새로운 브랜치 생성 후 새로운 브랜치에 커밋 + */ +router.post('/repos/:repo/contents/*', githubFileContentCommit); + +/** + * 브랜치 생성 + */ +router.post('/repos/:repo/branchs/:branch', githubBranchCreate); + +/** + * 브랜치 삭제 + */ +router.delete('/repos/:repo/branchs/:branch', githubBranchDelete); + +export default router; diff --git a/server/src/routes/index.ts b/server/src/route/index.ts similarity index 100% rename from server/src/routes/index.ts rename to server/src/route/index.ts diff --git a/server/src/routes/github.route.ts b/server/src/routes/github.route.ts deleted file mode 100644 index 053b445..0000000 --- a/server/src/routes/github.route.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Router } from 'express'; -import dotenv from 'dotenv'; -import { - githubRepoContent, - githubRepoCommit, - githubRepoCommitList, - githubRepoCreate, - githubRepoDelete, - githubRepoList, -} from '../controllers/github.controller'; - -dotenv.config(); -const router = Router(); - -/** - * 유저 전체 레포지토리 가져오기 - */ -router.get('/repos', githubRepoList); - -/** - * 레포지토리 생성 - */ -router.post('/repos', githubRepoCreate); - -/** - * 레포지토리 삭제 (github token에서 delete_repo 체크해줘야함) - */ -router.delete('/repos', githubRepoDelete); - -/** - * 레포지토리 커밋 가져오기 - */ -router.get('/repos/:repo/commits', githubRepoCommitList); - -/** - * 유저 특정 레포지토리 가져오기 - */ -router.get('/repos/:repo/contents/*', githubRepoContent); - -/** - * 파일 커밋 - */ -router.post('/repos/:repo/contents/*', githubRepoCommit); - -export default router; diff --git a/server/src/service/github.service.ts b/server/src/service/github.service.ts index 95d0752..b6944d0 100644 --- a/server/src/service/github.service.ts +++ b/server/src/service/github.service.ts @@ -1 +1,334 @@ -export const getContentsAPI = async () => {}; +import { RequestError } from '@octokit/request-error'; +import { Octokit } from '@octokit/rest'; +import { + BadCredentialsError, + NotFoundError, + ReferenceAlreadyExistsError, + ReferenceDoesNotExistError, + RepositoryCreationFailedError, + RequiresAuthenticationError, +} from '../constant/error/octokit.error'; +import { + BAD_CREDENTIALS, + REQUIRES_AUTHENTICATION, + REPOSITORY_CREATION_FAILED, + NOT_FOUND, + REFERENCE_ALREADY_EXISTS, + REFERENCE_DOES_NOT_EXIST, + REPOSITORY_IS_EMPTY, +} from '../constant/error/octokit.message'; +import { insertBranch, removeBranch } from '../octokit/branch.octokit'; +import { + insertFileContents, + updateFileContents, +} from '../octokit/commit.octokit'; +import { selectContent } from '../octokit/content.octokit'; +import { + insertRepo, + removeRepo, + selectRepo, + selectRepos, +} from '../octokit/repo.octokit'; + +export const findAllRepo = async (token: string) => { + const octokit = new Octokit({ + auth: token, + }); + try { + const repos = await selectRepos({ octokit }); + return repos; + } catch (error) { + if (error instanceof RequestError) { + switch (error.message) { + case BAD_CREDENTIALS: + throw new BadCredentialsError(); + case REQUIRES_AUTHENTICATION: + throw new RequiresAuthenticationError(); + } + } + throw new Error(); + } +}; + +export const findRepo = async ({ + token, + owner, + repoName, + ref, +}: { + token: string; + owner: string; + repoName: string; + ref?: string; +}) => { + try { + const repoStructure = await selectRepo({ + octokit: new Octokit({ auth: token }), + owner, + repoName, + ref, + }); + return repoStructure; + } catch (error) { + // console.log('findRepo: ' + error); + if (error instanceof RequestError) { + switch (error.message) { + case BAD_CREDENTIALS: + throw new BadCredentialsError(); + case REQUIRES_AUTHENTICATION: + throw new RequiresAuthenticationError(); + } + } + throw new Error(); + } +}; + +export const addRepo = async ({ + token, + repoName, + isPrivate, +}: { + token: string; + repoName: string; + isPrivate: boolean; +}) => { + try { + await insertRepo({ + octokit: new Octokit({ auth: token }), + repoName, + isPrivate, + }); + } catch (error) { + if (error instanceof RequestError) { + switch (error.message) { + case BAD_CREDENTIALS: + throw new BadCredentialsError(); + case REQUIRES_AUTHENTICATION: + throw new RequiresAuthenticationError(); + case REPOSITORY_CREATION_FAILED: + throw new RepositoryCreationFailedError(); + } + } + throw new Error(); + } +}; + +export const deleteRepo = async ({ + token, + repoName, +}: { + token: string; + repoName: string; +}) => { + try { + await removeRepo({ octokit: new Octokit({ auth: token }), repoName }); + } catch (error) { + if (error instanceof RequestError) { + switch (error.message) { + case BAD_CREDENTIALS: + throw new BadCredentialsError(); + case REQUIRES_AUTHENTICATION: + throw new RequiresAuthenticationError(); + case NOT_FOUND: + throw new NotFoundError(); + } + } + throw new Error(); + } +}; + +export const findContent = async ({ + token, + owner, + repoName, + path, + ref, +}: { + token: string; + owner: string; + repoName: string; + path: string; + ref?: string; +}) => { + try { + const content = await selectContent({ + octokit: new Octokit({ auth: token }), + owner, + repoName, + path, + ref, + }); + + return content; + } catch (error) { + if (error instanceof RequestError) { + switch (error.message) { + case BAD_CREDENTIALS: + throw new BadCredentialsError(); + case REQUIRES_AUTHENTICATION: + throw new RequiresAuthenticationError(); + case NOT_FOUND: + case REPOSITORY_IS_EMPTY: + return; + } + } + + throw new Error(); + } +}; + +export const addContent = async ({ + token, + owner, + repoName, + path, + content, + branchName, + message, +}: { + token: string; + owner: string; + repoName: string; + path: string; + content: string; + branchName?: string; + message?: string; +}) => { + try { + await insertFileContents({ + octokit: new Octokit({ auth: token }), + owner, + repoName, + path, + content, + branchName, + message, + }); + } catch (error) { + if (error instanceof RequestError) { + switch (error.message) { + case BAD_CREDENTIALS: + throw new BadCredentialsError(); + case REQUIRES_AUTHENTICATION: + throw new RequiresAuthenticationError(); + case NOT_FOUND: + throw new NotFoundError(); + } + } + throw new Error(); + } +}; + +export const modifyContent = async ({ + token, + owner, + repoName, + path, + content, + blobSha, + message, +}: { + token: string; + owner: string; + repoName: string; + path: string; + content: string; + blobSha: string; + message?: string; +}) => { + try { + await updateFileContents({ + octokit: new Octokit({ auth: token }), + owner, + repoName, + path, + content, + sha: blobSha, + message, + }); + } catch (error) { + if (error instanceof RequestError) { + switch (error.message) { + case BAD_CREDENTIALS: + throw new BadCredentialsError(); + case REQUIRES_AUTHENTICATION: + throw new RequiresAuthenticationError(); + case NOT_FOUND: + throw new NotFoundError(); + } + } + throw new Error(); + } +}; + +export const addBranch = async ({ + token, + owner, + repoName, + branchName, + commitSha, +}: { + token: string; + owner: string; + repoName: string; + branchName: string; + commitSha: string; +}) => { + try { + const lastCommitSha = await insertBranch({ + octokit: new Octokit({ auth: token }), + owner, + repoName, + branchName, + commitSha, + }); + + return lastCommitSha; + } catch (error) { + if (error instanceof RequestError) { + switch (error.message) { + case BAD_CREDENTIALS: + throw new BadCredentialsError(); + case REQUIRES_AUTHENTICATION: + throw new RequiresAuthenticationError(); + case REFERENCE_ALREADY_EXISTS: + throw new ReferenceAlreadyExistsError(); + } + } + throw new Error(); + } +}; + +export const deleteBranch = async ({ + token, + owner, + repoName, + branchName, +}: { + token: string; + owner: string; + repoName: string; + branchName: string; +}) => { + try { + await removeBranch({ + octokit: new Octokit({ auth: token }), + owner, + repoName, + branchName, + }); + } catch (error) { + console.log(error); + if (error instanceof RequestError) { + switch (error.message) { + case BAD_CREDENTIALS: + throw new BadCredentialsError(); + case REQUIRES_AUTHENTICATION: + throw new RequiresAuthenticationError(); + case REFERENCE_DOES_NOT_EXIST: + throw new ReferenceDoesNotExistError(); + } + } + throw new Error(); + } +}; diff --git a/server/src/service/user.service.ts b/server/src/service/user.service.ts new file mode 100644 index 0000000..754ac2d --- /dev/null +++ b/server/src/service/user.service.ts @@ -0,0 +1,6 @@ +export const findById = (id: number) => { + return { + username: process.env.MY_GITHUB_USERNAME, + token: process.env.MY_GITHUB_PERSONAL_ACCESS_TOKEN, + }; +};