Every CI action is a CRUD action, and the URLs do not move
`CiController` had three actions named after verbs — `pipelines`, `status`,
`trigger` — and built its JSON by hand in a private `pipeline_json`, in a
codebase where everything else is serialized by Blueprinter. Triggering a
build is the creation of a run, so it is a resource of its own:
GET /api/ci/pipelines -> Api::Ci::PipelinesController#index
GET /api/ci/pipelines/:id/status -> Api::Ci::PipelinesController#show
POST /api/ci/pipelines/:id/trigger -> Api::Ci::PipelineRunsController#create
The addresses are untouched on purpose: they are what the CI and the scripts
already call, and renaming them would be a breaking change for a version that
does not need one. What changed is the shape behind them — three standard
actions across two controllers, `require_ci!` and the pipeline scope in a
shared base, and a `PipelineSerializer` that carries the fields the endpoint
already answered with, `repo_id` and `woodpecker_repo_id` included (the
duplicate stays: a client may read either, and ci_controller_spec asserts they
agree).
`/api/software/highlighted` answers with `#show` now rather than `#index`: it
returns one title, and the action name should say so. Same address, same body.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
module WarpEngine
|
||||
module Api
|
||||
module Ci
|
||||
class BaseController < WarpEngine::ApiController
|
||||
before_action :require_ci!
|
||||
|
||||
private
|
||||
|
||||
def require_ci!
|
||||
return if WarpEngine.ci.configured?
|
||||
|
||||
render json: { error: "CI not configured" }, status: :service_unavailable
|
||||
end
|
||||
|
||||
def pipeline_scope = WarpEngine::Pipeline.all
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
module WarpEngine
|
||||
module Api
|
||||
module Ci
|
||||
class PipelineRunsController < BaseController
|
||||
include WarpEngine::UpdateAuthentication
|
||||
|
||||
resource_description do
|
||||
short "CI pipeline runs"
|
||||
end
|
||||
|
||||
api :POST, "/api/ci/pipelines/:id/trigger", "Trigger a pipeline"
|
||||
header "X-Update-Secret", "Shared secret or application token (update scope)", required: true
|
||||
param :id, :number, required: true, desc: "Pipeline id"
|
||||
param :branch, String, required: false, desc: "Branch to build (default: main)"
|
||||
returns code: 200, desc: "JSON with triggered run data"
|
||||
error code: 401, desc: "Invalid secret"
|
||||
error code: 503, desc: "No CI provider configured"
|
||||
def create
|
||||
unless update_authorized?(required_scope: WarpEngine::ApplicationToken::UPDATE_SCOPE)
|
||||
return render json: { error: "Unauthorized" }, status: :unauthorized
|
||||
end
|
||||
|
||||
pipeline = pipeline_scope.find(params[:id])
|
||||
result = WarpEngine::PipelineService.new.trigger(pipeline, branch: params[:branch].presence || "main")
|
||||
|
||||
render json: { triggered: true, pipeline: result }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
module WarpEngine
|
||||
module Api
|
||||
module Ci
|
||||
class PipelinesController < BaseController
|
||||
resource_description do
|
||||
short "CI pipelines"
|
||||
end
|
||||
|
||||
api :GET, "/api/ci/pipelines", "List active pipelines"
|
||||
returns code: 200, desc: "JSON array of tracked pipelines"
|
||||
error code: 503, desc: "No CI provider configured"
|
||||
def index
|
||||
pipelines = pipeline_scope.active.includes(:software)
|
||||
render json: WarpEngine::PipelineSerializer.render_as_hash(pipelines)
|
||||
end
|
||||
|
||||
api :GET, "/api/ci/pipelines/:id/status", "Get a pipeline with its latest run"
|
||||
param :id, :number, required: true, desc: "Pipeline id"
|
||||
returns code: 200, desc: "JSON with pipeline and latest run data"
|
||||
error code: 503, desc: "No CI provider configured"
|
||||
def show
|
||||
pipeline = pipeline_scope.find(params[:id])
|
||||
|
||||
render json: {
|
||||
pipeline: WarpEngine::PipelineSerializer.render_as_hash(pipeline),
|
||||
latest_run: latest_run(pipeline)
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def latest_run(pipeline)
|
||||
WarpEngine::PipelineService.new.run(pipeline, "latest")
|
||||
rescue WarpEngine::CI::Error
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,75 +0,0 @@
|
||||
module WarpEngine
|
||||
module Api
|
||||
class CiController < ApiController
|
||||
include UpdateAuthentication
|
||||
|
||||
before_action :require_ci!
|
||||
|
||||
resource_description do
|
||||
short "CI pipeline management"
|
||||
end
|
||||
|
||||
api :GET, "/api/ci/pipelines", "List active pipelines"
|
||||
returns code: 200, desc: "JSON array of tracked pipelines"
|
||||
error code: 503, desc: "No CI provider configured"
|
||||
def pipelines
|
||||
records = Pipeline.active.includes(:software)
|
||||
render json: records.map { |p| pipeline_json(p) }
|
||||
end
|
||||
|
||||
api :GET, "/api/ci/pipelines/:id/status", "Get a pipeline with its latest run"
|
||||
param :id, :number, required: true, desc: "Pipeline id"
|
||||
returns code: 200, desc: "JSON with pipeline and latest run data"
|
||||
error code: 503, desc: "No CI provider configured"
|
||||
def status
|
||||
pipeline = Pipeline.find(params[:id])
|
||||
latest_run = begin
|
||||
PipelineService.new.run(pipeline, "latest")
|
||||
rescue CI::Error
|
||||
nil
|
||||
end
|
||||
render json: { pipeline: pipeline_json(pipeline), latest_run: latest_run }
|
||||
end
|
||||
|
||||
api :POST, "/api/ci/pipelines/:id/trigger", "Trigger a pipeline"
|
||||
header "X-Update-Secret", "Shared secret or application token (update scope)", required: true
|
||||
param :id, :number, required: true, desc: "Pipeline id"
|
||||
param :branch, String, required: false, desc: "Branch to build (default: main)"
|
||||
returns code: 200, desc: "JSON with triggered run data"
|
||||
error code: 401, desc: "Invalid secret"
|
||||
error code: 503, desc: "No CI provider configured"
|
||||
def trigger
|
||||
unless update_authorized?(required_scope: ApplicationToken::UPDATE_SCOPE)
|
||||
return render json: { error: "Unauthorized" }, status: :unauthorized
|
||||
end
|
||||
|
||||
pipeline = Pipeline.find(params[:id])
|
||||
result = PipelineService.new.trigger(pipeline, branch: params[:branch] || "main")
|
||||
render json: { triggered: true, pipeline: result }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_ci!
|
||||
return if WarpEngine.ci.configured?
|
||||
|
||||
render json: { error: "CI not configured" }, status: :service_unavailable
|
||||
end
|
||||
|
||||
def pipeline_json(pipeline)
|
||||
{
|
||||
id: pipeline.id,
|
||||
repo_id: pipeline.remote_repo_id,
|
||||
woodpecker_repo_id: pipeline.remote_repo_id,
|
||||
repo_owner: pipeline.repo_owner,
|
||||
repo_name: pipeline.repo_name,
|
||||
platform: pipeline.platform,
|
||||
active: pipeline.active,
|
||||
software_name: pipeline.software&.name,
|
||||
last_pipeline_status: pipeline.last_pipeline_status,
|
||||
last_pipeline_at: pipeline.last_pipeline_at
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -14,8 +14,8 @@ module WarpEngine
|
||||
property :desc, String, desc: "Short description"
|
||||
property :story, String, desc: "Long description / story"
|
||||
property :license, String, desc: "License type"
|
||||
property :platform, String, desc: "Platform (tic80, love, ebitengine, c64, godot, bevy, phaser)"
|
||||
property :status, String, desc: "Status"
|
||||
property :platform, String, desc: "Platform (one of WarpEngine::Platform::NAMES)"
|
||||
property :status, String, desc: "Status (development, demo, released, archived)"
|
||||
property :highlighted, :boolean, desc: "Highlighted flag"
|
||||
property :imageUrl, String, desc: "Default image URL"
|
||||
end
|
||||
@@ -33,7 +33,7 @@ module WarpEngine
|
||||
property :totalDownloads, Integer, desc: "Total download count across all releases"
|
||||
end
|
||||
error code: 404, desc: "No highlighted software found"
|
||||
def index
|
||||
def show
|
||||
result = WarpEngine::SoftwareHighlightedService.new.index(subject: current_subject)
|
||||
if result
|
||||
render json: result
|
||||
|
||||
Reference in New Issue
Block a user