From 94c666a9936bba2a4faad782c618b3bd907419f3 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Thu, 19 Feb 2026 20:38:03 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=20Gitea=20=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=BB=93=E6=9E=84=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=A0=B8?= =?UTF-8?q?=E5=BF=83=20API=20=E5=B0=81=E8=A3=85=E5=92=8C=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cnb.yml | 19 ++++ .gitignore | 9 ++ .npmrc | 3 + README.md | 1 + bun.config.ts | 4 + bun.lock | 18 ++++ package.json | 23 +++++ src/core/core.ts | 152 ++++++++++++++++++++++++++++++ src/index.ts | 29 ++++++ src/issue/index.ts | 172 ++++++++++++++++++++++++++++++++++ src/org/index.ts | 227 +++++++++++++++++++++++++++++++++++++++++++++ src/repo/index.ts | 132 ++++++++++++++++++++++++++ src/user/index.ts | 141 ++++++++++++++++++++++++++++ test/common.ts | 13 +++ 14 files changed, 943 insertions(+) create mode 100644 .cnb.yml create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 README.md create mode 100644 bun.config.ts create mode 100644 bun.lock create mode 100644 package.json create mode 100644 src/core/core.ts create mode 100644 src/index.ts create mode 100644 src/issue/index.ts create mode 100644 src/org/index.ts create mode 100644 src/repo/index.ts create mode 100644 src/user/index.ts create mode 100644 test/common.ts diff --git a/.cnb.yml b/.cnb.yml new file mode 100644 index 0000000..f972dd3 --- /dev/null +++ b/.cnb.yml @@ -0,0 +1,19 @@ +# .cnb.yml +include: + - https://cnb.cool/kevisual/cnb/-/blob/main/.cnb/template.yml + +.common_env: &common_env + env: + USERNAME: root + imports: + - https://cnb.cool/kevisual/env/-/blob/main/.env.development + +$: + vscode: + - docker: + image: docker.cnb.cool/kevisual/dev-env:latest + services: + - vscode + - docker + imports: !reference [.common_env, imports] + stages: !reference [.dev_template, stages] \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9768462 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.env* +!.env*example + +node_modules +.pnpm-store + +dist + +storage \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..6948c8c --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN} +//npm.cnb.cool/kevisual/registry/-/packages/:_authToken=${CNB_API_KEY} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/README.md b/README.md new file mode 100644 index 0000000..b51a998 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# 基本的gitea的api封装 \ No newline at end of file diff --git a/bun.config.ts b/bun.config.ts new file mode 100644 index 0000000..88d32ab --- /dev/null +++ b/bun.config.ts @@ -0,0 +1,4 @@ +import { buildWithBun } from '@kevisual/code-builder'; + +await buildWithBun({ naming: 'app', entry: 'src/index.ts', dts: true, clean: true }); + diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..24b7130 --- /dev/null +++ b/bun.lock @@ -0,0 +1,18 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "@kevisual/gitea", + "devDependencies": { + "@kevisual/code-builder": "^0.0.6", + "@kevisual/context": "^0.0.8", + }, + }, + }, + "packages": { + "@kevisual/code-builder": ["@kevisual/code-builder@0.0.6", "", { "bin": { "code-builder": "bin/code.js", "builder": "bin/code.js" } }, "sha512-0aqATB31/yw4k4s5/xKnfr4DKbUnx8e3Z3BmKbiXTrc+CqWiWTdlGe9bKI9dZ2Df+xNp6g11W4xM2NICNyyCCw=="], + + "@kevisual/context": ["@kevisual/context@0.0.8", "", {}, "sha512-DTJpyHI34NE76B7g6f+QlIqiCCyqI2qkBMQE736dzeRDGxOjnbe2iQY9W+Rt2PE6kmymM3qyOmSfNovyWyWrkA=="], + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..031b9f7 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "@kevisual/gitea", + "version": "0.0.2", + "description": "", + "scripts": { + "build": "bun run bun.config.ts" + }, + "files": [ + "src", + "dist" + ], + "keywords": [], + "author": "abearxiong (https://www.xiongxiao.me)", + "license": "MIT", + "type": "module", + "devDependencies": { + "@kevisual/code-builder": "^0.0.6", + "@kevisual/context": "^0.0.8" + }, + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/src/core/core.ts b/src/core/core.ts new file mode 100644 index 0000000..198d9a9 --- /dev/null +++ b/src/core/core.ts @@ -0,0 +1,152 @@ +export type CNBCoreOptions = { + token: string; + gitea?: GiteaCore; + baseURL?: string; + cors?: { + baseUrl?: string + } +} & T; + +export type RequestOptions = { + url?: string; + method?: string; + data?: Record; + body?: any; + params?: Record; + headers?: Record; + useOrigin?: boolean; +}; +const API_BASER_URL = 'https://git.xiongxiao.me' +export class GiteaCore { + baseURL = API_BASER_URL; + public token: string; + public cookie?: string; + isCors: boolean; + constructor(options: CNBCoreOptions) { + this.token = options.token; + if (options?.gitea) { + if (!options.token) { + this.token = options.gitea.token; + } + + } + const baseURL = options.baseURL || API_BASER_URL; + if (options?.cors?.baseUrl) { + this.baseURL = options.cors.baseUrl + '/' + baseURL.replace('https://', ''); + } else { + this.baseURL = baseURL; + } + this.isCors = !!options?.cors?.baseUrl; + } + + async request({ url, method = 'GET', data, params, headers, body, useOrigin }: RequestOptions): Promise { + const defaultHeaders: Record = { + 'Content-Type': 'application/json', + // 'Accept': 'application/json, application/vnd.cnb.api+json, application/vnd.cnb.web+json', + 'Accept': 'application/json', + }; + if (this.token) { + defaultHeaders['Authorization'] = `Bearer ${this.token}`; + } + if (params) { + const queryString = new URLSearchParams(params).toString(); + url += `?${queryString}`; + defaultHeaders['Accept'] = 'application/json'; + } + const _headers = { ...defaultHeaders, ...headers }; + let _body = undefined; + if (data) { + _body = JSON.stringify(data); + } + if (body) { + _body = body; + } + if (!_headers.Authorization) { + delete _headers.Authorization; + } + console.log('Request URL:', url, data, _headers); + const response = await fetch(url || '', { + method, + headers: _headers, + body: _body, + }); + const res = (data: any, message?: string, code?: number) => { + if (useOrigin) { + return data; + } + return { + code: code ?? 200, + message: message || 'success', + data, + }; + } + if (!response.ok) { + const errorText = await response.text(); + if (useOrigin) + throw new Error(`Request failed with status ${response.status}: ${errorText}`); + return res(null, `Request failed with status ${response.status}: ${errorText}`, response.status); + } + + const contentType = response.headers.get('Content-Type'); + if (contentType && contentType.includes('application/json')) { + const values = await response.json(); + return res(values); + } else { + const text = await response.text(); + return res(text); + } + } + makeUrl(url: string = '', version = '/api/v1'): string { + const baseUrl = this.baseURL + version; + if (!url) return baseUrl; + if (url && url.startsWith('http')) { + return url; + } + if (url.startsWith('/')) { + return baseUrl + url; + } + return baseUrl + '/' + url; + } + get({ url, ...REST }: RequestOptions): Promise { + const fullUrl = this.makeUrl(url); + return this.request({ url: fullUrl, method: 'GET', ...REST }); + } + post({ url, ...REST }: RequestOptions): Promise { + const fullUrl = this.makeUrl(url); + return this.request({ url: fullUrl, method: 'POST', ...REST }); + } + put({ url, ...REST }: RequestOptions): Promise { + const fullUrl = this.makeUrl(url); + return this.request({ url: fullUrl, method: 'PUT', ...REST }); + } + delete({ url, ...REST }: RequestOptions): Promise { + const fullUrl = this.makeUrl(url); + return this.request({ url: fullUrl, method: 'DELETE', ...REST }); + } + patch({ url, ...REST }: RequestOptions): Promise { + const fullUrl = this.makeUrl(url); + return this.request({ url: fullUrl, method: 'PATCH', ...REST }); + } + /** + * 通过 PUT 请求上传文件内容 + * @param data 包含 URL、token 和文件内容 + * @returns 上传结果 + */ + async putFile(data: { url: string, token: string, content: string | Buffer }): Promise { + return this.request({ + url: data.url, + method: 'PUT', + body: data.content, + headers: { + 'Authorization': `Bearer ${data.token}`, + 'Content-Type': 'application/octet-stream' + } + }); + } +} + +export type Result = { + code: number; + message: string; + data: T +}; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..0eedf8c --- /dev/null +++ b/src/index.ts @@ -0,0 +1,29 @@ +import { GiteaRepo } from './repo'; +import { GiteaIssue } from './issue'; +import { GiteaUser } from './user'; +import { GiteaOrg } from './org'; +import { GiteaCore, CNBCoreOptions, Result } from './core/core'; + +export class Gitea { + repo: GiteaRepo; + issue: GiteaIssue; + user: GiteaUser; + org: GiteaOrg; + constructor(options: CNBCoreOptions) { + const core = new GiteaCore(options); + this.repo = new GiteaRepo(core); + this.issue = new GiteaIssue(core); + this.user = new GiteaUser(core); + this.org = new GiteaOrg(core); + } +} + +export { + GiteaRepo, + GiteaIssue, + GiteaUser, + GiteaOrg, + GiteaCore, + CNBCoreOptions, + Result, +} \ No newline at end of file diff --git a/src/issue/index.ts b/src/issue/index.ts new file mode 100644 index 0000000..e1d0f95 --- /dev/null +++ b/src/issue/index.ts @@ -0,0 +1,172 @@ +import { GiteaCore } from "../core/core"; + +export interface CreateIssueOptions { + title: string; + body?: string; + assignee?: string; + milestone?: number; + labels?: number[]; + due_date?: string; +} + +export interface UpdateIssueOptions { + title?: string; + body?: string; + assignee?: string; + milestone?: number | null; + state?: 'open' | 'closed'; + due_date?: string | null; +} + +export interface CreateCommentOptions { + body: string; + attachments?: Array<{ + name: string; + url: string; + }>; +} + +export class GiteaIssue extends GiteaCore { + /** + * 获取仓库问题列表 + */ + async listIssues(owner: string, repo: string, options?: { + page?: number; + limit?: number; + state?: 'open' | 'closed' | 'all'; + labels?: string; + milestone?: number; + assignee?: string; + since?: string; + before?: string; + }) { + const url = this.makeUrl(`/repos/${owner}/${repo}/issues`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 获取单个问题 + */ + async getIssue(owner: string, repo: string, index: number) { + const url = this.makeUrl(`/repos/${owner}/${repo}/issues/${index}`); + return this.request({ url }); + } + + /** + * 创建问题 + */ + async createIssue(owner: string, repo: string, data: CreateIssueOptions) { + const url = this.makeUrl(`/repos/${owner}/${repo}/issues`); + return this.request({ url, method: 'POST', data }); + } + + /** + * 更新问题 + */ + async updateIssue(owner: string, repo: string, index: number, data: UpdateIssueOptions) { + const url = this.makeUrl(`/repos/${owner}/${repo}/issues/${index}`); + return this.request({ url, method: 'PATCH', data }); + } + + /** + * 删除问题 + */ + async deleteIssue(owner: string, repo: string, index: number) { + const url = this.makeUrl(`/repos/${owner}/${repo}/issues/${index}`); + return this.request({ url, method: 'DELETE' }); + } + + /** + * 获取问题的评论列表 + */ + async listComments(owner: string, repo: string, index: number, options?: { + page?: number; + limit?: number; + since?: string; + }) { + const url = this.makeUrl(`/repos/${owner}/${repo}/issues/${index}/comments`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 创建评论 + */ + async createComment(owner: string, repo: string, index: number, data: CreateCommentOptions) { + const url = this.makeUrl(`/repos/${owner}/${repo}/issues/${index}/comments`); + return this.request({ url, method: 'POST', data }); + } + + /** + * 更新评论 + */ + async updateComment(owner: string, repo: string, commentId: number, body: string) { + const url = this.makeUrl(`/repos/${owner}/${repo}/issues/comments/${commentId}`); + return this.request({ url, method: 'PATCH', data: { body } }); + } + + /** + * 删除评论 + */ + async deleteComment(owner: string, repo: string, commentId: number) { + const url = this.makeUrl(`/repos/${owner}/${repo}/issues/comments/${commentId}`); + return this.request({ url, method: 'DELETE' }); + } + + /** + * 获取标签列表 + */ + async listLabels(owner: string, repo: string) { + const url = this.makeUrl(`/repos/${owner}/${repo}/labels`); + return this.request({ url, method: 'GET' }); + } + + /** + * 创建标签 + */ + async createLabel(owner: string, repo: string, data: { + name: string; + color: string; + description?: string; + }) { + const url = this.makeUrl(`/repos/${owner}/${repo}/labels`); + return this.request({ url, method: 'POST', data }); + } + + /** + * 删除标签 + */ + async deleteLabel(owner: string, repo: string, id: number) { + const url = this.makeUrl(`/repos/${owner}/${repo}/labels/${id}`); + return this.request({ url, method: 'DELETE' }); + } + + /** + * 获取里程碑列表 + */ + async listMilestones(owner: string, repo: string, options?: { + state?: 'open' | 'closed' | 'all'; + }) { + const url = this.makeUrl(`/repos/${owner}/${repo}/milestones`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 创建里程碑 + */ + async createMilestone(owner: string, repo: string, data: { + title: string; + description?: string; + due_date?: string; + }) { + const url = this.makeUrl(`/repos/${owner}/${repo}/milestones`); + return this.request({ url, method: 'POST', data }); + } + + /** + * 关闭里程碑 + */ + async closeMilestone(owner: string, repo: string, id: number) { + const url = this.makeUrl(`/repos/${owner}/${repo}/milestones/${id}`); + return this.request({ url, method: 'DELETE' }); + } +} diff --git a/src/org/index.ts b/src/org/index.ts new file mode 100644 index 0000000..9b99d2d --- /dev/null +++ b/src/org/index.ts @@ -0,0 +1,227 @@ +import { GiteaCore } from "../core/core"; + +export interface CreateOrgOptions { + username: string; + full_name?: string; + description?: string; + website?: string; + location?: string; + visibility?: 'public' | 'limited' | 'private'; +} + +export interface UpdateOrgOptions { + full_name?: string; + description?: string; + website?: string; + location?: string; + visibility?: 'public' | 'limited' | 'private'; +} + +export interface CreateTeamOptions { + name: string; + description?: string; + permission?: 'read' | 'write' | 'admin'; + repositories?: string[]; + includes_all_repositories?: boolean; +} + +export interface UpdateTeamOptions { + name?: string; + description?: string; + permission?: 'read' | 'write' | 'admin'; +} + +export class GiteaOrg extends GiteaCore { + /** + * 获取当前用户所属组织列表 + */ + async listOrgs(options?: { + page?: number; + limit?: number; + }) { + const url = this.makeUrl(`/user/orgs`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 获取指定组织信息 + */ + async getOrg(org: string) { + const url = this.makeUrl(`/orgs/${org}`); + return this.request({ url }); + } + + /** + * 创建组织 + */ + async createOrg(data: CreateOrgOptions) { + const url = this.makeUrl(`/admin/users/${data.username}/orgs`); + return this.request({ url, method: 'POST', data }); + } + + /** + * 更新组织信息 + */ + async updateOrg(org: string, data: UpdateOrgOptions) { + const url = this.makeUrl(`/orgs/${org}`); + return this.request({ url, method: 'PATCH', data }); + } + + /** + * 删除组织 + */ + async deleteOrg(org: string) { + const url = this.makeUrl(`/orgs/${org}`); + return this.request({ url, method: 'DELETE' }); + } + + /** + * 获取组织成员列表 + */ + async listMembers(org: string, options?: { + page?: number; + limit?: number; + }) { + const url = this.makeUrl(`/orgs/${org}/members`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 获取组织成员信息 + */ + async getMember(org: string, username: string) { + const url = this.makeUrl(`/orgs/${org}/members/${username}`); + return this.request({ url }); + } + + /** + * 添加组织成员 + */ + async addMember(org: string, username: string) { + const url = this.makeUrl(`/orgs/${org}/members/${username}`); + return this.request({ url, method: 'PUT' }); + } + + /** + * 删除组织成员 + */ + async removeMember(org: string, username: string) { + const url = this.makeUrl(`/orgs/${org}/members/${username}`); + return this.request({ url, method: 'DELETE' }); + } + + /** + * 获取组织仓库列表 + */ + async listRepos(org: string, options?: { + page?: number; + limit?: number; + type?: 'all' | 'fork' | 'source' | 'mirror'; + }) { + const url = this.makeUrl(`/orgs/${org}/repos`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 创建组织仓库 + */ + async createRepo(org: string, data: { + name: string; + description?: string; + private?: boolean; + gitignores?: string; + license?: string; + readme?: string; + }) { + const url = this.makeUrl(`/orgs/${org}/repos`); + return this.request({ url, method: 'POST', data }); + } + + /** + * 获取团队列表 + */ + async listTeams(org: string) { + const url = this.makeUrl(`/orgs/${org}/teams`); + return this.request({ url, method: 'GET' }); + } + + /** + * 获取团队信息 + */ + async getTeam(org: string, teamId: number) { + const url = this.makeUrl(`/orgs/${org}/teams/${teamId}`); + return this.request({ url }); + } + + /** + * 创建团队 + */ + async createTeam(org: string, data: CreateTeamOptions) { + const url = this.makeUrl(`/orgs/${org}/teams`); + return this.request({ url, method: 'POST', data }); + } + + /** + * 更新团队信息 + */ + async updateTeam(org: string, teamId: number, data: UpdateTeamOptions) { + const url = this.makeUrl(`/orgs/${org}/teams/${teamId}`); + return this.request({ url, method: 'PATCH', data }); + } + + /** + * 删除团队 + */ + async deleteTeam(org: string, teamId: number) { + const url = this.makeUrl(`/orgs/${org}/teams/${teamId}`); + return this.request({ url, method: 'DELETE' }); + } + + /** + * 获取团队成员列表 + */ + async listTeamMembers(org: string, teamId: number) { + const url = this.makeUrl(`/orgs/${org}/teams/${teamId}/members`); + return this.request({ url, method: 'GET' }); + } + + /** + * 添加团队成员 + */ + async addTeamMember(org: string, teamId: number, username: string) { + const url = this.makeUrl(`/orgs/${org}/teams/${teamId}/members/${username}`); + return this.request({ url, method: 'PUT' }); + } + + /** + * 删除团队成员 + */ + async removeTeamMember(org: string, teamId: number, username: string) { + const url = this.makeUrl(`/orgs/${org}/teams/${teamId}/members/${username}`); + return this.request({ url, method: 'DELETE' }); + } + + /** + * 获取团队仓库列表 + */ + async listTeamRepos(org: string, teamId: number) { + const url = this.makeUrl(`/orgs/${org}/teams/${teamId}/repos`); + return this.request({ url, method: 'GET' }); + } + + /** + * 添加团队仓库 + */ + async addTeamRepo(org: string, teamId: number, repo: string) { + const url = this.makeUrl(`/orgs/${org}/teams/${teamId}/repos/${repo}`); + return this.request({ url, method: 'PUT' }); + } + + /** + * 删除团队仓库 + */ + async removeTeamRepo(org: string, teamId: number, repo: string) { + const url = this.makeUrl(`/orgs/${org}/teams/${teamId}/repos/${repo}`); + return this.request({ url, method: 'DELETE' }); + } +} diff --git a/src/repo/index.ts b/src/repo/index.ts new file mode 100644 index 0000000..44318fa --- /dev/null +++ b/src/repo/index.ts @@ -0,0 +1,132 @@ +import { GiteaCore } from "../core/core"; + +export interface CreateRepoOptions { + name: string; + description?: string; + private?: boolean; + auto_init?: boolean; + gitignores?: string; + license?: string; + readme?: string; +} + +export interface UpdateRepoOptions { + name?: string; + description?: string; + private?: boolean; + allow_rebase?: boolean; + allow_rebase_explicit?: boolean; + allow_merge_commits?: boolean; + allow_squash_merge?: boolean; + allow_rebase_update?: boolean; + default_branch?: string; +} + +export class GiteaRepo extends GiteaCore { + /** + * 获取仓库信息 + */ + async getRepo(owner: string, repo: string) { + const url = this.makeUrl(`/repos/${owner}/${repo}`); + return this.request({ url }); + } + + /** + * 创建仓库 + * 格式: name="repo" -> 当前用户 + * 格式: name="org/repo" -> 组织 + */ + async createRepo(data: CreateRepoOptions) { + const name = data.name; + const parts = name.split('/'); + + if (parts.length === 2) { + const [orgOrUser, repoName] = parts; + const url = this.makeUrl(`/${orgOrUser}/repos`); + return this.request({ url, method: 'POST', data: { ...data, name: repoName } }); + } + + const url = this.makeUrl(`/user/repos`); + return this.request({ url, method: 'POST', data }); + } + + /** + * 更新仓库 + */ + async updateRepo(owner: string, repo: string, data: UpdateRepoOptions) { + const url = this.makeUrl(`/repos/${owner}/${repo}`); + return this.request({ url, method: 'PATCH', data }); + } + + /** + * 删除仓库 + */ + async deleteRepo(owner: string, repo: string) { + const url = this.makeUrl(`/repos/${owner}/${repo}`); + return this.request({ url, method: 'DELETE' }); + } + + /** + * 获取仓库列表 + */ + async listRepos(options?: { + page?: number; + limit?: number; + type?: 'all' | 'owner' | 'public' | 'private'; + }) { + const url = this.makeUrl(`/user/repos`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 获取组织仓库列表 + */ + async listOrgRepos(org: string, options?: { + page?: number; + limit?: number; + type?: 'all' | 'fork' | 'source' | 'mirror'; + }) { + const url = this.makeUrl(`/orgs/${org}/repos`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 获取仓库内容(文件或目录) + */ + async getContents(owner: string, repo: string, path: string, options?: { + ref?: string; + }) { + const url = this.makeUrl(`/repos/${owner}/${repo}/contents/${path}`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 获取仓库分支列表 + */ + async listBranches(owner: string, repo: string, options?: { + page?: number; + limit?: number; + }) { + const url = this.makeUrl(`/repos/${owner}/${repo}/branches`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 获取仓库标签列表 + */ + async listTags(owner: string, repo: string, options?: { + page?: number; + limit?: number; + }) { + const url = this.makeUrl(`/repos/${owner}/${repo}/tags`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * Fork 仓库 + */ + async forkRepo(owner: string, repo: string, organization?: string) { + const url = this.makeUrl(`/repos/${owner}/${repo}/forks`); + return this.request({ url, method: 'POST', data: { organization } }); + } +} diff --git a/src/user/index.ts b/src/user/index.ts new file mode 100644 index 0000000..a140b62 --- /dev/null +++ b/src/user/index.ts @@ -0,0 +1,141 @@ +import { GiteaCore } from "../core/core"; + +export interface UpdateUserOptions { + email?: string; + full_name?: string; + location?: string; + website?: string; + description?: string; + visibility?: 'public' | 'limited' | 'private'; +} + +export class GiteaUser extends GiteaCore { + /** + * 获取当前用户信息 + */ + async getCurrentUser() { + const url = this.makeUrl(`/user`); + return this.request({ url }); + } + + /** + * 获取指定用户信息 + */ + async getUser(username: string) { + const url = this.makeUrl(`/users/${username}`); + return this.request({ url }); + } + + /** + * 更新当前用户信息 + */ + async updateCurrentUser(data: UpdateUserOptions) { + const url = this.makeUrl(`/user`); + return this.request({ url, method: 'PATCH', data }); + } + + /** + * 获取当前用户邮箱列表 + */ + async listEmails() { + const url = this.makeUrl(`/user/emails`); + return this.request({ url, method: 'GET' }); + } + + /** + * 添加邮箱 + */ + async addEmail(email: string) { + const url = this.makeUrl(`/user/emails`); + return this.request({ url, method: 'POST', data: { email } }); + } + + /** + * 删除邮箱 + */ + async deleteEmail(email: string) { + const url = this.makeUrl(`/user/emails`); + return this.request({ url, method: 'DELETE', data: { email } }); + } + + /** + * 获取当前用户关注者 + */ + async listFollowers(username: string, options?: { + page?: number; + limit?: number; + }) { + const url = this.makeUrl(`/users/${username}/followers`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 获取用户关注的用户列表 + */ + async listFollowing(username: string, options?: { + page?: number; + limit?: number; + }) { + const url = this.makeUrl(`/users/${username}/following`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 关注用户 + */ + async followUser(username: string) { + const url = this.makeUrl(`/user/following/${username}`); + return this.request({ url, method: 'PUT' }); + } + + /** + * 取消关注用户 + */ + async unfollowUser(username: string) { + const url = this.makeUrl(`/user/following/${username}`); + return this.request({ url, method: 'DELETE' }); + } + + /** + * 检查是否关注用户 + */ + async checkFollowing(username: string, target: string) { + const url = this.makeUrl(`/users/${username}/following/${target}`); + return this.request({ url }); + } + + /** + * 获取用户仓库列表 + */ + async listRepos(username: string, options?: { + page?: number; + limit?: number; + type?: 'all' | 'owner' | 'public' | 'private'; + sort?: 'created' | 'updated' | 'pushed' | 'full_name'; + }) { + const url = this.makeUrl(`/users/${username}/repos`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 获取用户活动流 + */ + async listActivities(username: string, options?: { + page?: number; + limit?: number; + }) { + const url = this.makeUrl(`/users/${username}/activities`); + return this.request({ url, method: 'GET', params: options }); + } + + /** + * 搜索用户 + */ + async searchUsers(query: string, options?: { + page?: number; + limit?: number; + }) { + const url = this.makeUrl(`/users/search`); + return this.request({ url, method: 'GET', params: { q: query, ...options } }); + } +} diff --git a/test/common.ts b/test/common.ts new file mode 100644 index 0000000..6aed193 --- /dev/null +++ b/test/common.ts @@ -0,0 +1,13 @@ +import { GiteaRepo } from "../src/repo"; +import { useKey } from "@kevisual/context"; +const repo = new GiteaRepo({ + token: useKey("GITEA_TOKEN"), + baseURL: useKey("GITEA_URL"), +}); + +const createRepo = async () => { + const res = await repo.createRepo({ name: "kevisual/test-repo", description: "This is a test repository", private: false }); + console.log('createRepo', res); +}; + +createRepo(); \ No newline at end of file