Added create and delete operations for projects and boards with validation and error handling
This commit is contained in:
@@ -9,6 +9,12 @@ import (
|
||||
"git.franklin.lab/steve.cliff/pcli/model"
|
||||
)
|
||||
|
||||
// BoardCreateFields represents the fields required to create a board
|
||||
type BoardCreateFields struct {
|
||||
Name string `json:"name"`
|
||||
Position float64 `json:"position"`
|
||||
}
|
||||
|
||||
func (c *Client) ListBoards(ctx context.Context) ([]model.Board, error) {
|
||||
data, err := c.DoNoBody(ctx, "GET", "/api/projects")
|
||||
if err != nil {
|
||||
@@ -96,3 +102,25 @@ func (c *Client) ListBoardActions(ctx context.Context, boardId string, limit int
|
||||
|
||||
return all, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateBoard(ctx context.Context, projectId string, fields BoardCreateFields) (*model.Board, error) {
|
||||
data, err := c.Do(ctx, "POST", fmt.Sprintf("/api/projects/%s/boards", projectId), fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Item model.Board `json:"item"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &response); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal board response: %w", err)
|
||||
}
|
||||
|
||||
return &response.Item, nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteBoard(ctx context.Context, id string) error {
|
||||
_, err := c.DoNoBody(ctx, "DELETE", fmt.Sprintf("/api/boards/%s", id))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,13 @@ import (
|
||||
"git.franklin.lab/steve.cliff/pcli/model"
|
||||
)
|
||||
|
||||
// ProjectCreateFields represents the fields required to create a project
|
||||
type ProjectCreateFields struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) ListProjects(ctx context.Context) ([]model.Project, error) {
|
||||
data, err := c.DoNoBody(ctx, "GET", "/api/projects")
|
||||
if err != nil {
|
||||
@@ -41,3 +48,25 @@ func (c *Client) GetProject(ctx context.Context, id string) (*model.Project, err
|
||||
|
||||
return &response.Item, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateProject(ctx context.Context, fields ProjectCreateFields) (*model.Project, error) {
|
||||
data, err := c.Do(ctx, "POST", "/api/projects", fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Item model.Project `json:"item"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &response); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal project response: %w", err)
|
||||
}
|
||||
|
||||
return &response.Item, nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteProject(ctx context.Context, id string) error {
|
||||
_, err := c.DoNoBody(ctx, "DELETE", fmt.Sprintf("/api/projects/%s", id))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.franklin.lab/steve.cliff/pcli/client"
|
||||
"git.franklin.lab/steve.cliff/pcli/output"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -56,11 +58,66 @@ var boardActionsCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var boardCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new board",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
project, _ := cmd.Flags().GetString("project")
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
position, _ := cmd.Flags().GetFloat64("position")
|
||||
|
||||
// Validate required flags
|
||||
if project == "" {
|
||||
return cmd.Usage()
|
||||
}
|
||||
if name == "" {
|
||||
return cmd.Usage()
|
||||
}
|
||||
|
||||
fields := client.BoardCreateFields{
|
||||
Name: name,
|
||||
Position: position,
|
||||
}
|
||||
|
||||
board, err := getClient().CreateBoard(getContext(), project, fields)
|
||||
if err != nil {
|
||||
return friendlyAPIError(err, "create board", "requires project manager role")
|
||||
}
|
||||
|
||||
return output.Print(board, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var boardDeleteCmd = &cobra.Command{
|
||||
Use: "delete <id>",
|
||||
Short: "Delete a board",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := getClient().DeleteBoard(getContext(), args[0])
|
||||
if err != nil {
|
||||
return friendlyAPIError(err, "delete board", "requires project manager role")
|
||||
}
|
||||
|
||||
fmt.Println("Board deleted successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(boardCmd)
|
||||
boardCmd.AddCommand(boardListCmd)
|
||||
boardCmd.AddCommand(boardGetCmd)
|
||||
boardCmd.AddCommand(boardActionsCmd)
|
||||
boardCmd.AddCommand(boardCreateCmd)
|
||||
boardCmd.AddCommand(boardDeleteCmd)
|
||||
|
||||
boardActionsCmd.Flags().Int("limit", 0, "Limit number of actions (0 = no limit)")
|
||||
|
||||
// Flags for board create
|
||||
boardCreateCmd.Flags().String("project", "", "Project ID (required)")
|
||||
boardCreateCmd.Flags().String("name", "", "Board name (required)")
|
||||
boardCreateCmd.Flags().Float64("position", 65536, "Board position (optional, default 65536)")
|
||||
|
||||
boardCreateCmd.MarkFlagRequired("project")
|
||||
boardCreateCmd.MarkFlagRequired("name")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.franklin.lab/steve.cliff/pcli/model"
|
||||
)
|
||||
|
||||
// friendlyAPIError translates APIError status codes into human-readable messages with operation context.
|
||||
func friendlyAPIError(err error, operation string, permissionHint string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if it's an APIError
|
||||
apiErr, ok := err.(*model.APIError)
|
||||
if !ok {
|
||||
// Not an API error, return as-is
|
||||
return err
|
||||
}
|
||||
|
||||
switch apiErr.StatusCode {
|
||||
case 401:
|
||||
return fmt.Errorf("%s: authentication failed — check your API key", operation)
|
||||
case 403:
|
||||
if permissionHint != "" {
|
||||
return fmt.Errorf("%s: permission denied (%s)", operation, permissionHint)
|
||||
}
|
||||
return fmt.Errorf("%s: permission denied", operation)
|
||||
case 404:
|
||||
return fmt.Errorf("%s: not found — the resource may not exist or you may not have access to it", operation)
|
||||
default:
|
||||
// Unknown status code, return original error
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.franklin.lab/steve.cliff/pcli/client"
|
||||
"git.franklin.lab/steve.cliff/pcli/output"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -40,8 +42,67 @@ var projectGetCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var projectCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new project",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
projectType, _ := cmd.Flags().GetString("type")
|
||||
description, _ := cmd.Flags().GetString("description")
|
||||
|
||||
// Validate required flags
|
||||
if name == "" {
|
||||
return cmd.Usage()
|
||||
}
|
||||
if projectType == "" {
|
||||
return cmd.Usage()
|
||||
}
|
||||
|
||||
fields := client.ProjectCreateFields{
|
||||
Type: projectType,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
if description != "" {
|
||||
fields.Description = &description
|
||||
}
|
||||
|
||||
project, err := getClient().CreateProject(getContext(), fields)
|
||||
if err != nil {
|
||||
return friendlyAPIError(err, "create project", "")
|
||||
}
|
||||
|
||||
return output.Print(project, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var projectDeleteCmd = &cobra.Command{
|
||||
Use: "delete <id>",
|
||||
Short: "Delete a project",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := getClient().DeleteProject(getContext(), args[0])
|
||||
if err != nil {
|
||||
return friendlyAPIError(err, "delete project", "requires project manager role")
|
||||
}
|
||||
|
||||
fmt.Println("Project deleted successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(projectCmd)
|
||||
projectCmd.AddCommand(projectListCmd)
|
||||
projectCmd.AddCommand(projectGetCmd)
|
||||
projectCmd.AddCommand(projectCreateCmd)
|
||||
projectCmd.AddCommand(projectDeleteCmd)
|
||||
|
||||
// Flags for project create
|
||||
projectCreateCmd.Flags().String("name", "", "Project name (required)")
|
||||
projectCreateCmd.Flags().String("type", "", "Project type (required, values: public/private)")
|
||||
projectCreateCmd.Flags().String("description", "", "Project description (optional)")
|
||||
|
||||
projectCreateCmd.MarkFlagRequired("name")
|
||||
projectCreateCmd.MarkFlagRequired("type")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-12
|
||||
@@ -0,0 +1,49 @@
|
||||
## Context
|
||||
|
||||
The CLI currently supports read-only operations for projects and boards but lacks write operations. Users must use the web UI to create/delete projects and boards. Additionally, API permission errors return raw HTTP status codes rather than user-friendly guidance. The existing codebase has a clear separation: client layer handles API communication, cmd layer handles CLI commands and output formatting.
|
||||
|
||||
Current write operations (cards, comments, labels, tasks) follow a consistent pattern but do not have permission-aware error handling. The API client already returns structured APIError objects with status codes and messages.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Enable project and board CRUD operations via CLI
|
||||
- Provide clear, actionable error messages for permission failures
|
||||
- Establish a reusable pattern for permission-aware error handling
|
||||
- Maintain consistency with existing command structure and error handling
|
||||
|
||||
**Non-Goals:**
|
||||
- Support for Trello import or multipart file uploads
|
||||
- Changes to existing command behavior
|
||||
- New authentication mechanisms
|
||||
- Breaking changes to existing APIs
|
||||
|
||||
## Decisions
|
||||
|
||||
### Permission Error Translation Strategy
|
||||
Chose to handle permission errors at the command layer rather than client layer. This allows context-specific messages (e.g., "requires project manager role" for board creation) while keeping the client layer focused on API communication. The `friendlyAPIError` helper in `cmd/` package translates APIError status codes into human-readable messages.
|
||||
|
||||
### JSON-only for Board Creation
|
||||
Board creation API supports multipart/form-data for Trello import, but we're using `application/json` since we don't support import. This simplifies implementation and reuses the existing `Do()` method pattern.
|
||||
|
||||
### Incremental Adoption
|
||||
The permission error helper is added but existing commands are not modified. This allows the pattern to be proven out with the new commands before being applied elsewhere, reducing risk.
|
||||
|
||||
### Error Message Design
|
||||
Permission errors include both the operation context and specific permission requirements. For example: "create board: permission denied (requires project manager role)". This gives users clear guidance on what they need to do.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
[Risk] API error format changes could break the helper → Mitigation: Helper falls back to original error if status code mapping fails
|
||||
[Risk] Users might expect pre-flight permission checks → Mitigation: Clear error messages explain the permission requirement after the fact
|
||||
[Trade-off] More verbose error messages vs. brevity → Chose actionable messages that guide users to resolution
|
||||
|
||||
## Migration Plan
|
||||
|
||||
No migration needed - this is additive functionality. New commands are added alongside existing ones. The permission error helper can be adopted by existing commands incrementally.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should the permission error helper live in `cmd/` or `output/` package? (Chose `cmd/` since it's command-layer logic)
|
||||
- Should we add validation flags like `--dry-run`? (Deferred - can add later if needed)
|
||||
- Should board creation support additional optional fields from the API? (Started with minimal viable set)
|
||||
@@ -0,0 +1,31 @@
|
||||
## Why
|
||||
|
||||
The CLI currently supports read-only operations for projects and boards (list, get) but has no ability to create or delete them. Users who need to set up new projects and boards must use the Planka web UI. Additionally, write operations that fail due to insufficient permissions return raw API errors rather than actionable guidance.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add `project create` command with `--name`, `--type`, and optional `--description` flags
|
||||
- Add `project delete` command accepting a project ID
|
||||
- Add `board create` command with `--project`, `--name`, and optional `--position` flags
|
||||
- Add `board delete` command accepting a board ID
|
||||
- Add `CreateProject`, `DeleteProject`, `CreateBoard`, `DeleteBoard` client methods
|
||||
- Add a shared `friendlyAPIError` helper in the `cmd` package that translates API error status codes (401, 403, 404) into human-readable messages with permission context
|
||||
- New commands use the shared helper; existing commands are unchanged (can adopt it later)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `permission-errors`: Shared helper for translating API permission/auth errors into user-friendly CLI messages
|
||||
|
||||
### Modified Capabilities
|
||||
- `api-client`: Adding CreateProject, DeleteProject, CreateBoard, DeleteBoard client methods
|
||||
- `cli-commands`: Adding project create/delete and board create/delete subcommands
|
||||
|
||||
## Impact
|
||||
|
||||
- `client/projects.go` — new CreateProject, DeleteProject methods
|
||||
- `client/boards.go` — new CreateBoard, DeleteBoard methods
|
||||
- `cmd/errors.go` — new file with friendlyAPIError helper
|
||||
- `cmd/project.go` — new create and delete subcommands
|
||||
- `cmd/board.go` — new create and delete subcommands
|
||||
- No new dependencies, no model changes, no breaking changes
|
||||
@@ -0,0 +1,39 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### 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}`
|
||||
@@ -0,0 +1,83 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Project commands
|
||||
The system SHALL provide a `project` command group with subcommands `list`, `get`, `create`, and `delete`. `pcli project list` SHALL call the client's ListProjects method and output the result. `pcli project get <id>` SHALL accept a project ID as a positional argument, call GetProject, and output the result. `pcli project create` SHALL create a new project. `pcli project delete <id>` SHALL delete a project.
|
||||
|
||||
#### Scenario: List projects
|
||||
- **WHEN** `pcli project list` is executed
|
||||
- **THEN** the system SHALL output all accessible projects
|
||||
|
||||
#### Scenario: Get project by ID
|
||||
- **WHEN** `pcli project get <id>` is executed with a valid project ID
|
||||
- **THEN** the system SHALL output the project details
|
||||
|
||||
#### Scenario: Get project missing ID
|
||||
- **WHEN** `pcli project get` is executed without an ID argument
|
||||
- **THEN** the system SHALL print an error indicating the ID is required and exit with code 1
|
||||
|
||||
#### Scenario: Create project
|
||||
- **WHEN** `pcli project create --name "My Project" --type private --description "A test project"` is executed
|
||||
- **THEN** the system SHALL create the project and output the created project
|
||||
|
||||
#### Scenario: Create project missing required flags
|
||||
- **WHEN** `pcli project create` is executed without `--name` or `--type`
|
||||
- **THEN** the system SHALL print an error indicating the required flags and exit with code 1
|
||||
|
||||
#### Scenario: Create project with insufficient permissions
|
||||
- **WHEN** `pcli project create` is executed with invalid API credentials
|
||||
- **THEN** the system SHALL output "create project: authentication failed — check your API key"
|
||||
|
||||
#### Scenario: Delete project
|
||||
- **WHEN** `pcli project delete <id>` is executed with a valid project ID
|
||||
- **THEN** the system SHALL delete the project and output a success confirmation
|
||||
|
||||
#### Scenario: Delete project with insufficient permissions
|
||||
- **WHEN** `pcli project delete <id>` is executed by a user without project manager permissions
|
||||
- **THEN** the system SHALL output "delete project: permission denied (requires project manager role)"
|
||||
|
||||
#### Scenario: Delete project not found
|
||||
- **WHEN** `pcli project delete <id>` is executed with a non-existent project ID
|
||||
- **THEN** the system SHALL output "delete project: not found — the resource may not exist or you may not have access to it"
|
||||
|
||||
### Requirement: Board commands
|
||||
The system SHALL provide a `board` command group with subcommands `get`, `actions`, `create`, and `delete`. `pcli board get <id>` SHALL accept a board ID as a positional argument and output the board details. `pcli board actions <id>` SHALL accept a board ID and an optional `--limit` flag (int, default 0) and output the board's action history. `pcli board create` SHALL create a new board. `pcli board delete <id>` SHALL delete a board.
|
||||
|
||||
#### Scenario: Get board
|
||||
- **WHEN** `pcli board get <id>` is executed
|
||||
- **THEN** the system SHALL output the board details including its lists
|
||||
|
||||
#### Scenario: List board actions
|
||||
- **WHEN** `pcli board actions <id>` is executed
|
||||
- **THEN** the system SHALL output the board's action history
|
||||
|
||||
#### Scenario: List board actions with limit
|
||||
- **WHEN** `pcli board actions <id> --limit 10` is executed
|
||||
- **THEN** the system SHALL output at most 10 action entries
|
||||
|
||||
#### Scenario: Create board
|
||||
- **WHEN** `pcli board create --project <id> --name "Development Board" --position 65536` is executed
|
||||
- **THEN** the system SHALL create the board and output the created board
|
||||
|
||||
#### Scenario: Create board missing required flags
|
||||
- **WHEN** `pcli board create` is executed without `--project` or `--name`
|
||||
- **THEN** the system SHALL print an error indicating the required flags and exit with code 1
|
||||
|
||||
#### Scenario: Create board with insufficient permissions
|
||||
- **WHEN** `pcli board create --project <id> --name "Board"` is executed by a user without project manager permissions on the project
|
||||
- **THEN** the system SHALL output "create board: permission denied (requires project manager role)"
|
||||
|
||||
#### Scenario: Create board project not found
|
||||
- **WHEN** `pcli board create --project <id> --name "Board"` is executed with a project the user cannot access
|
||||
- **THEN** the system SHALL output "create board: not found — the resource may not exist or you may not have access to it"
|
||||
|
||||
#### Scenario: Delete board
|
||||
- **WHEN** `pcli board delete <id>` is executed with a valid board ID
|
||||
- **THEN** the system SHALL delete the board and output a success confirmation
|
||||
|
||||
#### Scenario: Delete board with insufficient permissions
|
||||
- **WHEN** `pcli board delete <id>` is executed by a user without project manager permissions
|
||||
- **THEN** the system SHALL output "delete board: permission denied (requires project manager role)"
|
||||
|
||||
#### Scenario: Delete board not found
|
||||
- **WHEN** `pcli board delete <id>` is executed with a non-existent board ID
|
||||
- **THEN** the system SHALL output "delete board: not found — the resource may not exist or you may not have access to it"
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Permission error translation helper
|
||||
The system SHALL provide a `friendlyAPIError` function in the `cmd` package that translates APIError status codes into human-readable messages with operation context.
|
||||
|
||||
#### Scenario: Authentication error (401)
|
||||
- **WHEN** an APIError with StatusCode 401 is passed to friendlyAPIError with operation "create board"
|
||||
- **THEN** the function SHALL return an error with message "create board: authentication failed — check your API key"
|
||||
|
||||
#### Scenario: Permission denied (403) with hint
|
||||
- **WHEN** an APIError with StatusCode 403 is passed to friendlyAPIError with operation "create board" and permissionHint "requires project manager role"
|
||||
- **THEN** the function SHALL return an error with message "create board: permission denied (requires project manager role)"
|
||||
|
||||
#### Scenario: Permission denied (403) without hint
|
||||
- **WHEN** an APIError with StatusCode 403 is passed to friendlyAPIError with operation "create project" and empty permissionHint
|
||||
- **THEN** the function SHALL return an error with message "create project: permission denied"
|
||||
|
||||
#### Scenario: Not found (404)
|
||||
- **WHEN** an APIError with StatusCode 404 is passed to friendlyAPIError with operation "delete board"
|
||||
- **THEN** the function SHALL return an error with message "delete board: not found — the resource may not exist or you may not have access to it"
|
||||
|
||||
#### Scenario: Non-API error
|
||||
- **WHEN** a non-APIError (e.g., network error) is passed to friendlyAPIError
|
||||
- **THEN** the function SHALL return the original error unchanged
|
||||
|
||||
#### Scenario: Unknown status code
|
||||
- **WHEN** an APIError with StatusCode 500 is passed to friendlyAPIError
|
||||
- **THEN** the function SHALL return the original APIError unchanged
|
||||
@@ -0,0 +1,28 @@
|
||||
## 1. Permission Error Helper
|
||||
|
||||
- [x] 1.1 Create `cmd/errors.go` with `friendlyAPIError(err error, operation string, permissionHint string) error` function
|
||||
- [x] 1.2 Handle 401 (auth failed), 403 (permission denied with optional hint), 404 (not found / no access), and passthrough for non-API errors and unknown status codes
|
||||
|
||||
## 2. Client Methods
|
||||
|
||||
- [x] 2.1 Add `CreateProject(ctx, fields) (*model.Project, error)` to `client/projects.go` — POST /api/projects
|
||||
- [x] 2.2 Add `DeleteProject(ctx, id) error` to `client/projects.go` — DELETE /api/projects/{id}
|
||||
- [x] 2.3 Add `CreateBoard(ctx, projectId, fields) (*model.Board, error)` to `client/boards.go` — POST /api/projects/{projectId}/boards
|
||||
- [x] 2.4 Add `DeleteBoard(ctx, id) error` to `client/boards.go` — DELETE /api/boards/{id}
|
||||
|
||||
## 3. Project Commands
|
||||
|
||||
- [x] 3.1 Add `projectCreateCmd` to `cmd/project.go` with `--name` (required), `--type` (required, values: public/private), and `--description` (optional) flags
|
||||
- [x] 3.2 Add `projectDeleteCmd` to `cmd/project.go` accepting a positional project ID argument
|
||||
- [x] 3.3 Wire both commands with `friendlyAPIError` — create uses empty hint, delete uses "requires project manager role"
|
||||
|
||||
## 4. Board Commands
|
||||
|
||||
- [x] 4.1 Add `boardCreateCmd` to `cmd/board.go` with `--project` (required), `--name` (required), and `--position` (optional, default 65536) flags
|
||||
- [x] 4.2 Add `boardDeleteCmd` to `cmd/board.go` accepting a positional board ID argument
|
||||
- [x] 4.3 Wire both commands with `friendlyAPIError` using "requires project manager role" hint
|
||||
|
||||
## 5. Verification
|
||||
|
||||
- [x] 5.1 Run `go build` to verify compilation
|
||||
- [x] 5.2 Run existing test suite (`test.sh`) to verify no regressions
|
||||
@@ -52,7 +52,7 @@ The system SHALL implement cursor-based pagination for all list endpoints that s
|
||||
- **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}`).
|
||||
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
|
||||
@@ -62,8 +62,16 @@ The client SHALL provide methods to list all accessible projects (`GET /projects
|
||||
- **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}`) and list board actions (`GET /boards/{boardId}/actions`) with pagination support.
|
||||
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
|
||||
@@ -73,6 +81,14 @@ The client SHALL provide a method to get a single board by ID (`GET /boards/{id}
|
||||
- **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.
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ The system SHALL provide a root command `pcli` that serves as the entry point. T
|
||||
- **AND** the system SHALL exit with code 1
|
||||
|
||||
### Requirement: Project commands
|
||||
The system SHALL provide a `project` command group with subcommands `list` and `get`. `pcli project list` SHALL call the client's ListProjects method and output the result. `pcli project get <id>` SHALL accept a project ID as a positional argument, call GetProject, and output the result.
|
||||
The system SHALL provide a `project` command group with subcommands `list`, `get`, `create`, and `delete`. `pcli project list` SHALL call the client's ListProjects method and output the result. `pcli project get <id>` SHALL accept a project ID as a positional argument, call GetProject, and output the result. `pcli project create` SHALL create a new project. `pcli project delete <id>` SHALL delete a project.
|
||||
|
||||
#### Scenario: List projects
|
||||
- **WHEN** `pcli project list` is executed
|
||||
@@ -36,8 +36,32 @@ The system SHALL provide a `project` command group with subcommands `list` and `
|
||||
- **WHEN** `pcli project get` is executed without an ID argument
|
||||
- **THEN** the system SHALL print an error indicating the ID is required and exit with code 1
|
||||
|
||||
#### Scenario: Create project
|
||||
- **WHEN** `pcli project create --name "My Project" --type private --description "A test project"` is executed
|
||||
- **THEN** the system SHALL create the project and output the created project
|
||||
|
||||
#### Scenario: Create project missing required flags
|
||||
- **WHEN** `pcli project create` is executed without `--name` or `--type`
|
||||
- **THEN** the system SHALL print an error indicating the required flags and exit with code 1
|
||||
|
||||
#### Scenario: Create project with insufficient permissions
|
||||
- **WHEN** `pcli project create` is executed with invalid API credentials
|
||||
- **THEN** the system SHALL output "create project: authentication failed — check your API key"
|
||||
|
||||
#### Scenario: Delete project
|
||||
- **WHEN** `pcli project delete <id>` is executed with a valid project ID
|
||||
- **THEN** the system SHALL delete the project and output a success confirmation
|
||||
|
||||
#### Scenario: Delete project with insufficient permissions
|
||||
- **WHEN** `pcli project delete <id>` is executed by a user without project manager permissions
|
||||
- **THEN** the system SHALL output "delete project: permission denied (requires project manager role)"
|
||||
|
||||
#### Scenario: Delete project not found
|
||||
- **WHEN** `pcli project delete <id>` is executed with a non-existent project ID
|
||||
- **THEN** the system SHALL output "delete project: not found — the resource may not exist or you may not have access to it"
|
||||
|
||||
### Requirement: Board commands
|
||||
The system SHALL provide a `board` command group with subcommands `get` and `actions`. `pcli board get <id>` SHALL accept a board ID as a positional argument and output the board details. `pcli board actions <id>` SHALL accept a board ID and an optional `--limit` flag (int, default 0) and output the board's action history.
|
||||
The system SHALL provide a `board` command group with subcommands `get`, `actions`, `create`, and `delete`. `pcli board get <id>` SHALL accept a board ID as a positional argument and output the board details. `pcli board actions <id>` SHALL accept a board ID and an optional `--limit` flag (int, default 0) and output the board's action history. `pcli board create` SHALL create a new board. `pcli board delete <id>` SHALL delete a board.
|
||||
|
||||
#### Scenario: Get board
|
||||
- **WHEN** `pcli board get <id>` is executed
|
||||
@@ -51,6 +75,34 @@ The system SHALL provide a `board` command group with subcommands `get` and `act
|
||||
- **WHEN** `pcli board actions <id> --limit 10` is executed
|
||||
- **THEN** the system SHALL output at most 10 action entries
|
||||
|
||||
#### Scenario: Create board
|
||||
- **WHEN** `pcli board create --project <id> --name "Development Board" --position 65536` is executed
|
||||
- **THEN** the system SHALL create the board and output the created board
|
||||
|
||||
#### Scenario: Create board missing required flags
|
||||
- **WHEN** `pcli board create` is executed without `--project` or `--name`
|
||||
- **THEN** the system SHALL print an error indicating the required flags and exit with code 1
|
||||
|
||||
#### Scenario: Create board with insufficient permissions
|
||||
- **WHEN** `pcli board create --project <id> --name "Board"` is executed by a user without project manager permissions on the project
|
||||
- **THEN** the system SHALL output "create board: permission denied (requires project manager role)"
|
||||
|
||||
#### Scenario: Create board project not found
|
||||
- **WHEN** `pcli board create --project <id> --name "Board"` is executed with a project the user cannot access
|
||||
- **THEN** the system SHALL output "create board: not found — the resource may not exist or you may not have access to it"
|
||||
|
||||
#### Scenario: Delete board
|
||||
- **WHEN** `pcli board delete <id>` is executed with a valid board ID
|
||||
- **THEN** the system SHALL delete the board and output a success confirmation
|
||||
|
||||
#### Scenario: Delete board with insufficient permissions
|
||||
- **WHEN** `pcli board delete <id>` is executed by a user without project manager permissions
|
||||
- **THEN** the system SHALL output "delete board: permission denied (requires project manager role)"
|
||||
|
||||
#### Scenario: Delete board not found
|
||||
- **WHEN** `pcli board delete <id>` is executed with a non-existent board ID
|
||||
- **THEN** the system SHALL output "delete board: not found — the resource may not exist or you may not have access to it"
|
||||
|
||||
### Requirement: Card commands
|
||||
The system SHALL provide a `card` command group with subcommands: `list`, `get`, `create`, `update`, `delete`, `duplicate`, `move`, `assign`, `unassign`, `add-label`, `remove-label`, `actions`.
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Permission error translation helper
|
||||
The system SHALL provide a `friendlyAPIError` function in the `cmd` package that translates APIError status codes into human-readable messages with operation context.
|
||||
|
||||
#### Scenario: Authentication error (401)
|
||||
- **WHEN** an APIError with StatusCode 401 is passed to friendlyAPIError with operation "create board"
|
||||
- **THEN** the function SHALL return an error with message "create board: authentication failed — check your API key"
|
||||
|
||||
#### Scenario: Permission denied (403) with hint
|
||||
- **WHEN** an APIError with StatusCode 403 is passed to friendlyAPIError with operation "create board" and permissionHint "requires project manager role"
|
||||
- **THEN** the function SHALL return an error with message "create board: permission denied (requires project manager role)"
|
||||
|
||||
#### Scenario: Permission denied (403) without hint
|
||||
- **WHEN** an APIError with StatusCode 403 is passed to friendlyAPIError with operation "create project" and empty permissionHint
|
||||
- **THEN** the function SHALL return an error with message "create project: permission denied"
|
||||
|
||||
#### Scenario: Not found (404)
|
||||
- **WHEN** an APIError with StatusCode 404 is passed to friendlyAPIError with operation "delete board"
|
||||
- **THEN** the function SHALL return an error with message "delete board: not found — the resource may not exist or you may not have access to it"
|
||||
|
||||
#### Scenario: Non-API error
|
||||
- **WHEN** a non-APIError (e.g., network error) is passed to friendlyAPIError
|
||||
- **THEN** the function SHALL return the original error unchanged
|
||||
|
||||
#### Scenario: Unknown status code
|
||||
- **WHEN** an APIError with StatusCode 500 is passed to friendlyAPIError
|
||||
- **THEN** the function SHALL return the original APIError unchanged
|
||||
Reference in New Issue
Block a user