diff --git a/.beads/export-state/6feeba40f0df6736.json b/.beads/export-state/6feeba40f0df6736.json index fdf3428..4676556 100644 --- a/.beads/export-state/6feeba40f0df6736.json +++ b/.beads/export-state/6feeba40f0df6736.json @@ -1,6 +1,6 @@ { "worktree_root": "/home/oli/Dev/bookstack-api", - "last_export_commit": "970699afe28e58200abdef9c3e8334dec919e640", - "last_export_time": "2026-01-30T09:54:09.809167697+01:00", - "jsonl_hash": "bfab1aefaece07cacb3867cadceb934c1d7e035ea5ff652f6ac66f7143c4d75d" + "last_export_commit": "04cf1565fa39daadeef5cbc4e54025a56ef1437f", + "last_export_time": "2026-01-30T09:55:03.022677489+01:00", + "jsonl_hash": "6b113c2659e59a21f3ad9cad6b08ef10b2dad8322ad787795135a5ecc0676d42" } \ No newline at end of file diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index d71b911..476a962 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -2,8 +2,8 @@ {"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":"closed","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-30T09:50:44.683680188+01:00","closed_at":"2026-01-30T09:50:44.683680188+01:00","close_reason":"Closed"} {"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":"closed","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-30T09:53:07.163294684+01:00","closed_at":"2026-01-30T09:53:07.163294684+01:00","close_reason":"Closed"} -{"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":"in_progress","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-30T09:54:14.75565358+01:00","comments":[{"id":5,"issue_id":"bookstack-api-5gi","author":"Oliver Jakoubek","text":"Plan: (1) Add Attachment type to types.go, (2) Create attachments.go with AttachmentsService, (3) For Create - Bookstack supports both file upload (multipart) and link attachments (JSON). Will implement link attachments via JSON for now since file upload requires multipart which is more complex. (4) List/Get/Update/Delete follow standard pattern. (5) Register service in bookstack.go Client struct.","created_at":"2026-01-30T08:54:20Z"}]} -{"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-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":"closed","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-30T09:55:03.114941395+01:00","closed_at":"2026-01-30T09:55:03.114941395+01:00","close_reason":"Closed","comments":[{"id":5,"issue_id":"bookstack-api-5gi","author":"Oliver Jakoubek","text":"Plan: (1) Add Attachment type to types.go, (2) Create attachments.go with AttachmentsService, (3) For Create - Bookstack supports both file upload (multipart) and link attachments (JSON). Will implement link attachments via JSON for now since file upload requires multipart which is more complex. (4) List/Get/Update/Delete follow standard pattern. (5) Register service in bookstack.go Client struct.","created_at":"2026-01-30T08:54:20Z"}]} +{"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":"in_progress","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-30T09:55:07.404913498+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":"closed","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-30T09:48:02.200483913+01:00","closed_at":"2026-01-30T09:48:02.200483913+01:00","close_reason":"Closed","comments":[{"id":1,"issue_id":"bookstack-api-8ea","author":"Oliver Jakoubek","text":"Plan: Implement http.go with do() method combining buildRequest+doRequest. Will implement: (1) do() as single entry point, (2) buildRequest with auth header + JSON body, (3) response handling with APIError parsing. Tests with httptest.Server. Keep existing ListOptions.","created_at":"2026-01-30T08:47:25Z"}]} {"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":"closed","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-30T09:45:31.33966902+01:00","closed_at":"2026-01-30T09:45:31.33966902+01:00","close_reason":"Closed"} {"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":"closed","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-30T09:54:09.877149269+01:00","closed_at":"2026-01-30T09:54:09.877149269+01:00","close_reason":"Closed"} diff --git a/bookstack.go b/bookstack.go index 30f5929..b7bb1b4 100644 --- a/bookstack.go +++ b/bookstack.go @@ -34,6 +34,7 @@ type Client struct { Attachments *AttachmentsService Books *BooksService Chapters *ChaptersService + Comments *CommentsService Pages *PagesService Search *SearchService Shelves *ShelvesService @@ -74,6 +75,7 @@ func NewClient(cfg Config) (*Client, error) { c.Attachments = &AttachmentsService{client: c} c.Books = &BooksService{client: c} c.Chapters = &ChaptersService{client: c} + c.Comments = &CommentsService{client: c} c.Pages = &PagesService{client: c} c.Search = &SearchService{client: c} c.Shelves = &ShelvesService{client: c} diff --git a/comments.go b/comments.go new file mode 100644 index 0000000..1bf0b6d --- /dev/null +++ b/comments.go @@ -0,0 +1,56 @@ +package bookstack + +import ( + "context" + "fmt" +) + +// CommentsService handles operations on comments. +type CommentsService struct { + client *Client +} + +// List returns a list of comments with optional filtering. +func (s *CommentsService) List(ctx context.Context, opts *ListOptions) ([]Comment, error) { + var resp listResponse[Comment] + err := s.client.do(ctx, "GET", "/api/comments"+opts.queryString(), nil, &resp) + if err != nil { + return nil, err + } + return resp.Data, nil +} + +// Get retrieves a single comment by ID. +func (s *CommentsService) Get(ctx context.Context, id int) (*Comment, error) { + var c Comment + err := s.client.do(ctx, "GET", fmt.Sprintf("/api/comments/%d", id), nil, &c) + if err != nil { + return nil, err + } + return &c, nil +} + +// Create creates a new comment on a page. +func (s *CommentsService) Create(ctx context.Context, req *CommentCreateRequest) (*Comment, error) { + var c Comment + err := s.client.do(ctx, "POST", "/api/comments", req, &c) + if err != nil { + return nil, err + } + return &c, nil +} + +// Update updates an existing comment. +func (s *CommentsService) Update(ctx context.Context, id int, req *CommentUpdateRequest) (*Comment, error) { + var c Comment + err := s.client.do(ctx, "PUT", fmt.Sprintf("/api/comments/%d", id), req, &c) + if err != nil { + return nil, err + } + return &c, nil +} + +// Delete deletes a comment by ID. +func (s *CommentsService) Delete(ctx context.Context, id int) error { + return s.client.do(ctx, "DELETE", fmt.Sprintf("/api/comments/%d", id), nil, nil) +} diff --git a/comments_test.go b/comments_test.go new file mode 100644 index 0000000..79e9c46 --- /dev/null +++ b/comments_test.go @@ -0,0 +1,114 @@ +package bookstack + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "testing" +) + +func TestCommentsService_List(t *testing.T) { + c := testClient(t, func(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode(map[string]any{ + "data": []map[string]any{{"id": 1, "page_id": 5, "html": "
Nice
"}}, + "total": 1, + }) + }) + + comments, err := c.Comments.List(context.Background(), nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(comments) != 1 { + t.Fatalf("got %d, want 1", len(comments)) + } +} + +func TestCommentsService_Get(t *testing.T) { + c := testClient(t, func(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode(map[string]any{ + "id": 1, "page_id": 5, "html": "Comment
", + }) + }) + + comment, err := c.Comments.Get(context.Background(), 1) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if comment.HTML != "Comment
" { + t.Errorf("HTML = %q", comment.HTML) + } +} + +func TestCommentsService_Create(t *testing.T) { + c := testClient(t, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + t.Errorf("method = %s, want POST", r.Method) + } + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]any{ + "id": 2, "page_id": 5, "html": "New
", + }) + }) + + comment, err := c.Comments.Create(context.Background(), &CommentCreateRequest{ + PageID: 5, + HTML: "New
", + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if comment.ID != 2 { + t.Errorf("ID = %d, want 2", comment.ID) + } +} + +func TestCommentsService_Update(t *testing.T) { + c := testClient(t, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "PUT" { + t.Errorf("method = %s, want PUT", r.Method) + } + json.NewEncoder(w).Encode(map[string]any{ + "id": 1, "html": "Updated
", + }) + }) + + comment, err := c.Comments.Update(context.Background(), 1, &CommentUpdateRequest{ + HTML: "Updated
", + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if comment.HTML != "Updated
" { + t.Errorf("HTML = %q", comment.HTML) + } +} + +func TestCommentsService_Delete(t *testing.T) { + c := testClient(t, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "DELETE" { + t.Errorf("method = %s, want DELETE", r.Method) + } + w.WriteHeader(http.StatusNoContent) + }) + + err := c.Comments.Delete(context.Background(), 1) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestCommentsService_Get_NotFound(t *testing.T) { + c := testClient(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(map[string]any{ + "error": map[string]string{"message": "Not found"}, + }) + }) + + _, err := c.Comments.Get(context.Background(), 999) + if !errors.Is(err, ErrNotFound) { + t.Errorf("expected ErrNotFound, got %v", err) + } +} diff --git a/types.go b/types.go index ffe96c8..b63890c 100644 --- a/types.go +++ b/types.go @@ -107,6 +107,30 @@ type AttachmentUpdateRequest struct { Link string `json:"link,omitempty"` } +// Comment represents a Bookstack comment on a page. +type Comment struct { + ID int `json:"id"` + PageID int `json:"page_id"` + ParentID int `json:"parent_id,omitempty"` + HTML string `json:"html"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + CreatedBy int `json:"created_by"` + UpdatedBy int `json:"updated_by"` +} + +// CommentCreateRequest contains fields for creating a comment. +type CommentCreateRequest struct { + PageID int `json:"page_id"` + ParentID int `json:"parent_id,omitempty"` + HTML string `json:"html"` +} + +// CommentUpdateRequest contains fields for updating a comment. +type CommentUpdateRequest struct { + HTML string `json:"html"` +} + // SearchResult represents a search result from Bookstack. type SearchResult struct { Type string `json:"type"` // "page", "chapter", "book", or "shelf"