feat(migration): import from Planka v2 - #3552
Conversation
Preview DeploymentPreview deployments for this PR are available at:
The preview environment will start automatically on first visit. Subsequent pushes to this PR will update the Run locally with Dockerdocker pull ghcr.io/go-vikunja/vikunja:pr-3552
docker run -p 3456:3456 ghcr.io/go-vikunja/vikunja:pr-3552Last updated for commit d797472 |
tink-bot
left a comment
There was a problem hiding this comment.
🤖 PR Swarm — automated review, round 1. See inline comments.
|
🤖 PR Swarm — automated review. Panel: bughunter · security · conventions · design · verifier — round 3 (final, round cap) @ d726567 (+270/−87 this round, PR +3328/−130) Verdict: ✅ APPROVE → all actionable items fixed; open question answered (accepted as is)Round-3 pre-fix: 1 MEDIUM (attachment downloads on S3-backed Planka), 1 convergent MEDIUM (partial comment order), rest LOW/NIT; security found nothing new. Round cap reached — the round-3 fixes (redirect split for downloads, comment order, session cleanup, dedupe) are covered by unit/webtests but were not re-driven at runtime. ❓ Needs your input (0) — answered
✅ Fixed this round (11)
💤 Resolved as nit (1)
🔎 Runtime verification (round 3, fake Planka stand-in — no real instance)✅ cross-host 302 on Reviewer summaries
Previous rounds (2)round 2 @ 51d6a0b (36m) — 💬 APPROVE WITH NITS → 17 fixed, 2 nits: redirect guard on data path, single deadline, half cursor, OAuth v2 event registration, frontend all-v2, novalidate. round 1 @ 645c870 (54m) — |
tink-bot
left a comment
There was a problem hiding this comment.
🤖 PR Swarm — automated review, round 2. See inline comments.
tink-bot
left a comment
There was a problem hiding this comment.
🤖 PR Swarm — automated review, round 3 (final, round cap). See inline comments.
771d002 to
a91c1f4
Compare
| } | ||
|
|
||
| // ErrCodeNoPlankaAtURL holds the unique world-error code of this error | ||
| const ErrCodeNoPlankaAtURL = 14010 |
There was a problem hiding this comment.
where is the 14xxx error block first defined?
There was a problem hiding this comment.
pkg/modules/migration/errors.go — 14001 ErrNotAZipFile … 14005 ErrMigrationAlreadyRunning; 14006–14010 continue that block. Note: pkg/models/error.go also uses 14001/14002 for API-token errors (pre-existing double booking — the frontend keys translations by bare code, so error.14001 renders the api-token text for the zip error). If you'd rather, I move the planka ones (or the whole migration block) to a fresh range, e.g. 18xxx — say which.
There was a problem hiding this comment.
Implemented in d79747228 — 14201 invalid credentials, 14202 login step required, 14203 invalid config, 14204 no planka at url (+ error.142xx keys).
Payloads can carry credentials and user data; the migration event carries the migrator with its credentials.
…mported bucket Creating a done task puts it in the view's default done bucket; moving it into the imported bucket flipped it back to open. The imported state (incl. done_at) is restored in bulk after the task loop.
…bounded json decode Migrators that talk to a user-supplied host need a client with a redirect policy, a cap on response bodies and no retries on permanent errors (utils.ErrDoNotRetry). Diagnostic body reads in the shared helpers are capped.
Authenticates with an API key, a JWT or username + password (Planka checks Bearer before X-Api-Key, so only one header is ever sent; downloads use the accessToken cookie). The synchronous credential check has one 15s deadline and no retries. Redirects to another host or to plain http are refused for the api, followed without credentials for attachment downloads. Response bodies are capped, url userinfo is stripped, pending login steps (totp, terms) and non-planka endpoints are reported as distinct client errors (142xx).
Pages archived cards and comments (Planka requires both cursor fields, pages until empty with a hard cap, partial results are kept), reads base custom field definitions from the projects payload and rejects Planka v1 payloads.
Everything lands under a "Migrated from Planka" root project; projects with several boards become a parent with one child per board. Lists become kanban buckets (trash skipped), closed/archived cards are done, labels keep their Planka colours, checklists, custom fields and link attachments are rendered into the description, comments are prefixed with the author unless it is the importing user. No assignees, memberships or other users' data.
Credentials migrators are verified synchronously before the async migration is queued (CredentialsChecker). Routes live on v2 only; the listener learns the migrator via a route-free RegisterMigratorForEvents, which the oauth v2 registrar uses as well. Shared status/migrate registration moved to migration_shared.go.
URL + API key or username/password, talking to the v2 migration api (all code migrators now use v2). Generic credentials form component with planka-specific help texts, status screens for running/previous migrations, translated error codes.
d797472 to
ecdfcd4
Compare












Vikunja had no way to import Planka boards. Planka has no export, only its REST API — this adds a
plankamigrator that pulls projects, boards, lists, cards, labels, checklists, custom fields, comments and attachments live from a Planka v2 instance and feeds them throughInsertFromStructure.Third migrator flavour next to OAuth-code and file upload: "credentials" (URL + API key, or URL + username/password). Credentials are verified synchronously (400 on bad url/credentials/TOTP, 10s cap, no retries) before the async migration is queued; they are never persisted or logged (they ride the in-process migration event only). Routes live on v2 only (
pkg/routes/api/v2/migration_credentials.go, always enabled); the listener learns the migrator via a route-freeRegisterMigratorForEvents.Mapping decisions (per
plans/feat-planka-migration.md):closed/archivelist cards andisClosedcards → done;trashskipped. Archive cards paged viaGET /api/lists/:id/cards.files.maxsize, skipped on failure; object-storage redirects followed with the Planka credentials stripped), cover attachment kept.type) → import fails with a clear message (async, reported by email).Also fixes a pre-existing importer bug: done tasks placed in an imported bucket lost their done state (moving out of the default "Done" bucket flipped it back).
Uses
richtext.MarkdownToHTMLinstead of moving trello's goldmark helper.How to verify
POST /api/users/:id/api-key) or username and password of a local account without 2FA. Click Start import.Expected: an inline error ("Could not reach a Planka API at the given url…" / "Planka rejected the provided credentials…"), no migration queued.
Expected: error asking to log in to Planka once / use an API key instead (
400, code14007).curl -X POST /api/v2/migration/planka/migrate -H 'Authorization: Bearer <jwt>' -d '{"url":"https://planka.example.com","token":"<api key>"}'→200 {"message":"Migration was started successfully."}; omittingurlor credentials → 400;GET /api/v1/infolistsplankainavailable_migrators.Docs stub (for the website repo)