gitea/routers/api/v2/api.go
logikonline dfc94f6408
Some checks failed
Build and Release / Build Binaries (amd64, darwin) (push) Blocked by required conditions
Build and Release / Build Binaries (amd64, linux) (push) Blocked by required conditions
Build and Release / Build Binaries (amd64, windows) (push) Blocked by required conditions
Build and Release / Build Binaries (arm64, darwin) (push) Blocked by required conditions
Build and Release / Build Binaries (arm64, linux) (push) Blocked by required conditions
Build and Release / Build Docker Image (push) Blocked by required conditions
Build and Release / Create Release (push) Blocked by required conditions
Build and Release / Lint and Test (push) Has been cancelled
fix: resolve all golangci-lint errors in v2 API
- Replace encoding/json with modules/json (depguard)
- Add error handling for json.Unmarshal and WriteItem calls (errcheck)
- Use slices.Contains instead of manual loops (modernize)
- Use any instead of interface{} (modernize)
- Use min/max built-in functions (modernize)
- Use strings.FieldsSeq and strings.SplitSeq (modernize)
- Use range over int for loops (modernize)
- Use http.MethodOptions constant (usestdlibvars)
- Use tagged switch statements (staticcheck)
- Use += and /= operators (gocritic)
- Fix gofumpt formatting issues
- Remove unused streamLargeFile function
- Remove unused primaryLang parameter

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 15:49:52 -05:00

191 lines
5.2 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// Package v2 Gitea API v2
//
// This is the v2 API with improved error handling, batch operations,
// and AI-friendly endpoints. It uses structured error codes for
// machine-readable error handling.
//
// Schemes: https, http
// License: MIT http://opensource.org/licenses/MIT
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
// - application/x-ndjson
//
// swagger:meta
package v2
import (
"net/http"
auth_model "code.gitea.io/gitea/models/auth"
apierrors "code.gitea.io/gitea/modules/errors"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/idempotency"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/context"
"github.com/go-chi/cors"
)
// Routes registers all v2 API routes to web application.
func Routes() *web.Router {
m := web.NewRouter()
m.Use(middleware.RequestID())
m.Use(middleware.RateLimitInfo())
m.Use(securityHeaders())
// Idempotency middleware for POST/PUT/PATCH requests
idempotencyMiddleware := idempotency.NewMiddleware(idempotency.GetDefaultStore())
m.Use(idempotencyMiddleware.Handler)
if setting.CORSConfig.Enabled {
m.Use(cors.Handler(cors.Options{
AllowedOrigins: setting.CORSConfig.AllowDomain,
AllowedMethods: setting.CORSConfig.Methods,
AllowCredentials: setting.CORSConfig.AllowCredentials,
AllowedHeaders: append([]string{"Authorization", "X-Gitea-OTP"}, setting.CORSConfig.Headers...),
MaxAge: int(setting.CORSConfig.MaxAge.Seconds()),
}))
}
m.Use(context.APIContexter())
// Get user from session if logged in
m.Use(apiAuth(buildAuthGroup()))
m.Group("", func() {
// Public endpoints (no auth required)
m.Get("/version", Version)
// API Documentation (Scalar)
m.Get("/docs", DocsScalar)
m.Get("/swagger.json", SwaggerJSON)
// Health check endpoints
m.Group("/health", func() {
m.Get("", HealthCheck)
m.Get("/live", LivenessCheck)
m.Get("/ready", ReadinessCheck)
m.Get("/component/{component}", ComponentHealthCheck)
})
// Operation progress endpoints (SSE)
m.Group("/operations", func() {
m.Get("/{id}/progress", OperationProgress)
m.Get("/{id}", GetOperation)
m.Delete("/{id}", CancelOperation)
})
// Authenticated endpoints
m.Group("", func() {
// User info
m.Get("/user", GetAuthenticatedUser)
// Batch operations - efficient bulk requests
m.Group("/batch", func() {
m.Post("/files", BatchGetFiles)
m.Post("/repos", BatchGetRepos)
})
// Streaming endpoints - NDJSON responses
m.Group("/stream", func() {
m.Post("/files", StreamFiles)
m.Post("/commits", StreamCommits)
m.Post("/issues", StreamIssues)
})
// AI context endpoints - rich context for AI tools
m.Group("/ai", func() {
m.Post("/repo/summary", GetAIRepoSummary)
m.Post("/repo/navigation", GetAINavigation)
m.Post("/issue/context", GetAIIssueContext)
})
}, reqToken())
// Wiki v2 API - repository wiki endpoints
m.Group("/repos/{owner}/{repo}/wiki", func() {
// Public read endpoints (access checked in handler)
m.Get("/pages", ListWikiPagesV2)
m.Get("/pages/{pageName}", GetWikiPageV2)
m.Get("/pages/{pageName}/revisions", GetWikiPageRevisionsV2)
m.Get("/search", SearchWikiV2)
m.Get("/graph", GetWikiGraphV2)
m.Get("/stats", GetWikiStatsV2)
// Write endpoints require authentication
m.Group("", func() {
m.Post("/pages", web.Bind(api.CreateWikiPageV2Option{}), CreateWikiPageV2)
m.Put("/pages/{pageName}", web.Bind(api.UpdateWikiPageV2Option{}), UpdateWikiPageV2)
m.Delete("/pages/{pageName}", DeleteWikiPageV2)
}, reqToken())
})
})
return m
}
func securityHeaders() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// CORS preflight
if req.Method == http.MethodOptions {
return
}
next.ServeHTTP(w, req)
})
}
}
func buildAuthGroup() *auth.Group {
group := auth.NewGroup(
&auth.OAuth2{},
&auth.HTTPSign{},
&auth.Basic{},
)
if setting.Service.EnableReverseProxyAuthAPI {
group.Add(&auth.ReverseProxy{})
}
if setting.IsWindows && auth_model.IsSSPIEnabled(graceful.GetManager().ShutdownContext()) {
group.Add(&auth.SSPI{})
}
return group
}
func apiAuth(authMethod auth.Method) func(*context.APIContext) {
return func(ctx *context.APIContext) {
ar, err := common.AuthShared(ctx.Base, nil, authMethod)
if err != nil {
msg, ok := auth.ErrAsUserAuthMessage(err)
msg = util.Iif(ok, msg, "invalid username, password or token")
ctx.APIErrorWithCodeAndMessage(apierrors.AuthInvalidCredentials, msg)
return
}
ctx.Doer = ar.Doer
ctx.IsSigned = ar.Doer != nil
ctx.IsBasicAuth = ar.IsBasicAuth
}
}
// reqToken requires authentication
func reqToken() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsSigned {
ctx.APIErrorWithCode(apierrors.AuthTokenMissing)
return
}
}
}