12 KiB
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-keyheader - 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
APIErrorcontaining 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_URLandPLANKA_API_KEYare set in the environment - AND no
--urlor--api-keyflags are provided - THEN the client SHALL use the environment variable values
Scenario: Flag overrides environment
- WHEN
--urlor--api-keyflags 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
ListProjectsis called - THEN the client SHALL send
GET /projectsand return a slice of Project models
Scenario: Get project
- WHEN
GetProjectis called with a project ID - THEN the client SHALL send
GET /projects/{id}and return a Project model
Scenario: Create project
- WHEN
CreateProjectis called with project fields (type, name, description) - THEN the client SHALL send
POST /projectswith the provided fields and return the created Project
Scenario: Delete project
- WHEN
DeleteProjectis 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
GetBoardis 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
ListBoardActionsis called with a board ID and limit - THEN the client SHALL send paginated
GET /boards/{boardId}/actionsrequests and return a slice of Action models
Scenario: Create board
- WHEN
CreateBoardis called with a project ID and board fields (name, position) - THEN the client SHALL send
POST /projects/{projectId}/boardswith the provided fields and return the created Board
Scenario: Delete board
- WHEN
DeleteBoardis 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
GetCardis called with a card ID - THEN the client SHALL send
GET /cards/{id}and return a Card model
Scenario: Create card
- WHEN
CreateCardis called with a list ID and card fields (name, description, type, position, dueDate, isDueCompleted) - THEN the client SHALL send
POST /lists/{listId}/cardswith the provided fields and return the created Card
Scenario: Update card
- WHEN
UpdateCardis 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
DeleteCardis called with a card ID - THEN the client SHALL send
DELETE /cards/{id}
Scenario: Duplicate card
- WHEN
DuplicateCardis called with a card ID, name, and position - THEN the client SHALL send
POST /cards/{id}/duplicateand return the new Card
Scenario: List cards in list
- WHEN
ListCardsis called with a list ID and limit - THEN the client SHALL send paginated
GET /lists/{listId}/cardsrequests and return a slice of Card models
Scenario: List card actions
- WHEN
ListCardActionsis called with a card ID and limit - THEN the client SHALL send paginated
GET /cards/{cardId}/actionsrequests 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
ListCommentsis called with a card ID and limit - THEN the client SHALL send paginated
GET /cards/{cardId}/commentsrequests and return a slice of Comment models
Scenario: Create comment
- WHEN
CreateCommentis called with a card ID and text - THEN the client SHALL send
POST /cards/{cardId}/commentswith the text and return the created Comment
Scenario: Update comment
- WHEN
UpdateCommentis called with a comment ID and text - THEN the client SHALL send
PATCH /comments/{id}and return the updated Comment
Scenario: Delete comment
- WHEN
DeleteCommentis 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
CreateTaskListis called with a card ID and fields (name, position, showOnFrontOfCard, hideCompletedTasks) - THEN the client SHALL send
POST /cards/{cardId}/task-listsand return the created TaskList
Scenario: Get task list
- WHEN
GetTaskListis 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
UpdateTaskListis 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
DeleteTaskListis 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
CreateTaskis called with a task list ID and fields (name, position, isCompleted, assigneeUserId, linkedCardId) - THEN the client SHALL send
POST /task-lists/{taskListId}/tasksand return the created Task
Scenario: Update task
- WHEN
UpdateTaskis 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
DeleteTaskis 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
CreateLabelis called with a board ID and fields (name, color, position) - THEN the client SHALL send
POST /boards/{boardId}/labelsand return the created Label
Scenario: Update label
- WHEN
UpdateLabelis 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
DeleteLabelis 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
AddCardLabelis called with a card ID and label ID - THEN the client SHALL send
POST /cards/{cardId}/card-labelswith the label ID
Scenario: Remove label from card
- WHEN
RemoveCardLabelis 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
AddCardMemberis called with a card ID and user ID - THEN the client SHALL send
POST /cards/{cardId}/card-membershipswith the user ID
Scenario: Unassign user from card
- WHEN
RemoveCardMemberis called with a card ID and user ID - THEN the client SHALL send
DELETE /cards/{cardId}/card-memberships/userId:{userId}