diff --git a/app/controllers/projects/base_controller.rb b/app/controllers/projects/base_controller.rb index 9811a2136..240bc91f1 100644 --- a/app/controllers/projects/base_controller.rb +++ b/app/controllers/projects/base_controller.rb @@ -4,4 +4,7 @@ class Projects::BaseController < ApplicationController before_action :load_project before_action :load_repository + def require_manager! + return render_forbidden('你没有权限操作') unless current_user.admin? || @project.manager?(current_user) + end end diff --git a/app/controllers/projects/webhooks_controller.rb b/app/controllers/projects/webhooks_controller.rb new file mode 100644 index 000000000..9f36da206 --- /dev/null +++ b/app/controllers/projects/webhooks_controller.rb @@ -0,0 +1,116 @@ +class Projects::WebhooksController < Projects::BaseController + before_action :require_manager! + before_action :find_webhook, only:[:edit, :update, :destroy, :tasks, :test] + + def index + @webhooks = @project.webhooks + @webhooks = kaminari_paginate(@webhooks) + end + + def create + ActiveRecord::Base.transaction do + return render_error("webhooks数量已到上限!请删除暂不使用的webhooks以进行添加操作") if @project.webhooks.size > 19 + return render_error("参数错误.") unless webhook_params.present? + form = Projects::Webhooks::CreateForm.new(webhook_params) + return render json: {status: -1, message: form.errors} unless form.validate! + response = Gitea::Repository::Webhooks::CreateService.new(operating_token, @project&.owner&.login, @project&.identifier, gitea_webhooks_params).call + if response[0] == 201 + @webhook = response[2] + else + render_error("创建失败.") + end + end + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + def edit + + end + + def update + return render_error("参数错误.") unless webhook_params.present? + form = Projects::Webhooks::CreateForm.new(webhook_params) + return render json: {status: -1, message: form.errors} unless form.validate! + response = Gitea::Repository::Webhooks::UpdateService.call(operating_token, @project&.owner&.login, @project&.identifier, @webhook.id, gitea_webhooks_params) + if response[0] == 200 + @webhook = response[2] + render_ok + else + render_error("更新失败.") + end + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + def destroy + response = Gitea::Repository::Webhooks::DeleteService.call(operating_token, @project&.owner&.login, @project&.identifier, @webhook.id) + if response[0] == 204 + @webhook = response[2] + render_ok + else + render_error("删除失败.") + end + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + def tasks + @tasks = @webhook.tasks.where(is_delivered: true).order("delivered desc") + @tasks = kaminari_paginate(@tasks) + end + + def test + ActiveRecord::Base.transaction do + response = Gitea::Repository::Webhooks::TestService.call(operating_token, @project&.owner&.login, @project&.identifier, @webhook.id) + if response[0] == 204 + render_ok + else + render_error("测试推送失败.") + end + end + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + private + def find_webhook + @webhook = @project.webhooks.find_by_id(params[:id]) + return render_not_found if @webhook.nil? + end + + def webhook_params + params.require(:webhook).permit(:url, :type, :http_method, :content_type, :secret, :active, :branch_filter, events: []) + end + + def webhook_type + webhook_params.fetch(:type, "gitea") + end + + def webhook_branch_filter + webhook_params.fetch(:branch_filter, "*") + end + + def gitea_webhooks_params + { + active: webhook_params[:active], + branch_filter: webhook_branch_filter, + config: { + content_type: webhook_params[:content_type], + url: webhook_params[:url], + http_method: webhook_params[:http_method], + secret: webhook_params[:secret] + }, + events: webhook_params[:events], + type: webhook_type, + } + end + + def operating_token + @project.member?(current_user) ? current_user.gitea_token : @project&.owner&.gitea_token + end +end \ No newline at end of file diff --git a/app/docs/slate/source/includes/_repositories.md b/app/docs/slate/source/includes/_repositories.md index 40d3ba2ff..1552655e4 100644 --- a/app/docs/slate/source/includes/_repositories.md +++ b/app/docs/slate/source/includes/_repositories.md @@ -867,3 +867,674 @@ await octokit.request('GET /api/jasder/jasder_test/sub_entries.json') + +## 获取仓库webhooks列表 +获取仓库webhooks列表 + +> 示例: + +```shell +curl -X GET \ +http://localhost:3000/api/yystopf/ceshi/webhooks.json +``` + +```javascript +await octokit.request('GET /api/yystopf/ceshi/webhooks.json') +``` + +### HTTP 请求 +`GET /api/:owner/:repo/webhooks.json` + +### 请求参数: +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| |string |用户登录名 | +|repo |是| |string |项目标识identifier | + + +### 返回字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |id | +|url |string|地址| +|http_method |string|请求方式| +|is_active |bool |是否激活| +|type |string|类型| +|last_status |string|最后一次推送的状态| +|create_time |string|创建时间| + + +> 返回的JSON示例: + +```json +{ + "total_count": 4, + "webhooks": [ + { + "id": 2, + "url": "https://oapi.dingtalk.com/robot/send?access_token=7e1e19d0eddb6a5e33c5c2c4e66f4c88f9437184b9ed2c2653194c6374c7d513", + "http_method": "", + "is_active": true, + "type": "dingtalk", + "last_status": "succeed", + "create_time": "2021-07-12 10:50:07" + }, + { + "id": 3, + "url": "http://localhost:3000", + "http_method": "GET", + "is_active": true, + "type": "gitea", + "last_status": "succeed", + "create_time": "2021-07-26 10:03:45" + }, + { + "id": 4, + "url": "http://localhost:10081", + "http_method": "POST", + "is_active": true, + "type": "gitea", + "last_status": "waiting", + "create_time": "2021-07-26 16:56:53" + }, + { + "id": 5, + "url": "http://localhost:3001", + "http_method": "POST", + "is_active": true, + "type": "gitea", + "last_status": "fail", + "create_time": "2021-07-26 16:58:23" + } + ] +} +``` + + +## 获取仓库单个webhook +获取仓库单个webhook + +> 示例: + +```shell +curl -X GET \ +http://localhost:3000/api/yystopf/ceshi/webhooks/3/edit.json +``` + +```javascript +await octokit.request('GET /api/yystopf/ceshi/webhooks/3/edit.json') +``` + +### HTTP 请求 +`GET /api/:owner/:repo/webhooks/:id/edit.json` + +### 请求参数: +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| |string |用户登录名 | +|repo |是| |string |项目标识identifier | +|id |是||integer|webhook ID| + + +### 返回字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |id | +|url |string|地址| +|content_type |string|POST Content Type| +|http_method |string|请求方式| +|secret| |string|密钥| +|is_active |bool |是否激活| +|type |string|类型| +|last_status |string|最后一次推送的状态, waiting 等待,fail 失败,succeed 成功| +|branch_filter |string|分支过滤| +|events |string|触发条件| +|create_time |string|创建时间| + + +参数| 含义| +--------- | ------- | ------- | +|create|创建分支或标签| +|delete|分支或标签删除| +|fork|仓库被fork| +|push|git仓库推送| +|issue|易修已打开、已关闭、已重新打开或编辑| +|issue_assign|易修被指派| +|issue_label|易修标签被更新或删除| +|issue_milestone|易修被收入里程碑| +|issue_comment|易修评论| +|pull_request|合并请求| +|pull_request_assign|合并请求被指派| +|pull_request_label|合并请求被贴上标签| +|pull_request_milestone|合并请求被记录于里程碑中| +|pull_request_comment|合并请求被评论| +|pull_request_review_approved|合并请求被批准| +|pull_request_review_rejected|合并请求被拒绝| +|pull_request_review_comment|合并请求被提出审查意见| +|pull_request_sync|合并请求被同步| +|repository|创建或删除仓库| +|release|版本发布| + + +> 返回的JSON示例: + +```json +{ + "id": 3, + "http_method": "GET", + "content_type": "form", + "url": "http://localhost:3000", + "secret": "123456", + "last_status": "succeed", + "is_active": true, + "type": "gitea", + "create_time": "2021-07-26 10:03:45", + "branch_filter": "*", + "events": [ + "create", + "delete", + "fork", + "issues", + "issue_assign", + "issue_label", + "issue_milestone", + "issue_comment", + "push", + "pull_request", + "pull_request_assign", + "pull_request_label", + "pull_request_milestone", + "pull_request_comment", + "pull_request_review", + "pull_request_sync", + "repository", + "release" + ] +} +``` + + +## 添加仓库webhook +添加仓库webhook + +> 示例: + +```shell +curl -X POST \ +http://localhost:3000/api/yystopf/ceshi/webhooks.json +``` + +```javascript +await octokit.request('POST /api/yystopf/ceshi/webhooks.json') +``` + +### HTTP 请求 +`POST /api/:owner/:repo/webhooks.json` + +### 请求参数: +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| | string |用户登录名 | +|repo |是| | string |项目标识identifier | +|webhook.url |是| | string |目标url | +|webhook.type |否| | string |类型| +|webhook.http_method |是| | string | http方法, POST和GET | +|webhook.content_type |是| | string | POST Content Type | +|webhook.secret |否| | string |密钥文本| +|webhook.active |是| | bool | 是否激活| +|webhook.branch_filter|否| |string|分支过滤| +|webhook.events |否| |array|触发事件| + +触发事件字段说明 + +参数| 含义| +--------- | ------- | ------- | +|create|创建分支或标签| +|delete|分支或标签删除| +|fork|仓库被fork| +|push|git仓库推送| +|issue|易修已打开、已关闭、已重新打开或编辑| +|issue_assign|易修被指派| +|issue_label|易修标签被更新或删除| +|issue_milestone|易修被收入里程碑| +|issue_comment|易修评论| +|pull_request|合并请求| +|pull_request_assign|合并请求被指派| +|pull_request_label|合并请求被贴上标签| +|pull_request_milestone|合并请求被记录于里程碑中| +|pull_request_comment|合并请求被评论| +|pull_request_review_approved|合并请求被批准| +|pull_request_review_rejected|合并请求被拒绝| +|pull_request_review_comment|合并请求被提出审查意见| +|pull_request_sync|合并请求被同步| +|repository|创建或删除仓库| +|release|版本发布| + + +> 请求的JSON示例: + +```json +{ + "active": true, + "content_type": "json", + "http_method": "GET", + "secret": "123456", + "url": "http://localhost:10000", + "branch_filter": "*", + "events": ["push"] +} +``` + +### 返回字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |id | +|url |string|地址| +|content_type |string|POST Content Type| +|is_active |bool |是否激活| +|type |string|类型| +|events | array|触发事件 | +|create_time |string|创建时间| + + +> 返回的JSON示例: + +```json +{ + "id": 18, + "type": "gitea", + "content_type": "json", + "url": "http://localhost:10000", + "events": [ + "push" + ], + "active": true, + "create_time": "2021-07-26 18:53:43" +} +``` + + +## 更新仓库webhook +更新仓库webhook + +> 示例: + +```shell +curl -X PATCH \ +http://localhost:3000/api/yystopf/ceshi/webhooks/7.json +``` + +```javascript +await octokit.request('PATCH /api/yystopf/ceshi/webhooks/7.json') +``` + +### HTTP 请求 +`PATCH /api/:owner/:repo/webhooks/:id.json` + +### 请求参数: +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| | string |用户登录名 | +|repo |是| | string |项目标识identifier | +|id |是| | string |webhook id | +|webhook.url |是| | string |目标url | +|webhook.type |否| | string |类型| +|webhook.http_method |是| | string | http方法, POST和GET | +|webhook.content_type |是| | string | POST Content Type | +|webhook.secret |否| | string |密钥文本| +|webhook.active |是| | bool | 是否激活| +|webhook.branch_filter|否| |string|分支过滤| +|webhook.events |否| |array|触发事件| + +触发事件字段说明 + +参数| 含义| +--------- | ------- | ------- | +|create|创建分支或标签| +|delete|分支或标签删除| +|fork|仓库被fork| +|push|git仓库推送| +|issue|易修已打开、已关闭、已重新打开或编辑| +|issue_assign|易修被指派| +|issue_label|易修标签被更新或删除| +|issue_milestone|易修被收入里程碑| +|issue_comment|易修评论| +|pull_request|合并请求| +|pull_request_assign|合并请求被指派| +|pull_request_label|合并请求被贴上标签| +|pull_request_milestone|合并请求被记录于里程碑中| +|pull_request_comment|合并请求被评论| +|pull_request_review_approved|合并请求被批准| +|pull_request_review_rejected|合并请求被拒绝| +|pull_request_review_comment|合并请求被提出审查意见| +|pull_request_sync|合并请求被同步| +|repository|创建或删除仓库| +|release|版本发布| + + +> 请求的JSON示例: + +```json +{ + "active": true, + "content_type": "json", + "http_method": "GET", + "secret": "123456", + "url": "http://localhost:10000", + "branch_filter": "*", + "events": ["push"] +} +``` + +### 返回字段说明: + +> 返回的JSON示例: + +```json +{ + "status": 0, + "message": "success" +} +``` + + + +## 删除仓库webhook +删除仓库webhook + +> 示例: + +```shell +curl -X DELETE \ +http://localhost:3000/api/yystopf/ceshi/webhooks/7.json +``` + +```javascript +await octokit.request('DELETE /api/yystopf/ceshi/webhooks/7.json') +``` + +### HTTP 请求 +`DELETE /api/:owner/:repo/webhooks/:id.json` + +### 请求参数: +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| | string |用户登录名 | +|repo |是| | string |项目标识identifier | +|id |是| | string |webhook id | + +### 返回字段说明: + +> 返回的JSON示例: + +```json +{ + "status": 0, + "message": "success" +} +``` + + +## 获取仓库webhook的历史推送列表 +获取仓库webhook的历史推送列表 + +> 示例: + +```shell +curl -X GET \ +http://localhost:3000/api/yystopf/ceshi/webhooks/3/tasks.json +``` + +```javascript +await octokit.request('GET /api/yystopf/ceshi/webhooks/3/tasks.json') +``` + +### HTTP 请求 +`GET /api/:owner/:repo/webhooks/:id/tasks.json` + +### 请求参数: +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| |string |用户登录名 | +|repo |是| |string |项目标识identifier | +|id |是| |integer |webhook ID| + +### 返回字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |id | +|uuid |string|推送uuid| +|type |string|类型| +|is_succeed |bool|是否推送成功| +|is_delivered |bool|是否完成推送| +|payload_content |json|请求主体内容| +|request_content |json|请求内容,头部等等| +|reponse_content |json|响应内容,状态,头部,主体等等| +|delivered_time |string|推送时间| + + +> 返回的JSON示例: + +```json +{ + "total_count": 6, + "tasks": [ + { + "id": 20, + "type": "gitea", + "uuid": "99aa2c23-6884-4c44-9020-5469320aa408", + "is_succeed": true, + "is_delivered": true, + "payload_content": { + "secret": "123456", + "ref": "refs/heads/master", + "before": "feb48e31362787a7620b53d4df3c4effddbb6f0b", + "after": "feb48e31362787a7620b53d4df3c4effddbb6f0b", + "compare_url": "", + "commits": [ + { + "id": "feb48e31362787a7620b53d4df3c4effddbb6f0b", + "message": "fix\n", + "url": "http://localhost:10081/yystopf/ceshi/commit/feb48e31362787a7620b53d4df3c4effddbb6f0b", + "author": { + "name": "viletyy", + "email": "yystopf@163.com", + "username": "root" + }, + "committer": { + "name": "viletyy", + "email": "yystopf@163.com", + "username": "root" + }, + "verification": { + "verified": false, + "reason": "gpg.error.not_signed_commit", + "signature": "", + "signer": null, + "payload": "" + }, + "timestamp": "2021-07-26T13:52:13+08:00", + "added": null, + "removed": null, + "modified": null + } + ], + "head_commit": null, + "repository": { + "id": 2, + "owner": { + "id": 3, + "login": "yystopf", + "full_name": "", + "email": "yystopf@forge.com", + "avatar_url": "http://localhost:10081/user/avatar/yystopf/-1", + "language": "zh-CN", + "is_admin": true, + "last_login": "2021-07-21T18:38:21+08:00", + "created": "2021-06-03T14:50:25+08:00", + "username": "yystopf" + }, + "name": "ceshi", + "full_name": "yystopf/ceshi", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 3846, + "html_url": "http://localhost:10081/yystopf/ceshi", + "ssh_url": "virus@localhost:10081:yystopf/ceshi.git", + "clone_url": "http://localhost:10081/yystopf/ceshi.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 1, + "watchers_count": 1, + "open_issues_count": 0, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "master", + "archived": false, + "created_at": "2021-06-03T15:15:30+08:00", + "updated_at": "2021-07-26T13:52:16+08:00", + "permissions": { + "admin": false, + "push": false, + "pull": false + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "has_pull_requests": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "avatar_url": "", + "internal": false + }, + "pusher": { + "id": 0, + "login": "yystopf", + "full_name": "", + "email": "yystopf@forge.com", + "avatar_url": "http://localhost:10081/user/avatar/yystopf/-1", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2021-06-03T14:50:25+08:00", + "username": "yystopf" + }, + "sender": { + "id": 0, + "login": "yystopf", + "full_name": "", + "email": "yystopf@forge.com", + "avatar_url": "http://localhost:10081/user/avatar/yystopf/-1", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2021-06-03T14:50:25+08:00", + "username": "yystopf" + } + }, + "request_content": { + "headers": { + "X-GitHub-Delivery": "99aa2c23-6884-4c44-9020-5469320aa408", + "X-GitHub-Event": "push", + "X-Gitea-Delivery": "99aa2c23-6884-4c44-9020-5469320aa408", + "X-Gitea-Event": "push", + "X-Gitea-Signature": "34a01edcd952ff6410ff6ebc946471161bde74aff86171f21621d2c2c4130f66", + "X-Gogs-Delivery": "99aa2c23-6884-4c44-9020-5469320aa408", + "X-Gogs-Event": "push", + "X-Gogs-Signature": "34a01edcd952ff6410ff6ebc946471161bde74aff86171f21621d2c2c4130f66" + } + }, + "response_content": { + "status": 200, + "headers": { + "Cache-Control": "no-store, must-revalidate, private, max-age=0", + "Content-Length": "2556", + "Content-Type": "text/html; charset=utf-8", + "Referrer-Policy": "strict-origin-when-cross-origin", + "Set-Cookie": "__profilin=p%3Dt; path=/; HttpOnly", + "Vary": "Origin", + "X-Content-Type-Options": "nosniff", + "X-Download-Options": "noopen", + "X-Frame-Options": "SAMEORIGIN", + "X-Miniprofiler-Ids": "9ynvpncz5xm0rpgorb5y,hgggd9mv6lr4a9drcrlr,j7zqlx2vy5aji2vtgoba,f1ktsmh3jxvq0z2hf612,mih3dvgvlqhi3zy8lf2x,5k1qbkvbnru8mye9cest,tj6ern8w6awqf2zsimbr,9isaehvubivd52wo5p9v,1rzfhtq1nhuwbgy9p76g,z0xzidzyywna0y7a69m0,hzoklky92ycjqt42gi0s,y0ai7y0t28mcn8x0py2x,322il7nadinp51mw2r5m,m6dukftfsh6tjcxzp1gq,667wlqbytfwbrirnmma1,jcehj3dl8lkw8gk510cr", + "X-Miniprofiler-Original-Cache-Control": "max-age=0, private, must-revalidate", + "X-Permitted-Cross-Domain-Policies": "none", + "X-Request-Id": "08bff080-bbb5-4183-b845-81de3d47120a", + "X-Runtime": "0.394766", + "X-Xss-Protection": "1; mode=block" + }, + "body": "
\n" + }, + "delivered_time": "2021-07-28 11:47:29" + } + ] +} +``` + + +## 仓库webhook测试推送 +仓库webhook测试推送 + +> 示例: + +```shell +curl -X POST \ +http://localhost:3000/api/yystopf/ceshi/webhooks/3/test.json +``` + +```javascript +await octokit.request('POST /api/yystopf/ceshi/webhooks/3/test.json') +``` + +### HTTP 请求 +`POST /api/:owner/:repo/webhooks/:id/test.json` + +### 请求参数: +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| | string |用户登录名 | +|repo |是| | string |项目标识identifier | +|id |是| | integer|webhook ID| + + + + +### 返回字段说明: + + +> 返回的JSON示例: + +```json +{ + "status": 0, + "message": "success" +} +``` + \ No newline at end of file diff --git a/app/forms/projects/webhooks/create_form.rb b/app/forms/projects/webhooks/create_form.rb new file mode 100644 index 000000000..75b07b1bd --- /dev/null +++ b/app/forms/projects/webhooks/create_form.rb @@ -0,0 +1,8 @@ +class Projects::Webhooks::CreateForm < BaseForm + attr_accessor :type, :url, :http_method, :content_type, :secret, :events, :active, :branch_filter + + validates :url, format: { with: URI::regexp(%w[http https]), message: "请输入正确的地址" } + validates :active, inclusion: {in: [true, false]} + validates :http_method, inclusion: { in: %w(POST GET), message: "请输入正确的请求方式"} + validates :content_type, inclusion: { in: %w(json form), message: "请输入正确的Content Type"} +end \ No newline at end of file diff --git a/app/models/gitea/public_key.rb b/app/models/gitea/public_key.rb index bc37c3bc7..bb2192358 100644 --- a/app/models/gitea/public_key.rb +++ b/app/models/gitea/public_key.rb @@ -4,6 +4,6 @@ class Gitea::PublicKey < Gitea::Base self.table_name = "public_key" - belongs_to :user, class_name: '::User', foreign_key: :gitea_uid, primary_key: :owner_id, optional: true + belongs_to :user, class_name: '::User', primary_key: :gitea_uid, foreign_key: :owner_id, optional: true end diff --git a/app/models/gitea/webhook.rb b/app/models/gitea/webhook.rb new file mode 100644 index 000000000..f60f56788 --- /dev/null +++ b/app/models/gitea/webhook.rb @@ -0,0 +1,13 @@ +class Gitea::Webhook < Gitea::Base + serialize :events, JSON + self.inheritance_column = nil + + self.table_name = 'webhook' + + has_many :tasks, class_name: "Gitea::WebhookTask", foreign_key: :hook_id + belongs_to :project, class_name: "::Project", primary_key: :gpid, foreign_key: :repo_id, optional: true + + enum hook_task_type: {gogs: 1, slack: 2, gitea: 3, discord: 4, dingtalk: 5, telegram: 6, msteams: 7, feishu: 8, matrix: 9} + enum last_status: {waiting: 0, succeed: 1, fail: 2} + enum content_type: {json: 1, form: 2} +end \ No newline at end of file diff --git a/app/models/gitea/webhook_task.rb b/app/models/gitea/webhook_task.rb new file mode 100644 index 000000000..d19a163aa --- /dev/null +++ b/app/models/gitea/webhook_task.rb @@ -0,0 +1,13 @@ +class Gitea::WebhookTask < Gitea::Base + serialize :payload_content, JSON + serialize :request_content, JSON + serialize :response_content, JSON + + self.inheritance_column = nil + + self.table_name = 'hook_task' + + belongs_to :webhook, class_name: "Gitea::Webhook", foreign_key: :hook_id + + enum type: {gogs: 1, slack: 2, gitea: 3, discord: 4, dingtalk: 5, telegram: 6, msteams: 7, feishu: 8, matrix: 9} +end \ No newline at end of file diff --git a/app/models/project.rb b/app/models/project.rb index a47ee4239..414f5b55a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -122,6 +122,7 @@ class Project < ApplicationRecord has_one :applied_transfer_project,-> { order created_at: :desc }, dependent: :destroy has_many :pinned_projects, dependent: :destroy has_many :has_pinned_users, through: :pinned_projects, source: :user + has_many :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id after_save :check_project_members, :reset_cache_data before_save :set_invite_code diff --git a/app/services/gitea/repository/webhooks/create_service.rb b/app/services/gitea/repository/webhooks/create_service.rb new file mode 100644 index 000000000..33c9a9b0c --- /dev/null +++ b/app/services/gitea/repository/webhooks/create_service.rb @@ -0,0 +1,23 @@ +class Gitea::Repository::Webhooks::CreateService < Gitea::ClientService + attr_reader :token, :owner, :repo, :params + def initialize(token, owner, repo, params) + @token = token + @owner = owner + @repo = repo + @params = params + end + + def call + response = post(url, request_params) + render_response(response) + end + + private + def request_params + Hash.new.merge({token: token, data: params}) + end + + def url + "/repos/#{owner}/#{repo}/hooks".freeze + end +end \ No newline at end of file diff --git a/app/services/gitea/repository/webhooks/delete_service.rb b/app/services/gitea/repository/webhooks/delete_service.rb new file mode 100644 index 000000000..997e9006e --- /dev/null +++ b/app/services/gitea/repository/webhooks/delete_service.rb @@ -0,0 +1,24 @@ +class Gitea::Repository::Webhooks::DeleteService < Gitea::ClientService + attr_reader :token, :owner, :repo, :id + + def initialize(token, owner, repo, id) + @token = token + @owner = owner + @repo = repo + @id = id + end + + def call + response = delete(url, params) + render_response(response) + end + + private + def params + Hash.new.merge(token: token) + end + + def url + "/repos/#{owner}/#{repo}/hooks/#{id}".freeze + end +end \ No newline at end of file diff --git a/app/services/gitea/repository/webhooks/tasks_service.rb b/app/services/gitea/repository/webhooks/tasks_service.rb new file mode 100644 index 000000000..e4c62edb4 --- /dev/null +++ b/app/services/gitea/repository/webhooks/tasks_service.rb @@ -0,0 +1,27 @@ +class Gitea::Repository::Webhooks::TasksService < Gitea::ClientService + attr_reader :token, :owner, :repo, :webhook_id + + # ref: The name of the commit/branch/tag. Default the repository’s default branch (usually master) + # repo_name: the name of repository + def initialize(token, owner, repo, webhook_id) + @token = token + @owner = owner + @repo = repo + @webhook_id = webhook_id + end + + def call + response = get(url, params) + render_response(response) + end + + private + def params + Hash.new.merge(token: user.gitea_token) + end + + def url + "/repos/#{owner}/#{repo}/hooks/#{webhook_id}/hook_tasks".freeze + end + +end diff --git a/app/services/gitea/repository/webhooks/test_service.rb b/app/services/gitea/repository/webhooks/test_service.rb new file mode 100644 index 000000000..7f1837611 --- /dev/null +++ b/app/services/gitea/repository/webhooks/test_service.rb @@ -0,0 +1,24 @@ +class Gitea::Repository::Webhooks::TestService < Gitea::ClientService + attr_reader :token, :owner, :repo, :webhook_id + + def initialize(token, owner, repo, webhook_id) + @token = token + @owner = owner + @repo = repo + @webhook_id = webhook_id + end + + def call + response = post(url, request_params) + render_response(response) + end + + private + def request_params + Hash.new.merge({token: token}) + end + + def url + "/repos/#{owner}/#{repo}/hooks/#{webhook_id}/tests".freeze + end +end \ No newline at end of file diff --git a/app/services/gitea/repository/webhooks/update_service.rb b/app/services/gitea/repository/webhooks/update_service.rb new file mode 100644 index 000000000..6094c6c51 --- /dev/null +++ b/app/services/gitea/repository/webhooks/update_service.rb @@ -0,0 +1,24 @@ +class Gitea::Repository::Webhooks::UpdateService < Gitea::ClientService + attr_reader :token, :owner, :repo, :id, :params + def initialize(token, owner, repo, id, params) + @token = token + @owner = owner + @repo = repo + @id = id + @params = params + end + + def call + response = patch(url, data_params) + render_response(response) + end + + private + def url + "/repos/#{owner}/#{repo}/hooks/#{id}" + end + + def data_params + Hash.new.merge(token: token, data: params).compact + end +end \ No newline at end of file diff --git a/app/views/projects/webhooks/_detail.json.jbuilder b/app/views/projects/webhooks/_detail.json.jbuilder new file mode 100644 index 000000000..2497e5c64 --- /dev/null +++ b/app/views/projects/webhooks/_detail.json.jbuilder @@ -0,0 +1,4 @@ +json.(webhook, :id, :url, :http_method, :is_active) +json.type webhook.hook_task_type +json.last_status webhook.last_status +json.create_time Time.at(webhook.created_unix).strftime("%Y-%m-%d %H:%M:%S") \ No newline at end of file diff --git a/app/views/projects/webhooks/create.json.jbuilder b/app/views/projects/webhooks/create.json.jbuilder new file mode 100644 index 000000000..6d6dde31f --- /dev/null +++ b/app/views/projects/webhooks/create.json.jbuilder @@ -0,0 +1,7 @@ +json.id @webhook["id"] +json.type @webhook["type"] +json.content_type @webhook["config"]["content_type"] +json.url @webhook["config"]["url"] +json.events @webhook["events"] +json.active @webhook["active"] +json.create_time @webhook["created_at"].to_time.strftime("%Y-%m-%d %H:%M:%S") \ No newline at end of file diff --git a/app/views/projects/webhooks/edit.json.jbuilder b/app/views/projects/webhooks/edit.json.jbuilder new file mode 100644 index 000000000..2ee6d24e8 --- /dev/null +++ b/app/views/projects/webhooks/edit.json.jbuilder @@ -0,0 +1,11 @@ +json.id @webhook.id +json.(@webhook, :id, :http_method, :content_type, :url, :secret, :last_status, :is_active) +json.type @webhook.hook_task_type +json.create_time Time.at(@webhook.created_unix).strftime("%Y-%m-%d %H:%M:%S") +event = @webhook.events +json.branch_filter event["branch_filter"] +if event["send_everything"] + json.events event["events"].keys.collect{|i| i == "pull_request" ? i + "_only" : i} +else + json.events event["events"].select{|k, v| v}.keys.collect{|i| i == "pull_request" ? i + "_only" : i} +end diff --git a/app/views/projects/webhooks/index.json.jbuilder b/app/views/projects/webhooks/index.json.jbuilder new file mode 100644 index 000000000..62722acbe --- /dev/null +++ b/app/views/projects/webhooks/index.json.jbuilder @@ -0,0 +1,4 @@ +json.total_count @webhooks.total_count +json.webhooks @webhooks.each do |webhook| + json.partial! 'detail', webhook: webhook +end \ No newline at end of file diff --git a/app/views/projects/webhooks/tasks.json.jbuilder b/app/views/projects/webhooks/tasks.json.jbuilder new file mode 100644 index 000000000..b8aef99f5 --- /dev/null +++ b/app/views/projects/webhooks/tasks.json.jbuilder @@ -0,0 +1,5 @@ +json.total_count @tasks.total_count +json.tasks @tasks.each do |task| + json.(task, :id, :type, :uuid, :is_succeed, :is_delivered, :payload_content, :request_content, :response_content) + json.delivered_time Time.at(task.delivered*10**-9).strftime("%Y-%m-%d %H:%M:%S") +end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 554f44d44..a3cc196ec 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -572,6 +572,12 @@ Rails.application.routes.draw do post :cancel end end + resources :webhooks, except: [:show, :new] do + member do + get :tasks + post :test + end + end scope do get( '/blob/*id/diff', diff --git a/public/docs/api.html b/public/docs/api.html index 47e51427b..35099a4e8 100644 --- a/public/docs/api.html +++ b/public/docs/api.html @@ -487,6 +487,27 @@
  • 获取仓库代码子目录或者文件
  • +
  • + 获取仓库webhooks列表 +
  • +
  • + 获取仓库单个webhook +
  • +
  • + 添加仓库webhook +
  • +
  • + 更新仓库webhook +
  • +
  • + 删除仓库webhook +
  • +
  • + 获取仓库webhook的历史推送列表 +
  • +
  • + 仓库webhook测试推送 +
  • @@ -6752,6 +6773,1223 @@ http://localhost:3000//api/jasder/jasder_test/sub_entries.json +

    获取仓库webhooks列表

    +

    获取仓库webhooks列表

    + +
    +

    示例:

    +
    +
    curl -X GET \
    +http://localhost:3000/api/yystopf/ceshi/webhooks.json
    +
    await octokit.request('GET /api/yystopf/ceshi/webhooks.json')
    +

    HTTP 请求

    +

    GET /api/:owner/:repo/webhooks.json

    +

    请求参数:

    + + + + + + + + + + + + + + + + + + + + + + + +
    参数必选默认类型字段说明
    ownerstring用户登录名
    repostring项目标识identifier
    +

    返回字段说明:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idintid
    urlstring地址
    http_methodstring请求方式
    is_activebool是否激活
    typestring类型
    last_statusstring最后一次推送的状态
    create_timestring创建时间
    + +
    +

    返回的JSON示例:

    +
    +
    {
    +    "total_count": 4,
    +    "webhooks": [
    +        {
    +            "id": 2,
    +            "url": "https://oapi.dingtalk.com/robot/send?access_token=7e1e19d0eddb6a5e33c5c2c4e66f4c88f9437184b9ed2c2653194c6374c7d513",
    +            "http_method": "",
    +            "is_active": true,
    +            "type": "dingtalk",
    +            "last_status": "succeed",
    +            "create_time": "2021-07-12 10:50:07"
    +        },
    +        {
    +            "id": 3,
    +            "url": "http://localhost:3000",
    +            "http_method": "GET",
    +            "is_active": true,
    +            "type": "gitea",
    +            "last_status": "succeed",
    +            "create_time": "2021-07-26 10:03:45"
    +        },
    +        {
    +            "id": 4,
    +            "url": "http://localhost:10081",
    +            "http_method": "POST",
    +            "is_active": true,
    +            "type": "gitea",
    +            "last_status": "waiting",
    +            "create_time": "2021-07-26 16:56:53"
    +        },
    +        {
    +            "id": 5,
    +            "url": "http://localhost:3001",
    +            "http_method": "POST",
    +            "is_active": true,
    +            "type": "gitea",
    +            "last_status": "fail",
    +            "create_time": "2021-07-26 16:58:23"
    +        }
    +    ]
    +}
    +
    + +

    获取仓库单个webhook

    +

    获取仓库单个webhook

    + +
    +

    示例:

    +
    +
    curl -X GET \
    +http://localhost:3000/api/yystopf/ceshi/webhooks/3/edit.json
    +
    await octokit.request('GET /api/yystopf/ceshi/webhooks/3/edit.json')
    +

    HTTP 请求

    +

    GET /api/:owner/:repo/webhooks/:id/edit.json

    +

    请求参数:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数必选默认类型字段说明
    ownerstring用户登录名
    repostring项目标识identifier
    idintegerwebhook ID
    +

    返回字段说明:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idintid
    urlstring地址
    content_typestringPOST Content Type
    http_methodstring请求方式
    secretstring
    is_activebool是否激活
    typestring类型
    last_statusstring最后一次推送的状态, waiting 等待,fail 失败,succeed 成功
    branch_filterstring分支过滤
    eventsstring触发条件
    create_timestring创建时间
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数含义
    create创建分支或标签
    delete分支或标签删除
    fork仓库被fork
    pushgit仓库推送
    issue易修已打开、已关闭、已重新打开或编辑
    issue_assign易修被指派
    issue_label易修标签被更新或删除
    issue_milestone易修被收入里程碑
    issue_comment易修评论
    pull_request合并请求
    pull_request_assign合并请求被指派
    pull_request_label合并请求被贴上标签
    pull_request_milestone合并请求被记录于里程碑中
    pull_request_comment合并请求被评论
    pull_request_review_approved合并请求被批准
    pull_request_review_rejected合并请求被拒绝
    pull_request_review_comment合并请求被提出审查意见
    pull_request_sync合并请求被同步
    repository创建或删除仓库
    release版本发布
    + +
    +

    返回的JSON示例:

    +
    +
    {
    +    "id": 3,
    +    "http_method": "GET",
    +    "content_type": "form",
    +    "url": "http://localhost:3000",
    +    "secret": "123456",
    +    "last_status": "succeed",
    +    "is_active": true,
    +    "type": "gitea",
    +    "create_time": "2021-07-26 10:03:45",
    +    "branch_filter": "*",
    +    "events": [
    +        "create",
    +        "delete",
    +        "fork",
    +        "issues",
    +        "issue_assign",
    +        "issue_label",
    +        "issue_milestone",
    +        "issue_comment",
    +        "push",
    +        "pull_request",
    +        "pull_request_assign",
    +        "pull_request_label",
    +        "pull_request_milestone",
    +        "pull_request_comment",
    +        "pull_request_review",
    +        "pull_request_sync",
    +        "repository",
    +        "release"
    +    ]
    +}
    +
    + +

    添加仓库webhook

    +

    添加仓库webhook

    + +
    +

    示例:

    +
    +
    curl -X POST \
    +http://localhost:3000/api/yystopf/ceshi/webhooks.json
    +
    await octokit.request('POST /api/yystopf/ceshi/webhooks.json')
    +

    HTTP 请求

    +

    POST /api/:owner/:repo/webhooks.json

    +

    请求参数:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数必选默认类型字段说明
    ownerstring用户登录名
    repostring项目标识identifier
    webhook.urlstring目标url
    webhook.typestring类型
    webhook.http_methodstringhttp方法, POST和GET
    webhook.content_typestringPOST Content Type
    webhook.secretstring密钥文本
    webhook.activebool是否激活
    webhook.branch_filterstring分支过滤
    webhook.eventsarray触发事件
    + +

    触发事件字段说明

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数含义
    create创建分支或标签
    delete分支或标签删除
    fork仓库被fork
    pushgit仓库推送
    issue易修已打开、已关闭、已重新打开或编辑
    issue_assign易修被指派
    issue_label易修标签被更新或删除
    issue_milestone易修被收入里程碑
    issue_comment易修评论
    pull_request合并请求
    pull_request_assign合并请求被指派
    pull_request_label合并请求被贴上标签
    pull_request_milestone合并请求被记录于里程碑中
    pull_request_comment合并请求被评论
    pull_request_review_approved合并请求被批准
    pull_request_review_rejected合并请求被拒绝
    pull_request_review_comment合并请求被提出审查意见
    pull_request_sync合并请求被同步
    repository创建或删除仓库
    release版本发布
    + +
    +

    请求的JSON示例:

    +
    +
    {
    +    "active": true, 
    +    "content_type": "json",
    +    "http_method": "GET",
    +    "secret": "123456",
    +    "url": "http://localhost:10000",
    +    "branch_filter": "*",
    +    "events": ["push"]
    +}
    +

    返回字段说明:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idintid
    urlstring地址
    content_typestringPOST Content Type
    is_activebool是否激活
    typestring类型
    eventsarray触发事件
    create_timestring创建时间
    + +
    +

    返回的JSON示例:

    +
    +
    {
    +    "id": 18,
    +    "type": "gitea",
    +    "content_type": "json",
    +    "url": "http://localhost:10000",
    +    "events": [
    +        "push"
    +    ],
    +    "active": true,
    +    "create_time": "2021-07-26 18:53:43"
    +}
    +
    + +

    更新仓库webhook

    +

    更新仓库webhook

    + +
    +

    示例:

    +
    +
    curl -X PATCH \
    +http://localhost:3000/api/yystopf/ceshi/webhooks/7.json
    +
    await octokit.request('PATCH /api/yystopf/ceshi/webhooks/7.json')
    +

    HTTP 请求

    +

    PATCH /api/:owner/:repo/webhooks/:id.json

    +

    请求参数:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数必选默认类型字段说明
    ownerstring用户登录名
    repostring项目标识identifier
    idstringwebhook id
    webhook.urlstring目标url
    webhook.typestring类型
    webhook.http_methodstringhttp方法, POST和GET
    webhook.content_typestringPOST Content Type
    webhook.secretstring密钥文本
    webhook.activebool是否激活
    webhook.branch_filterstring分支过滤
    webhook.eventsarray触发事件
    + +

    触发事件字段说明

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数含义
    create创建分支或标签
    delete分支或标签删除
    fork仓库被fork
    pushgit仓库推送
    issue易修已打开、已关闭、已重新打开或编辑
    issue_assign易修被指派
    issue_label易修标签被更新或删除
    issue_milestone易修被收入里程碑
    issue_comment易修评论
    pull_request合并请求
    pull_request_assign合并请求被指派
    pull_request_label合并请求被贴上标签
    pull_request_milestone合并请求被记录于里程碑中
    pull_request_comment合并请求被评论
    pull_request_review_approved合并请求被批准
    pull_request_review_rejected合并请求被拒绝
    pull_request_review_comment合并请求被提出审查意见
    pull_request_sync合并请求被同步
    repository创建或删除仓库
    release版本发布
    + +
    +

    请求的JSON示例:

    +
    +
    {
    +    "active": true, 
    +    "content_type": "json",
    +    "http_method": "GET",
    +    "secret": "123456",
    +    "url": "http://localhost:10000",
    +    "branch_filter": "*",
    +    "events": ["push"]
    +}
    +

    返回字段说明:

    +
    +

    返回的JSON示例:

    +
    +
    {
    +    "status": 0,
    +    "message": "success"
    +}
    +
    + +

    删除仓库webhook

    +

    删除仓库webhook

    + +
    +

    示例:

    +
    +
    curl -X DELETE \
    +http://localhost:3000/api/yystopf/ceshi/webhooks/7.json
    +
    await octokit.request('DELETE /api/yystopf/ceshi/webhooks/7.json')
    +

    HTTP 请求

    +

    DELETE /api/:owner/:repo/webhooks/:id.json

    +

    请求参数:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数必选默认类型字段说明
    ownerstring用户登录名
    repostring项目标识identifier
    idstringwebhook id
    +

    返回字段说明:

    +
    +

    返回的JSON示例:

    +
    +
    {
    +    "status": 0,
    +    "message": "success"
    +}
    +
    + +

    获取仓库webhook的历史推送列表

    +

    获取仓库webhook的历史推送列表

    + +
    +

    示例:

    +
    +
    curl -X GET \
    +http://localhost:3000/api/yystopf/ceshi/webhooks/3/tasks.json
    +
    await octokit.request('GET /api/yystopf/ceshi/webhooks/3/tasks.json')
    +

    HTTP 请求

    +

    GET /api/:owner/:repo/webhooks/:id/tasks.json

    +

    请求参数:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数必选默认类型字段说明
    ownerstring用户登录名
    repostring项目标识identifier
    idintegerwebhook ID
    +

    返回字段说明:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idintid
    uuidstring推送uuid
    typestring类型
    is_succeedbool是否推送成功
    is_deliveredbool是否完成推送
    payload_contentjson请求主体内容
    request_contentjson请求内容,头部等等
    reponse_contentjson响应内容,状态,头部,主体等等
    delivered_timestring推送时间
    + +
    +

    返回的JSON示例:

    +
    +
    {
    +  "total_count": 6,
    +  "tasks": [
    +    {
    +      "id": 20,
    +      "type": "gitea",
    +      "uuid": "99aa2c23-6884-4c44-9020-5469320aa408",
    +      "is_succeed": true,
    +      "is_delivered": true,
    +      "payload_content": {
    +          "secret": "123456",
    +          "ref": "refs/heads/master",
    +          "before": "feb48e31362787a7620b53d4df3c4effddbb6f0b",
    +          "after": "feb48e31362787a7620b53d4df3c4effddbb6f0b",
    +          "compare_url": "",
    +          "commits": [
    +              {
    +                  "id": "feb48e31362787a7620b53d4df3c4effddbb6f0b",
    +                  "message": "fix\n",
    +                  "url": "http://localhost:10081/yystopf/ceshi/commit/feb48e31362787a7620b53d4df3c4effddbb6f0b",
    +                  "author": {
    +                      "name": "viletyy",
    +                      "email": "yystopf@163.com",
    +                      "username": "root"
    +                  },
    +                  "committer": {
    +                      "name": "viletyy",
    +                      "email": "yystopf@163.com",
    +                      "username": "root"
    +                  },
    +                  "verification": {
    +                      "verified": false,
    +                      "reason": "gpg.error.not_signed_commit",
    +                      "signature": "",
    +                      "signer": null,
    +                      "payload": ""
    +                  },
    +                  "timestamp": "2021-07-26T13:52:13+08:00",
    +                  "added": null,
    +                  "removed": null,
    +                  "modified": null
    +              }
    +          ],
    +          "head_commit": null,
    +          "repository": {
    +              "id": 2,
    +              "owner": {
    +                  "id": 3,
    +                  "login": "yystopf",
    +                  "full_name": "",
    +                  "email": "yystopf@forge.com",
    +                  "avatar_url": "http://localhost:10081/user/avatar/yystopf/-1",
    +                  "language": "zh-CN",
    +                  "is_admin": true,
    +                  "last_login": "2021-07-21T18:38:21+08:00",
    +                  "created": "2021-06-03T14:50:25+08:00",
    +                  "username": "yystopf"
    +              },
    +              "name": "ceshi",
    +              "full_name": "yystopf/ceshi",
    +              "description": "",
    +              "empty": false,
    +              "private": false,
    +              "fork": false,
    +              "template": false,
    +              "parent": null,
    +              "mirror": false,
    +              "size": 3846,
    +              "html_url": "http://localhost:10081/yystopf/ceshi",
    +              "ssh_url": "virus@localhost:10081:yystopf/ceshi.git",
    +              "clone_url": "http://localhost:10081/yystopf/ceshi.git",
    +              "original_url": "",
    +              "website": "",
    +              "stars_count": 0,
    +              "forks_count": 1,
    +              "watchers_count": 1,
    +              "open_issues_count": 0,
    +              "open_pr_counter": 0,
    +              "release_counter": 0,
    +              "default_branch": "master",
    +              "archived": false,
    +              "created_at": "2021-06-03T15:15:30+08:00",
    +              "updated_at": "2021-07-26T13:52:16+08:00",
    +              "permissions": {
    +                  "admin": false,
    +                  "push": false,
    +                  "pull": false
    +              },
    +              "has_issues": true,
    +              "internal_tracker": {
    +                  "enable_time_tracker": true,
    +                  "allow_only_contributors_to_track_time": true,
    +                  "enable_issue_dependencies": true
    +              },
    +              "has_wiki": true,
    +              "has_pull_requests": true,
    +              "ignore_whitespace_conflicts": false,
    +              "allow_merge_commits": true,
    +              "allow_rebase": true,
    +              "allow_rebase_explicit": true,
    +              "allow_squash_merge": true,
    +              "avatar_url": "",
    +              "internal": false
    +          },
    +          "pusher": {
    +              "id": 0,
    +              "login": "yystopf",
    +              "full_name": "",
    +              "email": "yystopf@forge.com",
    +              "avatar_url": "http://localhost:10081/user/avatar/yystopf/-1",
    +              "language": "",
    +              "is_admin": false,
    +              "last_login": "0001-01-01T00:00:00Z",
    +              "created": "2021-06-03T14:50:25+08:00",
    +              "username": "yystopf"
    +          },
    +          "sender": {
    +              "id": 0,
    +              "login": "yystopf",
    +              "full_name": "",
    +              "email": "yystopf@forge.com",
    +              "avatar_url": "http://localhost:10081/user/avatar/yystopf/-1",
    +              "language": "",
    +              "is_admin": false,
    +              "last_login": "0001-01-01T00:00:00Z",
    +              "created": "2021-06-03T14:50:25+08:00",
    +              "username": "yystopf"
    +          }
    +      },
    +      "request_content": {
    +          "headers": {
    +              "X-GitHub-Delivery": "99aa2c23-6884-4c44-9020-5469320aa408",
    +              "X-GitHub-Event": "push",
    +              "X-Gitea-Delivery": "99aa2c23-6884-4c44-9020-5469320aa408",
    +              "X-Gitea-Event": "push",
    +              "X-Gitea-Signature": "34a01edcd952ff6410ff6ebc946471161bde74aff86171f21621d2c2c4130f66",
    +              "X-Gogs-Delivery": "99aa2c23-6884-4c44-9020-5469320aa408",
    +              "X-Gogs-Event": "push",
    +              "X-Gogs-Signature": "34a01edcd952ff6410ff6ebc946471161bde74aff86171f21621d2c2c4130f66"
    +          }
    +      },
    +      "response_content": {
    +          "status": 200,
    +          "headers": {
    +              "Cache-Control": "no-store, must-revalidate, private, max-age=0",
    +              "Content-Length": "2556",
    +              "Content-Type": "text/html; charset=utf-8",
    +              "Referrer-Policy": "strict-origin-when-cross-origin",
    +              "Set-Cookie": "__profilin=p%3Dt; path=/; HttpOnly",
    +              "Vary": "Origin",
    +              "X-Content-Type-Options": "nosniff",
    +              "X-Download-Options": "noopen",
    +              "X-Frame-Options": "SAMEORIGIN",
    +              "X-Miniprofiler-Ids": "9ynvpncz5xm0rpgorb5y,hgggd9mv6lr4a9drcrlr,j7zqlx2vy5aji2vtgoba,f1ktsmh3jxvq0z2hf612,mih3dvgvlqhi3zy8lf2x,5k1qbkvbnru8mye9cest,tj6ern8w6awqf2zsimbr,9isaehvubivd52wo5p9v,1rzfhtq1nhuwbgy9p76g,z0xzidzyywna0y7a69m0,hzoklky92ycjqt42gi0s,y0ai7y0t28mcn8x0py2x,322il7nadinp51mw2r5m,m6dukftfsh6tjcxzp1gq,667wlqbytfwbrirnmma1,jcehj3dl8lkw8gk510cr",
    +              "X-Miniprofiler-Original-Cache-Control": "max-age=0, private, must-revalidate",
    +              "X-Permitted-Cross-Domain-Policies": "none",
    +              "X-Request-Id": "08bff080-bbb5-4183-b845-81de3d47120a",
    +              "X-Runtime": "0.394766",
    +              "X-Xss-Protection": "1; mode=block"
    +          },
    +          "body": "<!doctype html><html lang=\"zh-CN\" class=\"notranslate translated-ltr\" translate=\"no\"><head><meta charset=\"utf-8\"><meta name=\"”Keywords”\" content=\"”trustie,trustieforge,forge,确实让创建更美好,协同开发平台″\"><meta name=\"”Keywords”\" content=\"”TrustieOpenSourceProject″\"><meta name=\"”Keywords”\" content=\"”issue,bug,tracker,软件工程,课程实践″\"><meta name=\"”Description”\" content=\"”持续构建协同、共享、可信的软件创建生态开源创作与软件生产相结合,支持大规模群体开展软件协同创新活动”\"><meta name=\"theme-color\" content=\"#000000\"><link rel=\"manifest\" href=\"/react/build//manifest.json\"><link rel=\"stylesheet\" href=\"/react/build/css/iconfont.css\"><link rel=\"stylesheet\" href=\"/react/build/css/edu-purge.css\"><link rel=\"stylesheet\" href=\"/react/build/css/editormd.min.css\"><link rel=\"stylesheet\" href=\"/react/build/css/merge.css\"><link href=\"/react/build/static/css/main.07f7e90c.chunk.css\" rel=\"stylesheet\"></head><body><div id=\"md_div\" style=\"display:none\"></div><div id=\"root\" class=\"page -layout-v -fit widthunit\"></div><div id=\"picture_display\" style=\"display:none\"></div><script src=\"/react/build/js/jquery-1.8.3.min.js\"></script><script src=\"/react/build/js/js_min_all.js\"></script><script src=\"/react/build/js/codemirror/codemirror.js\"></script><script src=\"/react/build/js/editormd/editormd.min.js\"></script><script src=\"/react/build/js/codemirror/merge/merge.js\"></script><script src=\"/react/build/./static/js/runtime~main.3d644966.js\"></script><script src=\"/react/build/./static/js/main.e46872e3.chunk.js\"></script><script async type=\"text/javascript\" id=\"mini-profiler\" src=\"/mini-profiler-resources/includes.js?v=67dd1c2571ced7fc74ae7f1813e47bdf\" data-version=\"67dd1c2571ced7fc74ae7f1813e47bdf\" data-path=\"/mini-profiler-resources/\" data-current-id=\"9ynvpncz5xm0rpgorb5y\" data-ids=\"9ynvpncz5xm0rpgorb5y,hgggd9mv6lr4a9drcrlr,j7zqlx2vy5aji2vtgoba,f1ktsmh3jxvq0z2hf612,mih3dvgvlqhi3zy8lf2x,5k1qbkvbnru8mye9cest,tj6ern8w6awqf2zsimbr,9isaehvubivd52wo5p9v,1rzfhtq1nhuwbgy9p76g,z0xzidzyywna0y7a69m0,hzoklky92ycjqt42gi0s,y0ai7y0t28mcn8x0py2x,322il7nadinp51mw2r5m,m6dukftfsh6tjcxzp1gq,667wlqbytfwbrirnmma1,jcehj3dl8lkw8gk510cr\" data-horizontal-position=\"left\" data-vertical-position=\"top\" data-trivial=\"false\" data-children=\"false\" data-max-traces=\"20\" data-controls=\"false\" data-total-sql-count=\"false\" data-authorized=\"true\" data-toggle-shortcut=\"alt+p\" data-start-hidden=\"false\" data-collapse-results=\"true\" data-html-container=\"body\"></script>\n</body></html>"
    +      },
    +      "delivered_time": "2021-07-28 11:47:29"
    +    }
    +  ]
    +}
    +
    + +

    仓库webhook测试推送

    +

    仓库webhook测试推送

    + +
    +

    示例:

    +
    +
    curl -X POST \
    +http://localhost:3000/api/yystopf/ceshi/webhooks/3/test.json
    +
    await octokit.request('POST /api/yystopf/ceshi/webhooks/3/test.json')
    +

    HTTP 请求

    +

    POST /api/:owner/:repo/webhooks/:id/test.json

    +

    请求参数:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数必选默认类型字段说明
    ownerstring用户登录名
    repostring项目标识identifier
    idintegerwebhook ID
    +

    返回字段说明:

    +
    +

    返回的JSON示例:

    +
    +
    {
    +    "status": 0,
    +    "message": "success"
    +}
    +
    +

    Pulls

    Issues

    Organizations

    Teams

    Errors