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,omitempty"`
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// GetNote retrieves a specific note by ID.
87// Note: name and content fields are E2EE and will be null in the response.
88func (c *Client) GetNote(ctx context.Context, noteID string) (*Note, error) {
89 if noteID == "" {
90 return nil, fmt.Errorf("%w: note ID cannot be empty", ErrBadRequest)
91 }
92
93 resp, _, err := doJSON[noteResponse](c, ctx, http.MethodGet, "/notes/"+noteID, nil)
94 if err != nil {
95 return nil, err
96 }
97
98 return &resp.Note, nil
99}
100
101// CreateNote creates a new note in Lunatask.
102// Returns nil, nil if a matching note already exists in the same notebook
103// with the same source/source_id (HTTP 204).
104func (c *Client) CreateNote(ctx context.Context, note *CreateNoteRequest) (*Note, error) {
105 resp, noContent, err := doJSON[noteResponse](c, ctx, http.MethodPost, "/notes", note)
106 if err != nil {
107 return nil, err
108 }
109 if noContent {
110 return nil, nil
111 }
112
113 return &resp.Note, nil
114}
115
116// UpdateNote updates an existing note in Lunatask.
117func (c *Client) UpdateNote(ctx context.Context, noteID string, note *UpdateNoteRequest) (*Note, error) {
118 if noteID == "" {
119 return nil, fmt.Errorf("%w: note ID cannot be empty", ErrBadRequest)
120 }
121
122 resp, _, err := doJSON[noteResponse](c, ctx, http.MethodPut, "/notes/"+noteID, note)
123 if err != nil {
124 return nil, err
125 }
126
127 return &resp.Note, nil
128}
129
130// DeleteNote deletes a note in Lunatask.
131func (c *Client) DeleteNote(ctx context.Context, noteID string) (*Note, error) {
132 if noteID == "" {
133 return nil, fmt.Errorf("%w: note ID cannot be empty", ErrBadRequest)
134 }
135
136 resp, _, err := doJSON[noteResponse](c, ctx, http.MethodDelete, "/notes/"+noteID, nil)
137 if err != nil {
138 return nil, err
139 }
140
141 return &resp.Note, nil
142}