Commit Graph
50 Commits
Author SHA1 Message Date
XingfenD cc1470f6e4 test: portsfake + router contract test + handler unit tests (Task 27)
- internal/ports/portsfake: hand-written in-memory fakes for all 9 ports
  (Users/Libraries/Books/Progress/Bookmarks/RateLimiter/Scanner/Media/Uploads);
  error semantics mirror the real store exactly — pgx.ErrNoRows for misses,
  store.ErrLastAdmin guard, and *pgconn.PgError{Code:23505} for unique
  violations (ports.IsUniqueViolation only accepts the PgError shape, so the
  fakes must produce it to exercise the 409 branch without touching prod code)
- Media/Uploads fakes are hook-programmable: one field per error branch, so a
  test can force e.g. CompleteErr=ErrIncomplete without stubbing the rest
- Scanner fake is mutex-guarded + WaitForScan: handler fires ScanLibraryByID
  in a goroutine, tests stay deterministic
- router_test.go: TestRouterContract pins all 27 routes — any route table
  change now fails the test explicitly
- handler unit tests (~30 cases, no PG/Redis): users CRUD branches (self-delete
  400, last-admin 400, dup 409, 204 ok), library reserved names (contract:
  code=bad_request message=reserved_name, per original impl), upload sentinel
  mapping (413/400/404 per branch)
- NewRouter takes pure port interfaces; main.go distributes *store.Store
  across the 5 store ports at the composition root

Full gate green: gofmt, vet, go test -p 1 (real PG+Redis, 0 skip)
2026-09-14 23:23:10 +08:00
XingfenD 7baadedeb4 refactor(handlers): consume port interfaces, wire media+upload assembly (Task 25+26)
- H now holds only small port interfaces (UserStore/LibraryStore/BookStore/
  ProgressStore/BookmarkStore/RateLimiter/Scanner/Media/UploadSessions);
  NewRouter is the composition root distributing *store.Store and *redispkg.R
- ports.Media gains EnsurePage; ChaptersOf returns ports.Chapter (media's
  local duplicate dropped); *media.M now provably satisfies ports.Media;
  ports.UploadSessions gains LibraryID for root validation before Complete
- content.go: duplicated page-index cache + cover self-heal + page extract
  logic removed in favor of media service — same redis keys, same contract;
  path traversal check stays in handler (403 semantics preserved)
- getLibrary/getLibRow merged into getLib(c, id); idParam helper dedupes
  :id parsing; isUnique replaced by ports.IsUniqueViolation (Task 28 partial)
- main.go assembles media + upload and passes upload.U as scanner Sweeper

Full gate green: gofmt, vet, go test -p 1 (real PG+Redis, 0 skip)
2026-09-14 22:56:15 +08:00
XingfenD b9b7022212 refactor(scanner): merge add/update into ingest, use shared utilities
- add/update consolidated into ingest(isNew) — persist branch differs,
  error handling and cover path fully shared (Task 24)
- zipIndex/cover use bookfile.OpenReaderAt instead of hand-rolled
  open+stat pairs
- root containment check uses bookfile.Contains (local inside removed)
- cover write goes through media.WriteAtomic (now exported, log-free —
  callers own context); media no longer logs inside the atomic helper
2026-09-14 22:12:30 +08:00
XingfenD fbd5cd243d feat(upload): extract upload subsystem + move sweep to scanner ticker (B16)
- internal/upload owns chunked-upload domain logic (fingerprint resume,
  part tmp+rename, assemble, session sweep) and the shared UniquePath
  helper used by both single-file and chunked completion paths
- handlers/uploads.go is now HTTP-only: bind params, call upload.U, map
  sentinel errors to the unchanged status/code/message contract
- B16: session sweep moved out of the UploadInit request path onto the
  scanner ticker via a Sweeper hook (upload.U satisfies it)
- unit tests for the package without PG/Redis; contract pinned by the
  existing handler integration tests (all green)
2026-09-14 22:00:25 +08:00
XingfenD 443f4acfa9 feat(media): Media service with EnsureCover, EnsurePage, PageIndex, ChaptersOf, writeAtomic
CI / frontend (push) Canceled after 0s
CI / backend (push) Canceled after 0s
2026-09-14 19:50:34 +08:00
XingfenD 5ad277241c feat(bookfile): add Contains and OpenReaderAt utilities 2026-09-14 19:49:50 +08:00
XingfenD 18bd32e0f6 refactor(store): split into per-aggregate files, unexport pool, remove dead ListBookIDs 2026-09-14 19:49:18 +08:00
XingfenD 0c3b34c184 feat(ports): define consumer-side interfaces, sentinel errors, IsUniqueViolation 2026-09-14 19:47:27 +08:00
XingfenD f99f5a7878 docs: changelog for batch B (B1-B13 bug fixes); gofmt store.go 2026-09-14 19:46:16 +08:00
XingfenD 08b81fde60 fix(upload): retry on O_EXCL collision in single-file upload (B12) 2026-09-14 19:45:08 +08:00
XingfenD 8023a7ec5c fix(serve): check cover write errors, clean tmp only on failure path (B11) 2026-09-14 19:43:27 +08:00
XingfenD 0c5dfd4353 fix(library): reject reserved names (cache, .uploads) with 400 reserved_name (B8) 2026-09-14 19:42:52 +08:00
XingfenD 0e62d4aa18 fix(handlers): Upload io.Copy error → 500 not 413 (B6), Me distinguishes no-rows from DB errors (B7) 2026-09-14 19:42:25 +08:00
XingfenD b82a891c50 fix: upload part tmp+rename (B4), transactional last-admin DeleteUser (B5), RowsAffected order (B13) 2026-09-14 19:42:11 +08:00
XingfenD 8fbc58eaaa fix(redis,scanner): atomic IncrWindow Lua, lock rand/ctx fixes, ScanLock renewal, single-flight, error logging (B1,B2,B3,B9,B10,B11) 2026-09-14 19:40:53 +08:00
XingfenD 2c2f6d3116 chore: gofmt content_test.go 2026-09-14 19:38:33 +08:00
XingfenD f26aac103b fix(config): validate DATABASE_URL required and parseable, log redis disabled (B15) 2026-09-14 19:37:26 +08:00
XingfenD f0437139f3 fix(main): channel-based serve error instead of log.Fatalf in goroutine (B14) 2026-09-14 19:36:57 +08:00
XingfenD 6d3d23c3f9 feat(db): ordered migration system with advisory lock and baseline detection 2026-09-14 19:36:21 +08:00
XingfenD 0fe3bc3057 feat(bookmarks): api+store — per-user CRUD, owner-scoped 404, percent-ordered list 2026-09-08 01:03:44 +08:00
XingfenD b8da9fd92e feat(cbz): adapt reader to real archive structure — PageIndex skips macOS junk (__MACOSX/ and ._ AppleDouble were polluting pages with 163-byte blacks), pages endpoint derives chapters[{title,start}] from folder layout, imageless zip now lands state=error instead of an empty reader, CbzReader gains 目录 sidebar + prev/next chapter (TextReader pattern, rd-* classes); pagesidx cache key bumped for one-time invalidation; e2e on 311MB 調教開關:第二季.zip: 15048→7524 real pages, 52 chapters, all JPEGs 2026-09-08 00:22:13 +08:00
XingfenD 7d650107d2 feat(upload): resumable chunked upload protocol for large files — init(parts≤32MB)/status/complete endpoints with fingerprint-derived deterministic uploadId (same file resumes, no restart), sessions in BOOKS_DIR/.uploads with 24h sweep, frontend uploadBook auto-switches >16MB to 8MB parts (retry x3 in place, skip received); oversize single POST now reports honest 413 too_large instead of "multipart field 'file' required" (was MaxBytesReader abort mislabeled); UPLOAD_MAX_MB wired into both compose stacks (cap total, .env.example 2048), nginx body limit drops to 32m; testCfg limit 1MB, CJK/fullwidth-colon filename proven unaffected; e2e verified with 311MB real file (39 parts, sha1 match) 2026-09-07 23:56:40 +08:00
XingfenD 45eefd6741 feat(libs): create library by name only; root_path derived server-side as BOOKS_DIR/SafeName(name), validated at creation; drop root_path input from admin UI; align changelogs to bilingual template; trim AGENTS.md to rules not derivable from repo layout 2026-09-07 21:56:59 +08:00
XingfenD ca64bc0d73 refactor(repo): conform structure to AGENTS.md (web->frontend, internal/api->cmd/webui/{api,handlers}, docs/README+CHANGELOGs, module .gitignores, drop stray library/ and committed debug bins) 2026-09-07 21:37:30 +08:00
XingfenD 0de824cc6a feat(deploy): dev compose runs full stack with source mounts, delve, shared volumes; vite proxy target via env (dlv --accept-multiclient, GOPROXY=goproxy.cn in dev image) 2026-09-07 21:04:40 +08:00
XingfenD 67dc4bc2bf feat(deploy): dev/prod Dockerfiles in service dirs (backend/,web/), templates live beside rendered outputs, entrypoint copies /etc/booklib conf before resolver injection 2026-09-07 20:53:41 +08:00
XingfenD 505f660dbd fix: self-heal missing library root on access; accept .zip in admin upload picker 2026-09-07 20:16:26 +08:00
XingfenD e0783e5e6a chore: smoke tmpdir hygiene, seed pw floor, README 2026-09-07 12:44:47 +08:00
XingfenD 93638ed647 fix(api): classify DB errors in library paths 2026-09-07 12:44:47 +08:00
XingfenD 12f6e82308 fix(api): lazy cover self-heal + tmp-file skip 2026-09-07 12:44:47 +08:00
XingfenD 4b40dd669b fix(redis): lock acquisition fails open per spec §9 2026-09-07 12:44:47 +08:00
XingfenD 6adbab44c3 fix(security): trusted-proxy-scoped login rate limit 2026-09-07 12:44:47 +08:00
XingfenD a11bf28155 fix(backend): dbErr 503 degradation for connection-class DB errors 2026-09-06 19:53:19 +08:00
XingfenD 04095b5438 feat(backend): main wiring, graceful shutdown, local smoke green 2026-09-06 19:52:42 +08:00
XingfenD 8f18072843 feat(backend): reading progress upsert + listing, path-keyed survival 2026-09-05 20:59:11 +08:00
XingfenD 8206c7a4ed fix(backend): single error body on pages paths, reject newline entry names, empty-index cache skip 2026-09-05 20:56:05 +08:00
XingfenD 0946f6be5a feat(backend): cover/file/pages endpoints — immutable URLs, Range, disk+redis caches 2026-09-05 00:48:40 +08:00
XingfenD 249619b857 feat(backend): book list/detail/delete with embedded progress + cache cleanup 2026-09-05 00:42:08 +08:00
XingfenD 193d7612b5 feat(backend): directory scanner — diff, error state, covers, cache sweep, redis lock 2026-09-05 00:31:32 +08:00
XingfenD 4e84a8aeb7 feat(backend): immutable cache dir scheme + stale sweep 2026-09-05 00:23:57 +08:00
XingfenD b02ca67d16 feat(backend): cover extraction for cbz and epub (opf cover-image/meta) 2026-09-05 00:23:02 +08:00
XingfenD 589cb210bb feat(backend): zip page index with natural sort, zip-slip rejection, content hash 2026-09-05 00:22:13 +08:00
XingfenD ed4ee2458f feat(backend): user + library admin API, atomic sanitized upload 2026-09-05 00:16:33 +08:00
XingfenD 51735680e0 test(backend): setupAPI points BooksDir at os.TempDir and resets shared loginrl key 2026-09-05 00:16:33 +08:00
XingfenD 18e31396f3 fix(backend): login timing-guard uses a valid bcrypt hash so the compare actually runs 2026-09-04 23:59:39 +08:00
XingfenD bc88cabc47 feat(backend): jwt middleware, login with redis rate limit, /auth/me 2026-09-04 23:58:26 +08:00
XingfenD a2f202ada6 fix(backend): seed admin tolerates only unique-violation, propagates other errors 2026-09-04 23:54:48 +08:00
XingfenD b224288dae feat(backend): bcrypt+jwt auth package and admin seeding 2026-09-04 23:50:52 +08:00
XingfenD c1761d0f3e feat(backend): pg schema + store queries incl. path-keyed progress 2026-09-04 23:48:05 +08:00
XingfenD ad934d9812 feat(backend): module scaffold, config, gin router with healthz 2026-09-04 23:39:31 +08:00