Implement Client struct with functional options
Add client.go with: - Client struct with baseURL, username, remoteKey, token, tokenExp, httpClient, retryConf, logger, and sync.RWMutex for thread safety - NewClient constructor accepting username, remoteKey, and variadic options - Default values: 30s timeout, Checkvist base URL Add options.go with: - RetryConfig struct (MaxRetries, BaseDelay, MaxDelay, Jitter) - DefaultRetryConfig() with 3 retries, 1s base, 30s max, jitter enabled - Option type for functional options pattern - WithHTTPClient, WithTimeout, WithRetryConfig, WithLogger, WithBaseURL Closes checkvist-api-ymg
This commit is contained in:
parent
832364a06f
commit
90c48d9323
3 changed files with 141 additions and 1 deletions
|
|
@ -23,4 +23,4 @@
|
|||
{"id":"checkvist-api-rl9","title":"Implement Task operations","description":"Create tasks.go with TaskService:\n- client.Tasks(checklistID) returns TaskService\n- List(ctx) ([]Task, error) - GET /checklists/{id}/tasks.json\n- Get(ctx, taskID) (*Task, error) - GET /checklists/{id}/tasks/{task_id}.json (includes parent hierarchy)\n- Create(ctx, builder *TaskBuilder) (*Task, error) - POST /checklists/{id}/tasks.json\n- Update(ctx, taskID, opts) (*Task, error) - PUT /checklists/{id}/tasks/{task_id}.json\n- Delete(ctx, taskID) error - DELETE /checklists/{id}/tasks/{task_id}.json\n- Close(ctx, taskID) (*Task, error) - POST /checklists/{id}/tasks/{task_id}/close.json\n- Reopen(ctx, taskID) (*Task, error) - POST /checklists/{id}/tasks/{task_id}/reopen.json\n- Invalidate(ctx, taskID) (*Task, error) - POST /checklists/{id}/tasks/{task_id}/invalidate.json\nParse DueDate from DueDateRaw when retrieving tasks.","status":"open","priority":0,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-14T12:30:53.90629838+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-14T12:30:53.90629838+01:00","dependencies":[{"issue_id":"checkvist-api-rl9","depends_on_id":"checkvist-api-8u6","type":"blocks","created_at":"2026-01-14T12:32:55.247292478+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"checkvist-api-rl9","depends_on_id":"checkvist-api-lpn","type":"blocks","created_at":"2026-01-14T12:32:55.536931433+01:00","created_by":"Oliver Jakoubek"}]}
|
||||
{"id":"checkvist-api-tjk","title":"Implement TaskBuilder fluent interface","description":"Enhance task creation with builder pattern:\n- NewTask(content string) *TaskBuilder\n- WithTags(tags ...string) *TaskBuilder\n- WithDueDate(due DueDate) *TaskBuilder\n- WithPriority(p int) *TaskBuilder\n- WithParent(parentID int64) *TaskBuilder\n- WithPosition(pos int) *TaskBuilder\n- Build() returns parameters for API call\nTaskBuilder should be chainable and return itself for fluent usage:\n checkvist.NewTask(\"Content\").WithTags(\"tag1\").WithDueDate(checkvist.DueTomorrow).WithPriority(1)","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-14T12:30:55.929907579+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-14T12:30:55.929907579+01:00","dependencies":[{"issue_id":"checkvist-api-tjk","depends_on_id":"checkvist-api-rl9","type":"blocks","created_at":"2026-01-14T12:33:02.20339206+01:00","created_by":"Oliver Jakoubek"}]}
|
||||
{"id":"checkvist-api-v2f","title":"Write unit tests for Tasks","description":"Create tasks_test.go with tests:\n- TestTasks_List\n- TestTasks_Get\n- TestTasks_Get_WithParentHierarchy\n- TestTasks_Create\n- TestTasks_Create_WithBuilder\n- TestTasks_Update\n- TestTasks_Delete\n- TestTasks_Close\n- TestTasks_Reopen\n- TestTasks_Invalidate\n- TestDueDate_Parsing\nUse table-driven tests. Create testdata/tasks/ fixtures.","status":"open","priority":0,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-14T12:31:37.538754679+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-14T12:31:37.538754679+01:00","dependencies":[{"issue_id":"checkvist-api-v2f","depends_on_id":"checkvist-api-rl9","type":"blocks","created_at":"2026-01-14T12:33:13.81085058+01:00","created_by":"Oliver Jakoubek"}]}
|
||||
{"id":"checkvist-api-ymg","title":"Implement Client struct with functional options","description":"Create client.go with:\n- Client struct (baseURL, username, remoteKey, token, tokenExp, httpClient, retryConf, logger, mu sync.RWMutex)\n- NewClient(username, remoteKey string, opts ...Option) *Client constructor\n- Create options.go with Option type and functional options:\n - WithHTTPClient(*http.Client)\n - WithTimeout(time.Duration)\n - WithRetryConfig(RetryConfig)\n - WithLogger(*slog.Logger)\n - WithBaseURL(string) for testing\n- RetryConfig struct (MaxRetries, BaseDelay, MaxDelay, Jitter)\n- Default values: 3 retries, 1s base delay, 30s max delay, jitter enabled","status":"open","priority":0,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-14T12:31:08.021154076+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-14T12:31:08.021154076+01:00","dependencies":[{"issue_id":"checkvist-api-ymg","depends_on_id":"checkvist-api-e9p","type":"blocks","created_at":"2026-01-14T12:32:47.077541448+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"checkvist-api-ymg","depends_on_id":"checkvist-api-mnh","type":"blocks","created_at":"2026-01-14T12:32:47.358639632+01:00","created_by":"Oliver Jakoubek"}]}
|
||||
{"id":"checkvist-api-ymg","title":"Implement Client struct with functional options","description":"Create client.go with:\n- Client struct (baseURL, username, remoteKey, token, tokenExp, httpClient, retryConf, logger, mu sync.RWMutex)\n- NewClient(username, remoteKey string, opts ...Option) *Client constructor\n- Create options.go with Option type and functional options:\n - WithHTTPClient(*http.Client)\n - WithTimeout(time.Duration)\n - WithRetryConfig(RetryConfig)\n - WithLogger(*slog.Logger)\n - WithBaseURL(string) for testing\n- RetryConfig struct (MaxRetries, BaseDelay, MaxDelay, Jitter)\n- Default values: 3 retries, 1s base delay, 30s max delay, jitter enabled","status":"closed","priority":0,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-14T12:31:08.021154076+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-14T13:24:55.101093793+01:00","closed_at":"2026-01-14T13:24:55.101093793+01:00","close_reason":"Closed","dependencies":[{"issue_id":"checkvist-api-ymg","depends_on_id":"checkvist-api-e9p","type":"blocks","created_at":"2026-01-14T12:32:47.077541448+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"checkvist-api-ymg","depends_on_id":"checkvist-api-mnh","type":"blocks","created_at":"2026-01-14T12:32:47.358639632+01:00","created_by":"Oliver Jakoubek"}]}
|
||||
|
|
|
|||
70
client.go
70
client.go
|
|
@ -10,4 +10,74 @@
|
|||
// checklists, err := client.Checklists().List(ctx)
|
||||
package checkvist
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// client.go contains the Client struct, constructor, and authentication logic.
|
||||
|
||||
const (
|
||||
// DefaultBaseURL is the default base URL for the Checkvist API.
|
||||
DefaultBaseURL = "https://checkvist.com"
|
||||
// DefaultTimeout is the default timeout for HTTP requests.
|
||||
DefaultTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
// Client is the Checkvist API client.
|
||||
type Client struct {
|
||||
// baseURL is the base URL for API requests.
|
||||
baseURL string
|
||||
// username is the user's email address.
|
||||
username string
|
||||
// remoteKey is the API key (remote key) for authentication.
|
||||
remoteKey string
|
||||
// token is the current authentication token.
|
||||
token string
|
||||
// tokenExp is the expiration time of the current token.
|
||||
tokenExp time.Time
|
||||
// httpClient is the HTTP client used for requests.
|
||||
httpClient *http.Client
|
||||
// retryConf is the retry configuration for failed requests.
|
||||
retryConf RetryConfig
|
||||
// logger is the logger for debug and error messages.
|
||||
logger *slog.Logger
|
||||
// mu protects token and tokenExp for concurrent access.
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewClient creates a new Checkvist API client.
|
||||
//
|
||||
// The username should be the user's email address, and remoteKey is the API key
|
||||
// which can be obtained from Checkvist settings.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// client := checkvist.NewClient("user@example.com", "your-api-key")
|
||||
//
|
||||
// With options:
|
||||
//
|
||||
// client := checkvist.NewClient("user@example.com", "your-api-key",
|
||||
// checkvist.WithTimeout(60 * time.Second),
|
||||
// checkvist.WithRetryConfig(checkvist.RetryConfig{MaxRetries: 5}),
|
||||
// )
|
||||
func NewClient(username, remoteKey string, opts ...Option) *Client {
|
||||
c := &Client{
|
||||
baseURL: DefaultBaseURL,
|
||||
username: username,
|
||||
remoteKey: remoteKey,
|
||||
httpClient: &http.Client{
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
retryConf: DefaultRetryConfig(),
|
||||
logger: slog.Default(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
|
|
|||
70
options.go
70
options.go
|
|
@ -1,3 +1,73 @@
|
|||
package checkvist
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// options.go contains functional options for configuring the Client.
|
||||
|
||||
// RetryConfig configures the retry behavior for failed requests.
|
||||
type RetryConfig struct {
|
||||
// MaxRetries is the maximum number of retry attempts.
|
||||
MaxRetries int
|
||||
// BaseDelay is the initial delay before the first retry.
|
||||
BaseDelay time.Duration
|
||||
// MaxDelay is the maximum delay between retries.
|
||||
MaxDelay time.Duration
|
||||
// Jitter enables randomized delay to prevent thundering herd.
|
||||
Jitter bool
|
||||
}
|
||||
|
||||
// DefaultRetryConfig returns the default retry configuration.
|
||||
func DefaultRetryConfig() RetryConfig {
|
||||
return RetryConfig{
|
||||
MaxRetries: 3,
|
||||
BaseDelay: 1 * time.Second,
|
||||
MaxDelay: 30 * time.Second,
|
||||
Jitter: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Option is a functional option for configuring the Client.
|
||||
type Option func(*Client)
|
||||
|
||||
// WithHTTPClient sets a custom HTTP client for the Checkvist client.
|
||||
func WithHTTPClient(client *http.Client) Option {
|
||||
return func(c *Client) {
|
||||
c.httpClient = client
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimeout sets the timeout for HTTP requests.
|
||||
// This creates a new HTTP client with the specified timeout.
|
||||
func WithTimeout(timeout time.Duration) Option {
|
||||
return func(c *Client) {
|
||||
c.httpClient = &http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithRetryConfig sets the retry configuration for failed requests.
|
||||
func WithRetryConfig(config RetryConfig) Option {
|
||||
return func(c *Client) {
|
||||
c.retryConf = config
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger sets a custom logger for the client.
|
||||
func WithLogger(logger *slog.Logger) Option {
|
||||
return func(c *Client) {
|
||||
c.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
// WithBaseURL sets a custom base URL for the API.
|
||||
// This is primarily useful for testing.
|
||||
func WithBaseURL(url string) Option {
|
||||
return func(c *Client) {
|
||||
c.baseURL = url
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue