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	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 := lunatask.NewNote().
 88//		WithName("Meeting notes").
 89//		WithContent("# Summary\n\n...").
 90//		InNotebook(notebookID).
 91//		Create(ctx, client)
 92type NoteBuilder struct {
 93	req createNoteRequest
 94}
 95
 96// NewNote starts building a note.
 97func NewNote() *NoteBuilder {
 98	return &NoteBuilder{} //nolint:exhaustruct
 99}
100
101// WithName sets the note's title.
102func (b *NoteBuilder) WithName(name string) *NoteBuilder {
103	b.req.Name = &name
104
105	return b
106}
107
108// WithContent sets the Markdown body.
109func (b *NoteBuilder) WithContent(content string) *NoteBuilder {
110	b.req.Content = &content
111
112	return b
113}
114
115// InNotebook places the note in a notebook. IDs are in the notebook's settings in the app.
116func (b *NoteBuilder) InNotebook(notebookID string) *NoteBuilder {
117	b.req.NotebookID = &notebookID
118
119	return b
120}
121
122// FromSource tags the note with a free-form origin identifier, useful for
123// tracking notes created by scripts or external integrations.
124func (b *NoteBuilder) FromSource(source, sourceID string) *NoteBuilder {
125	b.req.Source = &source
126	b.req.SourceID = &sourceID
127
128	return b
129}
130
131// Create sends the note to Lunatask. Returns (nil, nil) if a duplicate exists
132// in the same notebook with matching source/source_id.
133func (b *NoteBuilder) Create(ctx context.Context, c *Client) (*Note, error) {
134	return create(ctx, c, "/notes", b.req, func(r noteResponse) Note { return r.Note })
135}
136
137// NoteUpdateBuilder constructs and updates a note via method chaining.
138// Only fields you set will be modified; others remain unchanged.
139//
140//	note, err := lunatask.NewNoteUpdate(noteID).
141//		WithContent("# Updated content").
142//		Update(ctx, client)
143type NoteUpdateBuilder struct {
144	noteID string
145	req    updateNoteRequest
146}
147
148// NewNoteUpdate starts building a note update for the given note ID.
149func NewNoteUpdate(noteID string) *NoteUpdateBuilder {
150	return &NoteUpdateBuilder{noteID: noteID} //nolint:exhaustruct
151}
152
153// WithName sets the note's title.
154func (b *NoteUpdateBuilder) WithName(name string) *NoteUpdateBuilder {
155	b.req.Name = &name
156
157	return b
158}
159
160// WithContent sets the Markdown body.
161func (b *NoteUpdateBuilder) WithContent(content string) *NoteUpdateBuilder {
162	b.req.Content = &content
163
164	return b
165}
166
167// InNotebook moves the note to a notebook.
168func (b *NoteUpdateBuilder) InNotebook(notebookID string) *NoteUpdateBuilder {
169	b.req.NotebookID = &notebookID
170
171	return b
172}
173
174// OnDate sets the date for the note.
175func (b *NoteUpdateBuilder) OnDate(date Date) *NoteUpdateBuilder {
176	b.req.DateOn = &date
177
178	return b
179}
180
181// Update sends the changes to Lunatask.
182func (b *NoteUpdateBuilder) Update(ctx context.Context, c *Client) (*Note, error) {
183	return update(ctx, c, "/notes", b.noteID, "note", b.req, func(r noteResponse) Note { return r.Note })
184}