Support upserting cards during JSON bulk import - #399
Conversation
…ng progress POST /api/card previously did an unconditional JPA save, so re-importing a JSON card with an existing id replaced the whole row - resetting FSRS scheduling fields and readiness with the empty card the import always sends. Now an existing card only gets its data updated, preserving learning progress, readiness, and flags; new ids are created as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ek3VrnXDFQjs6R2HY9DNxD
OverviewThis PR changes Code quality / style
Potential issues / risks
Test coverage
Performance / security
Overall a small, focused change that achieves what the description states for the JSON-import happy path. The main open question before merging: are the upsert semantics meant to apply repo-wide to every caller of |
A card id colliding with a card in a different source (or with a different type) is now rejected with 400 instead of silently updating the other source's card. A missing id is also rejected with 400 rather than failing inside the repository lookup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ek3VrnXDFQjs6R2HY9DNxD
|
Addressed the review in d98f013:
On the open question: yes, the upsert semantics are intended for every caller of Generated by Claude Code |
|
Review: Support upserting cards during JSON bulk import Overview Strengths
Issues / Suggestions
Security Performance Overall this is a solid, well-tested fix that also closes an unrelated latent data-loss bug in the shared bulk-creation path. The suggestions above (transaction boundary, clarifying which fields are/aren't updated on upsert) are worth considering but not blocking. |
Wraps createCard in a transaction consistent with updateCard, includes the requested type in the type-collision error message, and extends the E2E suite with a type-collision rejection test plus an assertion that sourcePageNumber is preserved when an existing card is updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ek3VrnXDFQjs6R2HY9DNxD
|
Follow-ups from the second review round landed in e28b3e3:
On point 1: yes, updating only Generated by Claude Code |
Review: Support upserting cards during JSON bulk import (#399)OverviewTurns Code quality & conventions
Potential issues
Test coverageStrong addition — three new Playwright tests cover:
Tests correctly follow the repo's Playwright conventions (role/label-based selectors, no assertion messages, verifying via direct DB queries for backend state). One nice-to-have: none of the new tests assert the HTTP status code returned for the rejected cases — only the resulting UI text/DB state — so a regression that changed the guard from 400 to, say, 500 wouldn't be caught. Not blocking given the UI-level assertions already verify the practical behavior. SecurityThe cross-source ownership check is a good defense against one source's JSON import silently overwriting/hijacking a card id that belongs to a different source — nice catch, and it's properly authorization-scoped behind the existing SummarySolid, well-tested change that matches existing code patterns in the file. Main suggestions are documentation (mention the new validation behaviors in the PR description) and two minor polish items (error message clarity, frontend "as drafts" wording). No blocking issues found. |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ek3VrnXDFQjs6R2HY9DNxD
Review: Support upserting cards during JSON bulk importOverall this is a solid, well-scoped fix: it closes a real data-loss bug (a colliding ID used to silently overwrite the whole row, resetting FSRS progress), adds sensible validation, and comes with good E2E coverage for the new failure/success paths. A few things worth a look before merging. Code quality / correctness
Style / conventions
Test coverage
Security
SummaryThe core upsert logic and validation are correct and well tested at the E2E level. The main thing I'd want addressed before merge is the missing null-check on |
A null data payload now returns 400 instead of failing the NOT NULL constraint at flush time, so an update can never blank an existing card's content. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ek3VrnXDFQjs6R2HY9DNxD
|
The pre-merge item from the latest review is addressed in 75fb981: a null Notes on the remaining points: Generated by Claude Code |
Review: Support upserting cards during JSON bulk importOverviewThis PR changes Code quality & style
Potential issues
PerformanceNo meaningful concerns — the extra Security
Test coverageThe three new Playwright tests are solid and test from the user's perspective (via the JSON import dialog UI plus DB assertions), covering:
This is good coverage of the new behavior described in the PR. As noted above, the two basic required-field validations ( Overall this is a well-scoped, well-tested fix for a real bug. The one thing I'd want the author to double check before merging is the "page number and other request fields are dropped on update" behavior against all current callers of |
Summary
Enable the JSON bulk import feature to update existing cards with matching IDs instead of only creating new cards. This allows users to refresh card content from JSON sources while preserving learning metadata.
Note:
POST /api/cardis shared by all card-creation flows, so the upsert semantics (and the fix for silently overwriting existing rows, including their FSRS progress) apply beyond JSON import.Key Changes
idis rejected with 400 instead of failing inside the repository lookup.createCardis now@Transactional, consistent withupdateCard, so the read-then-write upsert is atomic.sourcePageNumberare preserved on updatesImplementation Details
The upsert logic uses Java's Optional API to handle both cases:
datafield, preserving all other attributesThis approach maintains backward compatibility while enabling the new upsert functionality.
https://claude.ai/code/session_01Ek3VrnXDFQjs6R2HY9DNxD