commit 94c666a9936bba2a4faad782c618b3bd907419f3 Author: abearxiong Date: Thu Feb 19 20:38:03 2026 +0800 初始化 Gitea 项目结构,添加核心 API 封装和相关配置文件 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