Initial MVP implementation of Claudia Docs CLI
- Auth commands: login, logout, status - Document commands: create, list, get, update, delete - Project commands: list, get - Folder commands: list, create - Tag commands: list, create, add - Search command - Reasoning save command - JSON output format with --output text option - Viper-based configuration management - Cobra CLI framework
This commit is contained in:
216
internal/api/documents.go
Normal file
216
internal/api/documents.go
Normal file
@@ -0,0 +1,216 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user