- Added CLAUDE.md with project overview and architecture details - Added PRD.md with product requirements - Added AGENTS.md for agent workflow documentation - Initialized beads issue tracking system - Added .gitattributes for Git configuration
13 KiB
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
--jsonFlag) - Sekundär: Go-Entwickler, die Bookstack programmatisch nutzen wollen
- Primär: AI-Agenten (via hqcli mit
-
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.
hqcli docs search "deployment" --json
US2: Seite abrufen
Als Benutzer möchte ich eine Dokumentationsseite anzeigen können, um deren Inhalt zu lesen.
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.
hqcli docs books --json
US4: Seite exportieren
Als Benutzer möchte ich eine Seite als Markdown oder PDF exportieren können.
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.
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
// 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
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:
client := bookstack.NewClient(bookstack.Config{
BaseURL: "https://docs.jakoubek.net",
TokenID: os.Getenv("BOOKSTACK_TOKEN_ID"),
TokenSecret: os.Getenv("BOOKSTACK_TOKEN_SECRET"),
})
Service-Struktur
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 <token_id>:<token_secret>
Pagination (Iterator-Pattern)
// 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:
ErrRateLimitedzurückgeben
6. Benutzeroberfläche
Nicht zutreffend – Library ohne UI
hqcli-Integration (separates Projekt)
# Bücher auflisten
hqcli docs books
hqcli docs books --json
# Seiten eines Buchs
hqcli docs pages --book=<id|slug>
# Seite anzeigen
hqcli docs page <id|slug>
hqcli docs page <id> --json
# Seite exportieren
hqcli docs page <id> --export=md
hqcli docs page <id> --export=pdf
# Suche
hqcli docs search "query"
hqcli docs search "query" --json
# Im Browser öffnen
hqcli docs open <id|slug>
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 alserrorzurückgegeben -
Konfiguration:
ViaConfig-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
// 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
// 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
// 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
-
Phase 1 (v0.1): Foundation
- Client-Setup, Auth, HTTP-Wrapper
- Books.List, Books.Get
- Pages.List, Pages.Get
- Error-Handling
-
Phase 2 (v0.2): Core Features
- Search.All
- Pages.ExportMarkdown, Pages.ExportPDF
- Iterator für Pagination
- Chapters, Shelves
-
Phase 3 (v0.3): Write Operations
- Pages.Create
- Pages.Update
-
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) | - |