Repository layer code review: fix critical transaction bug and high-severity issues #16
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Code Review Summary
OpenCode performed a detailed review of the repository layer (domain interfaces + SQLite/YDB adapters). Found 1 Critical, 5 High, 9 Medium, 10 Low issues plus significant test coverage gaps.
Priority 1 — Fix First
🔴 C1.
CreateBatchcommits partial failures & swallows commit errorsFile:
internal/kurious/adapters/sqlite_course_repository.go:292-327The loop
_, err := stmt.ExecContext(...)shadows the outererrvia:=. When a row insert fails, the defer reads the outer (still-nil)err, takes the else branch, and COMMITS the partially-inserted batch. The caller gets an error but half the rows are persisted — transaction guarantee is broken.On the happy path,
tx.Commit()failure is silently dropped becauseerr = errors.Join(err, errTx)writes to a discarded local (no named return).Fix: Use named return
(err error), rename loop var toexecErr.🟠 H2.
Organization().Listdrops query argumentsFile:
internal/kurious/adapters/sqlite_organization_repository.go:190r.db.SelectContext(ctx, &organizations, query)—argsnot passed! TheIN (?,?)placeholders are unbound.ListwithIDsalways errors.Fix:
r.db.SelectContext(ctx, &organizations, query, args...)🟠 H3.
CourseRepository.Getnever returnsErrNotFoundFile:
internal/kurious/adapters/sqlite_course_repository.go:279-281Wraps
sql.ErrNoRowswithfmt.Errorfinstead of translating tocerrors.ErrNotFound. Breaks the port contract. Organization and LearningCategory repos handle this correctly.Fix: Add
errors.Is(err, sql.ErrNoRows)check before wrapping.Priority 2 — Security & Contract
OrderByconcatenated raw into query (sqlite_course_repository.go:79). Mitigated by HTTP layer allowlist but repo should validate independently.ListignoresOffset,OrderBy,Ascending; cursor never fed back. Port contract divergence.service.go:57-65still wires it.Priority 3 — Consistency & Cleanup (Medium)
Createreturns emptyCourse{}— no created entity backtime.Now()— local vs UTC across reposOrganization.CreateOrganization.Getwith empty params returns arbitrary rowNextPageTokencomputed but never consumed (dead cursor)ListandlistCountmemory_mapperhas no mutex — potential data raceerrors.New(...)instead of sentinelPriority 4 — Typos & Style (Low)
OrganizaitonID,sqliteLearingCategoryRepository,nullable.ValutPtr(),"organizaitons"trace, duplicatetrace.WithSpanKind, no-opClose()timeout, deadFromDomainmethods, config naming drift.Test Coverage Gaps
No test covers C1, H2, or H3. Missing: transaction rollback, Organization.List with IDs, Course.Get not-found, all filter/order/pagination combos, ListStatistics, ListStats, Delete, all YDB repos. Existing
TestListLimitOffsetis fragile.Issue #16 Resolved — Merged
Root Cause
Code review identified a critical transaction bug and multiple high-severity issues in the repository layer (
sqlite_course_repository.go).Key Changes
map[string]booltomap[string]struct{}(zero-size value type, idiomatic Go for set membership)7bf95f1)Review Feedback Addressed
map[string]struct{}for the fields whitelist → implemented in590fed2, lookup updated to comma-ok idiomTest Results
go build ./...— cleango test ./...— all packages pass7e83e5a