212 lines
12 KiB
Markdown
212 lines
12 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 `x-api-key: <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}`
|