Skip to content

Latest commit

 

History

History
411 lines (321 loc) · 13.5 KB

File metadata and controls

411 lines (321 loc) · 13.5 KB

Kleister API — Agent Guide

This document provides guidance for AI coding agents (GitHub Copilot, Codex, Claude, etc.) working in this repository.


Shell Environment

This repository uses a Nix flake (flake.nix) to provide all required tools and packages (Go, Node.js, task, golangci-lint, etc.).

Always run bash commands inside the Nix dev shell:

nix develop --command bash -c "<command>"

Examples:

nix develop --command bash -c "task fe:build"
nix develop --command bash -c "go test ./..."
nix develop --command bash -c "task be:lint"

Do not assume system-installed versions of go, node, npm, task, or any other tool — they may be absent or wrong. All tools must be invoked through nix develop.


Project Overview

Kleister API is a backend and embedded Vue frontend for managing Minecraft mod packs and related metadata such as users, groups, mods, packs, versions, and game loader/build information.

The system currently builds a single binary:

Binary Entry point Role
kleister-api cmd/kleister-api REST API + embedded web UI

The Vue 3 single-page application is compiled and embedded directly into the binary; no separate web server is needed.


Repository Layout

cmd/
  kleister-api/     Main binary entry point
config/             Example YAML/JSON config files
frontend/           Vue 3 SPA source
  client/           Auto-generated TypeScript API client (do not edit)
  components/       Reusable UI components
  composables/      Vue composables
  feature/          Feature-based page/logic modules
  router/           Vue Router configuration
hack/
  compose/          Docker Compose files for local development
openapi/
  v1.yaml           OpenAPI v1 specification (source of truth for the API)
pkg/
  api/              API request/response types and handlers
  authn/            Authentication (OIDC, local, token)
  command/          Cobra sub-command implementations
  config/           Configuration structs (mapped from env / file)
  frontend/         Embeds the compiled frontend assets into the binary
  handler/          HTTP handler wiring
  identicon/        Avatar generation helpers
  internal/         Remote client integrations for Minecraft ecosystems
  manifest/         Manifest and metadata helpers
  metrics/          Prometheus metrics
  middleware/       HTTP middleware (auth, logging, etc.)
  migrations/       Bun ORM database migrations (timestamped Go files)
  model/            Domain model structs (bun models)
  router/           HTTP router setup (go-chi)
  scim/             SCIM 2.0 provisioning endpoint
  secret/           Secret encryption helpers
  store/            Database access layer (thin wrappers around bun)
  templates/        Server-side HTML templates
  token/            JWT token issuance and validation
  upload/           File upload drivers (local filesystem + S3)
  validate/         Input validation helpers (ozzo-validation)
  version/          Build-time version, revision, date
storage/            Runtime data (SQLite file, local uploads) — not committed

Technology Stack

Backend (Go)

  • Go ≥ 1.25, module github.com/kleister/kleister-api
  • HTTP router: go-chi/chi/v5
  • ORM: uptrace/bun (supports SQLite, MySQL/MariaDB, PostgreSQL)
  • API layer: generated from openapi/v1.yaml via oapi-codegen
  • Auth: OIDC (coreos/go-oidc), JWT (golang-jwt/jwt)
  • Config: spf13/viper + spf13/cobra
  • Mocks: golang/mock (mockgen — files named mock.go)
  • Logging: log/slog with jeffry-luqman/zlog
  • Metrics: prometheus/client_golang
  • S3: aws/aws-sdk-go-v2

Frontend (TypeScript)

  • Framework: Vue 3 (Composition API + <script setup>)
  • Build: Vite 8, TypeScript 5.9, vue-tsc
  • State: Pinia
  • Routing: Vue Router 5
  • UI: Reka UI / Radix Vue, TailwindCSS 4, Lucide icons, FontAwesome
  • Tables: TanStack Vue Table
  • Forms: vee-validate + Zod
  • API client: auto-generated by @hey-api/openapi-ts from openapi/v1.yaml

Build System

All commands are driven by go-task (Taskfile.yml).

Common tasks

# Full build (requires frontend to be built first)
task fe:install fe:build be:build

# Individual steps
task fe:install        # npm install --ci
task fe:build          # vue-tsc && vite build
task be:build          # builds the Go binary into bin/

# Code generation (run after changing openapi/v1.yaml or adding //go:generate)
task generate          # runs both be:generate and fe:generate

# Linting
task be:fmt            # gofmt -s -w
task be:vet            # go vet
task be:lint           # revive (revive.toml)
task be:golangci       # golangci-lint
task fe:lint           # eslint ./frontend
task fe:fmt            # eslint --fix + prettier

# Testing
task be:test           # go test -coverprofile coverage.out ./...
task fe:test           # vitest run (single pass)

# Development (hot-reload, run each in its own terminal)
task watch:server      # rebuild + run kleister on source changes
task watch:frontend    # npm run serve (Vite dev server on :5173)

Build flags

All Go binaries are built with:

  • CGO_ENABLED=0
  • -tags netgo
  • -ldflags '-s -w -extldflags "-static"'
  • Version/revision injected via ldflags into pkg/version

Code Generation

Two generation pipelines exist; always regenerate after changing their inputs.

1. OpenAPI → Go server stubs

task be:generate   # runs go generate ./...

The //go:generate directive in pkg/api/ calls oapi-codegen against openapi/v1.yaml to produce Go server interfaces and types.

2. OpenAPI → TypeScript client

task fe:generate   # npm run openapi

Runs openapi-ts against openapi/v1.yaml, writing output to frontend/client/. Do not hand-edit files in frontend/client/.

Supported OAuth / OIDC providers

Configured in config/auth.yml. Supported provider types:

Provider Type
Microsoft EntraID OIDC
Google OAuth2
GitHub OAuth2
Gitea OAuth2
GitLab OAuth2
Keycloak OIDC
Authentik OIDC
Kanidm OIDC

Database & Migrations

  • ORM: bun (uptrace/bun)
  • Supported drivers: sqlite3, mysql/mariadb, postgres/postgresql
  • Migrations live in pkg/migrations/ as timestamped Go files (e.g., 20250116073247_create_templates.go).
  • To add a migration: create a new YYYYMMDDHHMMSS_description.go file in pkg/migrations/ and register it in migrations.go.
  • Models live in pkg/model/ as bun-tagged structs.

Configuration

Configuration is loaded by Viper and can be provided via environment variables, a config file, or CLI flags. Config files live in the config/ directory:

  • config/config.yml (or .json) — main server settings
  • config/auth.yml (or .json) — OAuth/OIDC provider definitions

Environment variables follow the pattern KLEISTER_API_<SECTION>_<KEY> (uppercased).

Key config sections (pkg/config/config.go):

Section Env prefix Purpose
server KLEISTER_API_SERVER_* HTTP listen address, TLS, frontend path
database KLEISTER_API_DATABASE_* Driver, host, credentials
upload KLEISTER_API_UPLOAD_* File upload (local or S3)
token KLEISTER_API_TOKEN_* JWT secret and expiry
encrypt KLEISTER_API_ENCRYPT_* Encryption passphrase
log KLEISTER_API_LOG_* Log level, pretty printing
scim KLEISTER_API_SCIM_* SCIM provisioning
admin KLEISTER_API_ADMIN_* Initial admin user bootstrap
metrics KLEISTER_API_METRICS_* Prometheus metrics endpoint
runner KLEISTER_API_RUNNER_* Runner → server connection
cleanup KLEISTER_API_CLEANUP_* Background cleanup job

Secret values support two special prefixes:

  • file:///path/to/file — read value from file
  • base64://base64encodedvalue — decode from base64

Minimal development environment variables (see Taskfile.yml watch tasks):

KLEISTER_API_LOG_LEVEL=debug
KLEISTER_API_TOKEN_SECRET=<32-char random string>
KLEISTER_API_ENCRYPT_PASSPHRASE=<32-char random string>

Testing

Backend

task be:test
# or directly:
go test -coverprofile coverage.out ./...
  • Tests use stretchr/testify
  • Mocks are generated with mockgen and placed in files named mock.go
  • In-memory SQLite is used for store tests

Frontend

Runner: Vitest with the happy-dom environment. Component utilities: @vue/test-utils Config: vitest.config.ts at the repo root (separate from vite.config.ts).

task fe:test
# or directly:
npm run test        # vitest run (single pass)
npx vitest          # watch mode during development

Tests live co-located with the source file they test, using a .test.ts suffix:

frontend/lib/utils.test.ts
frontend/composables/useBreadcrumbs.test.ts
frontend/composables/useNavigationLinks.test.ts
frontend/feature/auth/store/auth.test.ts
frontend/feature/not-found/views/NotFound.test.ts

Conventions:

  • Import test helpers explicitly from vitest (describe, it, expect, vi, beforeEach, …) — do not rely on globals.
  • Use setActivePinia(createPinia()) in beforeEach for every Pinia store test.
  • Mock vue-router with vi.mock('vue-router', ...) when testing stores or composables that call useRouter().
  • Mock the generated API client (frontend/client/) with vi.mock — never make real HTTP calls from tests.
  • Use @vue/test-utils mount for component tests; assert on wrapper.text() and DOM queries rather than internal component state.

Linting & Formatting

Go

Run before committing:

task be:fmt        # gofmt -s -w on all non-mock .go files
task be:vet        # go vet
task be:lint       # revive with revive.toml
task be:golangci   # golangci-lint (comprehensive)

revive.toml enforces standard Go conventions. Notable rules:

  • exported — all exported symbols must have doc comments
  • package-comments — disabled
  • var-naming — skip-package-name-collision-with-go-std = true

TypeScript / Vue

task fe:fmt   # eslint --fix + prettier
task fe:lint  # eslint ./frontend

API Development Workflow

The OpenAPI spec (openapi/v1.yaml) is the single source of truth.

  1. Edit openapi/v1.yaml
  2. Run task generate to regenerate both Go stubs and the TypeScript client
  3. Implement the new handler in pkg/handler/ or pkg/api/
  4. Add routes in pkg/router/
  5. The frontend frontend/client/ is now updated automatically — use the new typed client methods in your Vue feature modules

Git & Contribution Conventions

  • DCO sign-off required on every commit:
    Signed-off-by: Your Name <your@email.com>
    
    Use git commit -s to add it automatically.
  • PRs should be squashed before merge.
  • CI must pass (gofmt, golangci-lint, tests) before merging.
  • Security issues → email kleister@webhippie.de before opening a public issue.

CI / GitHub Actions

Workflow Trigger Purpose
general.yml push / PR lint, vet, test (Go + TS)
binaries.yml tag / push goreleaser binary builds
docker.yml tag / push container image publishing (GHCR)
openapi.yml push OpenAPI validation
flake.yml push Nix flake check
tools.yml schedule dependency updates
automerge.yml PR auto-merge Dependabot PRs

Local Development with Docker Compose

cd hack/compose
docker compose -f base.yml -f local.yml up

Compose files in hack/compose/:

  • base.yml — service definitions
  • local.yml — local overrides (ports, volumes)
  • build.yml — build-time overrides
  • image.yml — published image overrides

Key Patterns to Follow

  1. Store layer (pkg/store/): each domain (users, projects, templates, …) has its own file. Methods receive a context.Context as the first argument. Use s.client.handle (bun DB) for queries.

  2. Models (pkg/model/): plain structs with bun: and json: struct tags. IDs are strings (ULIDs).

  3. Handlers (pkg/handler/): thin HTTP glue that calls the store and returns JSON. Input validation via pkg/validate/ using ozzo-validation.

  4. Config secrets: never hardcode secrets. Use config.Value() which handles file:// and base64:// prefixes transparently.

  5. Mock files: generated mocks are always in files named mock.go. Regenerate with task be:generate.

  6. Frontend features: follow the frontend/feature/ structure. Use the generated frontend/client/ for all API calls — never hand-roll fetch.

  7. Frontend tests — mandatory: every piece of frontend code you generate must be accompanied by a Vitest test file. This applies to:

    • Utility functions (frontend/lib/) → test all exported functions
    • Pinia stores (frontend/feature/*/store/) → test all actions and key computed properties; mock vue-router and the API client
    • Composables (frontend/composables/) → test returned values and reactivity; mock any store dependencies with vi.mock
    • Vue components (frontend/feature/*/views/, frontend/components/) → mount with @vue/test-utils and assert on rendered text/DOM

    Place the test file next to the source file with a .test.ts suffix and follow the conventions in the Testing → Frontend section above. Run npm run test to verify all tests pass before considering a task done.