feat(api): implement get issue templates
This commit is contained in:
		
							
								
								
									
										51
									
								
								src/apis/gitea.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/apis/gitea.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| import { URL } from "url" | ||||
|  | ||||
| import { BaseApi } from "./index.js" | ||||
|  | ||||
|  | ||||
| // /api/swagger | ||||
| const Prefix = "/api/v1" | ||||
| const IssueTemplatesUrl = (user: string, repo: string) => | ||||
|     `${Prefix}/repos/${user}/${repo}/issue_templates` | ||||
|  | ||||
| type GiteaIssueTemplatesResponse = { | ||||
|     name: string, | ||||
|     title: string, | ||||
|     about: string, | ||||
|     labels: string[] | null, | ||||
|     assignees: string[] | null, | ||||
|     ref: string, | ||||
|     content: string, | ||||
|     body: unknown | null, | ||||
|     file_name: string | ||||
| }[] | ||||
|  | ||||
| class Gitea extends BaseApi { | ||||
|     headers: Record<string, string> | ||||
|  | ||||
|     constructor(...options: ConstructorParameters<typeof BaseApi>) { | ||||
|         super(...options) | ||||
|         this.headers = { | ||||
|             "Authorization": `token ${this.token}` | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async getIssueTemplates() { | ||||
|         const url = new URL(IssueTemplatesUrl(this.user, this.repo), this.host) | ||||
|         const response = await fetch(url, { headers: this.headers }) | ||||
|  | ||||
|         switch (response.status) { | ||||
|             case 200: break | ||||
|             case 404: return { error: "Issue templates not found" } | ||||
|             default: return { error: "Unknown status code" } | ||||
|         } | ||||
|  | ||||
|         const json: GiteaIssueTemplatesResponse = await response.json() | ||||
|         return { | ||||
|             value: json.map(template => template.file_name) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| export default Gitea | ||||
							
								
								
									
										118
									
								
								src/apis/github.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/apis/github.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| import { URL } from "url" | ||||
|  | ||||
| import {BaseApi, type Result} from "./index.js" | ||||
|  | ||||
|  | ||||
| // docs.github.com/en/rest | ||||
| /* | ||||
| Folders searched by gitea: | ||||
|     "ISSUE_TEMPLATE" | ||||
|     "issue_template" | ||||
|     ".gitea/ISSUE_TEMPLATE" | ||||
|     ".gitea/issue_template" | ||||
|     ".github/ISSUE_TEMPLATE" | ||||
|     ".github/issue_template" | ||||
| */ | ||||
|  | ||||
| const IssueTemplateFolders = [ | ||||
|     "ISSUE_TEMPLATE", | ||||
|     "issue_template", | ||||
|     ".gitea/ISSUE_TEMPLATE", | ||||
|     ".gitea/issue_template", | ||||
|     ".github/ISSUE_TEMPLATE", | ||||
|     ".github/issue_template" | ||||
| ] | ||||
|  | ||||
| // /repos/${user}/${repo} -> .default_branch | ||||
| // /repos/${user}/${repo}/git/trees/{branch}?recursive=1 -> search for templates | ||||
|  | ||||
| const Prefix = "https://api.github.com" | ||||
| // https://docs.github.com/en/rest/repos/repos#get-a-repository | ||||
| const RepoInfoUrl = (user: string, repo: string) => | ||||
|     `${Prefix}/repos/${user}/${repo}` | ||||
| interface GitHubRepoInfoResponse { | ||||
|     default_branch: string | ||||
|     // ... | ||||
| } | ||||
|  | ||||
| // https://docs.github.com/en/rest/git/trees#get-a-tree | ||||
| const TreeUrl = (user: string, repo: string, branch: string) => | ||||
|     `${Prefix}/repos/${user}/${repo}/git/trees/${branch}?recursive=1` | ||||
|  | ||||
| interface GitHubTreeResponse { | ||||
|     sha: string | ||||
|     url: string | ||||
|     tree: { | ||||
|         path: string | ||||
|         mode: string | ||||
|         type: "tree" | "blob" | ||||
|         sha: string | ||||
|         size: number | ||||
|         url: string | ||||
|     }[] | ||||
| } | ||||
|  | ||||
| // fine-grained tokens required permissions: metadata, content | ||||
|  | ||||
| class GitHub extends BaseApi { | ||||
|     headers: Record<string, string> | ||||
|  | ||||
|     constructor(...options: ConstructorParameters<typeof BaseApi>) { | ||||
|         super(...options) | ||||
|         this.headers = { | ||||
|             "Authorization": `token ${this.token}` | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async getDefaultBranch(): Promise<Result<string, string>> { | ||||
|         const url = new URL(RepoInfoUrl(this.user, this.repo), this.host) | ||||
|         const response = await fetch(url, { headers: this.headers }) | ||||
|  | ||||
|         switch (response.status) { | ||||
|             case 301: return { error: "Moved permanently" } | ||||
|             case 403: return { error: "Forbidden" } | ||||
|             case 404: return { error: "Resource not found" } | ||||
|             case 200: break | ||||
|             default: return { error: "Unknown status code" } | ||||
|         } | ||||
|  | ||||
|         const json: GitHubRepoInfoResponse = await response.json() | ||||
|         return { value: json.default_branch } | ||||
|     } | ||||
|  | ||||
|     async getRepoFiles(): Promise<Result<string[], string>> { | ||||
|         const branch = await this.getDefaultBranch() | ||||
|         if (!branch.value) return { error: `Default branch error: ${branch.error}` } | ||||
|  | ||||
|         const url = new URL(TreeUrl(this.user, this.repo, branch.value), this.host) | ||||
|         const response = await fetch(url, { headers: this.headers }) | ||||
|  | ||||
|         switch (response.status) { | ||||
|             case 200: break | ||||
|             case 404: return { error: "Resource not found" } | ||||
|             case 409: return { error: "Conflict" } | ||||
|             case 422: return { error: "Validation failed, or the endpoint has been spammed" } | ||||
|             default: return { error: "Unknown status code" } | ||||
|         } | ||||
|  | ||||
|         const json: GitHubTreeResponse = await response.json() | ||||
|         return { | ||||
|             value: json.tree | ||||
|                 .filter(entry => entry.type == "blob") | ||||
|                 .map(entry => entry.path) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async getIssueTemplates() { | ||||
|         const files = await this.getRepoFiles() | ||||
|         if (!files.value) return { error: `Repo files error: ${files.error}` } | ||||
|         const issue_templates = files.value.filter( | ||||
|             file => IssueTemplateFolders.some(folder => file.startsWith(folder)) | ||||
|         ) | ||||
|  | ||||
|         return { value: issue_templates } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| export default GitHub | ||||
							
								
								
									
										21
									
								
								src/apis/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/apis/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| export type Result<Type, Err> = { | ||||
|     value: Type, | ||||
|     error?: null | ||||
| } | { | ||||
|     value?: null, | ||||
|     error: Err | ||||
| } | ||||
|  | ||||
| export abstract class BaseApi { | ||||
|     constructor( | ||||
|         readonly host: string, | ||||
|         readonly token: string, | ||||
|         readonly user: string, | ||||
|         readonly repo: string | ||||
|     ) {} | ||||
|  | ||||
|     /** | ||||
|      * @returns Array of filepath's | ||||
|      */ | ||||
|     abstract getIssueTemplates(): Promise<Result<string[], string>> | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/index.ts
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								src/index.ts
									
									
									
									
									
								
							| @@ -2,4 +2,21 @@ import * as cli from "./cli.js" | ||||
|  | ||||
|  | ||||
| const config = cli.parseArgs(process.argv) | ||||
| console.log(config) | ||||
| // console.log(config) | ||||
|  | ||||
| import type { BaseApi } from "./apis"; | ||||
| import Gitea from "./apis/gitea.js" | ||||
| import GitHub from "./apis/github.js"; | ||||
|  | ||||
|  | ||||
| let api: BaseApi | ||||
| api = new Gitea( | ||||
|     "http://localhost:3000", "...", | ||||
|     "admin", "test_repo" | ||||
| ) | ||||
| api = new GitHub( | ||||
|     "https://api.github.com", "...", | ||||
|     "TxFig", "test-api") | ||||
|  | ||||
| const result = await api.getIssueTemplates() | ||||
| console.log(result.value ?? result.error) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user