package api import ( "encoding/json" "fmt" "io" "net/http" "time" "github.com/claudia/docs-cli/pkg/types" ) // Documents returns the documents client type DocumentsClient struct { client *Client } // NewDocumentsClient creates a new documents client func NewDocumentsClient(c *Client) *DocumentsClient { return &DocumentsClient{client: c} } // Create creates a new document func (d *DocumentsClient) Create(projectID string, req types.CreateDocumentRequest) (*types.Document, error) { resp, err := d.client.doRequest(http.MethodPost, "/projects/"+projectID+"/documents", req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { return nil, handleError(resp, body) } var docResp types.Response if err := decodeJSON(body, &docResp); err != nil { return nil, err } doc, ok := docResp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response format") } return mapToDocument(doc), nil } // List lists documents func (d *DocumentsClient) List(projectID, folderID string, limit, offset int) ([]types.Document, error) { path := "/projects/" + projectID + "/documents" query := fmt.Sprintf("?limit=%d&offset=%d", limit, offset) if folderID != "" { query += "&folder_id=" + folderID } resp, err := d.client.doRequest(http.MethodGet, path+query, nil) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { return nil, handleError(resp, body) } var docResp types.Response if err := decodeJSON(body, &docResp); err != nil { return nil, err } return sliceToDocuments(docResp.Data), nil } // Get gets a document by ID func (d *DocumentsClient) Get(id string, includeReasoning, includeTags bool) (*types.Document, error) { path := "/documents/" + id query := "" if includeReasoning || includeTags { query = "?" if includeReasoning { query += "include_reasoning=true" } if includeTags { if includeReasoning { query += "&" } query += "include_tags=true" } } resp, err := d.client.doRequest(http.MethodGet, path+query, nil) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { return nil, handleError(resp, body) } var docResp types.Response if err := decodeJSON(body, &docResp); err != nil { return nil, err } doc, ok := docResp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response format") } return mapToDocument(doc), nil } // Update updates a document func (d *DocumentsClient) Update(id string, req types.UpdateDocumentRequest) (*types.Document, error) { resp, err := d.client.doRequest(http.MethodPut, "/documents/"+id, req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { return nil, handleError(resp, body) } var docResp types.Response if err := decodeJSON(body, &docResp); err != nil { return nil, err } doc, ok := docResp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response format") } return mapToDocument(doc), nil } // Delete deletes a document func (d *DocumentsClient) Delete(id string) error { resp, err := d.client.doRequest(http.MethodDelete, "/documents/"+id, nil) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return handleError(resp, body) } return nil } // Helper functions func decodeJSON(data []byte, v interface{}) error { return json.Unmarshal(data, v) } func mapToDocument(m map[string]interface{}) *types.Document { doc := &types.Document{} if v, ok := m["id"].(string); ok { doc.ID = v } if v, ok := m["title"].(string); ok { doc.Title = v } if v, ok := m["content"].(string); ok { doc.Content = v } if v, ok := m["project_id"].(string); ok { doc.ProjectID = v } if v, ok := m["folder_id"].(string); ok { doc.FolderID = v } if v, ok := m["created_at"].(string); ok { doc.CreatedAt, _ = time.Parse(time.RFC3339, v) } if v, ok := m["updated_at"].(string); ok { doc.UpdatedAt, _ = time.Parse(time.RFC3339, v) } return doc } func sliceToDocuments(data interface{}) []types.Document { var docs []types.Document if arr, ok := data.([]interface{}); ok { for _, item := range arr { if m, ok := item.(map[string]interface{}); ok { docs = append(docs, *mapToDocument(m)) } } } return docs }