2026-01-15 18:10:35 +01:00
|
|
|
package kanboard
|
|
|
|
|
|
|
|
|
|
import (
|
2026-01-15 18:14:22 +01:00
|
|
|
"log/slog"
|
2026-01-15 18:10:35 +01:00
|
|
|
"net/http"
|
|
|
|
|
"strings"
|
2026-01-15 18:14:22 +01:00
|
|
|
"time"
|
2026-01-15 18:10:35 +01:00
|
|
|
)
|
|
|
|
|
|
2026-01-15 18:14:22 +01:00
|
|
|
// DefaultTimeout is the default HTTP timeout for API requests.
|
|
|
|
|
const DefaultTimeout = 30 * time.Second
|
|
|
|
|
|
2026-01-15 18:10:35 +01:00
|
|
|
// Client is the Kanboard API client.
|
2026-01-15 18:14:22 +01:00
|
|
|
// It is safe for concurrent use by multiple goroutines.
|
2026-01-15 18:10:35 +01:00
|
|
|
type Client struct {
|
2026-01-23 18:26:55 +01:00
|
|
|
baseURL string
|
|
|
|
|
endpoint string
|
|
|
|
|
httpClient *http.Client
|
|
|
|
|
auth Authenticator
|
|
|
|
|
logger *slog.Logger
|
|
|
|
|
authHeaderName string // custom auth header, empty = use "Authorization"
|
2026-01-15 18:10:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewClient creates a new Kanboard API client.
|
|
|
|
|
// The baseURL should be the base URL of the Kanboard instance (e.g., "https://kanboard.example.com").
|
|
|
|
|
// The path /jsonrpc.php is appended automatically.
|
|
|
|
|
// Supports subdirectory installations (e.g., "https://example.com/kanboard" → POST https://example.com/kanboard/jsonrpc.php).
|
|
|
|
|
func NewClient(baseURL string) *Client {
|
|
|
|
|
// Ensure no trailing slash
|
|
|
|
|
baseURL = strings.TrimSuffix(baseURL, "/")
|
|
|
|
|
|
2026-01-27 10:27:23 +01:00
|
|
|
// Handle URLs that already include /jsonrpc.php
|
|
|
|
|
endpoint := baseURL
|
|
|
|
|
if !strings.HasSuffix(baseURL, "/jsonrpc.php") {
|
|
|
|
|
endpoint = baseURL + "/jsonrpc.php"
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-27 10:21:14 +01:00
|
|
|
c := &Client{
|
2026-01-15 18:14:22 +01:00
|
|
|
baseURL: baseURL,
|
2026-01-27 10:27:23 +01:00
|
|
|
endpoint: endpoint,
|
2026-01-15 18:10:35 +01:00
|
|
|
}
|
2026-01-27 10:21:14 +01:00
|
|
|
|
|
|
|
|
c.httpClient = &http.Client{
|
|
|
|
|
Timeout: DefaultTimeout,
|
|
|
|
|
CheckRedirect: c.redirectBehavior,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return c
|
2026-01-15 18:10:35 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-23 18:26:55 +01:00
|
|
|
// WithAuthHeader configures a custom header name for authentication.
|
|
|
|
|
// If not set, the standard "Authorization" header is used.
|
|
|
|
|
// This must be called before configuring authentication (WithAPIToken, WithBasicAuth, etc.).
|
|
|
|
|
// Example: WithAuthHeader("X-API-Auth")
|
|
|
|
|
func (c *Client) WithAuthHeader(headerName string) *Client {
|
|
|
|
|
c.authHeaderName = headerName
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 18:10:35 +01:00
|
|
|
// WithAPIToken configures the client to use API token authentication.
|
2026-01-23 17:55:31 +01:00
|
|
|
// Uses "jsonrpc" as the username for HTTP Basic Auth.
|
2026-01-15 18:10:35 +01:00
|
|
|
func (c *Client) WithAPIToken(token string) *Client {
|
2026-01-23 18:26:55 +01:00
|
|
|
c.auth = &apiTokenAuth{token: token, headerName: c.authHeaderName}
|
2026-01-15 18:10:35 +01:00
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-23 17:55:31 +01:00
|
|
|
// WithAPITokenUser configures the client to use API token authentication with a custom username.
|
|
|
|
|
// If user is empty, "jsonrpc" will be used as the default.
|
|
|
|
|
func (c *Client) WithAPITokenUser(token, user string) *Client {
|
2026-01-23 18:26:55 +01:00
|
|
|
c.auth = &apiTokenAuth{token: token, user: user, headerName: c.authHeaderName}
|
2026-01-23 17:55:31 +01:00
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 18:10:35 +01:00
|
|
|
// WithBasicAuth configures the client to use username/password authentication.
|
|
|
|
|
func (c *Client) WithBasicAuth(username, password string) *Client {
|
2026-01-23 18:26:55 +01:00
|
|
|
c.auth = &basicAuth{username: username, password: password, headerName: c.authHeaderName}
|
2026-01-15 18:10:35 +01:00
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithHTTPClient sets a custom HTTP client.
|
2026-01-27 10:21:14 +01:00
|
|
|
// This replaces the default client entirely, including timeout and redirect settings.
|
|
|
|
|
// Note: The custom client's CheckRedirect handler will be used instead of the
|
|
|
|
|
// built-in redirect handler that preserves authentication headers.
|
2026-01-15 18:10:35 +01:00
|
|
|
func (c *Client) WithHTTPClient(client *http.Client) *Client {
|
|
|
|
|
c.httpClient = client
|
|
|
|
|
return c
|
|
|
|
|
}
|
2026-01-15 18:14:22 +01:00
|
|
|
|
|
|
|
|
// WithTimeout sets the HTTP client timeout.
|
|
|
|
|
// This creates a new HTTP client with the specified timeout.
|
|
|
|
|
func (c *Client) WithTimeout(timeout time.Duration) *Client {
|
|
|
|
|
c.httpClient = &http.Client{
|
2026-01-27 10:21:14 +01:00
|
|
|
Timeout: timeout,
|
|
|
|
|
Transport: c.httpClient.Transport,
|
|
|
|
|
CheckRedirect: c.redirectBehavior,
|
2026-01-15 18:14:22 +01:00
|
|
|
}
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithLogger sets the logger for debug output.
|
|
|
|
|
// If set, the client will log request/response details at debug level.
|
|
|
|
|
func (c *Client) WithLogger(logger *slog.Logger) *Client {
|
|
|
|
|
c.logger = logger
|
|
|
|
|
return c
|
|
|
|
|
}
|