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)
13
14// unwrap extracts an entity from a response wrapper.
15type unwrap[E, R any] func(R) E
16
17// unwrapSlice extracts a slice of entities from a response wrapper.
18type unwrapSlice[E, R any] func(R) []E
19
20// SourceFilter provides optional source filtering for list operations.
21type SourceFilter interface {
22 GetSource() *string
23 GetSourceID() *string
24}
25
26// get fetches a single entity by ID.
27func get[E, R any](ctx context.Context, c *Client, path, id, name string, extract unwrap[E, R]) (*E, error) {
28 if id == "" {
29 return nil, fmt.Errorf("%w: %s ID cannot be empty", ErrBadRequest, name)
30 }
31
32 resp, _, err := doJSON[R](ctx, c, http.MethodGet, path+"/"+id, nil)
33 if err != nil {
34 return nil, err
35 }
36
37 e := extract(*resp)
38
39 return &e, nil
40}
41
42// list fetches all entities, optionally filtered by source.
43func list[E, R any](
44 ctx context.Context, c *Client, path string, opts SourceFilter, extract unwrapSlice[E, R],
45) ([]E, error) {
46 if opts != nil {
47 params := url.Values{}
48
49 if s := opts.GetSource(); s != nil && *s != "" {
50 params.Set("source", *s)
51 }
52
53 if s := opts.GetSourceID(); s != nil && *s != "" {
54 params.Set("source_id", *s)
55 }
56
57 if len(params) > 0 {
58 path = fmt.Sprintf("%s?%s", path, params.Encode())
59 }
60 }
61
62 resp, _, err := doJSON[R](ctx, c, http.MethodGet, path, nil)
63 if err != nil {
64 return nil, err
65 }
66
67 return extract(*resp), nil
68}
69
70// create adds a new entity. Returns (nil, nil) if duplicate exists (HTTP 204).
71func create[E, R any](ctx context.Context, c *Client, path string, body any, extract unwrap[E, R]) (*E, error) {
72 resp, noContent, err := doJSON[R](ctx, c, http.MethodPost, path, body)
73 if err != nil {
74 return nil, err
75 }
76
77 if noContent {
78 return nil, nil //nolint:nilnil
79 }
80
81 e := extract(*resp)
82
83 return &e, nil
84}
85
86// update modifies an entity by ID.
87func update[E, R any](
88 ctx context.Context, c *Client, path, id, name string, body any, extract unwrap[E, R],
89) (*E, error) {
90 if id == "" {
91 return nil, fmt.Errorf("%w: %s ID cannot be empty", ErrBadRequest, name)
92 }
93
94 resp, _, err := doJSON[R](ctx, c, http.MethodPut, path+"/"+id, body)
95 if err != nil {
96 return nil, err
97 }
98
99 e := extract(*resp)
100
101 return &e, nil
102}
103
104// del removes an entity by ID and returns its final state.
105func del[E, R any](ctx context.Context, c *Client, path, id, name string, extract unwrap[E, R]) (*E, error) {
106 if id == "" {
107 return nil, fmt.Errorf("%w: %s ID cannot be empty", ErrBadRequest, name)
108 }
109
110 resp, _, err := doJSON[R](ctx, c, http.MethodDelete, path+"/"+id, nil)
111 if err != nil {
112 return nil, err
113 }
114
115 e := extract(*resp)
116
117 return &e, nil
118}