From 62e299192d2ab528a124ed76c07404ed1cc94f27 Mon Sep 17 00:00:00 2001 From: Oliver Jakoubek Date: Fri, 30 Jan 2026 09:36:58 +0100 Subject: [PATCH] Set up Go module and project structure - Populated .gitignore with Go-specific patterns - Created placeholder Go files with basic package structure: - bookstack.go: Client and Config setup - types.go: Data structures (Book, Page, Chapter, Shelf, SearchResult) - errors.go: Error handling types with sentinel errors - http.go: HTTP helper placeholder with ListOptions - books.go, pages.go, chapters.go, shelves.go, search.go: Service placeholders - Verified build succeeds with go build ./... - No external dependencies added (stdlib only) All files compile successfully and follow flat package structure. --- .beads/interactions.jsonl | 0 .beads/issues.jsonl | 19 ++++++++++ .gitignore | 35 ++++++++++++++++++ books.go | 43 ++++++++++++++++++++++ bookstack.go | 63 ++++++++++++++++++++++++++++++++ chapters.go | 43 ++++++++++++++++++++++ errors.go | 49 +++++++++++++++++++++++++ http.go | 28 +++++++++++++++ pages.go | 43 ++++++++++++++++++++++ search.go | 15 ++++++++ shelves.go | 43 ++++++++++++++++++++++ types.go | 76 +++++++++++++++++++++++++++++++++++++++ 12 files changed, 457 insertions(+) create mode 100644 .beads/interactions.jsonl create mode 100644 .beads/issues.jsonl create mode 100644 books.go create mode 100644 bookstack.go create mode 100644 chapters.go create mode 100644 errors.go create mode 100644 http.go create mode 100644 pages.go create mode 100644 search.go create mode 100644 shelves.go create mode 100644 types.go diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl new file mode 100644 index 0000000..804cdc3 --- /dev/null +++ b/.beads/issues.jsonl @@ -0,0 +1,19 @@ +{"id":"bookstack-api-02m","title":"Create example code in examples/ directory","description":"Create example programs demonstrating library usage.\n\n## Requirements\nFrom PRD Section 8 (Definition of Done):\n- Beispiel-Code in examples/\n\nFrom PRD Section 9 (Go-Projektstruktur):\n```\nexamples/\n└── basic/\n └── main.go\n```\n\n## Examples to Create\n\n### examples/basic/main.go\nBasic usage: client setup, list books, get page\n\n### examples/search/main.go\nSearch workflow: search -\u003e select result -\u003e display page\n\n### examples/export/main.go\nExport page to markdown or PDF file\n\n### examples/iterator/main.go\nUsing ListAll() to iterate all pages\n\n## Technical Details\n- Each example should be runnable standalone\n- Use environment variables for credentials\n- Include error handling\n- Add comments explaining the code\n\n## Acceptance Criteria\n- [ ] examples/basic/main.go exists and runs\n- [ ] At least 2-3 different examples\n- [ ] All examples compile and work\n- [ ] Examples demonstrate key features","status":"open","priority":1,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:40:24.46706112+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:40:24.46706112+01:00"} +{"id":"bookstack-api-1us","title":"Write README with Quick-Start guide","description":"Create comprehensive README.md with usage documentation.\n\n## Requirements\nFrom PRD Section 8 (Definition of Done):\n- README mit Quick-Start\n\n## Content\n1. **Overview** - What the library does\n2. **Installation** - go get command\n3. **Quick Start** - Basic usage example\n4. **Authentication** - How to get and use API tokens\n5. **Usage Examples**\n - List books\n - Search documentation\n - Get page content\n - Export page\n6. **API Reference** - Link to GoDoc\n7. **License**\n\n## Example Code\n```go\nclient := bookstack.NewClient(bookstack.Config{\n BaseURL: \"https://docs.example.com\",\n TokenID: os.Getenv(\"BOOKSTACK_TOKEN_ID\"),\n TokenSecret: os.Getenv(\"BOOKSTACK_TOKEN_SECRET\"),\n})\n\n// Search documentation\nresults, err := client.Search.Search(ctx, \"deployment\", nil)\n\n// Get a page\npage, err := client.Pages.Get(ctx, 123)\n```\n\n## Acceptance Criteria\n- [ ] Clear installation instructions\n- [ ] Working quick-start example\n- [ ] All main features documented\n- [ ] Links to full documentation","status":"open","priority":1,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:40:24.187495378+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:40:24.187495378+01:00"} +{"id":"bookstack-api-2x5","title":"Implement SearchService","description":"Implement the SearchService for full-text search across Bookstack content.\n\n## Requirements\nFrom PRD Section 5:\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| All | GET /api/search?query=... | Full-text search |\n\n## API Methods\n```go\ntype SearchService struct {\n client *Client\n}\n\ntype SearchOptions struct {\n Query string\n // Pagination options\n Count int\n Offset int\n}\n\nfunc (s *SearchService) Search(ctx context.Context, query string, opts *SearchOptions) ([]*SearchResult, error)\n```\n\n## SearchResult\nReturns results across all types:\n- page\n- chapter\n- book\n- bookshelf\n\nEach result includes ID, Name, Slug, Type, URL, Preview.\n\n## User Story\nUS1: Als AI-Agent möchte ich die Bookstack-Dokumentation durchsuchen können, um relevante Seiten für Benutzeranfragen zu finden.\n\n## Acceptance Criteria\n- [ ] SearchService struct created\n- [ ] Search() accepts query string and options\n- [ ] Results include all content types\n- [ ] Proper error handling\n- [ ] Unit tests with mock server","status":"open","priority":0,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:31.430412917+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:31.430412917+01:00"} +{"id":"bookstack-api-42g","title":"Implement ShelvesService (List, Get)","description":"Implement the ShelvesService with List and Get operations.\n\n## Requirements\nFrom PRD Section 5:\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| List | GET /api/shelves | All shelves |\n| Get | GET /api/shelves/{id} | Single shelf |\n\n## API Methods\n```go\ntype ShelvesService struct {\n client *Client\n}\n\nfunc (s *ShelvesService) List(ctx context.Context, opts *ListOptions) ([]*Shelf, error)\nfunc (s *ShelvesService) Get(ctx context.Context, id int) (*Shelf, error)\nfunc (s *ShelvesService) ListAll(ctx context.Context) iter.Seq2[*Shelf, error]\n```\n\n## Shelf Fields\n- ID, Name, Slug, Description, CreatedAt, UpdatedAt\n\n## Bookstack Hierarchy\nShelf is the top-level container: Shelf -\u003e Book -\u003e Chapter -\u003e Page\n\n## Acceptance Criteria\n- [ ] ShelvesService struct created\n- [ ] List() returns paginated shelves\n- [ ] Get() returns single shelf by ID\n- [ ] ListAll() iterator implemented\n- [ ] Proper error handling\n- [ ] Unit tests with mock server","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:53.490673653+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:53.490673653+01:00"} +{"id":"bookstack-api-5gi","title":"Implement Attachments CRUD","description":"Implement CRUD operations for Attachments.\n\n## Requirements\nFrom PRD feature table: Attachments: CRUD (P2, v0.4)\n\n## API Endpoints (typical Bookstack pattern)\n- GET /api/attachments - List attachments\n- GET /api/attachments/{id} - Get attachment\n- POST /api/attachments - Create attachment\n- PUT /api/attachments/{id} - Update attachment\n- DELETE /api/attachments/{id} - Delete attachment\n\n## API Methods\n```go\ntype AttachmentsService struct {\n client *Client\n}\n\ntype Attachment struct {\n ID int `json:\"id\"`\n Name string `json:\"name\"`\n Extension string `json:\"extension\"`\n PageID int `json:\"uploaded_to\"`\n CreatedAt time.Time `json:\"created_at\"`\n UpdatedAt time.Time `json:\"updated_at\"`\n}\n\nfunc (s *AttachmentsService) List(ctx context.Context, opts *ListOptions) ([]*Attachment, error)\nfunc (s *AttachmentsService) Get(ctx context.Context, id int) (*Attachment, error)\nfunc (s *AttachmentsService) Create(ctx context.Context, req *AttachmentCreateRequest) (*Attachment, error)\nfunc (s *AttachmentsService) Update(ctx context.Context, id int, req *AttachmentUpdateRequest) (*Attachment, error)\nfunc (s *AttachmentsService) Delete(ctx context.Context, id int) error\n```\n\n## Technical Details\n- May need multipart/form-data for file uploads\n- Consider file size limits\n\n## Acceptance Criteria\n- [ ] Attachment type defined\n- [ ] AttachmentsService with all CRUD methods\n- [ ] File upload handling\n- [ ] Unit tests with mock server","status":"open","priority":2,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:54.242422591+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:54.242422591+01:00"} +{"id":"bookstack-api-7qx","title":"Implement Comments CRUD","description":"Implement CRUD operations for Comments.\n\n## Requirements\nFrom PRD feature table: Comments: CRUD (P3, v0.5)\n\n## API Endpoints (typical Bookstack pattern)\n- GET /api/comments - List comments\n- GET /api/comments/{id} - Get comment\n- POST /api/comments - Create comment\n- PUT /api/comments/{id} - Update comment\n- DELETE /api/comments/{id} - Delete comment\n\n## API Methods\n```go\ntype CommentsService struct {\n client *Client\n}\n\ntype Comment struct {\n ID int `json:\"id\"`\n PageID int `json:\"page_id\"`\n ParentID int `json:\"parent_id,omitempty\"`\n HTML string `json:\"html\"`\n CreatedAt time.Time `json:\"created_at\"`\n UpdatedAt time.Time `json:\"updated_at\"`\n CreatedBy int `json:\"created_by\"`\n}\n\nfunc (s *CommentsService) List(ctx context.Context, opts *ListOptions) ([]*Comment, error)\nfunc (s *CommentsService) Get(ctx context.Context, id int) (*Comment, error)\nfunc (s *CommentsService) Create(ctx context.Context, req *CommentCreateRequest) (*Comment, error)\nfunc (s *CommentsService) Update(ctx context.Context, id int, req *CommentUpdateRequest) (*Comment, error)\nfunc (s *CommentsService) Delete(ctx context.Context, id int) error\n```\n\n## Technical Details\n- Comments are attached to pages\n- Support nested comments (parent_id)\n\n## Acceptance Criteria\n- [ ] Comment type defined\n- [ ] CommentsService with all CRUD methods\n- [ ] Support for page filtering\n- [ ] Unit tests with mock server","status":"open","priority":3,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:40:23.920608941+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:40:23.920608941+01:00"} +{"id":"bookstack-api-8ea","title":"Implement HTTP helper and request building","description":"Create internal HTTP helper for making authenticated API requests.\n\n## Requirements\nImplement the internal `do` method on Client:\n\n```go\nfunc (c *Client) do(ctx context.Context, method, path string, body, result any) error {\n // 1. Build request\n // 2. Set Auth header: Authorization: Token \u003cid\u003e:\u003csecret\u003e\n // 3. Execute request\n // 4. Check response status\n // 5. On error: return APIError\n // 6. On success: unmarshal JSON into result\n}\n```\n\n## Technical Details\n- Support GET, POST, PUT, DELETE methods\n- JSON content type for request/response\n- Context support for cancellation\n- Proper URL joining (BaseURL + path)\n\n## Acceptance Criteria\n- [ ] do() method implemented\n- [ ] Auth header correctly formatted\n- [ ] JSON marshaling/unmarshaling works\n- [ ] Context cancellation supported\n- [ ] Unit tests with httptest.Server","status":"open","priority":0,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:06.247263859+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:06.247263859+01:00"} +{"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-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"} +{"id":"bookstack-api-dd0","title":"Implement ChaptersService (List, Get)","description":"Implement the ChaptersService with List and Get operations.\n\n## Requirements\nFrom PRD Section 5:\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| List | GET /api/chapters | All chapters |\n| Get | GET /api/chapters/{id} | Single chapter |\n\n## API Methods\n```go\ntype ChaptersService struct {\n client *Client\n}\n\nfunc (s *ChaptersService) List(ctx context.Context, opts *ListOptions) ([]*Chapter, error)\nfunc (s *ChaptersService) Get(ctx context.Context, id int) (*Chapter, error)\nfunc (s *ChaptersService) ListAll(ctx context.Context) iter.Seq2[*Chapter, error]\n```\n\n## Chapter Fields\n- ID, BookID, Name, Slug, Description, CreatedAt, UpdatedAt\n\n## Bookstack Hierarchy\nBook -\u003e Chapter -\u003e Page (Chapter is optional level)\n\n## Acceptance Criteria\n- [ ] ChaptersService struct created\n- [ ] List() returns paginated chapters\n- [ ] Get() returns single chapter by ID\n- [ ] ListAll() iterator implemented\n- [ ] Proper error handling\n- [ ] Unit tests with mock server","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:53.235414205+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:53.235414205+01:00"} +{"id":"bookstack-api-jt9","title":"Implement Pages Export (Markdown, PDF)","description":"Implement page export functionality for Markdown and PDF formats.\n\n## Requirements\nFrom PRD Section 5:\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| ExportMD | GET /api/pages/{id}/export/markdown | Markdown export |\n| ExportPDF | GET /api/pages/{id}/export/pdf | PDF export |\n\n## API Methods\n```go\nfunc (s *PagesService) ExportMarkdown(ctx context.Context, id int) ([]byte, error)\nfunc (s *PagesService) ExportPDF(ctx context.Context, id int) ([]byte, error)\n```\n\n## Technical Details\n- Markdown export returns plain text\n- PDF export returns binary data\n- Consider streaming for large PDFs (risk mitigation from PRD)\n- Return raw bytes, caller handles file writing\n\n## User Story\nUS4: Als Benutzer möchte ich eine Seite als Markdown oder PDF exportieren können.\n\n## Acceptance Criteria\n- [ ] ExportMarkdown() returns markdown content as bytes\n- [ ] ExportPDF() returns PDF content as bytes\n- [ ] Proper error handling (404 -\u003e ErrNotFound)\n- [ ] Large file handling considered\n- [ ] Unit tests with mock server","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:31.924404898+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:31.924404898+01:00"} +{"id":"bookstack-api-m6n","title":"Implement pagination iterator (ListAll)","description":"Implement Go 1.23+ iterator pattern for memory-efficient pagination.\n\n## Requirements\nFrom PRD Section 5 (Pagination Iterator-Pattern):\n\n```go\n// ListAll returns an iterator over all entries\n// Uses Go 1.23+ iter.Seq or custom implementation\nfunc (s *BooksService) ListAll(ctx context.Context) iter.Seq2[*Book, error]\n\n// Usage:\nfor book, err := range client.Books.ListAll(ctx) {\n if err != nil {\n return err\n }\n fmt.Println(book.Name)\n}\n```\n\n## Technical Details\n- Go 1.23+ iter.Seq2 for yield-based iteration\n- Memory-efficient: only one page in memory at a time\n- Supports early break\n- Handle 10,000+ entries without memory issues\n\n## Pagination Parameters\n- count: 100 default, max 500\n- offset: auto-incremented per page\n- total: from API response to know when done\n\n## Services to Implement\n- BooksService.ListAll()\n- PagesService.ListAll()\n- ChaptersService.ListAll() (v0.2)\n- ShelvesService.ListAll() (v0.2)\n- SearchService.SearchAll() (v0.2)\n\n## Acceptance Criteria\n- [ ] iter.Seq2 based iterator implementation\n- [ ] Automatic pagination through all results\n- [ ] Early termination works correctly\n- [ ] Error propagation through iterator\n- [ ] Unit tests for pagination logic","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:31.682196545+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:31.682196545+01:00"} +{"id":"bookstack-api-q8z","title":"Implement data types (Book, Page, Chapter, Shelf, SearchResult)","description":"Create all data structure types as defined in the PRD.\n\n## Requirements\nFrom PRD Section 4 (Datenstrukturen):\n\n```go\ntype Book struct {\n ID int `json:\"id\"`\n Name string `json:\"name\"`\n Slug string `json:\"slug\"`\n Description string `json:\"description\"`\n CreatedAt time.Time `json:\"created_at\"`\n UpdatedAt time.Time `json:\"updated_at\"`\n CreatedBy int `json:\"created_by\"`\n UpdatedBy int `json:\"updated_by\"`\n}\n\ntype Page struct {\n ID int `json:\"id\"`\n BookID int `json:\"book_id\"`\n ChapterID int `json:\"chapter_id\"`\n Name string `json:\"name\"`\n Slug string `json:\"slug\"`\n HTML string `json:\"html\"`\n RawHTML string `json:\"raw_html\"`\n Markdown string `json:\"markdown\"`\n CreatedAt time.Time `json:\"created_at\"`\n UpdatedAt time.Time `json:\"updated_at\"`\n}\n\ntype Chapter struct {\n ID int `json:\"id\"`\n BookID int `json:\"book_id\"`\n Name string `json:\"name\"`\n Slug string `json:\"slug\"`\n Description string `json:\"description\"`\n CreatedAt time.Time `json:\"created_at\"`\n UpdatedAt time.Time `json:\"updated_at\"`\n}\n\ntype Shelf struct {\n ID int `json:\"id\"`\n Name string `json:\"name\"`\n Slug string `json:\"slug\"`\n Description string `json:\"description\"`\n CreatedAt time.Time `json:\"created_at\"`\n UpdatedAt time.Time `json:\"updated_at\"`\n}\n\ntype SearchResult struct {\n ID int `json:\"id\"`\n Name string `json:\"name\"`\n Slug string `json:\"slug\"`\n Type string `json:\"type\"\\ // page, chapter, book, bookshelf\n URL string `json:\"url\"`\n Preview string `json:\"preview\"`\n}\n```\n\n## Acceptance Criteria\n- [ ] All types defined in types.go\n- [ ] JSON tags match Bookstack API format\n- [ ] time.Time fields parse correctly from API\n- [ ] GoDoc comments on all exported types","status":"open","priority":0,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:06.701609698+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:06.701609698+01:00"} +{"id":"bookstack-api-vl3","title":"Implement error types and APIError","description":"Create error handling infrastructure with sentinel errors and APIError type.\n\n## Requirements\nFrom PRD Section 9 (Error-Handling-Strategie):\n\n```go\nvar (\n ErrNotFound = errors.New(\"bookstack: resource not found\")\n ErrUnauthorized = errors.New(\"bookstack: unauthorized\")\n ErrForbidden = errors.New(\"bookstack: forbidden\")\n ErrRateLimited = errors.New(\"bookstack: rate limited\")\n ErrBadRequest = errors.New(\"bookstack: bad request\")\n)\n\ntype APIError struct {\n StatusCode int\n Code int `json:\"code\"`\n Message string `json:\"message\"`\n Body []byte\n}\n\nfunc (e *APIError) Error() string\nfunc (e *APIError) Is(target error) bool // Maps status codes to sentinel errors\n```\n\n## Status Code Mapping\n- 400 -\u003e ErrBadRequest\n- 401 -\u003e ErrUnauthorized\n- 403 -\u003e ErrForbidden\n- 404 -\u003e ErrNotFound\n- 429 -\u003e ErrRateLimited\n\n## Acceptance Criteria\n- [ ] All sentinel errors defined\n- [ ] APIError struct with Error() method\n- [ ] APIError.Is() correctly maps status codes\n- [ ] errors.Is() works with wrapped APIError\n- [ ] Unit tests for error matching","status":"open","priority":0,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-28T09:39:06.467984697+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-28T09:39:06.467984697+01:00"} diff --git a/.gitignore b/.gitignore index e69de29..b70e383 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,35 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin/ +dist/ + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool +*.out +coverage.txt +coverage.html + +# Go workspace file +go.work +go.work.sum + +# Dependency directories (vendor is optional) +vendor/ + +# IDE and editor directories +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Temporary files +*.tmp +tmp/ diff --git a/books.go b/books.go new file mode 100644 index 0000000..19c4e9d --- /dev/null +++ b/books.go @@ -0,0 +1,43 @@ +package bookstack + +import "context" + +// BooksService handles operations on books. +type BooksService struct { + client *Client +} + +// List returns a list of books with optional filtering. +// TODO: Implement API call to GET /api/books +func (s *BooksService) List(ctx context.Context, opts *ListOptions) ([]Book, error) { + // Placeholder for future implementation + return nil, nil +} + +// Get retrieves a single book by ID. +// TODO: Implement API call to GET /api/books/{id} +func (s *BooksService) Get(ctx context.Context, id int) (*Book, error) { + // Placeholder for future implementation + return nil, nil +} + +// Create creates a new book. +// TODO: Implement API call to POST /api/books +func (s *BooksService) Create(ctx context.Context, book *Book) (*Book, error) { + // Placeholder for future implementation + return nil, nil +} + +// Update updates an existing book. +// TODO: Implement API call to PUT /api/books/{id} +func (s *BooksService) Update(ctx context.Context, id int, book *Book) (*Book, error) { + // Placeholder for future implementation + return nil, nil +} + +// Delete deletes a book by ID. +// TODO: Implement API call to DELETE /api/books/{id} +func (s *BooksService) Delete(ctx context.Context, id int) error { + // Placeholder for future implementation + return nil +} diff --git a/bookstack.go b/bookstack.go new file mode 100644 index 0000000..42b762a --- /dev/null +++ b/bookstack.go @@ -0,0 +1,63 @@ +package bookstack + +import ( + "net/http" + "time" +) + +// Config holds configuration for the Bookstack API client. +type Config struct { + // BaseURL is the base URL of the Bookstack instance (e.g., "https://docs.example.com") + BaseURL string + + // TokenID is the Bookstack API token ID + TokenID string + + // TokenSecret is the Bookstack API token secret + TokenSecret string + + // HTTPClient is the HTTP client to use for requests. + // If nil, a default client with 30s timeout will be used. + HTTPClient *http.Client +} + +// Client is the main Bookstack API client. +type Client struct { + baseURL string + tokenID string + tokenSecret string + httpClient *http.Client + + // Service instances + Books *BooksService + Pages *PagesService + Chapters *ChaptersService + Shelves *ShelvesService + Search *SearchService +} + +// NewClient creates a new Bookstack API client. +func NewClient(cfg Config) *Client { + httpClient := cfg.HTTPClient + if httpClient == nil { + httpClient = &http.Client{ + Timeout: 30 * time.Second, + } + } + + c := &Client{ + baseURL: cfg.BaseURL, + tokenID: cfg.TokenID, + tokenSecret: cfg.TokenSecret, + httpClient: httpClient, + } + + // Initialize services + c.Books = &BooksService{client: c} + c.Pages = &PagesService{client: c} + c.Chapters = &ChaptersService{client: c} + c.Shelves = &ShelvesService{client: c} + c.Search = &SearchService{client: c} + + return c +} diff --git a/chapters.go b/chapters.go new file mode 100644 index 0000000..63c5548 --- /dev/null +++ b/chapters.go @@ -0,0 +1,43 @@ +package bookstack + +import "context" + +// ChaptersService handles operations on chapters. +type ChaptersService struct { + client *Client +} + +// List returns a list of chapters with optional filtering. +// TODO: Implement API call to GET /api/chapters +func (s *ChaptersService) List(ctx context.Context, opts *ListOptions) ([]Chapter, error) { + // Placeholder for future implementation + return nil, nil +} + +// Get retrieves a single chapter by ID. +// TODO: Implement API call to GET /api/chapters/{id} +func (s *ChaptersService) Get(ctx context.Context, id int) (*Chapter, error) { + // Placeholder for future implementation + return nil, nil +} + +// Create creates a new chapter. +// TODO: Implement API call to POST /api/chapters +func (s *ChaptersService) Create(ctx context.Context, chapter *Chapter) (*Chapter, error) { + // Placeholder for future implementation + return nil, nil +} + +// Update updates an existing chapter. +// TODO: Implement API call to PUT /api/chapters/{id} +func (s *ChaptersService) Update(ctx context.Context, id int, chapter *Chapter) (*Chapter, error) { + // Placeholder for future implementation + return nil, nil +} + +// Delete deletes a chapter by ID. +// TODO: Implement API call to DELETE /api/chapters/{id} +func (s *ChaptersService) Delete(ctx context.Context, id int) error { + // Placeholder for future implementation + return nil +} diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..8fab6ae --- /dev/null +++ b/errors.go @@ -0,0 +1,49 @@ +package bookstack + +import ( + "errors" + "fmt" +) + +// Sentinel errors for common API error conditions. +var ( + ErrNotFound = errors.New("resource not found") + ErrUnauthorized = errors.New("unauthorized") + ErrForbidden = errors.New("forbidden") + ErrRateLimited = errors.New("rate limited") + ErrBadRequest = errors.New("bad request") +) + +// APIError represents an error returned by the Bookstack API. +type APIError struct { + StatusCode int // HTTP status code + Code string // Error code from API response + Message string // Error message from API response + Body string // Raw response body +} + +// Error implements the error interface. +func (e *APIError) Error() string { + if e.Code != "" { + return fmt.Sprintf("bookstack API error (status %d, code %s): %s", e.StatusCode, e.Code, e.Message) + } + return fmt.Sprintf("bookstack API error (status %d): %s", e.StatusCode, e.Message) +} + +// Is implements error matching for sentinel errors. +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 + default: + return false + } +} diff --git a/http.go b/http.go new file mode 100644 index 0000000..542f95a --- /dev/null +++ b/http.go @@ -0,0 +1,28 @@ +package bookstack + +import ( + "context" + "net/http" +) + +// buildRequest creates an HTTP request with proper authentication headers. +// TODO: Implement request building with Authorization header (Token :) +func (c *Client) buildRequest(ctx context.Context, method, path string, body interface{}) (*http.Request, error) { + // Placeholder for future implementation + return nil, nil +} + +// doRequest executes an HTTP request and handles the response. +// TODO: Implement response handling, error parsing, and JSON unmarshaling +func (c *Client) doRequest(ctx context.Context, req *http.Request, v interface{}) error { + // Placeholder for future implementation + return nil +} + +// ListOptions contains common options for list operations. +type ListOptions struct { + Count int // Max items per page (default 100, max 500) + Offset int // Offset for pagination + Sort string // Sort field (e.g., "name", "-created_at") + Filter map[string]string // Filters (e.g., {"name": "value"}) +} diff --git a/pages.go b/pages.go new file mode 100644 index 0000000..d7cd086 --- /dev/null +++ b/pages.go @@ -0,0 +1,43 @@ +package bookstack + +import "context" + +// PagesService handles operations on pages. +type PagesService struct { + client *Client +} + +// List returns a list of pages with optional filtering. +// TODO: Implement API call to GET /api/pages +func (s *PagesService) List(ctx context.Context, opts *ListOptions) ([]Page, error) { + // Placeholder for future implementation + return nil, nil +} + +// Get retrieves a single page by ID. +// TODO: Implement API call to GET /api/pages/{id} +func (s *PagesService) Get(ctx context.Context, id int) (*Page, error) { + // Placeholder for future implementation + return nil, nil +} + +// Create creates a new page. +// TODO: Implement API call to POST /api/pages +func (s *PagesService) Create(ctx context.Context, page *Page) (*Page, error) { + // Placeholder for future implementation + return nil, nil +} + +// Update updates an existing page. +// TODO: Implement API call to PUT /api/pages/{id} +func (s *PagesService) Update(ctx context.Context, id int, page *Page) (*Page, error) { + // Placeholder for future implementation + return nil, nil +} + +// Delete deletes a page by ID. +// TODO: Implement API call to DELETE /api/pages/{id} +func (s *PagesService) Delete(ctx context.Context, id int) error { + // Placeholder for future implementation + return nil +} diff --git a/search.go b/search.go new file mode 100644 index 0000000..1f75605 --- /dev/null +++ b/search.go @@ -0,0 +1,15 @@ +package bookstack + +import "context" + +// SearchService handles search operations. +type SearchService struct { + client *Client +} + +// Search performs a search query across all content types. +// TODO: Implement API call to GET /api/search +func (s *SearchService) Search(ctx context.Context, query string, opts *ListOptions) ([]SearchResult, error) { + // Placeholder for future implementation + return nil, nil +} diff --git a/shelves.go b/shelves.go new file mode 100644 index 0000000..09083eb --- /dev/null +++ b/shelves.go @@ -0,0 +1,43 @@ +package bookstack + +import "context" + +// ShelvesService handles operations on shelves. +type ShelvesService struct { + client *Client +} + +// List returns a list of shelves with optional filtering. +// TODO: Implement API call to GET /api/shelves +func (s *ShelvesService) List(ctx context.Context, opts *ListOptions) ([]Shelf, error) { + // Placeholder for future implementation + return nil, nil +} + +// Get retrieves a single shelf by ID. +// TODO: Implement API call to GET /api/shelves/{id} +func (s *ShelvesService) Get(ctx context.Context, id int) (*Shelf, error) { + // Placeholder for future implementation + return nil, nil +} + +// Create creates a new shelf. +// TODO: Implement API call to POST /api/shelves +func (s *ShelvesService) Create(ctx context.Context, shelf *Shelf) (*Shelf, error) { + // Placeholder for future implementation + return nil, nil +} + +// Update updates an existing shelf. +// TODO: Implement API call to PUT /api/shelves/{id} +func (s *ShelvesService) Update(ctx context.Context, id int, shelf *Shelf) (*Shelf, error) { + // Placeholder for future implementation + return nil, nil +} + +// Delete deletes a shelf by ID. +// TODO: Implement API call to DELETE /api/shelves/{id} +func (s *ShelvesService) Delete(ctx context.Context, id int) error { + // Placeholder for future implementation + return nil +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..086e20a --- /dev/null +++ b/types.go @@ -0,0 +1,76 @@ +package bookstack + +import "time" + +// Book represents a Bookstack book. +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"` + OwnedBy int `json:"owned_by"` +} + +// Page represents a Bookstack page. +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"` + Markdown string `json:"markdown"` + Priority int `json:"priority"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + CreatedBy int `json:"created_by"` + UpdatedBy int `json:"updated_by"` + Draft bool `json:"draft"` + Revision int `json:"revision_count"` + Template bool `json:"template"` + OwnedBy int `json:"owned_by"` +} + +// Chapter represents a Bookstack chapter. +type Chapter struct { + ID int `json:"id"` + BookID int `json:"book_id"` + Name string `json:"name"` + Slug string `json:"slug"` + Description string `json:"description"` + Priority int `json:"priority"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + CreatedBy int `json:"created_by"` + UpdatedBy int `json:"updated_by"` + OwnedBy int `json:"owned_by"` +} + +// Shelf represents a Bookstack shelf. +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"` + CreatedBy int `json:"created_by"` + UpdatedBy int `json:"updated_by"` + OwnedBy int `json:"owned_by"` +} + +// SearchResult represents a search result from Bookstack. +type SearchResult struct { + Type string `json:"type"` // "page", "chapter", "book", or "shelf" + ID int `json:"id"` + Name string `json:"name"` + Slug string `json:"slug"` + BookID int `json:"book_id"` // For pages and chapters + ChapterID int `json:"chapter_id"` // For pages + Preview string `json:"preview"` + Score float64 `json:"score"` +}