diff --git a/.beads/.gitignore b/.beads/.gitignore new file mode 100644 index 0000000..d27a1db --- /dev/null +++ b/.beads/.gitignore @@ -0,0 +1,44 @@ +# SQLite databases +*.db +*.db?* +*.db-journal +*.db-wal +*.db-shm + +# Daemon runtime files +daemon.lock +daemon.log +daemon.pid +bd.sock +sync-state.json +last-touched + +# Local version tracking (prevents upgrade notification spam after git ops) +.local_version + +# Legacy database files +db.sqlite +bd.db + +# Worktree redirect file (contains relative path to main repo's .beads/) +# Must not be committed as paths would be wrong in other clones +redirect + +# Merge artifacts (temporary files from 3-way merge) +beads.base.jsonl +beads.base.meta.json +beads.left.jsonl +beads.left.meta.json +beads.right.jsonl +beads.right.meta.json + +# Sync state (local-only, per-machine) +# These files are machine-specific and should not be shared across clones +.sync.lock +sync_base.jsonl + +# NOTE: Do NOT add negation patterns (e.g., !issues.jsonl) here. +# They would override fork protection in .git/info/exclude, allowing +# contributors to accidentally commit upstream issue databases. +# The JSONL files (issues.jsonl, interactions.jsonl) and config files +# are tracked by git by default since no pattern above ignores them. diff --git a/.beads/PRIME.md b/.beads/PRIME.md new file mode 100644 index 0000000..20f07d7 --- /dev/null +++ b/.beads/PRIME.md @@ -0,0 +1,89 @@ +# Beads Workflow Context + +> **Context Recovery**: Run `bd prime` after compaction, clear, or new session +> Hooks auto-call this in Claude Code when .beads/ detected + +# 🚨 TWO-PHASE WORKFLOW 🚨 + +**This project uses a split workflow:** + +| Phase | Command | Actions | +|-------|---------|---------| +| **1. Implement** | `/start-issue ` | Set status → Plan → Implement | +| **2. Finalize** | `/finish-issue` | Commit → Close → Push | + +## What this means for you: + +### ✅ ALLOWED during implementation: +- Set ticket to `in_progress` +- Read, analyze, plan +- Write and modify code +- Run tests, build, verify + +### ❌ FORBIDDEN during implementation: +- `git add` / `git commit` / `git push` +- `bd close` +- `bd sync` (syncs commits) + +### When implementation is complete: + +**DO NOT** run the old "Session Close Protocol". Instead say: + +> "✅ Implementation complete. Files changed: [list files]. Run `/finish-issue` when ready to commit and close." + +Then **STOP** and wait for the user. + +--- + +## Core Rules +- Track strategic work in beads (multi-session, dependencies, discovered work) +- Use `bd create` for issues, TodoWrite for simple single-session execution +- When in doubt, prefer bd—persistence you don't need beats lost context +- Session management: check `bd ready` for available work + +## Essential Commands + +### Finding Work +- `bd ready` - Show issues ready to work (no blockers) +- `bd list --status=open` - All open issues +- `bd list --status=in_progress` - Your active work +- `bd show ` - Detailed issue view with dependencies + +### Creating & Updating +- `bd create --title="..." --type=task|bug|feature --priority=2` - New issue + - Priority: 0-4 or P0-P4 (0=critical, 2=medium, 4=backlog). NOT "high"/"medium"/"low" +- `bd update --status=in_progress` - Claim work +- `bd update --assignee=username` - Assign to someone +- `bd update --title/--description/--notes/--design` - Update fields inline +- `bd close ` - Mark complete (⚠️ only in /finish-issue!) +- **WARNING**: Do NOT use `bd edit` - it opens $EDITOR which blocks agents + +### Dependencies & Blocking +- `bd dep add ` - Add dependency +- `bd blocked` - Show all blocked issues +- `bd show ` - See what's blocking/blocked by this issue + +### Project Health +- `bd stats` - Project statistics +- `bd doctor` - Check for issues + +## Workflow Summary + +``` +/start-issue + │ + ├── bd update --status=in_progress + ├── [Plan mode] + ├── [Implement] + └── STOP → "Implementation complete" + + ... user reviews ... + +/finish-issue + │ + ├── bd close + ├── git add -A + ├── git commit -m "..." + ├── bd sync + └── Done! +``` diff --git a/.beads/README.md b/.beads/README.md new file mode 100644 index 0000000..50f281f --- /dev/null +++ b/.beads/README.md @@ -0,0 +1,81 @@ +# Beads - AI-Native Issue Tracking + +Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code. + +## What is Beads? + +Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git. + +**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads) + +## Quick Start + +### Essential Commands + +```bash +# Create new issues +bd create "Add user authentication" + +# View all issues +bd list + +# View issue details +bd show + +# Update issue status +bd update --status in_progress +bd update --status done + +# Sync with git remote +bd sync +``` + +### Working with Issues + +Issues in Beads are: +- **Git-native**: Stored in `.beads/issues.jsonl` and synced like code +- **AI-friendly**: CLI-first design works perfectly with AI coding agents +- **Branch-aware**: Issues can follow your branch workflow +- **Always in sync**: Auto-syncs with your commits + +## Why Beads? + +✨ **AI-Native Design** +- Built specifically for AI-assisted development workflows +- CLI-first interface works seamlessly with AI coding agents +- No context switching to web UIs + +🚀 **Developer Focused** +- Issues live in your repo, right next to your code +- Works offline, syncs when you push +- Fast, lightweight, and stays out of your way + +🔧 **Git Integration** +- Automatic sync with git commits +- Branch-aware issue tracking +- Intelligent JSONL merge resolution + +## Get Started with Beads + +Try Beads in your own projects: + +```bash +# Install Beads +curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash + +# Initialize in your repo +bd init + +# Create your first issue +bd create "Try out Beads" +``` + +## Learn More + +- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs) +- **Quick Start Guide**: Run `bd quickstart` +- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples) + +--- + +*Beads: Issue tracking that moves at the speed of thought* ⚡ diff --git a/.beads/config.yaml b/.beads/config.yaml new file mode 100644 index 0000000..f242785 --- /dev/null +++ b/.beads/config.yaml @@ -0,0 +1,62 @@ +# Beads Configuration File +# This file configures default behavior for all bd commands in this repository +# All settings can also be set via environment variables (BD_* prefix) +# or overridden with command-line flags + +# Issue prefix for this repository (used by bd init) +# If not set, bd init will auto-detect from directory name +# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc. +# issue-prefix: "" + +# Use no-db mode: load from JSONL, no SQLite, write back after each command +# When true, bd will use .beads/issues.jsonl as the source of truth +# instead of SQLite database +# no-db: false + +# Disable daemon for RPC communication (forces direct database access) +# no-daemon: false + +# Disable auto-flush of database to JSONL after mutations +# no-auto-flush: false + +# Disable auto-import from JSONL when it's newer than database +# no-auto-import: false + +# Enable JSON output by default +# json: false + +# Default actor for audit trails (overridden by BD_ACTOR or --actor) +# actor: "" + +# Path to database (overridden by BEADS_DB or --db) +# db: "" + +# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON) +# auto-start-daemon: true + +# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE) +# flush-debounce: "5s" + +# Git branch for beads commits (bd sync will commit to this branch) +# IMPORTANT: Set this for team projects so all clones use the same sync branch. +# This setting persists across clones (unlike database config which is gitignored). +# Can also use BEADS_SYNC_BRANCH env var for local override. +# If not set, bd sync will require you to run 'bd config set sync.branch '. +# sync-branch: "beads-sync" + +# Multi-repo configuration (experimental - bd-307) +# Allows hydrating from multiple repositories and routing writes to the correct JSONL +# repos: +# primary: "." # Primary repo (where this database lives) +# additional: # Additional repos to hydrate from (read-only) +# - ~/beads-planning # Personal planning repo +# - ~/work-planning # Work planning repo + +# Integration settings (access with 'bd config get/set') +# These are stored in the database, not in this file: +# - jira.url +# - jira.project +# - linear.url +# - linear.api-key +# - github.org +# - github.repo diff --git a/.beads/export-state/6feeba40f0df6736.json b/.beads/export-state/6feeba40f0df6736.json new file mode 100644 index 0000000..709de46 --- /dev/null +++ b/.beads/export-state/6feeba40f0df6736.json @@ -0,0 +1,6 @@ +{ + "worktree_root": "/home/oli/Dev/bookstack-api", + "last_export_commit": "a6adae98dd1697df76e68e562a4f84e7a38aa52d", + "last_export_time": "2026-01-30T09:36:58.353806809+01:00", + "jsonl_hash": "274bd67ea9399c10b88ec481bf082fe529a2d0cff39af1f6cbe89339a2f57f75" +} \ No newline at end of file diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 804cdc3..a9e4ccc 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -8,7 +8,7 @@ {"id":"bookstack-api-8op","title":"Implement Client and Config structs","description":"Create the main Client struct and Config for client initialization with token-based authentication.\n\n## Requirements\nFrom PRD Section 5 (API \u0026 Interface-Spezifikation):\n\n```go\ntype Config struct {\n BaseURL string // e.g. \"https://docs.jakoubek.net\"\n TokenID string // API Token ID\n TokenSecret string // API Token Secret\n HTTPClient *http.Client // optional, for tests/mocking\n}\n\ntype Client struct {\n Books *BooksService\n Pages *PagesService\n Chapters *ChaptersService\n Shelves *ShelvesService\n Search *SearchService\n}\n\nfunc NewClient(cfg Config) *Client\n```\n\n## Authentication\nHeader format: `Authorization: Token \u003ctoken_id\u003e:\u003ctoken_secret\u003e`\n\n## Acceptance Criteria\n- [ ] Config struct with BaseURL, TokenID, TokenSecret, optional HTTPClient\n- [ ] Client struct with service fields (initially nil)\n- [ ] NewClient() constructor validates required fields\n- [ ] Default http.Client used when not provided\n- [ ] Unit tests for NewClient()","status":"open","priority":0,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:06.037277135+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:06.037277135+01:00"} {"id":"bookstack-api-9at","title":"Implement Pages Delete","description":"Implement delete operation for Pages.\n\n## Requirements\nFrom PRD Section 5:\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| Delete | DELETE /api/pages/{id} | Delete page |\n\n## API Method\n```go\nfunc (s *PagesService) Delete(ctx context.Context, id int) error\n```\n\n## Technical Details\n- Returns no content on success\n- 404 if page not found\n- May require appropriate permissions\n\n## Acceptance Criteria\n- [ ] Delete() removes page by ID\n- [ ] Returns nil on success\n- [ ] Proper error handling (404 -\u003e ErrNotFound, 403 -\u003e ErrForbidden)\n- [ ] Unit tests with mock server","status":"open","priority":2,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:53.980583894+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:53.980583894+01:00"} {"id":"bookstack-api-9xo","title":"Implement BooksService (List, Get)","description":"Implement the BooksService with List and Get operations.\n\n## Requirements\nFrom PRD Section 5:\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| List | GET /api/books | All books |\n| Get | GET /api/books/{id} | Single book |\n\n## API Methods\n```go\ntype BooksService struct {\n client *Client\n}\n\nfunc (s *BooksService) List(ctx context.Context, opts *ListOptions) ([]*Book, error)\nfunc (s *BooksService) Get(ctx context.Context, id int) (*Book, error)\n```\n\n## ListOptions\nSupport pagination parameters:\n- count (max 500)\n- offset\n- sort (+name, -created_at, etc.)\n- filter[field]\n\n## Acceptance Criteria\n- [ ] BooksService struct created\n- [ ] List() returns paginated books\n- [ ] Get() returns single book by ID\n- [ ] Proper error handling (404 -\u003e ErrNotFound)\n- [ ] Unit tests with mock server","status":"open","priority":0,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:30.949469353+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:30.949469353+01:00"} -{"id":"bookstack-api-adp","title":"Set up Go module and project structure","description":"Initialize the Go module and create the basic project structure as defined in the PRD.\n\n## Requirements\n- Initialize go.mod with module name\n- Create placeholder files for the flat package structure:\n - bookstack.go (Client, Config, NewClient)\n - types.go (data structures)\n - errors.go (error types)\n - http.go (HTTP helpers)\n- Set up .gitignore for Go projects\n\n## Technical Details\n- Go 1.21+ required\n- Zero external dependencies (standard library only)\n- Module should be publishable as standalone Go module\n\n## Acceptance Criteria\n- [ ] go.mod exists with proper module name\n- [ ] Basic file structure created\n- [ ] `go build ./...` succeeds\n- [ ] .gitignore includes Go-specific patterns","status":"in_progress","priority":0,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:05.818311718+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-30T09:33:48.689831698+01:00"} +{"id":"bookstack-api-adp","title":"Set up Go module and project structure","description":"Initialize the Go module and create the basic project structure as defined in the PRD.\n\n## Requirements\n- Initialize go.mod with module name\n- Create placeholder files for the flat package structure:\n - bookstack.go (Client, Config, NewClient)\n - types.go (data structures)\n - errors.go (error types)\n - http.go (HTTP helpers)\n- Set up .gitignore for Go projects\n\n## Technical Details\n- Go 1.21+ required\n- Zero external dependencies (standard library only)\n- Module should be publishable as standalone Go module\n\n## Acceptance Criteria\n- [ ] go.mod exists with proper module name\n- [ ] Basic file structure created\n- [ ] `go build ./...` succeeds\n- [ ] .gitignore includes Go-specific patterns","status":"closed","priority":0,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:05.818311718+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-30T09:38:24.407997076+01:00","closed_at":"2026-01-30T09:38:24.407997076+01:00","close_reason":"Closed"} {"id":"bookstack-api-bu8","title":"Add GoDoc documentation for all public APIs","description":"Ensure all exported types and functions have proper GoDoc comments.\n\n## Requirements\nFrom PRD Section 8 (Definition of Done):\n- Alle public APIs dokumentiert (GoDoc)\n\n## Documentation Standards\n- Package-level doc comment in bookstack.go\n- All exported types documented\n- All exported functions documented\n- All exported constants/variables documented\n- Include usage examples in doc comments where helpful\n\n## Example\n```go\n// Client is the Bookstack API client. Use NewClient to create a new instance.\n// Client is safe for concurrent use.\ntype Client struct {\n // Books provides access to the Books API.\n Books *BooksService\n ...\n}\n\n// NewClient creates a new Bookstack API client with the provided configuration.\n// BaseURL, TokenID, and TokenSecret are required.\n//\n// Example:\n//\n// client := bookstack.NewClient(bookstack.Config{\n// BaseURL: \"https://docs.example.com\",\n// TokenID: \"token-id\",\n// TokenSecret: \"token-secret\",\n// })\nfunc NewClient(cfg Config) *Client\n```\n\n## Acceptance Criteria\n- [ ] Package documentation present\n- [ ] All exported types documented\n- [ ] All exported functions documented\n- [ ] Documentation renders correctly in pkg.go.dev style\n- [ ] No golint documentation warnings","status":"open","priority":1,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:40:24.752365504+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:40:24.752365504+01:00"} {"id":"bookstack-api-cpg","title":"Implement PagesService (List, Get)","description":"Implement the PagesService with List and Get operations.\n\n## Requirements\nFrom PRD Section 5:\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| List | GET /api/pages | All pages |\n| Get | GET /api/pages/{id} | Single page |\n\n## API Methods\n```go\ntype PagesService struct {\n client *Client\n}\n\nfunc (s *PagesService) List(ctx context.Context, opts *ListOptions) ([]*Page, error)\nfunc (s *PagesService) Get(ctx context.Context, id int) (*Page, error)\n```\n\n## Page Content\nThe Get response includes:\n- HTML content\n- RawHTML content \n- Markdown content (if available)\n\n## Acceptance Criteria\n- [ ] PagesService struct created\n- [ ] List() returns paginated pages\n- [ ] Get() returns single page by ID with content\n- [ ] Proper error handling (404 -\u003e ErrNotFound)\n- [ ] Unit tests with mock server","status":"open","priority":0,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:31.188568742+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:31.188568742+01:00"} {"id":"bookstack-api-d2c","title":"Implement Pages Create and Update","description":"Implement write operations for Pages: Create and Update.\n\n## Requirements\nFrom PRD Section 5:\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| Create | POST /api/pages | Create page |\n| Update | PUT /api/pages/{id} | Update page |\n\n## API Methods\n```go\ntype PageCreateRequest struct {\n BookID int `json:\"book_id\"`\n ChapterID int `json:\"chapter_id,omitempty\"`\n Name string `json:\"name\"`\n HTML string `json:\"html,omitempty\"`\n Markdown string `json:\"markdown,omitempty\"`\n}\n\ntype PageUpdateRequest struct {\n Name string `json:\"name,omitempty\"`\n HTML string `json:\"html,omitempty\"`\n Markdown string `json:\"markdown,omitempty\"`\n}\n\nfunc (s *PagesService) Create(ctx context.Context, req *PageCreateRequest) (*Page, error)\nfunc (s *PagesService) Update(ctx context.Context, id int, req *PageUpdateRequest) (*Page, error)\n```\n\n## Workflow (from PRD)\n1. Get page\n2. Edit content locally\n3. Update page\n\n## Acceptance Criteria\n- [ ] PageCreateRequest and PageUpdateRequest structs\n- [ ] Create() creates new page in book or chapter\n- [ ] Update() modifies existing page\n- [ ] Returns updated Page object\n- [ ] Proper error handling (400 -\u003e ErrBadRequest)\n- [ ] Unit tests with mock server","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:53.734987403+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:53.734987403+01:00"} diff --git a/.beads/metadata.json b/.beads/metadata.json new file mode 100644 index 0000000..c787975 --- /dev/null +++ b/.beads/metadata.json @@ -0,0 +1,4 @@ +{ + "database": "beads.db", + "jsonl_export": "issues.jsonl" +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..807d598 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ + +# Use bd merge for beads JSONL files +.beads/issues.jsonl merge=beads diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..df7a4af --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,40 @@ +# Agent Instructions + +This project uses **bd** (beads) for issue tracking. Run `bd onboard` to get started. + +## Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd update --status in_progress # Claim work +bd close # Complete work +bd sync # Sync with git +``` + +## Landing the Plane (Session Completion) + +**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds. + +**MANDATORY WORKFLOW:** + +1. **File issues for remaining work** - Create issues for anything that needs follow-up +2. **Run quality gates** (if code changed) - Tests, linters, builds +3. **Update issue status** - Close finished work, update in-progress items +4. **PUSH TO REMOTE** - This is MANDATORY: + ```bash + git pull --rebase + bd sync + git push + git status # MUST show "up to date with origin" + ``` +5. **Clean up** - Clear stashes, prune remote branches +6. **Verify** - All changes committed AND pushed +7. **Hand off** - Provide context for next session + +**CRITICAL RULES:** +- Work is NOT complete until `git push` succeeds +- NEVER stop before pushing - that leaves work stranded locally +- NEVER say "ready to push when you are" - YOU must push +- If push fails, resolve and retry until it succeeds + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d49afc8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,102 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +A Go library for the Bookstack REST API. Primary consumers are AI agents via CLI (with `--json` flag) and Go developers needing programmatic Bookstack access. + +## Build & Test Commands + +```bash +# Build +go build ./... + +# Run all tests +go test ./... + +# Run single test +go test -run TestName ./... + +# Run tests with coverage +go test -cover ./... + +# Lint (if golangci-lint is installed) +golangci-lint run + +# Format code +gofmt -w . +``` + +## Architecture + +### Flat Package Structure + +All code lives in the root package `bookstack`. No subpackages. + +``` +bookstack.go # Client, Config, NewClient() +books.go # BooksService +pages.go # PagesService +chapters.go # ChaptersService +shelves.go # ShelvesService +search.go # SearchService +types.go # All data structures (Book, Page, Chapter, Shelf, SearchResult) +errors.go # Error types (ErrNotFound, ErrUnauthorized, APIError, etc.) +http.go # HTTP helpers, request building +iterator.go # Pagination iterator using Go 1.23+ iter.Seq +``` + +### Client Pattern + +Services are attached to the main `Client` struct: + +```go +client := bookstack.NewClient(bookstack.Config{ + BaseURL: "https://docs.example.com", + TokenID: os.Getenv("BOOKSTACK_TOKEN_ID"), + TokenSecret: os.Getenv("BOOKSTACK_TOKEN_SECRET"), +}) + +// Access services via client +books, err := client.Books.List(ctx, nil) +page, err := client.Pages.Get(ctx, 123) +``` + +### Pagination via Iterators + +Use Go 1.23+ iterator pattern for list operations: + +```go +for book, err := range client.Books.ListAll(ctx) { + if err != nil { + return err + } + // process book +} +``` + +### Error Handling + +Use sentinel errors with `errors.Is()`: + +```go +if errors.Is(err, bookstack.ErrNotFound) { + // handle 404 +} +``` + +`APIError` provides detailed information when needed. + +## Key Design Decisions + +- **Zero external dependencies** - Only Go standard library +- **Go 1.21+** required +- **No caching/rate-limiting** - Caller responsibility +- **Token auth** via `Authorization: Token :` header +- **Bookstack hierarchy**: Shelf → Book → Chapter → Page + +## Bookstack API Reference + +- Rate limit: 180 req/min (server configurable) +- Pagination: `count` (max 500), `offset`, `sort`, `filter[field]` diff --git a/PRD.md b/PRD.md new file mode 100644 index 0000000..f246ae5 --- /dev/null +++ b/PRD.md @@ -0,0 +1,498 @@ +# Product Requirements Document: bookstack-api + +## 1. Projektübersicht + +- **Ziel und Vision:** + Eine Go-Library für die Bookstack REST-API, die programmatischen Zugriff auf das Dokumentationssystem ermöglicht. Die Library wird in hqcli integriert und soll veröffentlichungsfähig sein. + +- **Zielgruppe:** + - Primär: AI-Agenten (via hqcli mit `--json` Flag) + - Sekundär: Go-Entwickler, die Bookstack programmatisch nutzen wollen + +- **Erfolgs-Metriken:** + - Vollständige Abdeckung der Kern-API (Books, Pages, Search) + - Nutzbar in hqcli für AI-gestützte Dokumentationssuche + - Veröffentlichung als eigenständiges Go-Modul + +- **Projektscope:** + - **In Scope:** REST-API-Client für Bookstack, Iterator-basierte Pagination, Export-Funktionen + - **Out of Scope:** Webhooks, Caching, Admin-Funktionen (Users, Roles) + +## 2. Funktionale Anforderungen + +### Kern-Features + +| Feature | Priorität | Version | +|---------|-----------|---------| +| Books: List, Get | P0 | v0.1 | +| Pages: List, Get | P0 | v0.1 | +| Search: All | P0 | v0.1 | +| Pages: Export (Markdown, PDF) | P1 | v0.2 | +| Chapters: List, Get | P1 | v0.2 | +| Shelves: List, Get | P1 | v0.2 | +| Pages: Create, Update | P1 | v0.3 | +| Pages: Delete | P2 | v0.4 | +| Attachments: CRUD | P2 | v0.4 | +| Comments: CRUD | P3 | v0.5 | + +### User Stories + +**US1: Dokumentation durchsuchen (AI-Agent)** +> Als AI-Agent möchte ich die Bookstack-Dokumentation durchsuchen können, um relevante Seiten für Benutzeranfragen zu finden. + +```bash +hqcli docs search "deployment" --json +``` + +**US2: Seite abrufen** +> Als Benutzer möchte ich eine Dokumentationsseite anzeigen können, um deren Inhalt zu lesen. + +```bash +hqcli docs page 123 +hqcli docs page deployment-guide # via Slug +``` + +**US3: Bücher auflisten** +> Als Benutzer möchte ich alle verfügbaren Bücher sehen, um die Dokumentationsstruktur zu verstehen. + +```bash +hqcli docs books --json +``` + +**US4: Seite exportieren** +> Als Benutzer möchte ich eine Seite als Markdown oder PDF exportieren können. + +```bash +hqcli docs page 123 --export=md > page.md +hqcli docs page 123 --export=pdf > page.pdf +``` + +**US5: Seite im Browser öffnen** +> Als Benutzer möchte ich eine Seite schnell im Browser öffnen können. + +```bash +hqcli docs open 123 +``` + +### Detaillierte Workflows + +**Workflow: Suche und Anzeige** +``` +1. Benutzer/Agent führt Suche aus +2. API gibt Liste von Treffern zurück (ID, Typ, Name, Preview) +3. Benutzer/Agent wählt Treffer aus +4. Seite wird abgerufen und angezeigt (Markdown oder JSON) +``` + +**Workflow: Seite bearbeiten (v0.3)** +``` +1. Seite abrufen (Get) +2. Inhalt lokal bearbeiten +3. Seite aktualisieren (Update) +``` + +### Feature-Prioritäten + +- **Must-have (v1):** List, Get, Search für Books/Pages +- **Should-have (v1):** Export Markdown/PDF, Chapters, Shelves +- **Nice-to-have (v2):** Create, Update, Delete +- **Future:** Attachments, Comments, Image Gallery + +## 3. Technische Anforderungen + +- **Performance-Ziele:** + - API-Calls < 500ms (abhängig von Netzwerk) + - Iterator verarbeitet 10.000+ Einträge ohne Memory-Probleme + +- **Concurrent User-Kapazität:** + Nicht zutreffend (Library, kein Server) + +- **Real-time Features:** + Nicht zutreffend + +- **Sicherheitsstandards:** + - Token-basierte Authentifizierung (Token ID + Secret) + - Keine Speicherung von Credentials (Aufrufer-Verantwortung) + +- **Compliance-Vorgaben:** + Keine speziellen + +- **Plattform-Support:** + - Go 1.21+ + - Linux, macOS, Windows + +## 4. Datenarchitektur + +*Nicht zutreffend – keine eigene Datenhaltung* + +### Bookstack-Hierarchie (extern) + +``` +Shelf (Regal) +└── Book (Buch) + ├── Chapter (Kapitel) + │ └── Page (Seite) + └── Page (Seite) +``` + +### Datenstrukturen + +```go +// Book repräsentiert ein Bookstack-Buch +type Book struct { + ID int `json:"id"` + Name string `json:"name"` + Slug string `json:"slug"` + Description string `json:"description"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + CreatedBy int `json:"created_by"` + UpdatedBy int `json:"updated_by"` +} + +// Page repräsentiert eine Bookstack-Seite +type Page struct { + ID int `json:"id"` + BookID int `json:"book_id"` + ChapterID int `json:"chapter_id"` + Name string `json:"name"` + Slug string `json:"slug"` + HTML string `json:"html"` + RawHTML string `json:"raw_html"` + Markdown string `json:"markdown"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// Chapter repräsentiert ein Bookstack-Kapitel +type Chapter struct { + ID int `json:"id"` + BookID int `json:"book_id"` + Name string `json:"name"` + Slug string `json:"slug"` + Description string `json:"description"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// Shelf repräsentiert ein Bookstack-Regal +type Shelf struct { + ID int `json:"id"` + Name string `json:"name"` + Slug string `json:"slug"` + Description string `json:"description"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// SearchResult repräsentiert ein Suchergebnis +type SearchResult struct { + ID int `json:"id"` + Name string `json:"name"` + Slug string `json:"slug"` + Type string `json:"type"` // page, chapter, book, bookshelf + URL string `json:"url"` + Preview string `json:"preview"` +} +``` + +## 5. API & Interface-Spezifikation + +### Client-Initialisierung + +```go +type Config struct { + BaseURL string // z.B. "https://docs.jakoubek.net" + TokenID string // API Token ID + TokenSecret string // API Token Secret + HTTPClient *http.Client // optional, für Tests/Mocking +} + +func NewClient(cfg Config) *Client +``` + +**Beispiel:** +```go +client := bookstack.NewClient(bookstack.Config{ + BaseURL: "https://docs.jakoubek.net", + TokenID: os.Getenv("BOOKSTACK_TOKEN_ID"), + TokenSecret: os.Getenv("BOOKSTACK_TOKEN_SECRET"), +}) +``` + +### Service-Struktur + +```go +type Client struct { + Books *BooksService + Pages *PagesService + Chapters *ChaptersService + Shelves *ShelvesService + Search *SearchService +} +``` + +### REST-Endpoints (Bookstack API) + +| Service | Methode | Endpoint | Beschreibung | +|---------|---------|----------|--------------| +| Books | List | GET /api/books | Alle Bücher | +| Books | Get | GET /api/books/{id} | Einzelnes Buch | +| Pages | List | GET /api/pages | Alle Seiten | +| Pages | Get | GET /api/pages/{id} | Einzelne Seite | +| Pages | Create | POST /api/pages | Seite erstellen | +| Pages | Update | PUT /api/pages/{id} | Seite aktualisieren | +| Pages | Delete | DELETE /api/pages/{id} | Seite löschen | +| Pages | ExportMD | GET /api/pages/{id}/export/markdown | Markdown-Export | +| Pages | ExportPDF | GET /api/pages/{id}/export/pdf | PDF-Export | +| Chapters | List | GET /api/chapters | Alle Kapitel | +| Chapters | Get | GET /api/chapters/{id} | Einzelnes Kapitel | +| Shelves | List | GET /api/shelves | Alle Regale | +| Shelves | Get | GET /api/shelves/{id} | Einzelnes Regal | +| Search | All | GET /api/search?query=... | Volltextsuche | + +### Authentifizierung + +``` +Authorization: Token : +``` + +### Pagination (Iterator-Pattern) + +```go +// ListAll gibt einen Iterator über alle Einträge zurück +// Nutzt Go 1.23+ iter.Seq oder eigene Implementation +func (s *BooksService) ListAll(ctx context.Context) iter.Seq2[*Book, error] + +// Nutzung: +for book, err := range client.Books.ListAll(ctx) { + if err != nil { + return err + } + fmt.Println(book.Name) +} +``` + +**Begründung:** Iterator-Pattern ist Go-idiomatisch (ab 1.23), Memory-effizient und ermöglicht frühen Abbruch. + +### Rate Limiting + +- Bookstack: 180 Requests/Minute (default) +- **Keine Library-interne Behandlung** – Aufrufer muss Rate-Limiting selbst handhaben +- Bei 429-Response: `ErrRateLimited` zurückgeben + +## 6. Benutzeroberfläche + +*Nicht zutreffend – Library ohne UI* + +### hqcli-Integration (separates Projekt) + +```bash +# Bücher auflisten +hqcli docs books +hqcli docs books --json + +# Seiten eines Buchs +hqcli docs pages --book= + +# Seite anzeigen +hqcli docs page +hqcli docs page --json + +# Seite exportieren +hqcli docs page --export=md +hqcli docs page --export=pdf + +# Suche +hqcli docs search "query" +hqcli docs search "query" --json + +# Im Browser öffnen +hqcli docs open +``` + +**Output-Priorität:** `--json` für AI-Agenten ist Hauptanwendungsfall + +## 7. Nicht-funktionale Anforderungen + +- **Verfügbarkeit:** + Nicht zutreffend (Library) + +- **Dependencies:** + Nur Go-Standardbibliothek (net/http, encoding/json, etc.) + +- **Backward Compatibility:** + Semantic Versioning (v0.x während Entwicklung, v1.x nach Stabilisierung) + +- **Logging-Strategie:** + Keine eigene Logging – Fehler werden als `error` zurückgegeben + +- **Konfiguration:** + Via `Config`-Struct bei Client-Erstellung + +## 8. Qualitätssicherung + +### Definition of Done + +- [ ] Alle public APIs dokumentiert (GoDoc) +- [ ] Unit-Tests für alle Services (≥80% Coverage) +- [ ] Integration-Tests gegen Mock-Server +- [ ] Beispiel-Code in examples/ +- [ ] README mit Quick-Start + +### Test-Anforderungen + +```go +// Unit-Tests mit Mock-HTTP-Client +func TestBooksService_List(t *testing.T) { + server := httptest.NewServer(...) + client := bookstack.NewClient(bookstack.Config{ + BaseURL: server.URL, + TokenID: "test", + TokenSecret: "test", + }) + + books, err := client.Books.List(ctx, nil) + // assertions... +} +``` + +### Launch-Kriterien v1.0 + +- [ ] Books, Pages, Search vollständig implementiert +- [ ] Export (Markdown, PDF) funktioniert +- [ ] Dokumentation vollständig +- [ ] Keine bekannten Bugs +- [ ] hqcli-Integration getestet + +## 9. Technische Implementierungshinweise + +### Go-Projektstruktur + +``` +bookstack-api/ +├── bookstack.go # Client, Config, NewClient() +├── books.go # BooksService +├── pages.go # PagesService +├── chapters.go # ChaptersService +├── shelves.go # ShelvesService +├── search.go # SearchService +├── types.go # Alle Datenstrukturen +├── errors.go # Error-Typen +├── http.go # HTTP-Helfer, Request-Building +├── iterator.go # Pagination-Iterator +├── bookstack_test.go # Tests +├── README.md +├── go.mod +├── go.sum +└── examples/ + └── basic/ + └── main.go +``` + +### Error-Handling-Strategie + +```go +// Definierte Error-Typen für häufige Fälle +var ( + ErrNotFound = errors.New("bookstack: resource not found") + ErrUnauthorized = errors.New("bookstack: unauthorized") + ErrForbidden = errors.New("bookstack: forbidden") + ErrRateLimited = errors.New("bookstack: rate limited") + ErrBadRequest = errors.New("bookstack: bad request") +) + +// APIError für detaillierte Fehlerinformationen +type APIError struct { + StatusCode int + Code int `json:"code"` + Message string `json:"message"` + Body []byte // Original Response Body +} + +func (e *APIError) Error() string { + return fmt.Sprintf("bookstack: API error %d: %s", e.StatusCode, e.Message) +} + +func (e *APIError) Is(target error) bool { + switch target { + case ErrNotFound: + return e.StatusCode == 404 + case ErrUnauthorized: + return e.StatusCode == 401 + case ErrForbidden: + return e.StatusCode == 403 + case ErrRateLimited: + return e.StatusCode == 429 + case ErrBadRequest: + return e.StatusCode == 400 + } + return false +} +``` + +**Begründung:** `errors.Is()` ermöglicht einfache Fehlerprüfung, `APIError` bietet Details wenn nötig. + +### HTTP-Wrapper + +```go +// Interner HTTP-Helfer +func (c *Client) do(ctx context.Context, method, path string, body, result any) error { + // 1. Request bauen + // 2. Auth-Header setzen + // 3. Request ausführen + // 4. Response prüfen + // 5. Bei Fehler: APIError zurückgeben + // 6. Bei Erfolg: JSON in result unmarshalen +} +``` + +### Entwicklungs-Prioritäten + +1. **Phase 1 (v0.1):** Foundation + - Client-Setup, Auth, HTTP-Wrapper + - Books.List, Books.Get + - Pages.List, Pages.Get + - Error-Handling + +2. **Phase 2 (v0.2):** Core Features + - Search.All + - Pages.ExportMarkdown, Pages.ExportPDF + - Iterator für Pagination + - Chapters, Shelves + +3. **Phase 3 (v0.3):** Write Operations + - Pages.Create + - Pages.Update + +4. **Phase 4 (v1.0):** Release + - Dokumentation + - Beispiele + - CI/CD + - Veröffentlichung + +### Potenzielle Risiken + +| Risiko | Wahrscheinlichkeit | Mitigation | +|--------|-------------------|------------| +| API-Änderungen in Bookstack | Niedrig | Semantic Versioning, Tests | +| Rate-Limiting-Probleme | Mittel | Dokumentation für Aufrufer | +| Große PDF-Exports | Mittel | Streaming statt Buffer | + +--- + +## Anhang: Bookstack API-Referenz + +- **Basis-URL:** https://docs.jakoubek.net/api +- **Dokumentation:** https://docs.jakoubek.net/api/docs +- **Beispiele:** https://codeberg.org/bookstack/api-scripts +- **Rate Limit:** 180 req/min (konfigurierbar serverseitig) + +### Pagination-Parameter + +| Parameter | Beschreibung | Default | +|-----------|--------------|---------| +| count | Anzahl Ergebnisse | 100 (max 500) | +| offset | Start-Position | 0 | +| sort | Sortierung (+name, -created_at) | - | +| filter[field] | Filter (eq, ne, gt, lt, like) | - | diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4c84169 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module code.beautifulmachines.dev/jakoubek/bookstack-api + +go 1.25.6