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