mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-26 06:16:56 +09:00
feat(api): add REST API endpoints for repository webhook delivery history (#38775)
Webhook delivery history (request/response, status, timestamp) was only
reachable through the repo settings UI, which shows the last ~10
deliveries with no filtering. There was no way to fetch this data
programmatically for debugging integrations or auditing webhook
activity.
This adds two read endpoints at the repository level:
- `GET /repos/{owner}/{repo}/hooks/{id}/deliveries` — paginated list of
a hook's deliveries, most recent first
- `GET /repos/{owner}/{repo}/hooks/{id}/deliveries/{uuid}` — a single
delivery, including request/response headers, body, and status
Both reuse the existing `hook_task` storage and retention policy (the
`CleanupHookTaskTable` cron job) that already back the "Recent
Deliveries" web UI, and are gated by the same `reqAdmin()` +
`reqWebhooksEnabled()` guard as the existing hook endpoints.
Org, user, and admin/system-level webhooks are intentionally out of
scope for this PR to keep it reviewable; they can follow as separate
PRs using the same pattern.
## Usage
```bash
curl -H "Authorization: token $TOKEN" \
https://gitea.example.com/api/v1/repos/{owner}/{repo}/hooks/{id}/deliveries
curl -H "Authorization: token $TOKEN" \
https://gitea.example.com/api/v1/repos/{owner}/{repo}/hooks/{id}/deliveries/{uuid}
```
## Testing
- Unit tests for the new model-layer query functions
(`ListHookTasks`, `GetHookTaskByUUID`)
- Integration tests covering: list + get, an undelivered task's response
is `null`, a hook/delivery UUID from another repository 404s instead
of leaking across repos, and pagination (`page`/`limit`, `Link` and
`X-Total-Count` headers, correct ordering/no overlap across pages)
- `make lint-go`, `make generate-swagger`
Fixes: https://github.com/go-gitea/gitea/issues/38463.
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
11 files changed
+824
-6
No files matched your search
+238
@@ -624,6 +624,29 @@
|
||||
},
|
||||
"description": "Hook"
|
||||
},
|
||||
"HookDelivery": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HookDelivery"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "HookDelivery"
|
||||
},
|
||||
"HookDeliveryList": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/HookDelivery"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "HookDeliveryList"
|
||||
},
|
||||
"HookList": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
@@ -7248,6 +7271,102 @@
|
||||
"type": "object",
|
||||
"x-go-package": "gitea.dev/modules/structs"
|
||||
},
|
||||
"HookDelivery": {
|
||||
"description": "HookDelivery represents a single delivery attempt for a webhook",
|
||||
"properties": {
|
||||
"delivered_at": {
|
||||
"description": "The date and time when the delivery was sent",
|
||||
"format": "date-time",
|
||||
"type": "string",
|
||||
"x-go-name": "Delivered"
|
||||
},
|
||||
"event_type": {
|
||||
"description": "The event type that triggered this delivery",
|
||||
"type": "string",
|
||||
"x-go-name": "EventType"
|
||||
},
|
||||
"is_delivered": {
|
||||
"description": "Whether the delivery has been sent",
|
||||
"type": "boolean",
|
||||
"x-go-name": "IsDelivered"
|
||||
},
|
||||
"is_succeeded": {
|
||||
"description": "Whether the delivery was successful (2xx response)",
|
||||
"type": "boolean",
|
||||
"x-go-name": "IsSucceeded"
|
||||
},
|
||||
"request": {
|
||||
"$ref": "#/components/schemas/HookDeliveryRequest"
|
||||
},
|
||||
"response": {
|
||||
"$ref": "#/components/schemas/HookDeliveryResponse"
|
||||
},
|
||||
"uuid": {
|
||||
"description": "The unique identifier of the delivery",
|
||||
"type": "string",
|
||||
"x-go-name": "UUID"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"x-go-package": "gitea.dev/modules/structs"
|
||||
},
|
||||
"HookDeliveryRequest": {
|
||||
"description": "HookDeliveryRequest represents the request information of a webhook delivery",
|
||||
"properties": {
|
||||
"body": {
|
||||
"description": "The body sent with the delivery",
|
||||
"type": "string",
|
||||
"x-go-name": "Body"
|
||||
},
|
||||
"headers": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "The headers sent with the delivery",
|
||||
"type": "object",
|
||||
"x-go-name": "Headers"
|
||||
},
|
||||
"http_method": {
|
||||
"description": "The HTTP method used for the delivery",
|
||||
"type": "string",
|
||||
"x-go-name": "HTTPMethod"
|
||||
},
|
||||
"url": {
|
||||
"description": "The URL the delivery was sent to",
|
||||
"format": "uri",
|
||||
"type": "string",
|
||||
"x-go-name": "URL"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"x-go-package": "gitea.dev/modules/structs"
|
||||
},
|
||||
"HookDeliveryResponse": {
|
||||
"description": "HookDeliveryResponse represents the response information of a webhook delivery",
|
||||
"properties": {
|
||||
"body": {
|
||||
"description": "The body received in the response",
|
||||
"type": "string",
|
||||
"x-go-name": "Body"
|
||||
},
|
||||
"headers": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "The headers received in the response",
|
||||
"type": "object",
|
||||
"x-go-name": "Headers"
|
||||
},
|
||||
"status": {
|
||||
"description": "The HTTP status code of the response",
|
||||
"format": "int64",
|
||||
"type": "integer",
|
||||
"x-go-name": "Status"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"x-go-package": "gitea.dev/modules/structs"
|
||||
},
|
||||
"Identity": {
|
||||
"description": "Identity for a person's identity like an author or committer",
|
||||
"properties": {
|
||||
@@ -23104,6 +23223,125 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/hooks/{id}/deliveries": {
|
||||
"get": {
|
||||
"operationId": "repoListHookDeliveries",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "owner of the repo",
|
||||
"in": "path",
|
||||
"name": "owner",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "name of the repo",
|
||||
"in": "path",
|
||||
"name": "repo",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "id of the hook",
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "page number of results to return (1-based)",
|
||||
"in": "query",
|
||||
"name": "page",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "page size of results",
|
||||
"in": "query",
|
||||
"name": "limit",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/HookDeliveryList"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/notFound"
|
||||
}
|
||||
},
|
||||
"summary": "List the deliveries of a hook in a repository",
|
||||
"tags": [
|
||||
"repository"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/hooks/{id}/deliveries/{uuid}": {
|
||||
"get": {
|
||||
"operationId": "repoGetHookDelivery",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "owner of the repo",
|
||||
"in": "path",
|
||||
"name": "owner",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "name of the repo",
|
||||
"in": "path",
|
||||
"name": "repo",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "id of the hook",
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "uuid of the delivery",
|
||||
"in": "path",
|
||||
"name": "uuid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/HookDelivery"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/notFound"
|
||||
}
|
||||
},
|
||||
"summary": "Get a delivery of a hook in a repository",
|
||||
"tags": [
|
||||
"repository"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/hooks/{id}/tests": {
|
||||
"post": {
|
||||
"operationId": "repoTestHook",
|
||||
|
||||
+217
@@ -10997,6 +10997,113 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/hooks/{id}/deliveries": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "List the deliveries of a hook in a repository",
|
||||
"operationId": "repoListHookDeliveries",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "id of the hook",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "page number of results to return (1-based)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "page size of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/HookDeliveryList"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/hooks/{id}/deliveries/{uuid}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Get a delivery of a hook in a repository",
|
||||
"operationId": "repoGetHookDelivery",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "id of the hook",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "uuid of the delivery",
|
||||
"name": "uuid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/HookDelivery"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/hooks/{id}/tests": {
|
||||
"post": {
|
||||
"produces": [
|
||||
@@ -31083,6 +31190,101 @@
|
||||
},
|
||||
"x-go-package": "gitea.dev/modules/structs"
|
||||
},
|
||||
"HookDelivery": {
|
||||
"description": "HookDelivery represents a single delivery attempt for a webhook",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"delivered_at": {
|
||||
"description": "The date and time when the delivery was sent",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"x-go-name": "Delivered"
|
||||
},
|
||||
"event_type": {
|
||||
"description": "The event type that triggered this delivery",
|
||||
"type": "string",
|
||||
"x-go-name": "EventType"
|
||||
},
|
||||
"is_delivered": {
|
||||
"description": "Whether the delivery has been sent",
|
||||
"type": "boolean",
|
||||
"x-go-name": "IsDelivered"
|
||||
},
|
||||
"is_succeeded": {
|
||||
"description": "Whether the delivery was successful (2xx response)",
|
||||
"type": "boolean",
|
||||
"x-go-name": "IsSucceeded"
|
||||
},
|
||||
"request": {
|
||||
"$ref": "#/definitions/HookDeliveryRequest"
|
||||
},
|
||||
"response": {
|
||||
"$ref": "#/definitions/HookDeliveryResponse"
|
||||
},
|
||||
"uuid": {
|
||||
"description": "The unique identifier of the delivery",
|
||||
"type": "string",
|
||||
"x-go-name": "UUID"
|
||||
}
|
||||
},
|
||||
"x-go-package": "gitea.dev/modules/structs"
|
||||
},
|
||||
"HookDeliveryRequest": {
|
||||
"description": "HookDeliveryRequest represents the request information of a webhook delivery",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"body": {
|
||||
"description": "The body sent with the delivery",
|
||||
"type": "string",
|
||||
"x-go-name": "Body"
|
||||
},
|
||||
"headers": {
|
||||
"description": "The headers sent with the delivery",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"x-go-name": "Headers"
|
||||
},
|
||||
"http_method": {
|
||||
"description": "The HTTP method used for the delivery",
|
||||
"type": "string",
|
||||
"x-go-name": "HTTPMethod"
|
||||
},
|
||||
"url": {
|
||||
"description": "The URL the delivery was sent to",
|
||||
"type": "string",
|
||||
"x-go-name": "URL"
|
||||
}
|
||||
},
|
||||
"x-go-package": "gitea.dev/modules/structs"
|
||||
},
|
||||
"HookDeliveryResponse": {
|
||||
"description": "HookDeliveryResponse represents the response information of a webhook delivery",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"body": {
|
||||
"description": "The body received in the response",
|
||||
"type": "string",
|
||||
"x-go-name": "Body"
|
||||
},
|
||||
"headers": {
|
||||
"description": "The headers received in the response",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"x-go-name": "Headers"
|
||||
},
|
||||
"status": {
|
||||
"description": "The HTTP status code of the response",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"x-go-name": "Status"
|
||||
}
|
||||
},
|
||||
"x-go-package": "gitea.dev/modules/structs"
|
||||
},
|
||||
"Identity": {
|
||||
"description": "Identity for a person's identity like an author or committer",
|
||||
"type": "object",
|
||||
@@ -35495,6 +35697,21 @@
|
||||
"$ref": "#/definitions/Hook"
|
||||
}
|
||||
},
|
||||
"HookDelivery": {
|
||||
"description": "HookDelivery",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/HookDelivery"
|
||||
}
|
||||
},
|
||||
"HookDeliveryList": {
|
||||
"description": "HookDeliveryList",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookDelivery"
|
||||
}
|
||||
}
|
||||
},
|
||||
"HookList": {
|
||||
"description": "HookList",
|
||||
"schema": {
|
||||
|
||||
Reference in new issue
Block a user