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