feat: Add OpenAPI response types and top-level swag annotations

Named response types for handlers that were returning anonymous gin.H
literals, so a generated annotation and what the handler actually returns
cannot disagree. main.go carries the top-level swaggo info block (title,
description, security schemes for cookie, bearer token and ESO auth).
This commit is contained in:
2026-08-12 15:22:42 +00:00
parent 182752d9ab
commit bfd185adbb
2 changed files with 257 additions and 0 deletions
+18
View File
@@ -29,6 +29,24 @@ import (
"github.com/gin-gonic/gin"
)
// @title Vantage API
// @version 1.0
// @description The Vantage control plane REST API. Authenticate with a browser session cookie, or with an API token created under Settings → API tokens.
// @BasePath /api
//
// @securityDefinitions.apikey cookieAuth
// @in cookie
// @name km_session
//
// @securityDefinitions.apikey bearerAuth
// @in header
// @name Authorization
// @description An API token, sent as "Bearer vt_…". Scoped and optionally expiring.
//
// @securityDefinitions.apikey esoAuth
// @in header
// @name Authorization
// @description The External Secrets read token, rotated under Settings. It reaches /api/secrets/{group}/values and nothing else. It is a different credential from an API token, and the two must never be substituted for one another.
func main() {
mongoURI := getEnv("MONGO_URI", "mongodb://localhost:27017")
+239
View File
@@ -0,0 +1,239 @@
package api
import (
"time"
"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/models"
"gitea.hostxtra.co.uk/mrhid6/vantage/shared/license"
)
// ErrorResponse is the shape every failing endpoint answers with. Some also
// carry a machine-readable code; it is omitted when absent rather than empty.
type ErrorResponse struct {
Error string `json:"error"`
Code string `json:"code,omitempty"`
}
// LimitExceededResponse is what a create route answers when a licence cap
// would be exceeded.
type LimitExceededResponse struct {
Error string `json:"error"`
Limit string `json:"limit"`
Current int `json:"current"`
Max int `json:"max"`
}
// LicenceErrorResponse pairs an error with a machine-readable reason rather
// than a code — used only on the two licence rejection paths that predate the
// error/code convention used everywhere else.
type LicenceErrorResponse struct {
Error string `json:"error"`
Reason string `json:"reason,omitempty"`
}
// Small, reused acknowledgement shapes. Several unrelated handlers happen to
// answer with exactly one of these.
type DeletedResponse struct {
Deleted bool `json:"deleted"`
}
type RevokedResponse struct {
Revoked bool `json:"revoked"`
}
type SavedResponse struct {
Saved bool `json:"saved"`
}
type AcknowledgedResponse struct {
Acknowledged bool `json:"acknowledged"`
}
type OKResponse struct {
OK bool `json:"ok"`
}
type CancelledResponse struct {
Cancelled bool `json:"cancelled"`
}
type UpdatedResponse struct {
Updated bool `json:"updated"`
}
type MessageResponse struct {
Message string `json:"message"`
}
type StatusResponse struct {
Status string `json:"status"`
}
// --- servers / keys ---
type TagsResponse struct {
Tags map[string]string `json:"tags"`
}
type CreateServerResponse struct {
Server *models.Server `json:"server"`
Token string `json:"token"`
ServerID string `json:"server_id"`
}
type NewServerResponse struct {
ServerID string `json:"server_id"`
PreRegToken string `json:"pre_reg_token"`
InstallCommand string `json:"install_command"`
InstallCommandPS string `json:"install_command_ps"`
}
// ServerDetailResponse is a server with its resolved key assignments.
type ServerDetailResponse struct {
*models.Server
Keys interface{} `json:"keys"`
}
type GenerateKeyResponse struct {
Message string `json:"message"`
CommandID string `json:"command_id"`
ServerID string `json:"server_id"`
}
type PrivateKeyResponse struct {
PrivateKey string `json:"private_key"`
}
// KeyDetailResponse is a key with its resolved server assignments.
type KeyDetailResponse struct {
*models.Key
Assignments any `json:"assignments"`
}
type AgentVersionResponse struct {
Version string `json:"version"`
}
type UpdateAgentResponse struct {
Message string `json:"message"`
Version string `json:"version"`
}
type AuditEventsResponse struct {
Events []models.AuditEvent `json:"events"`
Total int64 `json:"total"`
}
// --- tokens ---
type ListTokensResponse struct {
Tokens []models.APIToken `json:"tokens"`
All bool `json:"all"`
}
type TokenScopesResponse struct {
Scopes []string `json:"scopes"`
}
type CreateTokenRequest struct {
Name string `json:"name"`
Role string `json:"role"`
Scopes []string `json:"scopes"`
ExpiresInDays *int `json:"expires_in_days,omitempty"`
}
type CreateTokenResponse struct {
// Token is the plaintext, returned exactly once and stored nowhere.
Token string `json:"token"`
Record models.APIToken `json:"record"`
}
// --- secrets ---
type GroupResponse struct {
Group string `json:"group"`
}
type SecretGroupResponse struct {
Group string `json:"group"`
Secrets []models.Secret `json:"secrets"`
}
type RevealSecretResponse struct {
Value string `json:"value"`
}
type SecretsTokenResponse struct {
Token string `json:"token"`
}
// --- auth providers ---
type TestProviderResponse struct {
OK bool `json:"ok"`
Message string `json:"message"`
}
// --- licence ---
type LicencePostResponse struct {
State license.State `json:"state"`
Tier string `json:"tier"`
ExpiresAt *time.Time `json:"expires_at"`
}
// --- vulnerabilities ---
// VulnSummaryResponse's four DB-freshness fields are only present at all when
// a vulndb_meta document exists; LastError is separately omitted from that
// group when empty, matching the handler's original conditional gin.H.
type VulnSummaryResponse struct {
Counts map[string]int `json:"counts"`
DBVersion *int `json:"db_version,omitempty"`
PulledAt *time.Time `json:"pulled_at,omitempty"`
LastFullScanAt *time.Time `json:"last_full_scan_at,omitempty"`
LastError string `json:"last_error,omitempty"`
}
type QueuedResponse struct {
Queued int64 `json:"queued"`
}
type ReportedResponse struct {
Reported bool `json:"reported"`
}
// --- workflows ---
type SeedDefaultsResponse struct {
Created int `json:"created"`
Updated int `json:"updated"`
}
type RunWorkflowResponse struct {
RunID string `json:"run_id"`
}
type ScheduleResponse struct {
Schedule models.Schedule `json:"schedule"`
NextRunAt *time.Time `json:"next_run_at"`
}
type OccurrencesResponse struct {
Occurrences []time.Time `json:"occurrences"`
}
// --- console ---
type ConsoleConnectResponse struct {
SessionID string `json:"session_id"`
Token string `json:"token"`
WSPath string `json:"ws_path"`
}
// --- workloads ---
type WorkloadLogsResponse struct {
Text string `json:"text"`
Truncated bool `json:"truncated"`
}