Files
Steve Cliff b07572fed5 Released v1
2026-02-12 10:37:19 +00:00

196 lines
11 KiB
Markdown

## 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 `Authorization: Bearer <token>` 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 bearer token 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_TOKEN` from the environment to determine the bearer token. Global flags `--url` and `--token` SHALL override the corresponding environment variables. Flag values SHALL take precedence over environment variables.
#### Scenario: Auth from environment variables
- **WHEN** `PLANKA_URL` and `PLANKA_TOKEN` are set in the environment
- **AND** no `--url` or `--token` flags are provided
- **THEN** the client SHALL use the environment variable values
#### Scenario: Flag overrides environment
- **WHEN** `--url` or `--token` 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 token
- **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`) and get a single project by ID (`GET /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
### Requirement: Board operations
The client SHALL provide a method to get a single board by ID (`GET /boards/{id}`) and list board actions (`GET /boards/{boardId}/actions`) with pagination support.
#### 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
### 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}`