This introduces a new v2 API at /api/v2/ with features designed for AI agents and automation tools while maintaining full backward compatibility with the existing v1 API. New features: - Structured error codes (70+ machine-readable codes) for precise error handling by automated tools - Scalar API documentation at /api/v2/docs (modern replacement for Swagger UI) - Batch operations for bulk file and repository fetching - NDJSON streaming endpoints for files, commits, and issues - AI context endpoints providing rich repository summaries, navigation hints, and issue context Files added: - modules/errors/codes.go - Error code definitions and catalog - modules/errors/api_error.go - Rich API error response builder - routers/api/v2/api.go - v2 router with auth middleware - routers/api/v2/docs.go - Scalar docs and OpenAPI spec - routers/api/v2/batch.go - Batch file/repo operations - routers/api/v2/streaming.go - NDJSON streaming endpoints - routers/api/v2/ai_context.go - AI context endpoints - routers/api/v2/misc.go - Version and user endpoints 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
248 lines
7.5 KiB
Go
248 lines
7.5 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package v2
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/services/context"
|
|
)
|
|
|
|
// scalarTemplate is the HTML template for Scalar API documentation
|
|
// Scalar is a modern, AI-friendly API documentation tool
|
|
const scalarTemplate = `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>{{.AppName}} API v2 Reference</title>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<style>
|
|
/* Custom theme to match Gitea */
|
|
:root {
|
|
--scalar-color-1: #4a90d9;
|
|
--scalar-color-accent: #4a90d9;
|
|
--scalar-background-1: #ffffff;
|
|
--scalar-background-2: #f8f9fa;
|
|
--scalar-background-3: #e9ecef;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--scalar-background-1: #1a1a1a;
|
|
--scalar-background-2: #252525;
|
|
--scalar-background-3: #333333;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<script
|
|
id="api-reference"
|
|
data-url="{{.SpecURL}}"
|
|
data-configuration='{
|
|
"theme": "default",
|
|
"layout": "modern",
|
|
"showSidebar": true,
|
|
"hideModels": false,
|
|
"hideDownloadButton": false,
|
|
"hiddenClients": [],
|
|
"defaultHttpClient": {
|
|
"targetKey": "shell",
|
|
"clientKey": "curl"
|
|
}
|
|
}'>
|
|
</script>
|
|
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
</body>
|
|
</html>`
|
|
|
|
// DocsScalar serves the Scalar API documentation UI for v2
|
|
func DocsScalar(ctx *context.APIContext) {
|
|
data := map[string]string{
|
|
"AppName": setting.AppName,
|
|
"SpecURL": setting.AppSubURL + "/api/v2/swagger.json",
|
|
}
|
|
|
|
ctx.Resp.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
ctx.Resp.WriteHeader(http.StatusOK)
|
|
|
|
// Simple template rendering
|
|
html := scalarTemplate
|
|
for key, value := range data {
|
|
html = replaceTemplateVar(html, key, value)
|
|
}
|
|
ctx.Resp.Write([]byte(html))
|
|
}
|
|
|
|
func replaceTemplateVar(template, key, value string) string {
|
|
placeholder := "{{." + key + "}}"
|
|
result := template
|
|
for i := 0; i < 10; i++ { // Replace up to 10 occurrences
|
|
newResult := ""
|
|
idx := 0
|
|
for {
|
|
pos := indexOf(result[idx:], placeholder)
|
|
if pos == -1 {
|
|
newResult += result[idx:]
|
|
break
|
|
}
|
|
newResult += result[idx : idx+pos]
|
|
newResult += value
|
|
idx = idx + pos + len(placeholder)
|
|
}
|
|
if newResult == result {
|
|
break
|
|
}
|
|
result = newResult
|
|
}
|
|
return result
|
|
}
|
|
|
|
func indexOf(s, substr string) int {
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
if s[i:i+len(substr)] == substr {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// SwaggerJSON serves the OpenAPI/Swagger JSON specification for v2
|
|
func SwaggerJSON(ctx *context.APIContext) {
|
|
spec := generateOpenAPISpec()
|
|
ctx.JSON(http.StatusOK, spec)
|
|
}
|
|
|
|
// generateOpenAPISpec creates the OpenAPI 3.0 specification for v2 API
|
|
func generateOpenAPISpec() map[string]any {
|
|
return map[string]any{
|
|
"openapi": "3.0.3",
|
|
"info": map[string]any{
|
|
"title": setting.AppName + " API v2",
|
|
"description": "Gitea API v2 with structured error codes, batch operations, and AI-friendly endpoints.",
|
|
"version": "2.0.0",
|
|
"contact": map[string]string{
|
|
"name": "Gitea",
|
|
"url": "https://gitea.com",
|
|
},
|
|
"license": map[string]string{
|
|
"name": "MIT",
|
|
"url": "https://opensource.org/licenses/MIT",
|
|
},
|
|
},
|
|
"servers": []map[string]string{
|
|
{"url": setting.AppURL + "api/v2", "description": "Current server"},
|
|
},
|
|
"tags": []map[string]string{
|
|
{"name": "miscellaneous", "description": "General API information"},
|
|
{"name": "user", "description": "User operations"},
|
|
{"name": "repository", "description": "Repository operations"},
|
|
{"name": "batch", "description": "Batch operations for bulk actions"},
|
|
{"name": "ai", "description": "AI-friendly context endpoints"},
|
|
},
|
|
"paths": map[string]any{
|
|
"/version": map[string]any{
|
|
"get": map[string]any{
|
|
"tags": []string{"miscellaneous"},
|
|
"summary": "Get API version",
|
|
"operationId": "getVersion",
|
|
"responses": map[string]any{
|
|
"200": map[string]any{
|
|
"description": "Version information",
|
|
"content": map[string]any{
|
|
"application/json": map[string]any{
|
|
"schema": map[string]any{
|
|
"$ref": "#/components/schemas/Version",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/user": map[string]any{
|
|
"get": map[string]any{
|
|
"tags": []string{"user"},
|
|
"summary": "Get authenticated user",
|
|
"operationId": "getAuthenticatedUser",
|
|
"security": []map[string][]string{{"bearerAuth": {}}},
|
|
"responses": map[string]any{
|
|
"200": map[string]any{
|
|
"description": "User information",
|
|
"content": map[string]any{
|
|
"application/json": map[string]any{
|
|
"schema": map[string]any{
|
|
"$ref": "#/components/schemas/User",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"401": map[string]any{
|
|
"description": "Authentication required",
|
|
"content": map[string]any{
|
|
"application/json": map[string]any{
|
|
"schema": map[string]any{
|
|
"$ref": "#/components/schemas/APIError",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"components": map[string]any{
|
|
"securitySchemes": map[string]any{
|
|
"bearerAuth": map[string]any{
|
|
"type": "http",
|
|
"scheme": "bearer",
|
|
"description": "API token authentication",
|
|
"bearerFormat": "token",
|
|
},
|
|
"basicAuth": map[string]any{
|
|
"type": "http",
|
|
"scheme": "basic",
|
|
"description": "Basic authentication with username and password",
|
|
},
|
|
},
|
|
"schemas": map[string]any{
|
|
"Version": map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"version": map[string]string{"type": "string", "description": "Gitea version"},
|
|
"api": map[string]string{"type": "string", "description": "API version"},
|
|
},
|
|
},
|
|
"User": map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"id": map[string]string{"type": "integer", "description": "User ID"},
|
|
"login": map[string]string{"type": "string", "description": "Username"},
|
|
"email": map[string]string{"type": "string", "description": "Email address"},
|
|
"is_admin": map[string]string{"type": "boolean", "description": "Is site admin"},
|
|
},
|
|
},
|
|
"APIError": map[string]any{
|
|
"type": "object",
|
|
"description": "Structured error response following RFC 7807",
|
|
"properties": map[string]any{
|
|
"error": map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"code": map[string]string{"type": "string", "description": "Machine-readable error code"},
|
|
"message": map[string]string{"type": "string", "description": "Human-readable error message"},
|
|
"status": map[string]string{"type": "integer", "description": "HTTP status code"},
|
|
"documentation_url": map[string]string{"type": "string", "description": "URL to error documentation"},
|
|
"request_id": map[string]string{"type": "string", "description": "Request ID for tracing"},
|
|
"details": map[string]string{"type": "object", "description": "Additional error context"},
|
|
"suggestions": map[string]any{"type": "array", "items": map[string]string{"type": "string"}, "description": "Suggested actions"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|