From 9234b6441c0549d7b396cd32417d12d857e1580e Mon Sep 17 00:00:00 2001 From: kimhji Date: Wed, 13 Nov 2024 23:01:46 +0900 Subject: [PATCH] =?UTF-8?q?feat(#4):=20=EC=9D=B4=EC=A0=84=20PR=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/src/app.module.ts | 4 +- apps/backend/src/gist/dto/comment.dto.ts | 15 + apps/backend/src/gist/dto/commit.dto.ts | 9 + apps/backend/src/gist/dto/gistApiFile.dto.ts | 17 ++ .../src/gist/dto/gistApiFileList.dto.ts | 18 ++ apps/backend/src/gist/dto/user.dto.ts | 11 + apps/backend/src/gist/gist.controller.spec.ts | 20 ++ apps/backend/src/gist/gist.controller.ts | 61 ++++ apps/backend/src/gist/gist.module.ts | 9 + apps/backend/src/gist/gist.service.spec.ts | 18 ++ apps/backend/src/gist/gist.service.ts | 282 ++++++++++++++++++ 11 files changed, 463 insertions(+), 1 deletion(-) create mode 100644 apps/backend/src/gist/dto/comment.dto.ts create mode 100644 apps/backend/src/gist/dto/commit.dto.ts create mode 100644 apps/backend/src/gist/dto/gistApiFile.dto.ts create mode 100644 apps/backend/src/gist/dto/gistApiFileList.dto.ts create mode 100644 apps/backend/src/gist/dto/user.dto.ts create mode 100644 apps/backend/src/gist/gist.controller.spec.ts create mode 100644 apps/backend/src/gist/gist.controller.ts create mode 100644 apps/backend/src/gist/gist.module.ts create mode 100644 apps/backend/src/gist/gist.service.spec.ts create mode 100644 apps/backend/src/gist/gist.service.ts diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index 64c1fb5b..3dcb45cf 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -5,6 +5,7 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { typeORMConfig } from './config/typeorm.config'; import { DockerModule } from './docker/docker.module'; +import { GistModule } from './gist/gist.module'; @Module({ imports: [ @@ -13,7 +14,8 @@ import { DockerModule } from './docker/docker.module'; envFilePath: '.env' }), TypeOrmModule.forRoot(typeORMConfig), - DockerModule + DockerModule, + GistModule ], controllers: [AppController], providers: [AppService] diff --git a/apps/backend/src/gist/dto/comment.dto.ts b/apps/backend/src/gist/dto/comment.dto.ts new file mode 100644 index 00000000..b0200ac3 --- /dev/null +++ b/apps/backend/src/gist/dto/comment.dto.ts @@ -0,0 +1,15 @@ +import { UserDto } from './user.dto'; + +export class CommentDto { + id: string; + created_at: string; + body: string; + owner: UserDto; + + constructor(id: string, created_at: string, body: string, owner: UserDto) { + this.id = id; + this.created_at = created_at; + this.body = body; + this.owner = owner; + } +} diff --git a/apps/backend/src/gist/dto/commit.dto.ts b/apps/backend/src/gist/dto/commit.dto.ts new file mode 100644 index 00000000..43c55790 --- /dev/null +++ b/apps/backend/src/gist/dto/commit.dto.ts @@ -0,0 +1,9 @@ +export class CommitDto { + committed_at: string; + url: string; + + constructor(committed_at: string, url: string) { + this.committed_at = committed_at; + this.url = url; + } +} diff --git a/apps/backend/src/gist/dto/gistApiFile.dto.ts b/apps/backend/src/gist/dto/gistApiFile.dto.ts new file mode 100644 index 00000000..be052310 --- /dev/null +++ b/apps/backend/src/gist/dto/gistApiFile.dto.ts @@ -0,0 +1,17 @@ +export class GistApiFileDto { + file_name?: string; + raw_url?: string; + type?: string; + language?: string; + size?: number; + content?: string; + + constructor(file_name: string, raw_url: string, type: string, language: string, size: number, content: string) { + this.file_name = file_name; + this.raw_url = raw_url; + this.type = type; + this.language = language; + this.size = size; + this.content = content; + } +} diff --git a/apps/backend/src/gist/dto/gistApiFileList.dto.ts b/apps/backend/src/gist/dto/gistApiFileList.dto.ts new file mode 100644 index 00000000..8410e0fc --- /dev/null +++ b/apps/backend/src/gist/dto/gistApiFileList.dto.ts @@ -0,0 +1,18 @@ +import { GistApiFileDto } from './gistApiFile.dto'; +import { UserDto } from './user.dto'; + +export class GistApiFileListDto { + id: string; + description: string; + files: GistApiFileDto[]; + owner: UserDto; + public: boolean; + + constructor(id: string, description: string, files: GistApiFileDto[], owner: UserDto, isPublic: boolean) { + this.id = id; + this.description = description; + this.files = files; + this.owner = owner; + this.public = isPublic; + } +} diff --git a/apps/backend/src/gist/dto/user.dto.ts b/apps/backend/src/gist/dto/user.dto.ts new file mode 100644 index 00000000..6b033bf4 --- /dev/null +++ b/apps/backend/src/gist/dto/user.dto.ts @@ -0,0 +1,11 @@ +export class UserDto { + login: string; + id: number; + avatar_url: string; + + constructor(login: string, id: number, avatar_url) { + this.login = login; + this.id = id; + this.avatar_url = avatar_url; + } +} diff --git a/apps/backend/src/gist/gist.controller.spec.ts b/apps/backend/src/gist/gist.controller.spec.ts new file mode 100644 index 00000000..97136137 --- /dev/null +++ b/apps/backend/src/gist/gist.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { GistController } from './gist.controller'; +import { GistService } from './gist.service'; + +describe('GistController', () => { + let controller: GistController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [GistController], + providers: [GistService] + }).compile(); + + controller = module.get(GistController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/apps/backend/src/gist/gist.controller.ts b/apps/backend/src/gist/gist.controller.ts new file mode 100644 index 00000000..a314ff0e --- /dev/null +++ b/apps/backend/src/gist/gist.controller.ts @@ -0,0 +1,61 @@ +import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'; +import { GistService } from './gist.service'; + +@Controller('gist') +export class GistController { + constructor(private readonly gistService: GistService) {} + + @Get() + findAll() { + return this.gistService.getAllGists(); + } + + @Get('/Last') + findLast() { + return this.gistService.getMostRecentGistInUser(); + } + + @Get('/user') + findUser() { + return this.gistService.getUserData(); + } + + @Get('/:gist_id/comments') + findComments(@Param('gist_id') gist_id: string) { + return this.gistService.getComments(gist_id); + } + + @Get(['/:id/commits', '/:id/commits/:pageIdx']) + findCommits(@Param('id') id: string, @Param('pageIdx') pageIdx: number) { + return this.gistService.getCommitsForAGist(id, pageIdx ? pageIdx : 1); + } + + @Get(['/:id']) + findOne(@Param('id') id: string) { + return this.gistService.getGistById(id); + } + + @Patch('/:gist_id/comment/:comment_id') + patchComment( + @Param('gist_id') gist_id: string, + @Param('comment_id') comment_id: string, + @Body('comment') comment: string + ) { + return this.gistService.updateComment(gist_id, comment_id, comment); + } + + @Post('/:gist_id/comment') + postComment(@Param('gist_id') gist_id: string, @Body('comment') comment: string) { + return this.gistService.createComments(gist_id, comment); + } + + @Delete('/:gist_id/comment/:comment_id') + deleteComment(@Param('gist_id') gist_id: string, @Param('comment_id') comment_id: string) { + return this.gistService.deleteComment(gist_id, comment_id); + } + + @Get(':gist_id/commit/:id') + getCommitFile(@Param('gist_id') gist_id: string, @Param('id') commit_id: number) { + return this.gistService.getCommit(gist_id, commit_id); + } +} diff --git a/apps/backend/src/gist/gist.module.ts b/apps/backend/src/gist/gist.module.ts new file mode 100644 index 00000000..a159c38e --- /dev/null +++ b/apps/backend/src/gist/gist.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { GistController } from './gist.controller'; +import { GistService } from './gist.service'; + +@Module({ + controllers: [GistController], + providers: [GistService] +}) +export class GistModule {} diff --git a/apps/backend/src/gist/gist.service.spec.ts b/apps/backend/src/gist/gist.service.spec.ts new file mode 100644 index 00000000..ec12b773 --- /dev/null +++ b/apps/backend/src/gist/gist.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { GistService } from './gist.service'; + +describe('GistService', () => { + let service: GistService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [GistService] + }).compile(); + + service = module.get(GistService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/backend/src/gist/gist.service.ts b/apps/backend/src/gist/gist.service.ts new file mode 100644 index 00000000..aa335699 --- /dev/null +++ b/apps/backend/src/gist/gist.service.ts @@ -0,0 +1,282 @@ +import { Injectable } from '@nestjs/common'; +import { CommentDto } from './dto/comment.dto'; +import { CommitDto } from './dto/commit.dto'; +import { GistApiFileDto } from './dto/gistApiFile.dto'; +import { GistApiFileListDto } from './dto/gistApiFileList.dto'; +import { UserDto } from './dto/user.dto'; + +@Injectable() +export class GistService { + gittoken: string; + constructor() { + this.gittoken = ''; + } + async getAllGists(): Promise { + let page = 1; + const perPage = 100; + const gistList: GistApiFileListDto[] = []; + while (true) { + const params = { + page: page.toString(), + per_page: perPage.toString() + }; + const queryParam = new URLSearchParams(params).toString(); + const data = await this.gistGetReq(`https://api.github.com/gists`, queryParam); + if (data.length === 0) { + break; + } + page++; + const gistFiles: GistApiFileListDto[] = await Promise.all( + data + .filter((gistfiltering: GistApiFileListDto) => { + return gistfiltering.id && gistfiltering.description && gistfiltering.files && gistfiltering.owner; + }) + .map(async (gist: GistApiFileListDto) => { + const fileArr: GistApiFileDto[] = []; + + return new GistApiFileListDto( + gist.id, + gist.description, + fileArr, + new UserDto(gist.owner.login, gist.owner.id, gist.owner.avatar_url), + gist.public + ); + }) + ); + gistList.push(...gistFiles); + } + return gistList; + } + + async getGistById(id: string): Promise { + const data = await this.gistGetReq(`https://api.github.com/gists/${id}`); + + const fileArr: GistApiFileDto[] = await Promise.all( + Object.keys(data.files).map(async (key) => { + const content = await this.getFileContent(data.files[key].raw_url); + return new GistApiFileDto( + key, + data.files[key].raw_url, + data.files[key].type, + data.files[key].language, + data.files[key].size, + content + ); + }) + ); + + const gist: GistApiFileListDto = new GistApiFileListDto( + data.id, + data.description, + fileArr, + new UserDto(data.owner.login, data.owner.id, data.owner.avatar_url), + data.public + ); + return gist; + } + + async getMostRecentGistInUser(): Promise { + const params = { + page: '1', + per_page: '1' + }; + const queryParam = new URLSearchParams(params).toString(); + const response = await this.gistGetReq('https://api.github.com/gists', queryParam); + + if (!response.length) { + throw new Error('404'); + } + const mostRecentData = response[0]; + + const fileArr: GistApiFileDto[] = await Promise.all( + Object.keys(mostRecentData.files).map(async (key) => { + const content = await this.getFileContent(mostRecentData.files[key].raw_url); + + return new GistApiFileDto( + key, + mostRecentData.files[key].raw_url, + mostRecentData.files[key].type, + mostRecentData.files[key].language, + mostRecentData.files[key].size, + content + ); + }) + ); + const gist: GistApiFileListDto = new GistApiFileListDto( + mostRecentData.id, + mostRecentData.description, + fileArr, + new UserDto(mostRecentData.owner.login, mostRecentData.owner.id, mostRecentData.owner.avatar_url), + mostRecentData.public + ); + + return gist; + } + + async getCommitsForAGist(gist_id: string, pageIdx = 1): Promise { + const page = pageIdx; + const perPage = 5; + const params = { + page: page.toString(), + per_page: perPage.toString() + }; + const queryParam = new URLSearchParams(params).toString(); + const data = await this.gistGetReq(`https://api.github.com/gists/${gist_id}/commits`, queryParam); + const commits: CommitDto[] = data.map((commit) => new CommitDto(commit.committed_at, commit.url)); + return commits; + } + + async getCommit(gist_id: string, commit_id: number) { + const commits = await this.getCommitsForAGist(gist_id); + const response = await this.getFilesFromCommit(commits[commit_id].url); + return response; + } + + async getFilesFromCommit(commit_url: string) { + const data = await this.getFileContent(commit_url); + const dataJson = JSON.parse(data); + const fileArr: GistApiFileDto[] = await Promise.all( + Object.keys(dataJson.files).map(async (key) => { + const content = await this.getFileContent(dataJson.files[key].raw_url); + + return new GistApiFileDto( + key, + dataJson.files[key].raw_url, + dataJson.files[key].type, + dataJson.files[key].language, + dataJson.files[key].size, + content + ); + }) + ); + const gist: GistApiFileListDto = new GistApiFileListDto( + dataJson.id, + dataJson.description, + fileArr, + new UserDto(dataJson.owner.login, dataJson.owner.id, dataJson.owner.avatar_url), + dataJson.public + ); + + return gist; + } + + async getUserData(): Promise { + const userData = await this.gistGetReq('https://api.github.com/user'); + if (!userData.id || !userData.avatar_url || !userData.login) { + throw new Error('404'); + } + const result: UserDto = new UserDto(userData.login, userData.id, userData.avatar_url); + return result; + } + + async getFileContent(raw_url: string) { + const response = await fetch(raw_url, { + headers: { + Authorization: `Bearer ${this.gittoken}` + } + }); + if (!response.ok) { + throw new Error('404'); + } + const data = await response.text(); + return data; + } + + async getComments(gist_id: string): Promise { + const data = await this.gistGetReq(`https://api.github.com/gists/${gist_id}/comments`); + const comments: CommentDto[] = data.map( + (comment) => + new CommentDto( + comment.id, + comment.created_at, + comment.body, + new UserDto(comment.user.login, comment.user.id, comment.user.avatar_url) + ) + ); + return comments; + } + + async createComments(gist_id: string, detail: string): Promise { + const data = await this.gistPostReq(`https://api.github.com/gists/${gist_id}/comments`, '', detail); + const comment: CommentDto = new CommentDto( + data.id, + data.created_at, + data.body, + new UserDto(data.user.login, data.user.id, data.user.avatar_url) + ); + return comment; + } + + async updateComment(gist_id: string, comment_id: string, detail: string): Promise { + const data = await this.gistPatchReq(`https://api.github.com/gists/${gist_id}/comments/${comment_id}`, '', detail); + if (!data.ok) { + throw new Error('404'); + } + return true; + } + + async deleteComment(gist_id: string, comment_id: string): Promise { + const data = await this.gistDeleteReq(`https://api.github.com/gists/${gist_id}/comments/${comment_id}`); + if (!data.ok) { + throw new Error('404'); + } + return true; + } + + async gistGetReq(commend: string, queryParam = ''): Promise { + const commendURL = queryParam ? commend + '?' + queryParam : commend; + const response = await fetch(commendURL, { + method: 'GET', + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${this.gittoken}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + }); + return await response.json(); + } + + async gistPostReq(commend: string, queryParam = '', body: string = null): Promise { + const commendURL = queryParam ? commend + '?' + queryParam : commend; + const response = await fetch(commendURL, { + method: 'POST', + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${this.gittoken}`, + 'X-GitHub-Api-Version': '2022-11-28', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ body: body }) + }); + console.log(response); + return await response.json(); + } + + async gistPatchReq(commend: string, queryParam = '', body: string = null): Promise { + const commendURL = queryParam ? commend + '?' + queryParam : commend; + const response = await fetch(commendURL, { + method: 'PATCH', + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${this.gittoken}`, + 'X-GitHub-Api-Version': '2022-11-28', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ body: body }) + }); + return response; + } + + async gistDeleteReq(commend: string, queryParam = ''): Promise { + const commendURL = queryParam ? commend + '?' + queryParam : commend; + const response = await fetch(commendURL, { + method: 'DELETE', + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${this.gittoken}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + }); + return response; + } +}