## ADDED Requirements ### Requirement: Base HTTP client The system SHALL provide a base HTTP client that sends requests to the Planka API. The client SHALL construct URLs by joining the configured base URL with the API path. The client SHALL attach an `x-api-key: ` header to every request. The client SHALL send and receive JSON (`Content-Type: application/json`). The client SHALL accept a `*slog.Logger` and log every request at DEBUG level with method, path, status code, and duration. The client SHALL log errors at WARN level. #### Scenario: Successful API request - **WHEN** the client sends a request to a valid endpoint - **THEN** the response body SHALL be returned as parsed JSON - **AND** the request SHALL include the `x-api-key` header - **AND** a DEBUG log entry SHALL be emitted with method, path, status, and duration #### Scenario: API returns error status - **WHEN** the API responds with a 4xx or 5xx status code - **THEN** the client SHALL return an `APIError` containing the HTTP status code and response message - **AND** a WARN log entry SHALL be emitted #### Scenario: Network failure - **WHEN** the HTTP request fails due to a network error (connection refused, timeout, DNS failure) - **THEN** the client SHALL return a Go error wrapping the underlying network error ### Requirement: Authentication from environment The system SHALL read `PLANKA_URL` from the environment to determine the API base URL. The system SHALL read `PLANKA_API_KEY` from the environment to determine the API key. Global flags `--url` and `--api-key` SHALL override the corresponding environment variables. Flag values SHALL take precedence over environment variables. #### Scenario: Auth from environment variables - **WHEN** `PLANKA_URL` and `PLANKA_API_KEY` are set in the environment - **AND** no `--url` or `--api-key` flags are provided - **THEN** the client SHALL use the environment variable values #### Scenario: Flag overrides environment - **WHEN** `--url` or `--api-key` flags are provided - **THEN** the flag values SHALL take precedence over environment variables #### Scenario: Missing configuration - **WHEN** neither the environment variable nor the flag is set for URL or API key - **THEN** the system SHALL print an error message and exit with code 1 ### Requirement: Cursor-based pagination The system SHALL implement cursor-based pagination for all list endpoints that support it. Paginated endpoints are: `GET /boards/{boardId}/actions` (`beforeId`), `GET /cards/{cardId}/actions` (`beforeId`), `GET /lists/{listId}/cards` (`before`), `GET /cards/{cardId}/comments` (`beforeId`). List methods SHALL accept a `limit` parameter. When `limit` is 0, the client SHALL fetch all pages. When `limit` is greater than 0, the client SHALL stop fetching after accumulating at least `limit` items and truncate the result to exactly `limit` items. Each page fetch SHALL be logged at DEBUG level. #### Scenario: Fetch all pages - **WHEN** a list method is called with limit 0 - **THEN** the client SHALL fetch pages until an empty page is returned - **AND** all items from all pages SHALL be returned #### Scenario: Fetch with limit - **WHEN** a list method is called with limit N (N > 0) - **THEN** the client SHALL stop fetching after accumulating N or more items - **AND** the returned slice SHALL contain exactly N items #### Scenario: Single page result - **WHEN** the API returns fewer items than a page size and no more pages exist - **THEN** the client SHALL return those items without making additional requests ### Requirement: Project operations The client SHALL provide methods to list all accessible projects (`GET /projects`), get a single project by ID (`GET /projects/{id}`), create a project (`POST /projects`), and delete a project (`DELETE /projects/{id}`). #### Scenario: List projects - **WHEN** `ListProjects` is called - **THEN** the client SHALL send `GET /projects` and return a slice of Project models #### Scenario: Get project - **WHEN** `GetProject` is called with a project ID - **THEN** the client SHALL send `GET /projects/{id}` and return a Project model #### Scenario: Create project - **WHEN** `CreateProject` is called with project fields (type, name, description) - **THEN** the client SHALL send `POST /projects` with the provided fields and return the created Project #### Scenario: Delete project - **WHEN** `DeleteProject` is called with a project ID - **THEN** the client SHALL send `DELETE /projects/{id}` ### Requirement: Board operations The client SHALL provide a method to get a single board by ID (`GET /boards/{id}`), list board actions (`GET /boards/{boardId}/actions`) with pagination support, create a board (`POST /projects/{projectId}/boards`), and delete a board (`DELETE /boards/{id}`). #### Scenario: Get board - **WHEN** `GetBoard` is called with a board ID - **THEN** the client SHALL send `GET /boards/{id}` and return a Board model including its included lists #### Scenario: List board actions - **WHEN** `ListBoardActions` is called with a board ID and limit - **THEN** the client SHALL send paginated `GET /boards/{boardId}/actions` requests and return a slice of Action models #### Scenario: Create board - **WHEN** `CreateBoard` is called with a project ID and board fields (name, position) - **THEN** the client SHALL send `POST /projects/{projectId}/boards` with the provided fields and return the created Board #### Scenario: Delete board - **WHEN** `DeleteBoard` is called with a board ID - **THEN** the client SHALL send `DELETE /boards/{id}` ### Requirement: Card CRUD operations The client SHALL provide methods for: get card (`GET /cards/{id}`), create card (`POST /lists/{listId}/cards`), update card (`PATCH /cards/{id}`), delete card (`DELETE /cards/{id}`), and duplicate card (`POST /cards/{id}/duplicate`). The client SHALL provide a method to list cards in a list (`GET /lists/{listId}/cards`) with pagination support. The client SHALL provide a method to list card actions (`GET /cards/{cardId}/actions`) with pagination support. #### Scenario: Get card - **WHEN** `GetCard` is called with a card ID - **THEN** the client SHALL send `GET /cards/{id}` and return a Card model #### Scenario: Create card - **WHEN** `CreateCard` is called with a list ID and card fields (name, description, type, position, dueDate, isDueCompleted) - **THEN** the client SHALL send `POST /lists/{listId}/cards` with the provided fields and return the created Card #### Scenario: Update card - **WHEN** `UpdateCard` is called with a card ID and update fields - **THEN** the client SHALL send `PATCH /cards/{id}` with only the provided fields and return the updated Card #### Scenario: Delete card - **WHEN** `DeleteCard` is called with a card ID - **THEN** the client SHALL send `DELETE /cards/{id}` #### Scenario: Duplicate card - **WHEN** `DuplicateCard` is called with a card ID, name, and position - **THEN** the client SHALL send `POST /cards/{id}/duplicate` and return the new Card #### Scenario: List cards in list - **WHEN** `ListCards` is called with a list ID and limit - **THEN** the client SHALL send paginated `GET /lists/{listId}/cards` requests and return a slice of Card models #### Scenario: List card actions - **WHEN** `ListCardActions` is called with a card ID and limit - **THEN** the client SHALL send paginated `GET /cards/{cardId}/actions` requests and return a slice of Action models ### Requirement: Comment operations The client SHALL provide methods for: list comments (`GET /cards/{cardId}/comments`) with pagination, create comment (`POST /cards/{cardId}/comments`), update comment (`PATCH /comments/{id}`), and delete comment (`DELETE /comments/{id}`). #### Scenario: List comments - **WHEN** `ListComments` is called with a card ID and limit - **THEN** the client SHALL send paginated `GET /cards/{cardId}/comments` requests and return a slice of Comment models #### Scenario: Create comment - **WHEN** `CreateComment` is called with a card ID and text - **THEN** the client SHALL send `POST /cards/{cardId}/comments` with the text and return the created Comment #### Scenario: Update comment - **WHEN** `UpdateComment` is called with a comment ID and text - **THEN** the client SHALL send `PATCH /comments/{id}` and return the updated Comment #### Scenario: Delete comment - **WHEN** `DeleteComment` is called with a comment ID - **THEN** the client SHALL send `DELETE /comments/{id}` ### Requirement: Task list operations The client SHALL provide methods for: create task list (`POST /cards/{cardId}/task-lists`), get task list (`GET /task-lists/{id}`), update task list (`PATCH /task-lists/{id}`), and delete task list (`DELETE /task-lists/{id}`). #### Scenario: Create task list - **WHEN** `CreateTaskList` is called with a card ID and fields (name, position, showOnFrontOfCard, hideCompletedTasks) - **THEN** the client SHALL send `POST /cards/{cardId}/task-lists` and return the created TaskList #### Scenario: Get task list - **WHEN** `GetTaskList` is called with a task list ID - **THEN** the client SHALL send `GET /task-lists/{id}` and return a TaskList model #### Scenario: Update task list - **WHEN** `UpdateTaskList` is called with a task list ID and update fields - **THEN** the client SHALL send `PATCH /task-lists/{id}` and return the updated TaskList #### Scenario: Delete task list - **WHEN** `DeleteTaskList` is called with a task list ID - **THEN** the client SHALL send `DELETE /task-lists/{id}` ### Requirement: Task operations The client SHALL provide methods for: create task (`POST /task-lists/{taskListId}/tasks`), update task (`PATCH /tasks/{id}`), and delete task (`DELETE /tasks/{id}`). #### Scenario: Create task - **WHEN** `CreateTask` is called with a task list ID and fields (name, position, isCompleted, assigneeUserId, linkedCardId) - **THEN** the client SHALL send `POST /task-lists/{taskListId}/tasks` and return the created Task #### Scenario: Update task - **WHEN** `UpdateTask` is called with a task ID and update fields (name, position, isCompleted, assigneeUserId, taskListId) - **THEN** the client SHALL send `PATCH /tasks/{id}` and return the updated Task #### Scenario: Delete task - **WHEN** `DeleteTask` is called with a task ID - **THEN** the client SHALL send `DELETE /tasks/{id}` ### Requirement: Label operations The client SHALL provide methods for: create label (`POST /boards/{boardId}/labels`), update label (`PATCH /labels/{id}`), and delete label (`DELETE /labels/{id}`). #### Scenario: Create label - **WHEN** `CreateLabel` is called with a board ID and fields (name, color, position) - **THEN** the client SHALL send `POST /boards/{boardId}/labels` and return the created Label #### Scenario: Update label - **WHEN** `UpdateLabel` is called with a label ID and update fields (name, color, position) - **THEN** the client SHALL send `PATCH /labels/{id}` and return the updated Label #### Scenario: Delete label - **WHEN** `DeleteLabel` is called with a label ID - **THEN** the client SHALL send `DELETE /labels/{id}` ### Requirement: Card label operations The client SHALL provide methods for: add label to card (`POST /cards/{cardId}/card-labels`) and remove label from card (`DELETE /cards/{cardId}/card-labels/labelId:{labelId}`). #### Scenario: Add label to card - **WHEN** `AddCardLabel` is called with a card ID and label ID - **THEN** the client SHALL send `POST /cards/{cardId}/card-labels` with the label ID #### Scenario: Remove label from card - **WHEN** `RemoveCardLabel` is called with a card ID and label ID - **THEN** the client SHALL send `DELETE /cards/{cardId}/card-labels/labelId:{labelId}` ### Requirement: Card membership operations The client SHALL provide methods for: assign user to card (`POST /cards/{cardId}/card-memberships`) and unassign user from card (`DELETE /cards/{cardId}/card-memberships/userId:{userId}`). #### Scenario: Assign user to card - **WHEN** `AddCardMember` is called with a card ID and user ID - **THEN** the client SHALL send `POST /cards/{cardId}/card-memberships` with the user ID #### Scenario: Unassign user from card - **WHEN** `RemoveCardMember` is called with a card ID and user ID - **THEN** the client SHALL send `DELETE /cards/{cardId}/card-memberships/userId:{userId}`