notes.go

  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 = &notebookID
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 = &notebookID
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}