Skip to content

GitTinkerer Service (VPS HTTP API)

This folder contains a lightweight HTTP service (Node 24) used by the Svelte web UI. The service exposes REST endpoints for run creation, status polling, analytics aggregation, and admin operations.

Quick Links: - API Reference — Complete endpoint documentation with examples - Development Guide — Setup, testing, and debugging instructions - Architecture Overview — System context and data flow diagrams

Architecture

The service follows a Clean Architecture pattern with clear separation of concerns:

graph TD
    Controllers["🎯 Controllers"]
    UseCases["⚙️ Use Cases"]
    Domain["🔶 Domain"]
    Infra["🔌 Infrastructure"]

    Controllers -->|orchestrate| UseCases
    UseCases -->|enforce rules| Domain
    UseCases -->|delegate to| Infra

    Infra -->|Postgres| DB["Database"]
    Infra -->|Redis| Cache["Cache & Rate Limits"]
    Infra -->|GitHub API| GitHub["GitHub"]
    Infra -->|Spawn Process| Process["Bash Runner"]
    Infra -->|Filesystem| FS["Artifacts"]
    Infra -->|Sentry| Monitoring["Error Tracking"]

    style Controllers fill:#4A90E2
    style UseCases fill:#F5A623
    style Domain fill:#7ED321
    style Infra fill:#BD10E0

Layer Responsibilities

  • Controllers (src/controllers/)
  • HTTP request/response handling via Fastify
  • Input validation and error translation
  • Dependency wiring (see Dependency Injection below)

  • Use Cases (src/usecases/)

  • Application logic orchestration
  • Business rule enforcement (run creation, rate limiting, pause checks)
  • Coordination between domain and infrastructure

  • Domain (src/domain/)

  • Pure entities (Run, RepoRef, Instruction, etc.)
  • Validation rules and value objects
  • No external dependencies

  • Infrastructure (src/infra/)

  • External service adapters (Postgres, Redis, GitHub, filesystem, process spawning)
  • Database repositories and queries
  • Cache and rate limit management

Dependency Injection Pattern

The service uses function-based dependency injection to wire dependencies between layers. Each controller creates a dependency object via factory functions in src/controllers/dependencies.ts:

// Example: Assembling RunController dependencies
function createRunControllerDeps(pool: Pool): RunControllerDependencies {
  return {
    // Injected use cases
    createRun,
    startRun,
    getRun,
    listRuns,

    // Injected repositories and external service adapters
    startRunDeps: {
      getRun: getRunRepo,        // Database adapter
      updateRunStatus,           // Database adapter
      spawnGittinkerer,          // Process spawning adapter
      cacheRunStatus: (...) => cacheRunStatus(...),  // Redis adapter
    },

    // Nested dependency objects follow a flat-then-nested pattern
    getRunDeps: {
      getRun: getRunRepo,
      getCachedRunStatus,        // Redis cache
      readArtifacts,             // Filesystem adapter
    },
  };
}

Benefits: - Testability: Swap real adapters for mocks in tests without touching business logic - Clarity: All dependencies explicit in one place per controller - Flexibility: Change implementations (e.g., Redis → in-memory cache) at the factory level

Database Architecture

graph TD
  subgraph "Application Layer"
    Server[server.ts]
    UseCases[Use Cases]
  end

  subgraph "Infrastructure Layer - Database"
    DBIndex[db/index.ts\\nPool + withClient]
    Migrations[db/migrations.ts\\nSchema Versions]
    Types[db/types.ts\\nInterfaces + Zod]

    subgraph "Repositories"
      RunsRepo[db/runsRepo.ts\\nCRUD for runs]
      MetricsRepo[db/metricsRepo.ts\\nMetrics operations]
      PausedRepo[db/pausedReposRepo.ts\\nPause management]
      RateLimitRepo[db/rateLimitsRepo.ts\\nRate limiting]
    end
  end

  subgraph "PostgreSQL"
    DB[(Database\\nruns, run_metrics,\\nrate_limits, paused_repos,\\npaused_run_targets)]
  end

  Server -->|initializeDatabase| DBIndex
  UseCases -->|withClient| DBIndex
  DBIndex -->|migrate| Migrations
  DBIndex -->|exports| RunsRepo
  DBIndex -->|exports| MetricsRepo
  DBIndex -->|exports| PausedRepo
  DBIndex -->|exports| RateLimitRepo

  RunsRepo -->|queries| DB
  MetricsRepo -->|queries| DB
  PausedRepo -->|queries| DB
  RateLimitRepo -->|queries| DB
  Migrations -->|DDL| DB

  Types -.->|types| RunsRepo
  Types -.->|types| MetricsRepo
  Types -.->|types| PausedRepo
  Types -.->|types| RateLimitRepo

  style DBIndex fill:#f9f,stroke:#333,stroke-width:3px
  style Types fill:#bbf,stroke:#333
  style DB fill:#dfd,stroke:#333

File Structure

service/src/infra/db/
├── index.ts             # Pool, withClient, exports
├── types.ts             # TypeScript interfaces + Zod schemas
├── migrations.ts        # Migration definitions
├── runsRepo.ts          # Runs table operations
├── metricsRepo.ts       # Metrics table operations
├── pausedReposRepo.ts   # Paused repos/targets operations
└── rateLimitsRepo.ts    # Rate limits operations

Type System Architecture

graph TD
  subgraph Domain["Domain Layer (src/domain/)"]
    Run[Run Entity\\n- Immutable\\n- State transitions\\n- Business logic]
    RunStatus[RunStatus Enum\\n- Type-safe statuses\\n- Converters]
    RunErrors[RunErrors\\n- Typed exceptions\\n- Stage tracking]
    RepoRef[RepoRef\\n- Repo identity\\n- Validation]
    Instruction[Instruction\\n- Comment parsing\\n- Prompt building]
  end

  subgraph Types["Types Layer (src/types/)"]
    GitHub[GitHub Types\\n- Webhook payloads\\n- Zod schemas]
    Payload[Payload Types\\n- CLI payload\\n- Validation]
    API[API Types\\n- Request/Response\\n- Zod schemas]
  end

  subgraph Infra["Infrastructure (src/infra/)"]
    DB[(Database\\nRun, RunMetric)]
    GitHubAPI[GitHub API\\nClient]
    Process[Process\\nSpawner]
  end

  Run --> RunStatus
  Run --> RunErrors
  Run --> RepoRef
  Run --> Instruction

  Payload --> RepoRef
  Payload --> Instruction

  API --> Run
  API --> Payload

  GitHub --> API

  DB -.->|maps to| Run
  GitHubAPI -.->|uses| GitHub
  Process -.->|uses| Payload

  style Domain fill:#e1f5e1
  style Types fill:#e1e5f5
  style Infra fill:#f5e1e1

File Structure

service/src/
├── domain/
   ├── run/
      ├── Run.ts              # Domain entity with business logic
      ├── RunStatus.ts        # Enum and converters
      ├── RunErrors.ts        # Typed error classes
      └── index.ts            # Re-exports
   ├── repo/
      ├── RepoRef.ts          # Repository reference model
      └── index.ts
   └── instruction/
       ├── Instruction.ts      # Instruction/comment model
       └── index.ts
└── types/
  ├── github.ts               # GitHub webhook types + Zod schemas
  ├── payload.ts              # CLI payload types + Zod schemas
  ├── api.ts                  # API request/response types + Zod schemas
  ├── guards.ts               # Type guard utilities
  └── index.ts                # Re-exports

API Quick Reference

For complete API documentation with request/response examples, see API_REFERENCE.md.

  • POST /api/runs — Create a new run
  • GET /api/runs/:run_id — Get run status and artifacts
  • GET /api/runs?page=1&limit=20 — List runs with pagination
  • POST /api/github/webhook — GitHub webhook delivery handler
  • GET /api/analytics — Aggregate usage metrics
  • POST /api/admin/pause-repo — Admin: pause a repository
  • GET /health — Health check endpoint

Testing

Test Suites

The test suite is organized into unit and integration layers:

  • Unit tests (npm run test:unit)
  • Target domain entities and use cases
  • Fast, isolated, no external dependencies
  • Mock repositories and external adapters

  • Integration tests (npm run test:integration)

  • Cover infrastructure adapters (Postgres/Redis) and HTTP routes
  • Exercise end-to-end flows: request → controller → use case → repository
  • Slower than unit tests; require real database/cache instances
  • Lifecycle tests in src/__tests__/integration/ exercise server/app bootstrap, health checks, and shutdown paths

  • Full test run: npm run test:coverage (runs all tests + coverage report)

  • Interactive UI: npm run test:ui (opens test explorer)

Coverage Targets

All tests must meet the following coverage thresholds (enforced by Vitest):

Metric Target
Line Coverage 80%
Function Coverage 75%
Statement Coverage 80%
Branch Coverage 70%

Test Helpers & Mocks

Reusable test utilities live in src/__tests__/helpers/:

  • Factories: Create test domain objects (Run, RepoRef, etc.)
  • Database mocks: In-memory pool and client for unit tests; real Postgres for integration tests
  • Redis mocks: Stub Redis client by default; set REDIS_URL to point at a real instance for integration testing
  • GitHub API mocks: Mock GitHub API client for webhook and PR fetch scenarios
  • Process spawning mocks: Mock bin/gittinkerer execution
  • Filesystem mocks: Mock artifact creation and reading
  • Sentry mock: Stub error tracking for tests
  • Signal handlers: Mock process signals for lifecycle tests

Test Database Setup

  • Set DATABASE_URL_TEST to a reachable Postgres instance
  • Migrations run automatically in test helpers (see src/__tests__/integration/setup.ts)
  • Database is cleaned between test suites

Local Development

For comprehensive setup instructions, see DEVELOPMENT.md.

Configuration

  • GitHub App private key: Store as service/private-key.pem (service prefers this, falls back to GH_APP_PRIVATE_KEY env var)
  • Environment variables: Automatically loaded from service/.env via dotenv (copy from root .env.example)
  • TypeScript compilation: Runtime entrypoint is src/server.ts (compiled to dist/server.js)

Known Limitations

These are tracked in TODO.md: