diff --git a/lunatask/notes.go b/lunatask/notes.go index 19a7f2af7683e79440e77af2861d19e270f54e59..66ad4ba44c75830a5947036f613a6eae2034674a 100644 --- a/lunatask/notes.go +++ b/lunatask/notes.go @@ -3,3 +3,129 @@ // SPDX-License-Identifier: AGPL-3.0-or-later package lunatask + +import ( + "context" + "fmt" + "net/http" + "net/url" + "time" +) + +// Note represents a note returned from the Lunatask API. +// Note: name and content are E2EE and not returned by the API. +type Note struct { + ID string `json:"id"` + NotebookID string `json:"notebook_id"` + DateOn *Date `json:"date_on"` + Sources []Source `json:"sources"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// CreateNoteRequest represents the request to create a note in Lunatask. +type CreateNoteRequest struct { + Name *string `json:"name,omitempty"` + Content *string `json:"content,omitempty"` + NotebookID string `json:"notebook_id"` + Source *string `json:"source,omitempty"` + SourceID *string `json:"source_id,omitempty"` +} + +// UpdateNoteRequest represents the request to update a note in Lunatask. +// All fields are optional; only provided fields will be updated. +// Note: updating content replaces the entire content (E2EE prevents appending). +type UpdateNoteRequest struct { + Name *string `json:"name,omitempty"` + Content *string `json:"content,omitempty"` + NotebookID *string `json:"notebook_id,omitempty"` + DateOn *Date `json:"date_on,omitempty"` +} + +// noteResponse represents a single note response from the API. +type noteResponse struct { + Note Note `json:"note"` +} + +// notesResponse represents a list of notes response from the API. +type notesResponse struct { + Notes []Note `json:"notes"` +} + +// ListNotesOptions contains optional filters for listing notes. +type ListNotesOptions struct { + Source *string + SourceID *string +} + +// ListNotes retrieves all notes, optionally filtered by source and/or source_id. +func (c *Client) ListNotes(ctx context.Context, opts *ListNotesOptions) ([]Note, error) { + path := "/notes" + + if opts != nil { + params := url.Values{} + if opts.Source != nil && *opts.Source != "" { + params.Set("source", *opts.Source) + } + if opts.SourceID != nil && *opts.SourceID != "" { + params.Set("source_id", *opts.SourceID) + } + if len(params) > 0 { + path = fmt.Sprintf("%s?%s", path, params.Encode()) + } + } + + resp, _, err := doJSON[notesResponse](c, ctx, http.MethodGet, path, nil) + if err != nil { + return nil, err + } + + return resp.Notes, nil +} + +// CreateNote creates a new note in Lunatask. +// Returns nil, nil if a matching note already exists in the same notebook +// with the same source/source_id (HTTP 204). +func (c *Client) CreateNote(ctx context.Context, note *CreateNoteRequest) (*Note, error) { + if note.NotebookID == "" { + return nil, fmt.Errorf("%w: notebook_id is required", ErrBadRequest) + } + + resp, noContent, err := doJSON[noteResponse](c, ctx, http.MethodPost, "/notes", note) + if err != nil { + return nil, err + } + if noContent { + return nil, nil + } + + return &resp.Note, nil +} + +// UpdateNote updates an existing note in Lunatask. +func (c *Client) UpdateNote(ctx context.Context, noteID string, note *UpdateNoteRequest) (*Note, error) { + if noteID == "" { + return nil, fmt.Errorf("%w: note ID cannot be empty", ErrBadRequest) + } + + resp, _, err := doJSON[noteResponse](c, ctx, http.MethodPut, "/notes/"+noteID, note) + if err != nil { + return nil, err + } + + return &resp.Note, nil +} + +// DeleteNote deletes a note in Lunatask. +func (c *Client) DeleteNote(ctx context.Context, noteID string) (*Note, error) { + if noteID == "" { + return nil, fmt.Errorf("%w: note ID cannot be empty", ErrBadRequest) + } + + resp, _, err := doJSON[noteResponse](c, ctx, http.MethodDelete, "/notes/"+noteID, nil) + if err != nil { + return nil, err + } + + return &resp.Note, nil +}