package api import ( "net/http" "strconv" "strings" "browser.local/platform/domain" "browser.local/platform/dto" "browser.local/platform/service" "browser.local/platform/validator" ) type coreHandlers struct { core service.Core enforceAuthorization bool } func newCoreHandlers(core service.Core, enforceAuthorization bool) *coreHandlers { return &coreHandlers{core: core, enforceAuthorization: enforceAuthorization} } func (h *coreHandlers) register(mux *http.ServeMux) { mux.HandleFunc("/api/v1/auth/register", h.authRegister) mux.HandleFunc("/api/v1/auth/login", h.authLogin) mux.HandleFunc("/api/v1/auth/logout", h.authLogout) mux.HandleFunc("/api/v1/auth/rotate", h.authRotate) mux.HandleFunc("/api/v1/users/current", h.currentUser) mux.HandleFunc("/api/v1/users/current/profile", h.currentUserProfile) mux.HandleFunc("/api/v1/users/current/theme", h.currentUserTheme) mux.HandleFunc("/api/v1/users", h.users) mux.HandleFunc("/api/v1/users/{id}", h.userDetail) mux.HandleFunc("/api/v1/ai-providers", h.aiProviders) mux.HandleFunc("/api/v1/ai-providers/{id}", h.aiProviderDetail) mux.HandleFunc("/api/v1/ai-providers/{id}/status", h.aiProviderStatus) mux.HandleFunc("/api/v1/ai-providers/{id}/test", h.aiProviderTest) mux.HandleFunc("/api/v1/ai-providers/{id}/models", h.aiProviderModels) mux.HandleFunc("/api/v1/ai/invocations", h.aiInvocation) mux.HandleFunc("/api/v1/ai/config-suggestions", h.aiConfigSuggestion) mux.HandleFunc("/api/v1/game-plugins", h.gamePlugins) mux.HandleFunc("/api/v1/game-plugins/register-manifest", h.gamePluginManifestRegistration) mux.HandleFunc("/api/v1/plugin-marketplace/plugins", h.marketplacePlugins) mux.HandleFunc("/api/v1/plugin-marketplace/plugins/{id}/state", h.marketplacePluginState) mux.HandleFunc("/api/v1/plugin-marketplace/plugins/{id}", h.marketplacePluginDetail) mux.HandleFunc("/api/v1/plugin-bridge/authorize", h.pluginBridgeAuthorize) mux.HandleFunc("/api/v1/plugin-bridge/execute", h.pluginBridgeExecute) mux.HandleFunc("/api/v1/game-plugins/{id}", h.gamePluginDetail) mux.HandleFunc("/api/v1/metrics/platform", h.platformMetrics) mux.HandleFunc("/api/v1/metrics/server-instances", h.serverInstanceMetrics) mux.HandleFunc("/api/v1/production/capacity", h.productionCapacity) mux.HandleFunc("/api/v1/production/capacity/admission", h.productionCapacityAdmission) mux.HandleFunc("/api/v1/alerts", h.alerts) mux.HandleFunc("/api/v1/alerts/{id}/acknowledge", h.alertAcknowledge) mux.HandleFunc("/api/v1/alerts/{id}/resolve", h.alertResolve) mux.HandleFunc("/api/v1/alerts/{id}/retry", h.alertRetry) mux.HandleFunc("/api/v1/plugin-lifecycles", h.pluginLifecycles) mux.HandleFunc("/api/v1/plugin-lifecycles/{pluginId}/actions", h.pluginLifecycleAction) mux.HandleFunc("/api/v1/ai/config-diffs", h.aiConfigDiffs) mux.HandleFunc("/api/v1/ai/config-diffs/{id}/approve", h.aiConfigDiffApprove) mux.HandleFunc("/api/v1/metrics/server-instances/history", h.metricHistory) mux.HandleFunc("/api/v1/run/metrics/batches", h.requireRunSignature(h.runMetricBatchIngest)) mux.HandleFunc("/api/v1/backups", h.backups) mux.HandleFunc("/api/v1/backups/{id}", h.backupDetail) mux.HandleFunc("/api/v1/server-instances", h.serverInstances) mux.HandleFunc("/api/v1/server-instances/workflows/create", h.serverInstanceCreateWorkflow) mux.HandleFunc("/api/v1/server-instances/{id}/start", h.serverInstanceStart) mux.HandleFunc("/api/v1/server-instances/{id}/stop", h.serverInstanceStop) mux.HandleFunc("/api/v1/server-instances/{id}/process/status", h.serverInstanceProcessStatus) mux.HandleFunc("/api/v1/server-instances/{id}/runtime/actions", h.serverRuntimeActions) mux.HandleFunc("/api/v1/server-instances/{id}/runtime-binding", h.serverRuntimeBinding) mux.HandleFunc("/api/v1/server-instances/{id}/deployment/reveal", h.serverDeploymentReveal) mux.HandleFunc("/api/v1/server-instances/{id}/deployment", h.serverDeployment) mux.HandleFunc("/api/v1/server-instances/{id}/deploy", h.serverInstanceDeploy) mux.HandleFunc("/api/v1/server-instances/{id}/remote-adapters", h.remoteAdapters) mux.HandleFunc("/api/v1/server-instances/{id}/rcon/commands", h.sourceRCONCommands) mux.HandleFunc("/api/v1/server-instances/{id}/run/generate", h.serverRunGenerate) mux.HandleFunc("/api/v1/server-instances/{id}/run/download", h.serverRunDownload) mux.HandleFunc("/api/v1/server-instances/{id}/run/key/reset", h.serverRunKeyReset) mux.HandleFunc("/api/v1/server-instances/{id}/run/update", h.serverRunUpdate) mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/generate", h.serverClientManagerGenerate) mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/download", h.serverClientManagerDownload) mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/key/reset", h.serverClientManagerKeyReset) mux.HandleFunc("/api/v1/server-instances/{id}/client-managers", h.serverClientManagerLifecycles) mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/{profileKey}", h.serverClientManagerLifecycleDetail) mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/deploy", h.serverClientManagerDeploy) mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/control", h.serverClientManagerControl) mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/update", h.serverClientManagerUpdateLifecycle) mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/retry", h.serverClientManagerRetry) mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/revoke-session", h.serverClientManagerRevokeSession) mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/uninstall", h.serverClientManagerUninstall) mux.HandleFunc("/api/v1/server-instances/{id}/game-client-bridge", h.serverGameClientBridgeStatus) mux.HandleFunc("/api/v1/server-instances/{id}/game-client-bridge/commands", h.serverGameClientBridgeCommands) mux.HandleFunc("/api/v1/server-instances/{id}/game-client-bridge/commands/{commandId}/cancel", h.serverGameClientBridgeCommandCancel) mux.HandleFunc("/api/v1/server-instances/{id}/game-client-bridge/commands/{commandId}", h.serverGameClientBridgeCommandDetail) mux.HandleFunc("/api/v1/server-instances/{id}/game-client-bridge/snapshots", h.serverGameClientBridgeSnapshots) mux.HandleFunc("/api/v1/server-instances/{id}/game-players", h.serverGamePlayers) mux.HandleFunc("/api/v1/server-instances/{id}/game-players/{playerId}", h.serverGamePlayerDetail) mux.HandleFunc("/api/v1/server-instances/{id}/game-players/{playerId}/state", h.serverGamePlayerState) mux.HandleFunc("/api/v1/server-instances/{id}/game-players/{playerId}/state-patches", h.serverGamePlayerStatePatches) mux.HandleFunc("/api/v1/server-instances/{id}/game-players/{playerId}/state-patches/{patchId}/approve", h.serverGamePlayerStatePatchApprove) mux.HandleFunc("/api/v1/server-instances/{id}/game-gifts", h.serverGameGiftCatalogs) mux.HandleFunc("/api/v1/server-instances/{id}/game-gifts/{catalogId}/publish", h.serverGameGiftCatalogPublish) mux.HandleFunc("/api/v1/server-instances/{id}/game-gifts/{catalogId}/revisions", h.serverGameGiftCatalogRevisions) mux.HandleFunc("/api/v1/server-instances/{id}/game-gift-grants", h.serverGameGiftGrants) mux.HandleFunc("/api/v1/server-instances/{id}/game-gift-grants/{grantId}/approve", h.serverGameGiftGrantApprove) mux.HandleFunc("/api/v1/server-instances/{id}/dependencies/check", h.serverDependenciesCheck) mux.HandleFunc("/api/v1/server-instances/{id}/dependencies/install", h.serverDependenciesInstall) mux.HandleFunc("/api/v1/server-instances/{id}/dependencies", h.serverDependencies) mux.HandleFunc("/api/v1/server-instances/{id}/logs/live", h.serverLiveLogs) mux.HandleFunc("/api/v1/server-instances/{id}/logs/backfill", h.serverLogsBackfill) mux.HandleFunc("/api/v1/server-instances/{id}/config/diff", h.serverInstanceConfigDiff) mux.HandleFunc("/api/v1/server-instances/{id}/config/approve", h.serverInstanceConfigApprove) mux.HandleFunc("/api/v1/server-instances/{id}/config", h.serverInstanceConfig) mux.HandleFunc("/api/v1/server-instances/{id}/administrators/candidates", h.serverAdministratorCandidates) mux.HandleFunc("/api/v1/server-instances/{id}/administrators", h.serverAdministrators) mux.HandleFunc("/api/v1/server-instances/{id}/administrators/{userId}", h.serverAdministratorDetail) mux.HandleFunc("/api/v1/server-instances/{id}", h.serverInstanceDetail) mux.HandleFunc("/api/v1/run/control/hello", h.runControlHello) mux.HandleFunc("/api/v1/run/control/heartbeat", h.requireRunSignature(h.runControlHeartbeat)) mux.HandleFunc("/api/v1/run/jobs/claim", h.requireRunSignature(h.runJobClaim)) mux.HandleFunc("/api/v1/run/jobs/ack", h.requireRunSignature(h.runJobAck)) mux.HandleFunc("/api/v1/run/jobs/progress", h.requireRunSignature(h.runJobProgress)) mux.HandleFunc("/api/v1/run/jobs/result", h.requireRunSignature(h.runJobResult)) mux.HandleFunc("/api/v1/run/jobs/build-input", h.requireRunSignature(h.runJobBuildInput)) mux.HandleFunc("/api/v1/run/jobs/dependency-input", h.requireRunSignature(h.runJobDependencyInput)) mux.HandleFunc("/api/v1/run/jobs/source-rcon-input", h.requireRunSignature(h.runSourceRCONInput)) mux.HandleFunc("/api/v1/run/jobs/update-input", h.requireRunSignature(h.runJobUpdateInput)) mux.HandleFunc("/api/v1/run/jobs/update-chunk", h.requireRunSignature(h.runJobUpdateChunk)) mux.HandleFunc("/api/v1/run/jobs/update-health", h.requireRunSignature(h.runJobUpdateHealth)) mux.HandleFunc("/api/v1/run/jobs/client-manager-input", h.requireRunSignature(h.runClientManagerLifecycleInput)) mux.HandleFunc("/api/v1/run/jobs/client-manager-chunk", h.requireRunSignature(h.runClientManagerLifecycleChunk)) mux.HandleFunc("/api/v1/run/jobs/cancel", h.requireRunSignature(h.runJobCancelPoll)) mux.HandleFunc("/api/v1/run/jobs/reconcile", h.requireRunSignature(h.runJobReconcile)) mux.HandleFunc("/api/v1/run/logs/batches", h.requireRunSignature(h.runLogBatchIngest)) mux.HandleFunc("/api/v1/run/artifacts/open", h.requireRunSignature(h.runArtifactOpen)) mux.HandleFunc("/api/v1/run/artifacts/chunks", h.requireRunSignature(h.runArtifactChunkUpload)) mux.HandleFunc("/api/v1/run/artifacts/status", h.requireRunSignature(h.runArtifactStatus)) mux.HandleFunc("/api/v1/run/artifacts/complete", h.requireRunSignature(h.runArtifactComplete)) mux.HandleFunc("/api/v1/run/endpoints", h.runEndpoints) mux.HandleFunc("/api/v1/run/endpoints/{id}", h.runEndpointDetail) mux.HandleFunc("/api/v1/jobs", h.jobs) mux.HandleFunc("/api/v1/jobs/{id}/cancel", h.jobCancel) mux.HandleFunc("/api/v1/jobs/{id}", h.jobDetail) mux.HandleFunc("/api/v1/file-operations/dispatch", h.fileOperationDispatch) mux.HandleFunc("/api/v1/artifacts", h.artifacts) mux.HandleFunc("/api/v1/artifacts/{id}/download", h.artifactDownload) mux.HandleFunc("/api/v1/artifacts/{id}/content", h.artifactContent) mux.HandleFunc("/api/v1/artifacts/{id}", h.artifactDetail) mux.HandleFunc("/api/v1/log-streams", h.logStreams) mux.HandleFunc("/api/v1/log-streams/query", h.logStreamQuery) mux.HandleFunc("/api/v1/log-streams/{id}", h.logStreamDetail) mux.HandleFunc("/api/v1/audit-events", h.auditEvents) mux.HandleFunc("/api/v1/audit-events/{id}", h.auditEventDetail) mux.HandleFunc("/api/v1/client-managers/register", h.clientManagerRegister) mux.HandleFunc("/api/v1/client-managers/heartbeat", h.clientManagerHeartbeat) mux.HandleFunc("/api/v1/game-client-bridge/companion/commands/claim", h.gameClientBridgeCompanionClaim) mux.HandleFunc("/api/v1/game-client-bridge/companion/commands/{commandId}/ack", h.gameClientBridgeCompanionAck) mux.HandleFunc("/api/v1/game-client-bridge/companion/commands/{commandId}/result", h.gameClientBridgeCompanionResult) mux.HandleFunc("/api/v1/game-client-bridge/companion/snapshots", h.gameClientBridgeCompanionSnapshot) mux.HandleFunc("/api/v1/game-client-bridge/companion/diagnostics", h.gameClientBridgeCompanionDiagnostics) } // productionCapacity godoc // @Summary Get production capacity governance state // @Description Returns bounded Run endpoint capacity and durable pressure counts visible to the current operator. // @Tags production-operations // @Produce json // @Success 200 {object} dto.ProductionCapacitySummaryResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/production/capacity [get] func (h *coreHandlers) productionCapacity(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } summary, err := h.core.GetProductionCapacityForSession(bearerToken(r)) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ProductionCapacityFromDomain(summary)) } // productionCapacityAdmission godoc // @Summary Check production capacity admission // @Description Evaluates endpoint heartbeat, capability, durable jobs, and bounded backlog pressure without dispatching work. // @Tags production-operations // @Accept json // @Produce json // @Param body body dto.CapacityAdmissionRequest true "Capacity admission request" // @Success 200 {object} dto.CapacityAdmissionDecisionResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/production/capacity/admission [post] func (h *coreHandlers) productionCapacityAdmission(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.CapacityAdmissionRequest](r) if err != nil { writeDecodeError(w, err) return } decision, err := h.core.CheckCapacityAdmissionForSession(bearerToken(r), request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.CapacityDecisionFromDomain(decision)) } // alerts godoc // @Summary List durable production alerts // @Description Lists alerts visible to the current operator with optional safe state/source/severity filters. // @Tags production-operations // @Produce json // @Success 200 {object} dto.AlertListResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/alerts [get] func (h *coreHandlers) alerts(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } alerts, err := h.core.ListAlertsForSession(bearerToken(r), domain.AlertFilter{State: domain.AlertState(r.URL.Query().Get("state")), SourceKind: r.URL.Query().Get("sourceKind"), SourceID: r.URL.Query().Get("sourceId"), Severity: domain.AlertSeverity(r.URL.Query().Get("severity"))}) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.AlertListFromDomain(alerts)) } // alertAcknowledge godoc // @Summary Acknowledge a durable alert // @Description Persists acknowledgement actor, timestamp, and linked audit evidence for one alert. // @Tags production-operations // @Accept json // @Produce json // @Param id path string true "Alert ID" // @Param body body dto.AlertAcknowledgeRequest true "Acknowledgement request" // @Success 200 {object} dto.AlertResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/alerts/{id}/acknowledge [post] func (h *coreHandlers) alertAcknowledge(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.AlertAcknowledgeRequest](r) if err != nil { writeDecodeError(w, err) return } alert, err := h.core.AcknowledgeAlertForSession(bearerToken(r), domain.AlertAcknowledgeRequest{AlertID: r.PathValue("id"), Note: request.Note}) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.AlertFromDomain(alert)) } // alertResolve godoc // @Summary Resolve a durable alert // @Description Resolves one alert with a safe operator note and linked audit evidence. // @Tags production-operations // @Accept json // @Produce json // @Param id path string true "Alert ID" // @Param body body dto.AlertResolveRequest true "Resolution request" // @Success 200 {object} dto.AlertResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/alerts/{id}/resolve [post] func (h *coreHandlers) alertResolve(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.AlertResolveRequest](r) if err != nil { writeDecodeError(w, err) return } alert, err := h.core.ResolveAlertForSession(bearerToken(r), domain.AlertResolveRequest{AlertID: r.PathValue("id"), Note: request.Note}) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.AlertFromDomain(alert)) } // alertRetry godoc // @Summary Retry one durable alert source // @Description Retries only the bounded source represented by an alert and preserves idempotency. // @Tags production-operations // @Accept json // @Produce json // @Param id path string true "Alert ID" // @Param body body dto.AlertRetryRequest true "Scoped retry request" // @Success 202 {object} dto.AlertRetryResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/alerts/{id}/retry [post] func (h *coreHandlers) alertRetry(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.AlertRetryRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.RetryAlertForSession(bearerToken(r), domain.AlertRetryRequest{AlertID: r.PathValue("id"), IdempotencyKey: request.IdempotencyKey}) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusAccepted, dto.AlertRetryFromDomain(result)) } // pluginLifecycles godoc // @Summary List server-bound plugin lifecycle state // @Description Lists durable plugin installation, desired/current state, compatibility, dependency, job, alert, and audit metadata. // @Tags production-operations // @Produce json // @Success 200 {object} dto.PluginLifecycleListResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/plugin-lifecycles [get] func (h *coreHandlers) pluginLifecycles(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } items, err := h.core.ListPluginLifecyclesForSession(bearerToken(r), domain.PluginLifecycleFilter{PluginID: r.URL.Query().Get("pluginId"), ServerInstanceID: r.URL.Query().Get("serverInstanceId"), CurrentState: domain.PluginLifecycleState(r.URL.Query().Get("currentState"))}) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.PluginLifecycleListFromDomain(items)) } // pluginLifecycleAction godoc // @Summary Dispatch a platform-mediated plugin lifecycle action // @Description Runs compatibility and capacity gates before creating one durable bounded Run job. // @Tags production-operations // @Accept json // @Produce json // @Param pluginId path string true "Plugin ID" // @Param body body dto.PluginLifecycleActionRequest true "Plugin lifecycle action" // @Success 202 {object} dto.PluginLifecycleActionResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/plugin-lifecycles/{pluginId}/actions [post] func (h *coreHandlers) pluginLifecycleAction(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.PluginLifecycleActionRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.RunPluginLifecycleForSession(bearerToken(r), request.ToDomain(r.PathValue("pluginId"))) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusAccepted, dto.PluginLifecycleResultFromDomain(result)) } // aiConfigDiffs godoc // @Summary List reviewable AI config diffs // @Description Lists persisted AI recommendations visible to the current operator without provider credentials or transport configuration. // @Tags production-operations // @Produce json // @Success 200 {object} dto.AIConfigDiffListResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/ai/config-diffs [get] func (h *coreHandlers) aiConfigDiffs(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } items, err := h.core.ListAIConfigDiffsForSession(bearerToken(r), domain.AIConfigDiffFilter{ServerInstanceID: r.URL.Query().Get("serverInstanceId"), PluginID: r.URL.Query().Get("pluginId"), State: domain.AIConfigDiffState(r.URL.Query().Get("state"))}) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.AIConfigDiffListFromDomain(items)) } // aiConfigDiffApprove godoc // @Summary Approve one reviewable AI config diff // @Description Revalidates actor/server/config revision fences before dispatching one bounded config write job. // @Tags production-operations // @Accept json // @Produce json // @Param id path string true "AI config diff ID" // @Param body body dto.AIConfigDiffApprovalRequest true "AI config diff approval" // @Success 202 {object} dto.AIConfigDiffApprovalResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/ai/config-diffs/{id}/approve [post] func (h *coreHandlers) aiConfigDiffApprove(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.AIConfigDiffApprovalRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.ApproveAIConfigDiffForSession(bearerToken(r), request.ToDomain(r.PathValue("id"))) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusAccepted, dto.AIConfigDiffApprovalFromDomain(result)) } // authRegister godoc // @Summary Register a platform account // @Description Creates a pending low-privilege platform account without granting platform administrator rights. // @Tags auth // @Accept json // @Produce json // @Param body body dto.RegisterRequest true "Registration request" // @Success 200 {object} dto.AuthSessionResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 409 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/auth/register [post] func (h *coreHandlers) authRegister(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.RegisterRequest](r) if err != nil { writeDecodeError(w, err) return } session, err := h.core.RegisterUser(request.ToDomain()) if err != nil { writeServiceError(w, err) return } h.writeAuthSession(w, r, session) } // authLogin godoc // @Summary Login to the platform // @Description Authenticates an active platform user and returns a bearer session token. // @Tags auth // @Accept json // @Produce json // @Param body body dto.LoginRequest true "Login request" // @Success 200 {object} dto.AuthSessionResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/auth/login [post] func (h *coreHandlers) authLogin(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.LoginRequest](r) if err != nil { writeDecodeError(w, err) return } session, err := h.core.LoginUser(request.ToDomain()) if err != nil { writeServiceError(w, err) return } h.writeAuthSession(w, r, session) } // authLogout godoc // @Summary Logout of the platform // @Description Invalidates the active bearer session token. // @Tags auth // @Produce json // @Success 204 // @Failure 401 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/auth/logout [post] func (h *coreHandlers) authLogout(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } if err := h.core.LogoutUser(bearerToken(r)); err != nil { writeServiceError(w, err) return } h.clearSessionCookie(w, r) w.WriteHeader(http.StatusNoContent) } // authRotate godoc // @Summary Rotate the active platform session // @Description Revokes the current bearer token and returns a new bounded session token. // @Tags auth // @Produce json // @Success 200 {object} dto.AuthSessionResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/auth/rotate [post] func (h *coreHandlers) authRotate(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } session, err := h.core.RotateUserSession(bearerToken(r)) if err != nil { writeServiceError(w, err) return } h.writeAuthSession(w, r, session) } // currentUser godoc // @Summary Get current platform user // @Description Returns the authenticated current user's bounded identity, roles, profile, and theme preference. // @Tags users // @Produce json // @Success 200 {object} dto.CurrentUserResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/users/current [get] func (h *coreHandlers) currentUser(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } user, err := h.core.GetCurrentUser(bearerToken(r)) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.CurrentUserFromDomain(user)) } // currentUserProfile godoc // @Summary Update current platform user profile // @Description Updates bounded profile fields for the authenticated current user. // @Tags users // @Accept json // @Produce json // @Param body body dto.UserProfileBody true "Profile update request" // @Success 200 {object} dto.CurrentUserResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/users/current/profile [put] func (h *coreHandlers) currentUserProfile(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPut { writeMethodNotAllowed(w, http.MethodPut) return } request, err := decodeJSON[dto.UserProfileBody](r) if err != nil { writeDecodeError(w, err) return } user, err := h.core.UpdateCurrentUserProfile(bearerToken(r), request.ToDomain()) if err != nil { writeServiceError(w, err) return } if request.DisplayName != "" { user.DisplayName = request.DisplayName user, err = h.core.UpdateUser(user.ID, user) if err != nil { writeServiceError(w, err) return } } writeJSON(w, http.StatusOK, dto.CurrentUserFromDomain(user)) } // currentUserTheme godoc // @Summary Update current platform user theme // @Description Persists the authenticated user's console theme preference. // @Tags users // @Accept json // @Produce json // @Param body body dto.UserThemePreferenceRequest true "Theme preference request" // @Success 200 {object} dto.UserThemePreferenceResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/users/current/theme [put] func (h *coreHandlers) currentUserTheme(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPut { writeMethodNotAllowed(w, http.MethodPut) return } request, err := decodeJSON[dto.UserThemePreferenceRequest](r) if err != nil { writeDecodeError(w, err) return } preference, err := h.core.UpdateCurrentUserTheme(bearerToken(r), request.ToDomain("")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.UserThemePreferenceFromDomain(preference)) } func bearerToken(r *http.Request) string { const prefix = "Bearer " header := r.Header.Get("Authorization") if len(header) >= len(prefix) && header[:len(prefix)] == prefix { return header[len(prefix):] } cookie, err := r.Cookie(platformSessionCookieName) if err != nil { return "" } return cookie.Value } // pluginBridgeAuthorize godoc // @Summary Authorize plugin page bridge action // @Description Evaluates one plugin bridge action against installed plugin manifest permissions without executing privileged work. // @Tags plugin-bridge // @Accept json // @Produce json // @Param body body dto.PluginBridgeAuthorizeRequest true "Plugin bridge authorization request" // @Success 200 {object} dto.PluginBridgeAuthorizeResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/plugin-bridge/authorize [post] func (h *coreHandlers) pluginBridgeAuthorize(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.PluginBridgeAuthorizeRequest](r) if err != nil { writeDecodeError(w, err) return } var result domain.PluginBridgeAuthorization if h.enforceAuthorization { result, err = h.core.AuthorizePluginBridgeActionForSession(bearerToken(r), request.ToDomain()) } else { result, err = h.core.AuthorizePluginBridgeAction(request.ToDomain()) } if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.PluginBridgeAuthorizeFromDomain(result)) } // pluginBridgeExecute godoc // @Summary Execute plugin page bridge action // @Description Authorizes and executes one platform-mediated plugin page bridge action without exposing platform auth, run sockets, host paths, or provider credentials. // @Tags plugin-bridge // @Accept json // @Produce json // @Param body body dto.PluginBridgeExecuteRequest true "Plugin bridge execution request" // @Success 200 {object} dto.PluginBridgeExecuteResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/plugin-bridge/execute [post] func (h *coreHandlers) pluginBridgeExecute(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.PluginBridgeExecuteRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.ExecutePluginBridgeAction(bearerToken(r), request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.PluginBridgeExecuteFromDomain(result)) } // users godoc // @Summary Create or list users // @Description Creates a platform user or lists platform users. // @Tags users // @Accept json // @Produce json // @Param body body dto.UserCreateRequest false "User create request" // @Success 200 {object} dto.UserListResponse // @Success 201 {object} dto.UserResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/users [get] // @Router /api/v1/users [post] func (h *coreHandlers) users(w http.ResponseWriter, r *http.Request) { if _, ok := h.requirePlatformAdmin(w, r); !ok { return } switch r.Method { case http.MethodGet: users, err := h.core.ListUsers(domain.UserFilter{Status: domain.UserStatus(r.URL.Query().Get("status"))}) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.UserListFromDomain(users)) case http.MethodPost: request, err := decodeJSON[dto.UserCreateRequest](r) if err != nil { writeDecodeError(w, err) return } user, err := h.core.CreateUser(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusCreated, dto.UserFromDomain(user)) default: writeMethodNotAllowed(w, "GET, POST") } } // userDetail godoc // @Summary Get or update user // @Description Returns or updates one platform user by ID. // @Tags users // @Accept json // @Produce json // @Param id path string true "User ID" // @Param body body dto.UserUpdateRequest false "User update request" // @Success 200 {object} dto.UserResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/users/{id} [get] // @Router /api/v1/users/{id} [put] func (h *coreHandlers) userDetail(w http.ResponseWriter, r *http.Request) { if _, ok := h.requirePlatformAdmin(w, r); !ok { return } switch r.Method { case http.MethodGet: user, err := h.core.GetUser(r.PathValue("id")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.UserFromDomain(user)) case http.MethodPut: existing, err := h.core.GetUser(r.PathValue("id")) if err != nil { writeServiceError(w, err) return } request, err := decodeJSON[dto.UserUpdateRequest](r) if err != nil { writeDecodeError(w, err) return } user, err := h.core.UpdateUser(r.PathValue("id"), request.ApplyTo(existing)) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.UserFromDomain(user)) default: writeMethodNotAllowed(w, "GET, PUT") return } } func (h *coreHandlers) requirePlatformAdmin(w http.ResponseWriter, r *http.Request) (domain.User, bool) { user, err := h.core.GetCurrentUser(bearerToken(r)) if err != nil { writeServiceError(w, err) return domain.User{}, false } for _, role := range user.Roles { switch role { case "platform-admin", "admin": return user, true } } writeServiceError(w, service.ErrForbidden) return domain.User{}, false } // aiProviders godoc // @Summary Create or list AI providers // @Description Creates or lists platform-managed AI provider metadata without raw keys. // @Tags ai-providers // @Accept json // @Produce json // @Param body body dto.AIProviderCreateRequest false "AI provider create request" // @Success 200 {object} dto.AIProviderListResponse // @Success 201 {object} dto.AIProviderResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/ai-providers [get] // @Router /api/v1/ai-providers [post] func (h *coreHandlers) aiProviders(w http.ResponseWriter, r *http.Request) { if _, ok := h.requirePlatformAdmin(w, r); !ok { return } switch r.Method { case http.MethodGet: providers, err := h.core.ListAIProviders(domain.AIProviderFilter{ Kind: domain.AIProviderKind(r.URL.Query().Get("kind")), Status: domain.AIProviderStatus(r.URL.Query().Get("status")), }) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.AIProviderListFromDomain(providers)) case http.MethodPost: request, err := decodeJSON[dto.AIProviderCreateRequest](r) if err != nil { writeDecodeError(w, err) return } provider, err := h.core.CreateAIProvider(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusCreated, dto.AIProviderFromDomain(provider)) default: writeMethodNotAllowed(w, "GET, POST") } } // aiProviderDetail godoc // @Summary Get AI provider // @Description Returns one platform-managed AI provider by ID without raw key or base URL material. // @Tags ai-providers // @Produce json // @Param id path string true "AI provider ID" // @Success 200 {object} dto.AIProviderResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/ai-providers/{id} [get] // @Router /api/v1/ai-providers/{id} [put] func (h *coreHandlers) aiProviderDetail(w http.ResponseWriter, r *http.Request) { if _, ok := h.requirePlatformAdmin(w, r); !ok { return } switch r.Method { case http.MethodGet: provider, err := h.core.GetAIProvider(r.PathValue("id")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.AIProviderFromDomain(provider)) case http.MethodPut: existing, err := h.core.GetAIProvider(r.PathValue("id")) if err != nil { writeServiceError(w, err) return } request, err := decodeJSON[dto.AIProviderUpdateRequest](r) if err != nil { writeDecodeError(w, err) return } update := request.ToDomain(r.PathValue("id"), existing.Status) if strings.TrimSpace(update.BaseURL) == "" { update.BaseURL = existing.BaseURL } if strings.TrimSpace(update.APIKeyRef) == "" { update.APIKeyRef = existing.APIKeyRef } provider, err := h.core.UpdateAIProvider(r.PathValue("id"), update) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.AIProviderFromDomain(provider)) default: writeMethodNotAllowed(w, "GET, PUT") } } // aiProviderStatus godoc // @Summary Set AI provider status // @Description Enables or disables one AI provider without exposing raw key material. // @Tags ai-providers // @Accept json // @Produce json // @Param id path string true "AI provider ID" // @Param body body dto.AIProviderStatusRequest true "AI provider status request" // @Success 200 {object} dto.AIProviderResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/ai-providers/{id}/status [post] func (h *coreHandlers) aiProviderStatus(w http.ResponseWriter, r *http.Request) { if _, ok := h.requirePlatformAdmin(w, r); !ok { return } if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.AIProviderStatusRequest](r) if err != nil { writeDecodeError(w, err) return } provider, err := h.core.SetAIProviderStatus(r.PathValue("id"), request.Status) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.AIProviderFromDomain(provider)) } // aiProviderTest godoc // @Summary Test AI provider metadata // @Description Performs local metadata validation for one AI provider without external network calls. // @Tags ai-providers // @Produce json // @Param id path string true "AI provider ID" // @Success 200 {object} dto.AIProviderTestResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/ai-providers/{id}/test [post] func (h *coreHandlers) aiProviderTest(w http.ResponseWriter, r *http.Request) { if _, ok := h.requirePlatformAdmin(w, r); !ok { return } if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } result, err := h.core.TestAIProvider(r.PathValue("id")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.AIProviderTestFromDomain(result)) } // aiProviderModels godoc // @Summary List AI provider configured models // @Description Returns configured model names for one AI provider without exposing credentials. // @Tags ai-providers // @Produce json // @Param id path string true "AI provider ID" // @Success 200 {object} dto.AIProviderModelsResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/ai-providers/{id}/models [get] func (h *coreHandlers) aiProviderModels(w http.ResponseWriter, r *http.Request) { if _, ok := h.requirePlatformAdmin(w, r); !ok { return } if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } models, err := h.core.ListAIProviderModels(r.PathValue("id")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.AIProviderModelsFromDomain(models)) } // aiInvocation godoc // @Summary Invoke platform-mediated AI // @Description Invokes AI through platform-owned provider metadata and returns redacted recommendations without exposing provider credentials. // @Tags ai // @Accept json // @Produce json // @Param body body dto.AIInvocationRequest true "AI invocation request" // @Success 200 {object} dto.AIInvocationResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/ai/invocations [post] func (h *coreHandlers) aiInvocation(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.AIInvocationRequest](r) if err != nil { writeDecodeError(w, err) return } response, err := h.core.InvokeAIForSession(bearerToken(r), request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.AIInvocationFromDomain(response)) } func (h *coreHandlers) aiConfigSuggestion(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.LlmConfigSuggestionRequest](r) if err != nil { writeDecodeError(w, err) return } response, err := h.core.InvokeAIForSession(bearerToken(r), domain.AIInvocationRequest{ RequestID: "config-suggestion:" + request.ServerInstanceID, ServerInstanceID: request.ServerInstanceID, Purpose: "config.suggest", Prompt: request.Prompt, CurrentConfig: request.CurrentConfig, ContextRefs: map[string]string{"server": "server://" + request.ServerInstanceID}, }) if err != nil { writeServiceError(w, err) return } if response.Status != "ok" { recommendation := "AI suggestion unavailable" if response.Error != nil { recommendation = response.Error.Message } writeJSON(w, http.StatusOK, dto.LlmConfigSuggestionResponse{ServerInstanceID: request.ServerInstanceID, Recommendation: recommendation}) return } suggested := "" if response.ConfigRecommendation != nil { suggested = response.ConfigRecommendation.SuggestedConfig } writeJSON(w, http.StatusOK, dto.LlmConfigSuggestionResponse{ServerInstanceID: request.ServerInstanceID, Recommendation: response.Recommendation, SuggestedConfig: suggested}) } // gamePlugins godoc // @Summary Create or list game management plugins // @Description Creates or lists installed game management plugin metadata. // @Tags game-plugins // @Accept json // @Produce json // @Param body body dto.GamePluginCreateRequest false "Game plugin create request" // @Success 200 {object} dto.GamePluginListResponse // @Success 201 {object} dto.GamePluginResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/game-plugins [get] // @Router /api/v1/game-plugins [post] func (h *coreHandlers) gamePlugins(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: plugins, err := h.core.ListGamePlugins(domain.GamePluginFilter{ ServerType: r.URL.Query().Get("serverType"), Status: domain.GamePluginStatus(r.URL.Query().Get("status")), }) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.GamePluginListFromDomain(plugins)) case http.MethodPost: request, err := decodeJSON[dto.GamePluginCreateRequest](r) if err != nil { writeDecodeError(w, err) return } plugin, err := h.core.CreateGamePlugin(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusCreated, dto.GamePluginFromDomain(plugin)) default: writeMethodNotAllowed(w, "GET, POST") } } // gamePluginManifestRegistration godoc // @Summary Register game management plugin manifest // @Description Validates and registers one game management plugin manifest as installed registry metadata. // @Tags game-plugins // @Accept json // @Produce json // @Param body body dto.GamePluginManifestRegistrationRequest true "Game plugin manifest registration request" // @Success 201 {object} dto.GamePluginResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 409 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/game-plugins/register-manifest [post] func (h *coreHandlers) gamePluginManifestRegistration(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.GamePluginManifestRegistrationRequest](r) if err != nil { writeDecodeError(w, err) return } plugin, err := h.core.RegisterGamePluginManifest(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusCreated, dto.GamePluginFromDomain(plugin)) } // gamePluginDetail godoc // @Summary Get game management plugin // @Description Returns one game management plugin by ID. // @Tags game-plugins // @Produce json // @Param id path string true "Game plugin ID" // @Success 200 {object} dto.GamePluginResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/game-plugins/{id} [get] func (h *coreHandlers) gamePluginDetail(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } plugin, err := h.core.GetGamePlugin(r.PathValue("id")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.GamePluginFromDomain(plugin)) } // marketplacePlugins godoc // @Summary List plugin marketplace entries // @Description Lists marketplace plugin metadata projected from the platform registry without commerce data, host paths, run sockets, or raw credentials. // @Tags plugin-marketplace // @Produce json // @Param status query string false "Plugin status" // @Param serverType query string false "Server type" // @Param capability query string false "Run or bridge capability" // @Param keyword query string false "Keyword search" // @Success 200 {object} dto.MarketplacePluginListResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/plugin-marketplace/plugins [get] func (h *coreHandlers) marketplacePlugins(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } plugins, err := h.core.ListMarketplacePlugins(domain.PluginMarketplaceFilter{ ServerType: r.URL.Query().Get("serverType"), Status: domain.GamePluginStatus(r.URL.Query().Get("status")), Capability: r.URL.Query().Get("capability"), Keyword: r.URL.Query().Get("keyword"), }) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.MarketplacePluginListFromDomain(plugins)) } // marketplacePluginDetail godoc // @Summary Get plugin marketplace detail // @Description Returns one plugin marketplace entry with manifest-backed metadata and redacted platform-safe fields. // @Tags plugin-marketplace // @Produce json // @Param id path string true "Plugin ID" // @Success 200 {object} dto.MarketplacePluginResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/plugin-marketplace/plugins/{id} [get] func (h *coreHandlers) marketplacePluginDetail(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } plugin, err := h.core.GetMarketplacePlugin(r.PathValue("id")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.MarketplacePluginFromDomain(plugin)) } // marketplacePluginState godoc // @Summary Change plugin marketplace state // @Description Applies metadata-only install, enable, or disable state changes without package download or run execution. // @Tags plugin-marketplace // @Accept json // @Produce json // @Param id path string true "Plugin ID" // @Param body body dto.MarketplacePluginStateRequest true "Marketplace state action" // @Success 200 {object} dto.MarketplacePluginResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/plugin-marketplace/plugins/{id}/state [post] func (h *coreHandlers) marketplacePluginState(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.MarketplacePluginStateRequest](r) if err != nil { writeDecodeError(w, err) return } plugin, err := h.core.SetMarketplacePluginState(r.PathValue("id"), request.Action) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.MarketplacePluginFromDomain(plugin)) } // serverInstances godoc // @Summary Create or list server instances // @Description Creates or lists server instances linked to installed game plugins and run endpoints. // @Tags server-instances // @Accept json // @Produce json // @Param body body dto.ServerInstanceCreateRequest false "Server instance create request" // @Success 200 {object} dto.ServerInstanceListResponse // @Success 201 {object} dto.ServerInstanceResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/server-instances [get] // @Router /api/v1/server-instances [post] func (h *coreHandlers) serverInstances(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: instances, err := h.core.ListServerInstancesForSession(bearerToken(r), domain.ServerInstanceFilter{ PluginID: r.URL.Query().Get("pluginId"), RunEndpointID: r.URL.Query().Get("runEndpointId"), State: domain.ServerInstanceState(r.URL.Query().Get("state")), }) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ServerInstanceListFromDomain(instances)) case http.MethodPost: request, err := decodeJSON[dto.ServerInstanceCreateRequest](r) if err != nil { writeDecodeError(w, err) return } instance, err := h.core.CreateServerInstanceForSession(bearerToken(r), request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusCreated, dto.ServerInstanceFromDomain(instance)) default: writeMethodNotAllowed(w, "GET, POST") } } // serverInstanceDetail godoc // @Summary Get, update, or delete server instance // @Description Returns one server instance by ID, updates safe metadata, or deletes it by marking the instance deleted after safety validation and password confirmation. Delete requests send a JSON body with the current password. // @Tags server-instances // @Accept json // @Produce json // @Param id path string true "Server instance ID" // @Param body body dto.ServerInstanceUpdateRequest false "Server metadata update request" // @Success 204 // @Success 200 {object} dto.ServerInstanceResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/server-instances/{id} [get] // @Router /api/v1/server-instances/{id} [put] // @Router /api/v1/server-instances/{id} [delete] func (h *coreHandlers) serverInstanceDetail(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: instance, err := h.core.GetServerInstanceForSession(bearerToken(r), r.PathValue("id")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ServerInstanceFromDomain(instance)) case http.MethodPut: request, err := decodeJSON[dto.ServerInstanceUpdateRequest](r) if err != nil { writeDecodeError(w, err) return } instance, err := h.core.UpdateServerInstanceForSession(bearerToken(r), r.PathValue("id"), request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ServerInstanceFromDomain(instance)) case http.MethodDelete: request, err := decodeJSON[dto.ServerDeletionRequest](r) if err != nil { writeDecodeError(w, err) return } _, err = h.core.DeleteServerInstanceForSession(bearerToken(r), r.PathValue("id"), request.Password) if err != nil { writeServiceError(w, err) return } w.WriteHeader(http.StatusNoContent) default: writeMethodNotAllowed(w, http.MethodGet+", "+http.MethodPut+", "+http.MethodDelete) } } // platformMetrics godoc // @Summary Get platform resource usage metrics // @Description Returns bounded platform resource usage derived from platform metadata without host paths or secrets. // @Tags metrics // @Produce json // @Success 200 {object} dto.PlatformResourceUsageResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/metrics/platform [get] func (h *coreHandlers) platformMetrics(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } if _, ok := h.requirePlatformAdmin(w, r); !ok { return } usage, err := h.core.GetPlatformResourceUsage() if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.PlatformResourceUsageFromDomain(usage)) } // serverInstanceMetrics godoc // @Summary List visible server metrics // @Description Returns bounded metrics for server instances visible to the authenticated user. // @Tags metrics // @Produce json // @Success 200 {object} dto.ServerMetricsListResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/metrics/server-instances [get] func (h *coreHandlers) serverInstanceMetrics(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } metrics, err := h.core.ListServerMetricsForSession(bearerToken(r)) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ServerMetricsListFromDomain(metrics)) } // serverInstanceConfig godoc // @Summary Read server configuration // @Description Returns logical server configuration content for an authorized server instance without exposing run internals. // @Tags server-instances // @Produce json // @Param id path string true "Server instance ID" // @Success 200 {object} dto.ServerConfigResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/server-instances/{id}/config [get] func (h *coreHandlers) serverInstanceConfig(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } config, err := h.core.GetServerConfigForSession(bearerToken(r), r.PathValue("id")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ServerConfigFromDomain(config)) } // serverInstanceConfigDiff godoc // @Summary Preview server config diff // @Description Compares current logical server config with proposed content without dispatching a write job. // @Tags server-instances // @Accept json // @Produce json // @Param id path string true "Server instance ID" // @Param body body dto.ServerConfigDiffPreviewRequest true "Config diff preview request" // @Success 200 {object} dto.ServerConfigDiffPreviewResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/server-instances/{id}/config/diff [post] func (h *coreHandlers) serverInstanceConfigDiff(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.ServerConfigDiffPreviewRequest](r) if err != nil { writeDecodeError(w, err) return } preview, err := h.core.PreviewServerConfigWriteForSession(bearerToken(r), request.ToDomain(r.PathValue("id"))) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ServerConfigDiffPreviewFromDomain(preview)) } // serverInstanceConfigApprove godoc // @Summary Approve server config write // @Description Validates a reviewed config diff and queues a scoped config.write run job without exposing host paths or raw credentials. // @Tags server-instances // @Accept json // @Produce json // @Param id path string true "Server instance ID" // @Param body body dto.ServerConfigWriteApprovalRequest true "Config write approval request" // @Success 202 {object} dto.ServerConfigWriteDispatchResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/server-instances/{id}/config/approve [post] func (h *coreHandlers) serverInstanceConfigApprove(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.ServerConfigWriteApprovalRequest](r) if err != nil { writeDecodeError(w, err) return } dispatch, err := h.core.ApproveServerConfigWriteForSession(bearerToken(r), request.ToDomain(r.PathValue("id"))) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusAccepted, dto.ServerConfigWriteDispatchFromDomain(dispatch)) } // fileOperationDispatch godoc // @Summary Dispatch scoped file operation // @Description Queues a scoped files.read or files.write job using logical file keys or refs, never raw host paths. // @Tags file-operations // @Accept json // @Produce json // @Param body body dto.FileOperationDispatchRequest true "File operation dispatch request" // @Success 202 {object} dto.FileOperationDispatchResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/file-operations/dispatch [post] func (h *coreHandlers) fileOperationDispatch(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.FileOperationDispatchRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.DispatchFileOperationForSession(bearerToken(r), request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusAccepted, dto.FileOperationDispatchFromDomain(result)) } // serverAdministratorCandidates godoc // @Summary List server administrator invite candidates // @Description Returns active non-platform-admin users that the authenticated server owner can invite for one server. // @Tags server-instances // @Produce json // @Param id path string true "Server instance ID" // @Success 200 {object} dto.ServerMemberListResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/server-instances/{id}/administrators/candidates [get] func (h *coreHandlers) serverAdministratorCandidates(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } users, err := h.core.ListServerAdministratorCandidates(bearerToken(r), r.PathValue("id")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ServerMemberListFromDomain(users)) } // serverAdministrators godoc // @Summary Add server administrator // @Description Adds an active non-platform-admin user as a server-scoped administrator when requested by the server owner. // @Tags server-instances // @Accept json // @Produce json // @Param id path string true "Server instance ID" // @Param body body dto.ServerMemberRequest true "Server administrator request" // @Success 200 {object} dto.ServerInstanceResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/server-instances/{id}/administrators [post] func (h *coreHandlers) serverAdministrators(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.ServerMemberRequest](r) if err != nil { writeDecodeError(w, err) return } instance, err := h.core.AddServerAdministrator(bearerToken(r), r.PathValue("id"), request.UserID) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ServerInstanceFromDomain(instance)) } // serverAdministratorDetail godoc // @Summary Remove server administrator // @Description Removes a server-scoped administrator from a server when requested by the server owner. // @Tags server-instances // @Produce json // @Param id path string true "Server instance ID" // @Param userId path string true "User ID" // @Success 200 {object} dto.ServerInstanceResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/server-instances/{id}/administrators/{userId} [delete] func (h *coreHandlers) serverAdministratorDetail(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodDelete { writeMethodNotAllowed(w, http.MethodDelete) return } instance, err := h.core.RemoveServerAdministrator(bearerToken(r), r.PathValue("id"), r.PathValue("userId")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ServerInstanceFromDomain(instance)) } // runControlHello godoc // @Summary Register run control session // @Description Accepts lightweight run hello metadata, creates or updates run endpoint metadata, and returns a platform-issued session token. // @Tags run // @Accept json // @Produce json // @Param body body dto.RunControlHelloRequest true "Run control hello request" // @Success 200 {object} dto.RunControlHelloResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/control/hello [post] func (h *coreHandlers) runControlHello(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.RunControlHelloRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.RegisterRunHello(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunControlHelloFromDomain(result)) } // runControlHeartbeat godoc // @Summary Accept run control heartbeat // @Description Accepts lightweight run heartbeat metadata when the active platform-issued session token matches. // @Tags run // @Accept json // @Produce json // @Param body body dto.RunControlHeartbeatRequest true "Run control heartbeat request" // @Success 200 {object} dto.RunControlHeartbeatResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/control/heartbeat [post] func (h *coreHandlers) runControlHeartbeat(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.RunControlHeartbeatRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.AcceptRunHeartbeat(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunControlHeartbeatFromDomain(result)) } // runJobClaim godoc // @Summary Claim one run job // @Description Lets a registered run endpoint claim one queued job assigned to it using the active session token. // @Tags run-jobs // @Accept json // @Produce json // @Param body body dto.RunJobClaimRequest true "Run job claim request" // @Success 200 {object} dto.RunJobClaimResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/jobs/claim [post] func (h *coreHandlers) runJobClaim(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.RunJobClaimRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.ClaimRunJob(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunJobClaimFromDomain(result)) } // runJobAck godoc // @Summary Acknowledge one run job // @Description Lets a registered run endpoint acknowledge an active job lease before execution. // @Tags run-jobs // @Accept json // @Produce json // @Param body body dto.RunJobAckRequest true "Run job ack request" // @Success 200 {object} dto.RunJobAckResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/jobs/ack [post] func (h *coreHandlers) runJobAck(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.RunJobAckRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.AckRunJob(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunJobAckFromDomain(result)) } // runJobProgress godoc // @Summary Update run job progress // @Description Lets a registered run endpoint report bounded progress for an active job lease. // @Tags run-jobs // @Accept json // @Produce json // @Param body body dto.RunJobProgressRequest true "Run job progress request" // @Success 200 {object} dto.RunJobProgressResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/jobs/progress [post] func (h *coreHandlers) runJobProgress(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.RunJobProgressRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.UpdateRunJobProgress(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunJobProgressFromDomain(result)) } // runJobResult godoc // @Summary Complete run job // @Description Lets a registered run endpoint submit a bounded terminal result for an active job lease. // @Tags run-jobs // @Accept json // @Produce json // @Param body body dto.RunJobResultRequest true "Run job result request" // @Success 200 {object} dto.RunJobResultResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/jobs/result [post] func (h *coreHandlers) runJobResult(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.RunJobResultRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.CompleteRunJob(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunJobResultFromDomain(result)) } // runJobBuildInput returns secret-bearing build input only to the active leased run worker. func (h *coreHandlers) runJobBuildInput(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.DistributionBuildInputRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.GetDistributionBuildInput(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.DistributionBuildInputFromDomain(result)) } // runJobDependencyInput returns declared and resolved dependency input only to the active fenced Run attempt. func (h *coreHandlers) runJobDependencyInput(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.DependencyExecutionInputRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.GetDependencyExecutionInput(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.DependencyExecutionInputFromDomain(result)) } // runJobUpdateInput returns update metadata only to the active fenced Run attempt. func (h *coreHandlers) runJobUpdateInput(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.RunUpdateInputRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.GetRunUpdateInput(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunUpdateInputFromDomain(result)) } // runJobUpdateChunk serves one bounded update range only to the active fenced Run attempt. func (h *coreHandlers) runJobUpdateChunk(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.RunUpdateChunkRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.ReadRunUpdateChunk(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunUpdateChunkFromDomain(result)) } // runJobUpdateHealth godoc // @Summary Confirm a reconciled Run self-update outcome // @Description Accepts a signed current-session health or rollback report fenced to the terminal update job attempt. // @Tags run-jobs // @Accept json // @Produce json // @Param body body dto.RunUpdateHealthRequest true "Run update health report" // @Success 200 {object} dto.RunUpdateHealthResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/jobs/update-health [post] func (h *coreHandlers) runJobUpdateHealth(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.RunUpdateHealthRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.ReportRunUpdateHealth(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunUpdateHealthFromDomain(result)) } // runJobCancelPoll godoc // @Summary Poll run job cancellation // @Description Lets a registered run endpoint poll for cancellation requests on active leased jobs. // @Tags run-jobs // @Accept json // @Produce json // @Param body body dto.RunJobCancelPollRequest true "Run job cancel poll request" // @Success 200 {object} dto.RunJobCancelPollResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/jobs/cancel [post] func (h *coreHandlers) runJobCancelPoll(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.RunJobCancelPollRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.PollRunJobCancel(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunJobCancelPollFromDomain(result)) } // runJobReconcile godoc // @Summary Reconcile run jobs // @Description Lets a registered run endpoint reconcile active platform jobs after restart or reconnect. // @Tags run-jobs // @Accept json // @Produce json // @Param body body dto.RunJobReconcileRequest true "Run job reconcile request" // @Success 200 {object} dto.RunJobReconcileResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/jobs/reconcile [post] func (h *coreHandlers) runJobReconcile(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.RunJobReconcileRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.ReconcileRunJobs(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunJobReconcileFromDomain(result)) } // runLogBatchIngest godoc // @Summary Ingest run log batch // @Description Accepts one bounded durable log batch from a registered run endpoint and returns an acknowledgement range. // @Tags run-logs // @Accept json // @Produce json // @Param body body dto.LogBatchIngestRequest true "Log batch ingest request" // @Success 200 {object} dto.LogBatchIngestResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/logs/batches [post] func (h *coreHandlers) runLogBatchIngest(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.LogBatchIngestRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.IngestLogBatch(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.LogBatchIngestFromDomain(result)) } // runArtifactOpen godoc // @Summary Open run artifact upload transfer // @Description Lets a registered run endpoint open a resumable upload transfer for a scoped artifact owner. // @Tags run-artifacts // @Accept json // @Produce json // @Param body body dto.ArtifactTransferOpenRequest true "Artifact transfer open request" // @Success 200 {object} dto.ArtifactTransferOpenResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/artifacts/open [post] func (h *coreHandlers) runArtifactOpen(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.ArtifactTransferOpenRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.OpenArtifactTransfer(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ArtifactTransferOpenFromDomain(result)) } // runArtifactChunkUpload godoc // @Summary Upload run artifact chunk // @Description Accepts one bounded artifact chunk from a registered run endpoint and returns resumable acknowledgement state. // @Tags run-artifacts // @Accept json // @Produce json // @Param body body dto.ArtifactChunkUploadRequest true "Artifact chunk upload request" // @Success 200 {object} dto.ArtifactChunkUploadResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/artifacts/chunks [post] func (h *coreHandlers) runArtifactChunkUpload(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.ArtifactChunkUploadRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.UploadArtifactChunk(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ArtifactChunkUploadFromDomain(result)) } // runArtifactStatus godoc // @Summary Query run artifact upload status // @Description Returns resumable chunk acknowledgement state for one artifact transfer. // @Tags run-artifacts // @Accept json // @Produce json // @Param body body dto.ArtifactTransferStatusRequest true "Artifact transfer status request" // @Success 200 {object} dto.ArtifactTransferStatusResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/artifacts/status [post] func (h *coreHandlers) runArtifactStatus(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.ArtifactTransferStatusRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.QueryArtifactTransferStatus(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ArtifactTransferStatusFromDomain(result)) } // runArtifactComplete godoc // @Summary Complete run artifact upload transfer // @Description Marks an artifact available only after every chunk is present and final checksum validation passes. // @Tags run-artifacts // @Accept json // @Produce json // @Param body body dto.ArtifactTransferCompleteRequest true "Artifact transfer complete request" // @Success 200 {object} dto.ArtifactTransferCompleteResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/artifacts/complete [post] func (h *coreHandlers) runArtifactComplete(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.ArtifactTransferCompleteRequest](r) if err != nil { writeDecodeError(w, err) return } result, err := h.core.CompleteArtifactTransfer(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ArtifactTransferCompleteFromDomain(result)) } // runEndpoints godoc // @Summary Create or list run endpoints // @Description Creates or lists run endpoint metadata used by platform-mediated jobs. // @Tags run // @Accept json // @Produce json // @Param body body dto.RunEndpointCreateRequest false "Run endpoint create request" // @Success 200 {object} dto.RunEndpointListResponse // @Success 201 {object} dto.RunEndpointResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/endpoints [get] // @Router /api/v1/run/endpoints [post] func (h *coreHandlers) runEndpoints(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: endpoints, err := h.core.ListRunEndpoints(domain.RunEndpointFilter{Status: domain.RunEndpointStatus(r.URL.Query().Get("status"))}) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunEndpointListFromDomain(endpoints)) case http.MethodPost: request, err := decodeJSON[dto.RunEndpointCreateRequest](r) if err != nil { writeDecodeError(w, err) return } endpoint, err := h.core.CreateRunEndpoint(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusCreated, dto.RunEndpointFromDomain(endpoint)) default: writeMethodNotAllowed(w, "GET, POST") } } // runEndpointDetail godoc // @Summary Get run endpoint // @Description Returns one run endpoint by ID. // @Tags run // @Produce json // @Param id path string true "Run endpoint ID" // @Success 200 {object} dto.RunEndpointResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/run/endpoints/{id} [get] func (h *coreHandlers) runEndpointDetail(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } endpoint, err := h.core.GetRunEndpoint(r.PathValue("id")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunEndpointFromDomain(endpoint)) } // jobs godoc // @Summary Create or list jobs // @Description Creates or lists platform job metadata. // @Tags jobs // @Accept json // @Produce json // @Param body body dto.JobCreateRequest false "Job create request" // @Success 200 {object} dto.JobListResponse // @Success 201 {object} dto.JobResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/jobs [get] // @Router /api/v1/jobs [post] func (h *coreHandlers) jobs(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: filter := domain.JobFilter{ ServerInstanceID: r.URL.Query().Get("serverInstanceId"), RunEndpointID: r.URL.Query().Get("runEndpointId"), State: domain.JobState(r.URL.Query().Get("state")), } var jobs []domain.Job var err error if h.enforceAuthorization { jobs, err = h.core.ListJobsForSession(bearerToken(r), filter) } else { jobs, err = h.core.ListJobs(filter) } if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.JobListFromDomain(jobs)) case http.MethodPost: request, err := decodeJSON[dto.JobCreateRequest](r) if err != nil { writeDecodeError(w, err) return } job, err := h.core.CreateJob(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusCreated, dto.JobFromDomain(job)) default: writeMethodNotAllowed(w, "GET, POST") } } // jobCancel godoc // @Summary Request job cancellation // @Description Records a cancellation request for an accepted or running job; run observes it through the job cancel poll route. // @Tags jobs // @Accept json // @Produce json // @Param id path string true "Job ID" // @Param body body dto.RunJobCancelRequestBody true "Job cancel request" // @Success 200 {object} dto.RunJobCancelRequestResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/jobs/{id}/cancel [post] func (h *coreHandlers) jobCancel(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.RunJobCancelRequestBody](r) if err != nil { writeDecodeError(w, err) return } request.JobID = r.PathValue("id") var result domain.RunJobCancelRequestResult if h.enforceAuthorization { result, err = h.core.RequestRunJobCancelForSession(bearerToken(r), request.ToDomain()) } else { result, err = h.core.RequestRunJobCancel(request.ToDomain()) } if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.RunJobCancelRequestFromDomain(result)) } // jobDetail godoc // @Summary Get job // @Description Returns one platform job by ID. // @Tags jobs // @Produce json // @Param id path string true "Job ID" // @Success 200 {object} dto.JobResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/jobs/{id} [get] func (h *coreHandlers) jobDetail(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } var job domain.Job var err error if h.enforceAuthorization { job, err = h.core.GetJobForSession(bearerToken(r), r.PathValue("id")) } else { job, err = h.core.GetJob(r.PathValue("id")) } if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.JobFromDomain(job)) } // artifacts godoc // @Summary Create or list artifact metadata // @Description Creates or lists artifact metadata; run chunk upload and browser content download use dedicated artifact transfer routes. // @Tags artifacts // @Accept json // @Produce json // @Param body body dto.ArtifactCreateRequest false "Artifact create request" // @Success 200 {object} dto.ArtifactListResponse // @Success 201 {object} dto.ArtifactResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/artifacts [get] // @Router /api/v1/artifacts [post] func (h *coreHandlers) artifacts(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: filter := domain.ArtifactFilter{ OwnerKind: domain.ArtifactOwnerKind(r.URL.Query().Get("ownerKind")), OwnerID: r.URL.Query().Get("ownerId"), State: domain.ArtifactState(r.URL.Query().Get("state")), } var artifacts []domain.Artifact var err error if h.enforceAuthorization { artifacts, err = h.core.ListArtifactsForSession(bearerToken(r), filter) } else { artifacts, err = h.core.ListArtifacts(filter) } if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ArtifactListFromDomain(artifacts)) case http.MethodPost: request, err := decodeJSON[dto.ArtifactCreateRequest](r) if err != nil { writeDecodeError(w, err) return } artifact, err := h.core.CreateArtifact(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusCreated, dto.ArtifactFromDomain(artifact)) default: writeMethodNotAllowed(w, "GET, POST") } } // artifactDetail godoc // @Summary Get artifact metadata // @Description Returns one artifact metadata record by ID. // @Tags artifacts // @Produce json // @Param id path string true "Artifact ID" // @Success 200 {object} dto.ArtifactResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/artifacts/{id} [get] func (h *coreHandlers) artifactDetail(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } artifact, err := h.core.GetArtifactForSession(bearerToken(r), r.PathValue("id")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ArtifactFromDomain(artifact)) } // artifactDownload godoc // @Summary Open a browser-safe artifact download reference // @Description Returns a platform-owned artifact download reference without exposing storage paths, direct run sockets, or credentials. // @Tags artifacts // @Produce json // @Param id path string true "Artifact ID" // @Success 200 {object} dto.ArtifactDownloadReferenceResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/artifacts/{id}/download [post] func (h *coreHandlers) artifactDownload(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } reference, err := h.core.OpenArtifactDownloadForSession(bearerToken(r), domain.ArtifactDownloadReferenceRequest{ArtifactID: r.PathValue("id")}) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.ArtifactDownloadReferenceFromDomain(reference)) } // artifactContent godoc // @Summary Read bounded artifact content // @Description Streams a bounded artifact byte range through platform authorization with safe integrity headers. // @Tags artifacts // @Produce octet-stream // @Param id path string true "Artifact ID" // @Param offset query int false "Byte offset" // @Param limit query int false "Maximum bytes" // @Success 200 {file} binary // @Success 206 {file} binary // @Failure 400 {object} dto.ErrorResponse // @Failure 401 {object} dto.ErrorResponse // @Failure 403 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/artifacts/{id}/content [get] func (h *coreHandlers) artifactContent(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } offset, limit, err := artifactRangeRequest(r) if err != nil { writeServiceError(w, err) return } content, err := h.core.ReadArtifactContentForSession(bearerToken(r), domain.ArtifactContentRequest{ArtifactID: r.PathValue("id"), Offset: offset, Limit: limit}) if err != nil { writeServiceError(w, err) return } w.Header().Set("Content-Type", content.ContentType) w.Header().Set("Content-Disposition", "attachment; filename=\""+strings.ReplaceAll(content.Filename, "\"", "")+"\"") w.Header().Set("Accept-Ranges", "bytes") w.Header().Set("Content-Length", strconv.FormatInt(content.SizeBytes, 10)) w.Header().Set("X-Artifact-Id", content.ArtifactID) w.Header().Set("X-Artifact-Checksum", content.Checksum) w.Header().Set("X-Artifact-Content-Checksum", content.ContentChecksum) w.Header().Set("X-Artifact-Storage", content.StorageBehavior) if content.Partial { end := content.Offset + content.SizeBytes - 1 w.Header().Set("Content-Range", "bytes "+strconv.FormatInt(content.Offset, 10)+"-"+strconv.FormatInt(end, 10)+"/"+strconv.FormatInt(content.TotalSizeBytes, 10)) w.WriteHeader(http.StatusPartialContent) } else { w.WriteHeader(http.StatusOK) } _, _ = w.Write(content.Payload) } func artifactRangeRequest(r *http.Request) (int64, int, error) { query := r.URL.Query() offset, err := parseOptionalInt64(query.Get("offset")) if err != nil { return 0, 0, validator.ValidationError{Violations: []string{"offset must be a number"}} } limit64, err := parseOptionalInt64(query.Get("limit")) if err != nil { return 0, 0, validator.ValidationError{Violations: []string{"limit must be a number"}} } limit := int(limit64) if rangeHeader := strings.TrimSpace(r.Header.Get("Range")); rangeHeader != "" { rangeOffset, rangeLimit, ok := parseByteRange(rangeHeader) if !ok { return 0, 0, validator.ValidationError{Violations: []string{"range header is invalid"}} } offset = rangeOffset limit = rangeLimit } return offset, limit, nil } func parseOptionalInt64(value string) (int64, error) { if strings.TrimSpace(value) == "" { return 0, nil } return strconv.ParseInt(value, 10, 64) } func parseByteRange(header string) (int64, int, bool) { if !strings.HasPrefix(header, "bytes=") { return 0, 0, false } parts := strings.Split(strings.TrimPrefix(header, "bytes="), "-") if len(parts) != 2 || strings.TrimSpace(parts[0]) == "" || strings.TrimSpace(parts[1]) == "" { return 0, 0, false } start, err := strconv.ParseInt(parts[0], 10, 64) if err != nil { return 0, 0, false } end, err := strconv.ParseInt(parts[1], 10, 64) if err != nil || end < start { return 0, 0, false } length := end - start + 1 if length > int64(validator.MaxArtifactDownloadBytes) { return 0, 0, false } return start, int(length), true } // logStreams godoc // @Summary Create or list log stream metadata // @Description Creates or lists log stream metadata; durable ingest and cursor query use dedicated log routes while browser tail transport remains future work. // @Tags logs // @Accept json // @Produce json // @Param body body dto.LogStreamCreateRequest false "Log stream create request" // @Success 200 {object} dto.LogStreamListResponse // @Success 201 {object} dto.LogStreamResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/log-streams [get] // @Router /api/v1/log-streams [post] func (h *coreHandlers) logStreams(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: filter := domain.LogStreamFilter{ ServerInstanceID: r.URL.Query().Get("serverInstanceId"), StreamKey: r.URL.Query().Get("streamKey"), } var streams []domain.LogStream var err error if h.enforceAuthorization { streams, err = h.core.ListLogStreamsForSession(bearerToken(r), filter) } else { streams, err = h.core.ListLogStreams(filter) } if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.LogStreamListFromDomain(streams)) case http.MethodPost: request, err := decodeJSON[dto.LogStreamCreateRequest](r) if err != nil { writeDecodeError(w, err) return } stream, err := h.core.CreateLogStream(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusCreated, dto.LogStreamFromDomain(stream)) default: writeMethodNotAllowed(w, "GET, POST") } } // logStreamQuery godoc // @Summary Query log stream entries // @Description Returns bounded stored log entries after a stream sequence cursor. // @Tags logs // @Accept json // @Produce json // @Param body body dto.LogStreamCursorRequest true "Log stream cursor request" // @Success 200 {object} dto.LogStreamCursorResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/log-streams/query [post] func (h *coreHandlers) logStreamQuery(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMethodNotAllowed(w, http.MethodPost) return } request, err := decodeJSON[dto.LogStreamCursorRequest](r) if err != nil { writeDecodeError(w, err) return } var result domain.LogStreamCursorResult if h.enforceAuthorization { result, err = h.core.QueryLogStreamForSession(bearerToken(r), request.ToDomain()) } else { result, err = h.core.QueryLogStream(request.ToDomain()) } if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.LogStreamCursorFromDomain(result)) } // logStreamDetail godoc // @Summary Get log stream metadata // @Description Returns one log stream metadata record by ID. // @Tags logs // @Produce json // @Param id path string true "Log stream ID" // @Success 200 {object} dto.LogStreamResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/log-streams/{id} [get] func (h *coreHandlers) logStreamDetail(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } var stream domain.LogStream var err error if h.enforceAuthorization { stream, err = h.core.GetLogStreamForSession(bearerToken(r), r.PathValue("id")) } else { stream, err = h.core.GetLogStream(r.PathValue("id")) } if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.LogStreamFromDomain(stream)) } // auditEvents godoc // @Summary Create or list audit events // @Description Creates or lists audit event metadata. // @Tags audit-events // @Accept json // @Produce json // @Param body body dto.AuditEventCreateRequest false "Audit event create request" // @Success 200 {object} dto.AuditEventListResponse // @Success 201 {object} dto.AuditEventResponse // @Failure 400 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/audit-events [get] // @Router /api/v1/audit-events [post] func (h *coreHandlers) auditEvents(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: events, err := h.core.ListAuditEvents(domain.AuditEventFilter{ ActorID: r.URL.Query().Get("actorId"), ResourceKind: r.URL.Query().Get("resourceKind"), ResourceID: r.URL.Query().Get("resourceId"), Result: domain.AuditResult(r.URL.Query().Get("result")), }) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.AuditEventListFromDomain(events)) case http.MethodPost: request, err := decodeJSON[dto.AuditEventCreateRequest](r) if err != nil { writeDecodeError(w, err) return } event, err := h.core.CreateAuditEvent(request.ToDomain()) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusCreated, dto.AuditEventFromDomain(event)) default: writeMethodNotAllowed(w, "GET, POST") } } // auditEventDetail godoc // @Summary Get audit event // @Description Returns one audit event by ID. // @Tags audit-events // @Produce json // @Param id path string true "Audit event ID" // @Success 200 {object} dto.AuditEventResponse // @Failure 404 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/audit-events/{id} [get] func (h *coreHandlers) auditEventDetail(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMethodNotAllowed(w, http.MethodGet) return } event, err := h.core.GetAuditEvent(r.PathValue("id")) if err != nil { writeServiceError(w, err) return } writeJSON(w, http.StatusOK, dto.AuditEventFromDomain(event)) }