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 "fmt"
10 "net/http"
11 "net/url"
12 "time"
13)
14
15// Person represents a person/relationship returned from the Lunatask API.
16// Note: first_name and last_name are E2EE and not returned by the API.
17type Person struct {
18 ID string `json:"id"`
19 RelationshipStrength *string `json:"relationship_strength"`
20 Sources []Source `json:"sources"`
21 CreatedAt time.Time `json:"created_at"`
22 UpdatedAt time.Time `json:"updated_at"`
23}
24
25// CreatePersonRequest represents the request to create a person in Lunatask.
26type CreatePersonRequest struct {
27 FirstName *string `json:"first_name,omitempty"`
28 LastName *string `json:"last_name,omitempty"`
29 RelationshipStrength *string `json:"relationship_strength,omitempty"`
30 Source *string `json:"source,omitempty"`
31 SourceID *string `json:"source_id,omitempty"`
32}
33
34// personResponse represents a single person response from the API.
35type personResponse struct {
36 Person Person `json:"person"`
37}
38
39// peopleResponse represents a list of people response from the API.
40type peopleResponse struct {
41 People []Person `json:"people"`
42}
43
44// ListPeopleOptions contains optional filters for listing people.
45type ListPeopleOptions struct {
46 Source *string
47 SourceID *string
48}
49
50// ListPeople retrieves all people, optionally filtered by source and/or source_id.
51func (c *Client) ListPeople(ctx context.Context, opts *ListPeopleOptions) ([]Person, error) {
52 path := "/people"
53
54 if opts != nil {
55 params := url.Values{}
56 if opts.Source != nil && *opts.Source != "" {
57 params.Set("source", *opts.Source)
58 }
59 if opts.SourceID != nil && *opts.SourceID != "" {
60 params.Set("source_id", *opts.SourceID)
61 }
62 if len(params) > 0 {
63 path = fmt.Sprintf("%s?%s", path, params.Encode())
64 }
65 }
66
67 resp, _, err := doJSON[peopleResponse](c, ctx, http.MethodGet, path, nil)
68 if err != nil {
69 return nil, err
70 }
71
72 return resp.People, nil
73}
74
75// GetPerson retrieves a specific person by ID.
76func (c *Client) GetPerson(ctx context.Context, personID string) (*Person, error) {
77 if personID == "" {
78 return nil, fmt.Errorf("%w: person ID cannot be empty", ErrBadRequest)
79 }
80
81 resp, _, err := doJSON[personResponse](c, ctx, http.MethodGet, "/people/"+personID, nil)
82 if err != nil {
83 return nil, err
84 }
85
86 return &resp.Person, nil
87}
88
89// CreatePerson creates a new person/relationship in Lunatask.
90// Returns nil, nil if a matching person already exists with the same
91// source/source_id (HTTP 204).
92func (c *Client) CreatePerson(ctx context.Context, person *CreatePersonRequest) (*Person, error) {
93 resp, noContent, err := doJSON[personResponse](c, ctx, http.MethodPost, "/people", person)
94 if err != nil {
95 return nil, err
96 }
97 if noContent {
98 return nil, nil
99 }
100
101 return &resp.Person, nil
102}
103
104// DeletePerson deletes a person in Lunatask.
105func (c *Client) DeletePerson(ctx context.Context, personID string) (*Person, error) {
106 if personID == "" {
107 return nil, fmt.Errorf("%w: person ID cannot be empty", ErrBadRequest)
108 }
109
110 resp, _, err := doJSON[personResponse](c, ctx, http.MethodDelete, "/people/"+personID, nil)
111 if err != nil {
112 return nil, err
113 }
114
115 return &resp.Person, nil
116}