1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package lunatask
6
7import "time"
8
9// TaskBuilder constructs a [CreateTaskRequest] via method chaining.
10//
11// req := lunatask.NewTask("Review PR").
12// InArea(areaID).
13// WithStatus("next").
14// WithEstimate(30).
15// Build()
16// task, err := client.CreateTask(ctx, req)
17type TaskBuilder struct {
18 req CreateTaskRequest
19}
20
21// NewTask starts building a task with the given name.
22func NewTask(name string) *TaskBuilder {
23 return &TaskBuilder{req: CreateTaskRequest{Name: name}} //nolint:exhaustruct
24}
25
26// InArea assigns the task to an area. IDs are in the area's settings in the app.
27func (b *TaskBuilder) InArea(areaID string) *TaskBuilder {
28 b.req.AreaID = &areaID
29
30 return b
31}
32
33// InGoal assigns the task to a goal. IDs are in the goal's settings in the app.
34func (b *TaskBuilder) InGoal(goalID string) *TaskBuilder {
35 b.req.GoalID = &goalID
36
37 return b
38}
39
40// WithNote attaches a Markdown note to the task.
41func (b *TaskBuilder) WithNote(note string) *TaskBuilder {
42 b.req.Note = ¬e
43
44 return b
45}
46
47// WithStatus sets the workflow status: later, next, started, waiting, or completed.
48func (b *TaskBuilder) WithStatus(status string) *TaskBuilder {
49 b.req.Status = &status
50
51 return b
52}
53
54// WithMotivation sets why this task matters: must, should, or want.
55func (b *TaskBuilder) WithMotivation(motivation string) *TaskBuilder {
56 b.req.Motivation = &motivation
57
58 return b
59}
60
61// WithEstimate sets the expected duration in minutes (0–720).
62func (b *TaskBuilder) WithEstimate(minutes int) *TaskBuilder {
63 b.req.Estimate = &minutes
64
65 return b
66}
67
68// WithPriority sets importance from -2 (lowest) to 2 (highest).
69func (b *TaskBuilder) WithPriority(priority int) *TaskBuilder {
70 b.req.Priority = &priority
71
72 return b
73}
74
75// WithEisenhower sets the matrix quadrant (0–4).
76func (b *TaskBuilder) WithEisenhower(eisenhower int) *TaskBuilder {
77 b.req.Eisenhower = &eisenhower
78
79 return b
80}
81
82// ScheduledOn sets when the task should appear on your schedule.
83func (b *TaskBuilder) ScheduledOn(date Date) *TaskBuilder {
84 b.req.ScheduledOn = &date
85
86 return b
87}
88
89// CompletedAt marks the task completed at a specific time.
90func (b *TaskBuilder) CompletedAt(t time.Time) *TaskBuilder {
91 b.req.CompletedAt = &t
92
93 return b
94}
95
96// FromSource tags the task with a free-form origin identifier, useful for
97// tracking tasks created by scripts or external integrations.
98func (b *TaskBuilder) FromSource(source, sourceID string) *TaskBuilder {
99 b.req.Source = &source
100 b.req.SourceID = &sourceID
101
102 return b
103}
104
105// Build returns the constructed request.
106func (b *TaskBuilder) Build() *CreateTaskRequest {
107 return &b.req
108}
109
110// TaskUpdateBuilder constructs an [UpdateTaskRequest] via method chaining.
111// Only fields you set will be modified; others remain unchanged.
112//
113// req := lunatask.NewTaskUpdate().
114// WithStatus("completed").
115// CompletedAt(time.Now()).
116// Build()
117// task, err := client.UpdateTask(ctx, taskID, req)
118type TaskUpdateBuilder struct {
119 req UpdateTaskRequest
120}
121
122// NewTaskUpdate starts building a task update.
123func NewTaskUpdate() *TaskUpdateBuilder {
124 return &TaskUpdateBuilder{} //nolint:exhaustruct
125}
126
127// Name changes the task's name.
128func (b *TaskUpdateBuilder) Name(name string) *TaskUpdateBuilder {
129 b.req.Name = &name
130
131 return b
132}
133
134// InArea moves the task to an area. IDs are in the area's settings in the app.
135func (b *TaskUpdateBuilder) InArea(areaID string) *TaskUpdateBuilder {
136 b.req.AreaID = &areaID
137
138 return b
139}
140
141// InGoal moves the task to a goal. IDs are in the goal's settings in the app.
142func (b *TaskUpdateBuilder) InGoal(goalID string) *TaskUpdateBuilder {
143 b.req.GoalID = &goalID
144
145 return b
146}
147
148// WithNote replaces the task's Markdown note.
149func (b *TaskUpdateBuilder) WithNote(note string) *TaskUpdateBuilder {
150 b.req.Note = ¬e
151
152 return b
153}
154
155// WithStatus sets the workflow status: later, next, started, waiting, or completed.
156func (b *TaskUpdateBuilder) WithStatus(status string) *TaskUpdateBuilder {
157 b.req.Status = &status
158
159 return b
160}
161
162// WithMotivation sets why this task matters: must, should, or want.
163func (b *TaskUpdateBuilder) WithMotivation(motivation string) *TaskUpdateBuilder {
164 b.req.Motivation = &motivation
165
166 return b
167}
168
169// WithEstimate sets the expected duration in minutes (0–720).
170func (b *TaskUpdateBuilder) WithEstimate(minutes int) *TaskUpdateBuilder {
171 b.req.Estimate = &minutes
172
173 return b
174}
175
176// WithPriority sets importance from -2 (lowest) to 2 (highest).
177func (b *TaskUpdateBuilder) WithPriority(priority int) *TaskUpdateBuilder {
178 b.req.Priority = &priority
179
180 return b
181}
182
183// WithEisenhower sets the matrix quadrant (0–4).
184func (b *TaskUpdateBuilder) WithEisenhower(eisenhower int) *TaskUpdateBuilder {
185 b.req.Eisenhower = &eisenhower
186
187 return b
188}
189
190// ScheduledOn sets when the task should appear on your schedule.
191func (b *TaskUpdateBuilder) ScheduledOn(date Date) *TaskUpdateBuilder {
192 b.req.ScheduledOn = &date
193
194 return b
195}
196
197// CompletedAt marks the task completed at a specific time.
198func (b *TaskUpdateBuilder) CompletedAt(t time.Time) *TaskUpdateBuilder {
199 b.req.CompletedAt = &t
200
201 return b
202}
203
204// Build returns the constructed request.
205func (b *TaskUpdateBuilder) Build() *UpdateTaskRequest {
206 return &b.req
207}
208
209// NoteBuilder constructs a [CreateNoteRequest] via method chaining.
210// Note fields are encrypted client-side by Lunatask; the API accepts them
211// on create but returns null on read.
212//
213// req := lunatask.NewNote().
214// WithName("Meeting notes").
215// WithContent("# Summary\n\n...").
216// InNotebook(notebookID).
217// Build()
218// note, err := client.CreateNote(ctx, req)
219type NoteBuilder struct {
220 req CreateNoteRequest
221}
222
223// NewNote starts building a note.
224func NewNote() *NoteBuilder {
225 return &NoteBuilder{} //nolint:exhaustruct
226}
227
228// WithName sets the note's title.
229func (b *NoteBuilder) WithName(name string) *NoteBuilder {
230 b.req.Name = &name
231
232 return b
233}
234
235// WithContent sets the Markdown body.
236func (b *NoteBuilder) WithContent(content string) *NoteBuilder {
237 b.req.Content = &content
238
239 return b
240}
241
242// InNotebook places the note in a notebook. IDs are in the notebook's settings in the app.
243func (b *NoteBuilder) InNotebook(notebookID string) *NoteBuilder {
244 b.req.NotebookID = ¬ebookID
245
246 return b
247}
248
249// FromSource tags the note with a free-form origin identifier, useful for
250// tracking notes created by scripts or external integrations.
251func (b *NoteBuilder) FromSource(source, sourceID string) *NoteBuilder {
252 b.req.Source = &source
253 b.req.SourceID = &sourceID
254
255 return b
256}
257
258// Build returns the constructed request.
259func (b *NoteBuilder) Build() *CreateNoteRequest {
260 return &b.req
261}
262
263// JournalEntryBuilder constructs a [CreateJournalEntryRequest] via method chaining.
264// Journal content is encrypted client-side; the API accepts it on create but
265// returns null on read.
266//
267// req := lunatask.NewJournalEntry(lunatask.Today()).
268// WithContent("Shipped the new feature!").
269// Build()
270// entry, err := client.CreateJournalEntry(ctx, req)
271type JournalEntryBuilder struct {
272 req CreateJournalEntryRequest
273}
274
275// NewJournalEntry starts building a journal entry for the given date.
276func NewJournalEntry(date Date) *JournalEntryBuilder {
277 return &JournalEntryBuilder{req: CreateJournalEntryRequest{DateOn: date}} //nolint:exhaustruct
278}
279
280// WithName sets the entry's title. Defaults to the weekday name if omitted.
281func (b *JournalEntryBuilder) WithName(name string) *JournalEntryBuilder {
282 b.req.Name = &name
283
284 return b
285}
286
287// WithContent sets the Markdown body.
288func (b *JournalEntryBuilder) WithContent(content string) *JournalEntryBuilder {
289 b.req.Content = &content
290
291 return b
292}
293
294// Build returns the constructed request.
295func (b *JournalEntryBuilder) Build() *CreateJournalEntryRequest {
296 return &b.req
297}
298
299// PersonBuilder constructs a [CreatePersonRequest] via method chaining.
300// Name fields are encrypted client-side; the API accepts them on create but
301// returns null on read.
302//
303// req := lunatask.NewPerson().
304// WithFirstName("Ada").
305// WithLastName("Lovelace").
306// WithRelationshipStrength("close").
307// Build()
308// person, err := client.CreatePerson(ctx, req)
309type PersonBuilder struct {
310 req CreatePersonRequest
311}
312
313// NewPerson starts building a person entry.
314func NewPerson() *PersonBuilder {
315 return &PersonBuilder{} //nolint:exhaustruct
316}
317
318// WithFirstName sets the person's first name.
319func (b *PersonBuilder) WithFirstName(name string) *PersonBuilder {
320 b.req.FirstName = &name
321
322 return b
323}
324
325// WithLastName sets the person's last name.
326func (b *PersonBuilder) WithLastName(name string) *PersonBuilder {
327 b.req.LastName = &name
328
329 return b
330}
331
332// WithRelationshipStrength categorizes the closeness of the relationship.
333// Use one of the Relationship* constants (e.g., RelationshipCloseFriend).
334func (b *PersonBuilder) WithRelationshipStrength(strength RelationshipStrength) *PersonBuilder {
335 b.req.RelationshipStrength = &strength
336
337 return b
338}
339
340// FromSource tags the person with a free-form origin identifier, useful for
341// tracking entries created by scripts or external integrations.
342func (b *PersonBuilder) FromSource(source, sourceID string) *PersonBuilder {
343 b.req.Source = &source
344 b.req.SourceID = &sourceID
345
346 return b
347}
348
349// WithCustomField sets an arbitrary custom field. Lunatask supports "email",
350// "birthday", and "phone" out of the box; other fields must first be defined
351// in the app or [ErrUnprocessableEntity] is returned.
352func (b *PersonBuilder) WithCustomField(key string, value any) *PersonBuilder {
353 if b.req.CustomFields == nil {
354 b.req.CustomFields = make(map[string]any)
355 }
356
357 b.req.CustomFields[key] = value
358
359 return b
360}
361
362// Build returns the constructed request.
363func (b *PersonBuilder) Build() *CreatePersonRequest {
364 return &b.req
365}
366
367// TimelineNoteBuilder constructs a [CreatePersonTimelineNoteRequest] via method chaining.
368// Content is encrypted client-side; the API accepts it on create but returns null on read.
369//
370// req := lunatask.NewTimelineNote(personID).
371// OnDate(lunatask.Today()).
372// WithContent("Had coffee, discussed the project.").
373// Build()
374// note, err := client.CreatePersonTimelineNote(ctx, req)
375type TimelineNoteBuilder struct {
376 req CreatePersonTimelineNoteRequest
377}
378
379// NewTimelineNote starts building a timeline note for the given person.
380// Get person IDs from [Client.ListPeople].
381func NewTimelineNote(personID string) *TimelineNoteBuilder {
382 return &TimelineNoteBuilder{req: CreatePersonTimelineNoteRequest{PersonID: personID}} //nolint:exhaustruct
383}
384
385// OnDate sets when this interaction occurred.
386func (b *TimelineNoteBuilder) OnDate(date Date) *TimelineNoteBuilder {
387 b.req.DateOn = &date
388
389 return b
390}
391
392// WithContent sets the Markdown body describing the interaction.
393func (b *TimelineNoteBuilder) WithContent(content string) *TimelineNoteBuilder {
394 b.req.Content = &content
395
396 return b
397}
398
399// Build returns the constructed request.
400func (b *TimelineNoteBuilder) Build() *CreatePersonTimelineNoteRequest {
401 return &b.req
402}