Files
Steve Cliff 7937266262 feat(status): add --project flag for filtering boards by project name
- Implemented the --project flag in the pcli status command to filter boards based on the specified project name.
- Updated the command to resolve project names to IDs using case-insensitive matching.
- Adjusted the totalBoards count in the output to reflect the number of boards matching the project filter.
- Enhanced command help text and README documentation to include usage examples for the new flag.
- Verified functionality through manual testing and ensured default behavior remains unchanged when the flag is omitted.

feat(board): expand GetBoard response to include labels and card associations

- Modified the Board struct to include Labels, CardLabels, and CardMemberships fields.
- Updated the GetBoard method to parse additional fields from the API response.
- Enhanced ListCardsByBoard to include label names for each card based on the enriched board data.
- Ensured backward compatibility by making new fields optional and preserving existing output structure.
2026-02-18 21:38:41 +00:00

225 lines
13 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}`). `GetBoard` SHALL parse the `included` object from the response and populate the Board model with `lists`, `cards`, `labels`, `cardLabels`, and `cardMemberships`.
#### 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, cards, labels, cardLabels, and cardMemberships
#### Scenario: Get board includes labels
- **WHEN** `GetBoard` is called and the board has labels defined
- **THEN** the returned Board SHALL contain a `Labels` slice with all board labels
#### Scenario: Get board includes card-label associations
- **WHEN** `GetBoard` is called and cards on the board have labels attached
- **THEN** the returned Board SHALL contain a `CardLabels` slice with all card-label associations
- **AND** each `CardLabel` entry SHALL contain `cardId` and `labelId` fields
#### Scenario: Get board includes card memberships
- **WHEN** `GetBoard` is called and cards on the board have members assigned
- **THEN** the returned Board SHALL contain a `CardMemberships` slice with all card-membership associations
#### 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}`