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 "time"
10)
11
12// Note is a note in Lunatask. Name and Content are encrypted client-side
13// and will be null when read back from the API.
14type Note struct {
15 ID string `json:"id"`
16 NotebookID *string `json:"notebook_id"`
17 DateOn *Date `json:"date_on"`
18 Pinned bool `json:"pinned"`
19 Sources []Source `json:"sources"`
20 CreatedAt time.Time `json:"created_at"`
21 UpdatedAt time.Time `json:"updated_at"`
22}
23
24// createNoteRequest defines a new note for JSON serialization.
25type createNoteRequest struct {
26 Name *string `json:"name,omitempty"`
27 Content *string `json:"content,omitempty"`
28 NotebookID *string `json:"notebook_id,omitempty"`
29 Source *string `json:"source,omitempty"`
30 SourceID *string `json:"source_id,omitempty"`
31}
32
33// updateNoteRequest specifies which fields to change on a note.
34type updateNoteRequest struct {
35 Name *string `json:"name,omitempty"`
36 Content *string `json:"content,omitempty"`
37 NotebookID *string `json:"notebook_id,omitempty"`
38 DateOn *Date `json:"date_on,omitempty"`
39}
40
41// noteResponse wraps a single note from the API.
42type noteResponse struct {
43 Note Note `json:"note"`
44}
45
46// notesResponse wraps a list of notes from the API.
47type notesResponse struct {
48 Notes []Note `json:"notes"`
49}
50
51// ListNotesOptions filters notes by source integration.
52type ListNotesOptions struct {
53 Source *string
54 SourceID *string
55}
56
57// GetSource implements [SourceFilter].
58func (o *ListNotesOptions) GetSource() *string { return o.Source }
59
60// GetSourceID implements [SourceFilter].
61func (o *ListNotesOptions) GetSourceID() *string { return o.SourceID }
62
63// ListNotes returns all notes, optionally filtered. Pass nil for all notes.
64func (c *Client) ListNotes(ctx context.Context, opts *ListNotesOptions) ([]Note, error) {
65 var filter SourceFilter
66 if opts != nil {
67 filter = opts
68 }
69
70 return list(ctx, c, "/notes", filter, func(r notesResponse) []Note { return r.Notes })
71}
72
73// GetNote fetches a note by ID. Name and Content will be null (E2EE).
74func (c *Client) GetNote(ctx context.Context, noteID string) (*Note, error) {
75 return get(ctx, c, "/notes", noteID, "note", func(r noteResponse) Note { return r.Note })
76}
77
78// DeleteNote removes a note and returns its final state.
79func (c *Client) DeleteNote(ctx context.Context, noteID string) (*Note, error) {
80 return del(ctx, c, "/notes", noteID, "note", func(r noteResponse) Note { return r.Note })
81}
82
83// NoteBuilder constructs and creates a note via method chaining.
84// Note fields are encrypted client-side by Lunatask; the API accepts them
85// on create but returns null on read.
86//
87// note, err := client.NewNote().
88// WithName("Meeting notes").
89// WithContent("# Summary\n\n...").
90// InNotebook(notebookID).
91// Create(ctx)
92type NoteBuilder struct {
93 client *Client
94 req createNoteRequest
95}
96
97// NewNote starts building a note.
98func (c *Client) NewNote() *NoteBuilder {
99 return &NoteBuilder{client: c}
100}
101
102// WithName sets the note's title.
103func (b *NoteBuilder) WithName(name string) *NoteBuilder {
104 b.req.Name = &name
105
106 return b
107}
108
109// WithContent sets the Markdown body.
110func (b *NoteBuilder) WithContent(content string) *NoteBuilder {
111 b.req.Content = &content
112
113 return b
114}
115
116// InNotebook places the note in a notebook. IDs are in the notebook's settings in the app.
117func (b *NoteBuilder) InNotebook(notebookID string) *NoteBuilder {
118 b.req.NotebookID = ¬ebookID
119
120 return b
121}
122
123// FromSource tags the note with a free-form origin identifier, useful for
124// tracking notes created by scripts or external integrations.
125func (b *NoteBuilder) FromSource(source, sourceID string) *NoteBuilder {
126 b.req.Source = &source
127 b.req.SourceID = &sourceID
128
129 return b
130}
131
132// Create sends the note to Lunatask. Returns (nil, nil) if a duplicate exists
133// in the same notebook with matching source/source_id.
134func (b *NoteBuilder) Create(ctx context.Context) (*Note, error) {
135 return create(ctx, b.client, "/notes", b.req, func(r noteResponse) Note { return r.Note })
136}
137
138// NoteUpdateBuilder constructs and updates a note via method chaining.
139// Only fields you set will be modified; others remain unchanged.
140//
141// note, err := client.NewNoteUpdate(noteID).
142// WithContent("# Updated content").
143// Update(ctx)
144type NoteUpdateBuilder struct {
145 client *Client
146 noteID string
147 req updateNoteRequest
148}
149
150// NewNoteUpdate starts building a note update for the given note ID.
151func (c *Client) NewNoteUpdate(noteID string) *NoteUpdateBuilder {
152 return &NoteUpdateBuilder{client: c, noteID: noteID}
153}
154
155// WithName sets the note's title.
156func (b *NoteUpdateBuilder) WithName(name string) *NoteUpdateBuilder {
157 b.req.Name = &name
158
159 return b
160}
161
162// WithContent sets the Markdown body.
163func (b *NoteUpdateBuilder) WithContent(content string) *NoteUpdateBuilder {
164 b.req.Content = &content
165
166 return b
167}
168
169// InNotebook moves the note to a notebook.
170func (b *NoteUpdateBuilder) InNotebook(notebookID string) *NoteUpdateBuilder {
171 b.req.NotebookID = ¬ebookID
172
173 return b
174}
175
176// OnDate sets the date for the note.
177func (b *NoteUpdateBuilder) OnDate(date Date) *NoteUpdateBuilder {
178 b.req.DateOn = &date
179
180 return b
181}
182
183// Update sends the changes to Lunatask.
184func (b *NoteUpdateBuilder) Update(ctx context.Context) (*Note, error) {
185 return update(ctx, b.client, "/notes", b.noteID, "note", b.req, func(r noteResponse) Note { return r.Note })
186}