1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package lunatask
6
7import (
8 "context"
9 "fmt"
10 "net/http"
11 "net/url"
12 "time"
13)
14
15// Note represents a note returned from the Lunatask API.
16// Note: name and content are E2EE and not returned by the API.
17type Note struct {
18 ID string `json:"id"`
19 NotebookID string `json:"notebook_id"`
20 DateOn *Date `json:"date_on"`
21 Sources []Source `json:"sources"`
22 CreatedAt time.Time `json:"created_at"`
23 UpdatedAt time.Time `json:"updated_at"`
24}
25
26// CreateNoteRequest represents the request to create a note in Lunatask.
27type CreateNoteRequest struct {
28 Name *string `json:"name,omitempty"`
29 Content *string `json:"content,omitempty"`
30 NotebookID string `json:"notebook_id"`
31 Source *string `json:"source,omitempty"`
32 SourceID *string `json:"source_id,omitempty"`
33}
34
35// UpdateNoteRequest represents the request to update a note in Lunatask.
36// All fields are optional; only provided fields will be updated.
37// Note: updating content replaces the entire content (E2EE prevents appending).
38type UpdateNoteRequest struct {
39 Name *string `json:"name,omitempty"`
40 Content *string `json:"content,omitempty"`
41 NotebookID *string `json:"notebook_id,omitempty"`
42 DateOn *Date `json:"date_on,omitempty"`
43}
44
45// noteResponse represents a single note response from the API.
46type noteResponse struct {
47 Note Note `json:"note"`
48}
49
50// notesResponse represents a list of notes response from the API.
51type notesResponse struct {
52 Notes []Note `json:"notes"`
53}
54
55// ListNotesOptions contains optional filters for listing notes.
56type ListNotesOptions struct {
57 Source *string
58 SourceID *string
59}
60
61// ListNotes retrieves all notes, optionally filtered by source and/or source_id.
62func (c *Client) ListNotes(ctx context.Context, opts *ListNotesOptions) ([]Note, error) {
63 path := "/notes"
64
65 if opts != nil {
66 params := url.Values{}
67 if opts.Source != nil && *opts.Source != "" {
68 params.Set("source", *opts.Source)
69 }
70 if opts.SourceID != nil && *opts.SourceID != "" {
71 params.Set("source_id", *opts.SourceID)
72 }
73 if len(params) > 0 {
74 path = fmt.Sprintf("%s?%s", path, params.Encode())
75 }
76 }
77
78 resp, _, err := doJSON[notesResponse](c, ctx, http.MethodGet, path, nil)
79 if err != nil {
80 return nil, err
81 }
82
83 return resp.Notes, nil
84}
85
86// CreateNote creates a new note in Lunatask.
87// Returns nil, nil if a matching note already exists in the same notebook
88// with the same source/source_id (HTTP 204).
89func (c *Client) CreateNote(ctx context.Context, note *CreateNoteRequest) (*Note, error) {
90 if note.NotebookID == "" {
91 return nil, fmt.Errorf("%w: notebook_id is required", ErrBadRequest)
92 }
93
94 resp, noContent, err := doJSON[noteResponse](c, ctx, http.MethodPost, "/notes", note)
95 if err != nil {
96 return nil, err
97 }
98 if noContent {
99 return nil, nil
100 }
101
102 return &resp.Note, nil
103}
104
105// UpdateNote updates an existing note in Lunatask.
106func (c *Client) UpdateNote(ctx context.Context, noteID string, note *UpdateNoteRequest) (*Note, error) {
107 if noteID == "" {
108 return nil, fmt.Errorf("%w: note ID cannot be empty", ErrBadRequest)
109 }
110
111 resp, _, err := doJSON[noteResponse](c, ctx, http.MethodPut, "/notes/"+noteID, note)
112 if err != nil {
113 return nil, err
114 }
115
116 return &resp.Note, nil
117}
118
119// DeleteNote deletes a note in Lunatask.
120func (c *Client) DeleteNote(ctx context.Context, noteID string) (*Note, error) {
121 if noteID == "" {
122 return nil, fmt.Errorf("%w: note ID cannot be empty", ErrBadRequest)
123 }
124
125 resp, _, err := doJSON[noteResponse](c, ctx, http.MethodDelete, "/notes/"+noteID, nil)
126 if err != nil {
127 return nil, err
128 }
129
130 return &resp.Note, nil
131}