- Implemented `pcli project export` command to export project hierarchy as JSON. - Added `pcli project import` command to import project data from JSON. - Created user client to fetch user details for comment attribution. - Introduced new data structures for export and import processes. - Ensured name-based references in exports and handled conflicts during imports. - Added versioning and progress reporting for import operations. - Updated documentation and specifications for new features.
5.7 KiB
Context
pcli currently supports CRUD operations on individual Planka resources but has no way to bulk export or import project data. The Planka API returns nested data for boards (lists, cards, labels, card-labels, memberships) but requires per-card fetches for comments, task lists, and tasks. There is no existing user client — we need one to resolve comment author userIds to names.
Goals / Non-Goals
Goals:
- Export an entire project (or single board) to a portable JSON file via stdout
- Import from that JSON file, creating all resources with fresh IDs on the target instance
- Use names (not IDs) as portable identity throughout the export format
- Preserve comment authorship textually via
(Original comment by <username>)prefix - Fail safely on import if project+board combination already exists
Non-Goals:
- Attachments/file export (binary assets — out of scope)
- Merge/update import (replace-only for now)
- Multi-board filter on export (single
--boardflag only) - User/membership migration (not portable across instances)
- Incremental/differential export
Decisions
1. Export format: flat nested JSON, no ID references
Decision: Export uses a fully nested structure where child objects are inline under their parent. Cards reference lists and labels by name, not ID.
Rationale: Avoids needing an ID remapping table in the export file. Makes the format human-readable and trivially parseable. Names are the natural portable identifier.
Alternative considered: Flat arrays with ID cross-references (like the Planka API response format). Rejected — adds complexity for both export and import with no benefit since IDs are regenerated anyway.
2. User resolution: one-time fetch during export
Decision: Export fetches the full user list once at the start, builds a userId→name map, and uses it to prefix comments.
Rationale: Comment API returns userId but not the user's name. A single bulk fetch is more efficient than per-comment lookups. Falls back to "unknown user" for unresolvable IDs.
Implementation: New client/users.go with ListUsers() method hitting GET /api/users (Planka v2 endpoint).
3. Import conflict detection: project+board name pair
Decision: Import checks if the target Planka instance already has a board with the same name under the same project. If so, it fails immediately before creating anything.
Rationale: This is the simplest safe behavior. Project existing alone is fine (we add the board to it). Board name collision means data would overlap, so we fail rather than risk duplicates.
Implementation flow:
- List projects, find by name → use existing or create new
- Get project's boards (via board list filtered by project)
- For each board in export: check name against existing boards → fail if collision
- Proceed with creation only after all boards pass the check
4. Export data gathering strategy
Decision: Use the existing board GET response (includes lists, cards, labels, card-labels) as the base, then fetch per-card details (comments, task lists, tasks) individually.
Rationale: Board GET already returns most of what we need in one call. Only comments and task lists/tasks require additional fetches. This minimizes API calls while getting complete data.
Sequence per board:
GET /api/boards/<id>→ lists, cards, labels, card-labels- For each card:
GET /api/cards/<id>→ task lists, tasks - For each card:
GET /api/cards/<id>/comments→ comments
5. Command structure: subcommands under project
Decision: pcli project export and pcli project import as subcommands of the existing project command.
Rationale: Export/import are project-level operations. Keeps the CLI hierarchy clean and discoverable.
6. Import input: stdin and --file flag
Decision: Support both pcli project import < file.json and pcli project import --file file.json.
Rationale: stdin is natural for piping; --file is explicit and clearer in scripts. Minimal implementation cost to support both.
7. Import creation order
Decision: Create resources top-down: project → board → lists → labels → cards → card-labels → task lists → tasks → comments. Build name→ID maps as each level is created.
Rationale: Each child resource needs its parent's ID. Creating top-down and maintaining maps (e.g., listName→listID) lets us resolve references as we go.
ID mapping during import:
listName → listID(created when lists are created)labelName+color → labelID(created when labels are created)- Cards are created with the resolved listID, then card-label associations are created separately
Risks / Trade-offs
[Slow export for large projects] → Accept the cost. Per-card fetches for comments/tasks could be slow with hundreds of cards. Could be optimized later with concurrency but keeping it simple (sequential) for now.
[Comment attribution may be inaccurate] → If users have been deleted or the user list endpoint requires admin permissions, some comments may show "unknown user". This is acceptable — textual attribution is best-effort.
[No atomicity on import] → If import fails partway through (e.g., API error on card 50 of 100), partial data will exist on the target. Mitigation: the conflict check happens upfront before any creation starts, so the most likely failure mode (name collision) is caught early. For API failures mid-import, the user would need to manually clean up or re-run after fixing the issue.
[Label matching by name+color] → Two labels with the same name but different colors would be treated as distinct. This matches Planka's model where labels are board-scoped and identified by name+color.