ground_control-api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.github/dependabot.yml +24 -0
  3. data/.github/workflows/ci.yml +59 -0
  4. data/.github/workflows/claude-code-review.yml +24 -0
  5. data/.github/workflows/claude.yml +38 -0
  6. data/.github/workflows/codeql-actions.yml +28 -0
  7. data/.github/workflows/release.yml +89 -0
  8. data/.lefthook/bundle-sync.sh +58 -0
  9. data/.lefthook/lib/git-upstream.sh +13 -0
  10. data/.lefthook/verify-signatures.sh +46 -0
  11. data/.lefthook/whitespace-check.sh +8 -0
  12. data/.rspec +1 -0
  13. data/.ruby-version +1 -0
  14. data/CHANGELOG.md +16 -0
  15. data/CLAUDE.md +78 -0
  16. data/MIT-LICENSE +20 -0
  17. data/README.md +130 -0
  18. data/Rakefile +8 -0
  19. data/app/controllers/concerns/ground_control/api/adapter_features.rb +36 -0
  20. data/app/controllers/concerns/ground_control/api/error_handling.rb +25 -0
  21. data/app/controllers/concerns/ground_control/api/job_filters.rb +42 -0
  22. data/app/controllers/ground_control/api/application_controller.rb +24 -0
  23. data/app/controllers/ground_control/api/applications_controller.rb +13 -0
  24. data/app/controllers/ground_control/api/bulk_discards_controller.rb +21 -0
  25. data/app/controllers/ground_control/api/bulk_retries_controller.rb +15 -0
  26. data/app/controllers/ground_control/api/discards_controller.rb +14 -0
  27. data/app/controllers/ground_control/api/dispatches_controller.rb +14 -0
  28. data/app/controllers/ground_control/api/features_controller.rb +11 -0
  29. data/app/controllers/ground_control/api/jobs_controller.rb +37 -0
  30. data/app/controllers/ground_control/api/queues/pauses_controller.rb +29 -0
  31. data/app/controllers/ground_control/api/queues_controller.rb +26 -0
  32. data/app/controllers/ground_control/api/recurring_tasks_controller.rb +40 -0
  33. data/app/controllers/ground_control/api/retries_controller.rb +14 -0
  34. data/app/controllers/ground_control/api/workers_controller.rb +33 -0
  35. data/app/resources/ground_control/api/application_resource.rb +12 -0
  36. data/app/resources/ground_control/api/base_resource.rb +13 -0
  37. data/app/resources/ground_control/api/job_resource.rb +46 -0
  38. data/app/resources/ground_control/api/page_resource.rb +47 -0
  39. data/app/resources/ground_control/api/queue_resource.rb +22 -0
  40. data/app/resources/ground_control/api/recurring_task_resource.rb +17 -0
  41. data/app/resources/ground_control/api/server_resource.rb +10 -0
  42. data/app/resources/ground_control/api/worker_resource.rb +14 -0
  43. data/config/routes.rb +27 -0
  44. data/lefthook.yml +17 -0
  45. data/lib/ground_control/api/engine.rb +18 -0
  46. data/lib/ground_control/api/version.rb +7 -0
  47. data/lib/ground_control/api.rb +15 -0
  48. data/sig/ground_control/api.rbs +6 -0
  49. data/spec/spec_helper.rb +15 -0
  50. metadata +136 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fa4151207335fd8c45c84426ef8743b3291149dc545d093537c94237ec29a7f5
4
+ data.tar.gz: 3a5f9fcbc1939b84be71dea74a914039c5a8bbfd0f5ec60e50e1d6bdd6de1e98
5
+ SHA512:
6
+ metadata.gz: 7fdc683e0ba9125b04fa84d18329719a84e40aa8226d9681f9287658f0e636dadb8c401223dadfde2aafbd9e7340bbcc451f933d708c74f1e85c8284e9a84667
7
+ data.tar.gz: dd589635c37e608fe4be787a1e039540da9bdf6dd31f5cc3b94bc5aded654020b2bcaa43c29c271647f6fe777defb3729dac3390254d0d800b22a81311b6d14d
@@ -0,0 +1,24 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: weekly
7
+ day: monday
8
+ open-pull-requests-limit: 10
9
+ groups:
10
+ development-dependencies:
11
+ dependency-type: "development"
12
+ production-dependencies:
13
+ dependency-type: "production"
14
+
15
+ - package-ecosystem: github-actions
16
+ directory: "/"
17
+ schedule:
18
+ interval: weekly
19
+ day: monday
20
+ open-pull-requests-limit: 10
21
+ groups:
22
+ actions:
23
+ patterns:
24
+ - "*"
@@ -0,0 +1,59 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [ main ]
7
+
8
+ concurrency:
9
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ permissions:
13
+ contents: read
14
+ pull-requests: read
15
+
16
+ jobs:
17
+ lint:
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 10
20
+ steps:
21
+ - name: Checkout code
22
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
23
+
24
+ - name: Set up Ruby
25
+ uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0
26
+ with:
27
+ ruby-version: 3.4.4
28
+ bundler-cache: true
29
+
30
+ - name: Security scan with Brakeman
31
+ run: bundle exec brakeman --no-pager --quiet
32
+ - name: Audit gem dependencies for CVEs (bundler-audit)
33
+ run: bundle exec bundler-audit check --update
34
+ - name: Lint code for consistent style
35
+ run: bundle exec rubocop -f github
36
+
37
+ test:
38
+ runs-on: ubuntu-latest
39
+ timeout-minutes: 15
40
+
41
+ steps:
42
+ - name: Checkout code
43
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
44
+
45
+ - name: Set up Ruby
46
+ uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0
47
+ with:
48
+ ruby-version: 3.4.4
49
+ bundler-cache: true
50
+
51
+ - name: Run tests
52
+ env:
53
+ RAILS_ENV: test
54
+ run: |
55
+ if find spec -name '*_spec.rb' 2>/dev/null | grep -q .; then
56
+ bundle exec rspec
57
+ else
58
+ echo "No spec files found, skipping."
59
+ fi
@@ -0,0 +1,24 @@
1
+ name: Claude Code Review
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize]
6
+
7
+ concurrency:
8
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
9
+ cancel-in-progress: true
10
+
11
+ permissions:
12
+ contents: read
13
+ pull-requests: write
14
+ id-token: write
15
+
16
+ jobs:
17
+ review:
18
+ permissions:
19
+ contents: read
20
+ pull-requests: write
21
+ issues: write
22
+ id-token: write
23
+ uses: rarebit-one/.github/.github/workflows/claude-code-review.yml@main
24
+ secrets: inherit
@@ -0,0 +1,38 @@
1
+ name: Claude Code
2
+
3
+ on:
4
+ issue_comment:
5
+ types: [created]
6
+ pull_request_review_comment:
7
+ types: [created]
8
+ issues:
9
+ types: [opened, assigned]
10
+ pull_request_review:
11
+ types: [submitted]
12
+
13
+ concurrency:
14
+ group: ${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number || github.event.comment.id }}
15
+ cancel-in-progress: false
16
+
17
+ permissions:
18
+ contents: read
19
+ pull-requests: write
20
+ issues: write
21
+ id-token: write
22
+ actions: read
23
+
24
+ jobs:
25
+ claude:
26
+ if: |
27
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
28
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
29
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
30
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
31
+ permissions:
32
+ contents: read
33
+ pull-requests: read
34
+ issues: read
35
+ id-token: write
36
+ actions: read
37
+ uses: rarebit-one/.github/.github/workflows/claude-agent.yml@main
38
+ secrets: inherit
@@ -0,0 +1,28 @@
1
+ name: CodeQL (Actions)
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ paths:
9
+ - '.github/workflows/**'
10
+ schedule:
11
+ - cron: '30 6 * * 1'
12
+
13
+ permissions:
14
+ contents: read
15
+ security-events: write
16
+
17
+ jobs:
18
+ analyze:
19
+ name: Analyze Actions workflows
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
23
+ - uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3 (bundle 2.25.2)
24
+ with:
25
+ languages: actions
26
+ - uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
27
+ with:
28
+ category: "/language:actions"
@@ -0,0 +1,89 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ # CI is not gated here — branch protection ensures CI passes before
13
+ # merge to main, and tags are only pushed after the version bump PR
14
+ # is merged. Running CI again on tag push is redundant.
15
+ release:
16
+ runs-on: ubuntu-latest
17
+ timeout-minutes: 10
18
+ steps:
19
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
20
+
21
+ - name: Set up Ruby
22
+ uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: "3.4.4"
25
+
26
+ - name: Verify tag matches gem version
27
+ run: |
28
+ tag_version="${GITHUB_REF_NAME#v}"
29
+ gem_version=$(ruby -e "spec = Gem::Specification.load(Dir['*.gemspec'].first); puts spec.version")
30
+ if [ "$tag_version" != "$gem_version" ]; then
31
+ echo "::error::Tag version ($tag_version) does not match gemspec version ($gem_version)"
32
+ exit 1
33
+ fi
34
+
35
+ - name: Extract changelog entry
36
+ id: changelog
37
+ run: |
38
+ version="${GITHUB_REF_NAME#v}"
39
+ notes=$(awk -v ver="$version" '
40
+ /^## \[/ {
41
+ if (found) exit
42
+ if ($0 ~ "^## \\[" ver "\\]") found=1
43
+ next
44
+ }
45
+ found { print }
46
+ ' CHANGELOG.md)
47
+
48
+ if [ -z "$notes" ]; then
49
+ echo "::error::No CHANGELOG.md entry found for version $version"
50
+ exit 1
51
+ fi
52
+
53
+ # Use a random delimiter to prevent injection via changelog content
54
+ delimiter="EOF_$(openssl rand -hex 8)"
55
+ {
56
+ echo "notes<<${delimiter}"
57
+ echo "$notes"
58
+ echo "${delimiter}"
59
+ } >> "$GITHUB_OUTPUT"
60
+
61
+ - name: Create GitHub Release
62
+ env:
63
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
64
+ RELEASE_NOTES: ${{ steps.changelog.outputs.notes }}
65
+ run: |
66
+ printf "%s" "$RELEASE_NOTES" > /tmp/release_notes.txt
67
+ gh release create "$GITHUB_REF_NAME" \
68
+ --title "$GITHUB_REF_NAME" \
69
+ --notes-file /tmp/release_notes.txt
70
+
71
+ publish:
72
+ needs: release
73
+ runs-on: ubuntu-latest
74
+ timeout-minutes: 10
75
+ environment: rubygems
76
+ permissions:
77
+ contents: read
78
+ id-token: write
79
+ steps:
80
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
81
+
82
+ - name: Set up Ruby
83
+ uses: ruby/setup-ruby@v1
84
+ with:
85
+ ruby-version: "3.4.4"
86
+ bundler-cache: true
87
+
88
+ - name: Push to RubyGems
89
+ uses: rubygems/release-gem@6317d8d1f7e28c24d28f6eff169ea854948bd9f7 # v1.2.0
@@ -0,0 +1,58 @@
1
+ #!/bin/bash
2
+ # Sync gems after git operations that may change Gemfile.lock.
3
+
4
+ GEMFILE_CHANGED=false
5
+
6
+ if [[ -n "$LEFTHOOK_GIT_HOOK" ]]; then
7
+ HOOK_NAME="$LEFTHOOK_GIT_HOOK"
8
+ else
9
+ HOOK_NAME="unknown"
10
+ fi
11
+
12
+ case "$HOOK_NAME" in
13
+ post-checkout)
14
+ OLD_REF="${1:-}"
15
+ NEW_REF="${2:-}"
16
+ BRANCH_FLAG="${3:-}"
17
+ if [[ "$BRANCH_FLAG" == "0" ]]; then
18
+ exit 0
19
+ fi
20
+ if [[ -n "$OLD_REF" && -n "$NEW_REF" && "$OLD_REF" != "$NEW_REF" ]]; then
21
+ if git diff --name-only "$OLD_REF" "$NEW_REF" 2>/dev/null | grep -q '^Gemfile\.lock$'; then
22
+ GEMFILE_CHANGED=true
23
+ fi
24
+ fi
25
+ ;;
26
+ post-rewrite)
27
+ while read -r OLD_REF NEW_REF _REST; do
28
+ if [[ -n "$OLD_REF" && -n "$NEW_REF" ]]; then
29
+ if git diff --name-only "$OLD_REF" "$NEW_REF" 2>/dev/null | grep -q '^Gemfile\.lock$'; then
30
+ GEMFILE_CHANGED=true
31
+ break
32
+ fi
33
+ fi
34
+ done
35
+ ;;
36
+ post-merge)
37
+ if git diff --name-only HEAD@{1} HEAD 2>/dev/null | grep -q '^Gemfile\.lock$'; then
38
+ GEMFILE_CHANGED=true
39
+ fi
40
+ ;;
41
+ *)
42
+ if git diff --name-only HEAD@{1} HEAD 2>/dev/null | grep -q '^Gemfile\.lock$'; then
43
+ GEMFILE_CHANGED=true
44
+ fi
45
+ ;;
46
+ esac
47
+
48
+ if [[ "$GEMFILE_CHANGED" != true ]]; then
49
+ exit 0
50
+ fi
51
+
52
+ echo "Gemfile.lock changed — syncing gems..." >&2
53
+
54
+ if ! bundle install; then
55
+ echo "Gem sync failed. Run manually: bundle install" >&2
56
+ fi
57
+
58
+ exit 0
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+ # Shared upstream detection logic for lefthook scripts.
3
+ #
4
+ # Sets UPSTREAM and MERGE_BASE variables.
5
+ # Source this file from other scripts:
6
+ # source "$(dirname "$0")/lib/git-upstream.sh"
7
+
8
+ UPSTREAM=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null) || UPSTREAM="origin/main"
9
+
10
+ MERGE_BASE=$(git merge-base "$UPSTREAM" HEAD 2>/dev/null) || {
11
+ echo "Failed to find merge-base with $UPSTREAM" >&2
12
+ exit 1
13
+ }
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+ # Verify all commits to be pushed have valid signatures (GPG or SSH).
3
+ #
4
+ # Checks every commit between the remote tracking branch and HEAD.
5
+ # Exits non-zero if any unsigned or badly-signed commits are found.
6
+ #
7
+ # Accepted signature statuses: G (good), U (good but untrusted key)
8
+ # Rejected: N (none), B (bad), E (can't check), X (expired), Y (expired key), R (revoked)
9
+
10
+ set -eo pipefail
11
+
12
+ source "$(dirname "$0")/lib/git-upstream.sh"
13
+
14
+ COMMITS=$(git log "$MERGE_BASE..HEAD" --format="%H" 2>/dev/null) || {
15
+ echo "Failed to list commits between merge-base and HEAD" >&2
16
+ exit 1
17
+ }
18
+
19
+ if [[ -z "$COMMITS" ]]; then
20
+ echo "No new commits to verify"
21
+ exit 0
22
+ fi
23
+
24
+ UNSIGNED_COMMITS=()
25
+ while IFS= read -r commit; do
26
+ SIG_STATUS=$(git log --format="%G?" -1 "$commit" 2>/dev/null || echo "N")
27
+ if [[ "$SIG_STATUS" != "G" && "$SIG_STATUS" != "U" ]]; then
28
+ SHORT_HASH=$(git rev-parse --short "$commit")
29
+ SUBJECT=$(git log --format="%s" -1 "$commit")
30
+ UNSIGNED_COMMITS+=(" $SHORT_HASH ($SIG_STATUS): $SUBJECT")
31
+ fi
32
+ done <<< "$COMMITS"
33
+
34
+ if [[ ${#UNSIGNED_COMMITS[@]} -gt 0 ]]; then
35
+ echo "Unsigned commits detected:" >&2
36
+ for line in "${UNSIGNED_COMMITS[@]}"; do
37
+ echo "$line" >&2
38
+ done
39
+ echo "" >&2
40
+ echo "To fix:" >&2
41
+ echo " Single commit: git commit --amend --no-edit -S" >&2
42
+ echo " Multiple commits: git rebase --exec 'git commit --amend --no-edit -S' $UPSTREAM" >&2
43
+ exit 1
44
+ fi
45
+
46
+ echo "All commits are signed"
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+ # Check for whitespace errors across all commits being pushed.
3
+
4
+ set -eo pipefail
5
+
6
+ source "$(dirname "$0")/lib/git-upstream.sh"
7
+
8
+ exec git --no-pager log --check "$MERGE_BASE..HEAD"
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-04-21
11
+
12
+ ### Added
13
+
14
+ - Initial release of Ground Control API
15
+ - JSON API endpoints for managing Active Job queues, jobs, workers, and recurring tasks
16
+ - Built on top of mission_control-jobs
data/CLAUDE.md ADDED
@@ -0,0 +1,78 @@
1
+ # CLAUDE.md
2
+
3
+ ## Worktree-Only Workflow (Enforced)
4
+
5
+ **All file modifications are blocked in the main checkout.** A PreToolUse hook (`enforce-worktree.sh`) rejects Edit, Write, and NotebookEdit operations targeting files outside a worktree. There are no opt-outs. Do not use Bash to write files in the main checkout either (e.g., `echo >`, `sed -i`, `tee`, `cp`) — the hook cannot intercept shell commands, so this rule is instruction-enforced.
6
+
7
+ Before writing any code, create a worktree:
8
+
9
+ ```bash
10
+ DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@refs/remotes/origin/@@')
11
+ DEFAULT_BRANCH=${DEFAULT_BRANCH:-main}
12
+ git fetch origin "$DEFAULT_BRANCH"
13
+ git worktree add .worktrees/<name> -b <branch-name> "origin/$DEFAULT_BRANCH"
14
+ ```
15
+
16
+ Then work inside `.worktrees/<name>/` for the rest of the session.
17
+
18
+ **Naming:** Use the Linear issue identifier if available (e.g., `.worktrees/<identifier>`), a task slug (e.g., `.worktrees/fix-auth-timeout`), or today's date (e.g., `.worktrees/2026-04-01`) as fallback.
19
+
20
+ **The hook allows modifications only when:**
21
+
22
+ 1. The file is inside a git worktree (detected via `git rev-parse --git-dir` returning a path under `.git/worktrees/`)
23
+ 2. Running in a CI/automated context where the checkout is already isolated
24
+ **Why this matters:** Working directly on the main checkout causes cross-contamination between sessions — uncommitted changes, wrong branches, and dirty state leak into unrelated work. Worktrees eliminate this entirely.
25
+
26
+ See the `/worktree` and `/start` skills for full conventions and flags.
27
+
28
+ Guidance for Claude Code working with the ground_control-api gem.
29
+
30
+ ## Overview
31
+
32
+ Ground Control API is a headless JSON API Rails engine for managing Active Job queues, jobs, workers, and recurring tasks. It builds on top of `mission_control-jobs` for the adapter/query layer and provides Alba-serialized JSON responses.
33
+
34
+ ## Quick Reference
35
+
36
+ ```bash
37
+ # Testing
38
+ bundle exec rspec # Run all tests
39
+ bundle exec rspec spec/path/to/file:42 # Run specific test
40
+
41
+ # Linting
42
+ bundle exec rubocop # Ruby linting
43
+ bundle exec rubocop -a # Auto-fix
44
+ ```
45
+
46
+ ## Architecture
47
+
48
+ **Type:** Rails engine gem (JSON API only, no views)
49
+
50
+ **Dependencies:**
51
+ - `mission_control-jobs` — adapter layer, query objects, job proxy models
52
+ - `alba` — JSON serialization
53
+ - `rails` — engine framework
54
+
55
+ **Key directories:**
56
+ - `app/controllers/ground_control/api/` — JSON API controllers
57
+ - `app/resources/ground_control/api/` — Alba serialization resources
58
+ - `app/controllers/concerns/ground_control/api/` — Shared controller concerns
59
+ - `lib/ground_control/api/` — Engine, configuration, version
60
+ - `config/routes.rb` — API route definitions
61
+
62
+ **Namespace:** `GroundControl::Api`
63
+
64
+ ## Key Patterns
65
+
66
+ 1. **Controllers** render JSON via Alba resources — never build hashes inline
67
+ 2. **Mutating actions** (retry, discard, dispatch, pause) return `{ message: "..." }` with status 200
68
+ 3. **Read actions** return `{ data: ..., meta: ... }` structure
69
+ 4. **Pagination** uses `MissionControl::Jobs::Page` wrapped in `PageResource`
70
+ 5. **Error handling** via `ErrorHandling` concern — rescues job/resource not found as 404
71
+ 6. **Auth** is delegated to host apps via `GroundControl::Api.authenticate_with` proc
72
+
73
+ ## Conventions
74
+
75
+ - All Ruby files use `frozen_string_literal: true`
76
+ - Alba resources inherit from `BaseResource` (lower_camel key transform)
77
+ - Rubocop follows `rubocop-rails-omakase` style
78
+ - Commits must be signed (GPG or SSH)
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) Jaryl Sim
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # Ground Control API
2
+
3
+ A headless JSON API Rails engine for managing Active Job queues, jobs, workers, and recurring tasks. Built on top of [mission_control-jobs](https://github.com/rails/mission_control-jobs) for the adapter and query layer.
4
+
5
+ Use standalone for custom frontends, or as the foundation for [ground_control-inertia](https://github.com/rarebit-one/ground-control-inertia).
6
+
7
+ ## Installation
8
+
9
+ ```ruby
10
+ # Gemfile
11
+ gem "ground_control-api", github: "rarebit-one/ground-control-api"
12
+ ```
13
+
14
+ ## Setup
15
+
16
+ Mount the engine in your routes:
17
+
18
+ ```ruby
19
+ # config/routes.rb
20
+ mount GroundControl::Api::Engine, at: "/ground-control/api"
21
+ ```
22
+
23
+ ### Authentication
24
+
25
+ By default, endpoints are unauthenticated. Configure a custom auth strategy:
26
+
27
+ ```ruby
28
+ # config/initializers/ground_control.rb
29
+ GroundControl::Api.authenticate_with = ->(controller) do
30
+ controller.head :unauthorized unless controller.current_user&.admin?
31
+ end
32
+ ```
33
+
34
+ ### Multi-application support
35
+
36
+ If you run multiple queue backends, register them explicitly:
37
+
38
+ ```ruby
39
+ MissionControl::Jobs.applications.add("Primary", {
40
+ "solid_queue" => ActiveJob::QueueAdapters::SolidQueueAdapter.new
41
+ })
42
+
43
+ MissionControl::Jobs.applications.add("Legacy", {
44
+ "resque" => ActiveJob::QueueAdapters::ResqueAdapter.new(Redis.new)
45
+ })
46
+ ```
47
+
48
+ Otherwise, the engine auto-registers your default `active_job.queue_adapter`.
49
+
50
+ ## API
51
+
52
+ All endpoints are scoped under `/applications/:application_id`. When only one application is registered, the application ID can be inferred.
53
+
54
+ ### Queues
55
+
56
+ | Method | Path | Description |
57
+ |--------|------|-------------|
58
+ | GET | `/applications/:app/queues` | List all queues |
59
+ | GET | `/applications/:app/queues/:id` | Show queue with paginated jobs |
60
+ | POST | `/applications/:app/queues/:id/pause` | Pause a queue |
61
+ | DELETE | `/applications/:app/queues/:id/pause` | Resume a queue |
62
+
63
+ ### Jobs
64
+
65
+ | Method | Path | Description |
66
+ |--------|------|-------------|
67
+ | GET | `/applications/:app/:status/jobs` | List jobs by status (paginated, filterable) |
68
+ | GET | `/applications/:app/jobs/:id` | Show job details |
69
+ | POST | `/applications/:app/jobs/:id/retry` | Retry a failed job |
70
+ | POST | `/applications/:app/jobs/:id/discard` | Discard a job |
71
+ | POST | `/applications/:app/jobs/:id/dispatch` | Dispatch a blocked/scheduled job |
72
+ | POST | `/applications/:app/jobs/bulk_retries` | Retry all matching failed jobs |
73
+ | POST | `/applications/:app/jobs/bulk_discards` | Discard all matching failed jobs |
74
+
75
+ **Filtering:** Pass `filter[job_class_name]`, `filter[queue_name]`, `filter[finished_at_start]`, `filter[finished_at_end]` as query params.
76
+
77
+ **Pagination:** Pass `page` as a query param. Default page size is 10, configurable via `GroundControl::Api.page_size`.
78
+
79
+ ### Workers
80
+
81
+ | Method | Path | Description |
82
+ |--------|------|-------------|
83
+ | GET | `/applications/:app/workers` | List workers (if adapter supports it) |
84
+ | GET | `/applications/:app/workers/:id` | Show worker details |
85
+
86
+ ### Recurring Tasks
87
+
88
+ | Method | Path | Description |
89
+ |--------|------|-------------|
90
+ | GET | `/applications/:app/recurring_tasks` | List recurring tasks |
91
+ | GET | `/applications/:app/recurring_tasks/:id` | Show task with execution history |
92
+ | PATCH | `/applications/:app/recurring_tasks/:id` | Trigger a recurring task |
93
+
94
+ ### Feature Discovery
95
+
96
+ | Method | Path | Description |
97
+ |--------|------|-------------|
98
+ | GET | `/applications/:app/features` | Adapter capabilities (supported statuses, workers, recurring tasks, queue pausing) |
99
+
100
+ ## Configuration
101
+
102
+ ```ruby
103
+ GroundControl::Api.authenticate_with = ->(controller) { ... } # Auth callback
104
+ GroundControl::Api.page_size = 25 # Default: 10
105
+ ```
106
+
107
+ ## Architecture
108
+
109
+ ```
110
+ ground_control-api
111
+ ├── app/controllers/ground_control/api/ # JSON API controllers
112
+ ├── app/resources/ground_control/api/ # Alba serialization resources
113
+ ├── app/controllers/concerns/ # JobFilters, AdapterFeatures, ErrorHandling
114
+ ├── config/routes.rb # Engine routes
115
+ └── lib/ground_control/api/ # Engine, config, version
116
+ ↓ depends on
117
+ mission_control-jobs # Adapter layer, query objects, models
118
+ ```
119
+
120
+ ## Development
121
+
122
+ ```bash
123
+ bundle install
124
+ bundle exec rspec # Run tests
125
+ bundle exec rubocop # Lint
126
+ ```
127
+
128
+ ## License
129
+
130
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
5
+
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: :rubocop