18 Commits

Author SHA1 Message Date
aee0241bb7 fix: address code review findings
- Start Web UI before the blocking initial sync so the dashboard is
  reachable during the (rate-limited, potentially multi-minute) first
  sync; fold the immediate sync into startPeriodicSync's overlap guard
  so it can never race a concurrent tick over the shared DB / MB client.
- Make MarkNotificationSent idempotent: INSERT OR IGNORE for same-second
  PK collisions, and explicitly swallow FK violations when a release was
  pruned by a concurrent re-sync. Prevents a single vanished/duplicate
  release from aborting the digest mark-sent loop and re-sending.
- Do not abort NotifyOnce's mark-sent loop on a single failure; log and
  continue so every release in the batch is marked.
- NULL-safe reads: COALESCE(type,''), COALESCE(release_date,'') in the
  external_releases and unnotified readers to match the cache reader.
- Update/extend tests for the new idempotency and startup contracts.
2026-07-20 06:27:42 +03:00
a8aa445d94 fix: address code review findings
- Honor ignore_singles/ignore_compilations at scanner read time so toggles
  take effect immediately on the dashboard, artist page, and digest instead
  of waiting for the MusicBrainz cache to expire and prune rows.
- Run notifier notify synchronously in the scheduler loop to avoid overlapping
  read-send-mark runs double-sending the digest.
- Show artist name (with ID fallback) on the archive page instead of raw IDs.
- Select last_synced in GetAllArtistSettings for contract consistency.
- Fix stale startPeriodicSync comment and remove redundant error var.
- Remove dead ignored-branch from the artist template (never rendered).
- Add tests: CSRF sameOrigin, ArtistCacheFresh, secondary_types round-trip,
  and scanner type-toggle filtering.
- Update Specification.md schema/config to reflect mbid, last_synced,
  secondary_types, sync.interval, and server.public_url.
2026-07-20 06:18:21 +03:00
e0211343e0 fix: address code review findings 2026-07-20 00:05:22 +03:00
389d177d85 fix: address code review findings
- Fix duplicate Telegram notifications: SyncArtistDiscography no longer wipes
  notifications_sent for the whole artist on every cache-miss re-sync; only
  markers for releases that disappear are pruned (FK-safe via INSERT OR REPLACE
  + rgid NOT IN (...)).
- Cache empty MusicBrainz discographies via a new artist_settings.last_synced
  column (migration 009) so zero-release artists honor the TTL instead of being
  re-fetched every cycle.
- Wire the Web UI server and Telegram notifier scheduler into main.run/NewApp.
- Guard startPeriodicSync against overlapping syncs with a done-channel slot.
- Add server.public_url config; NewServerWithConfig derives reachable links
  and no longer advertises the 0.0.0.0 bind address.
- Web handlers: use scanner.ScanArtist per artist, drop always-false
  releaseIgnored lookup and dead endsWith, thread configured threshold.
- Limit :memory: DB pool to one connection so migrations and queries share the
  same in-memory store.
2026-07-19 23:38:33 +03:00
1a644dede7 feat: verify acceptance criteria and document sync config
Add a scan->notify->web end-to-end smoke test against a seeded :memory: DB,
fix .gitignore so the cmd/naviwatcher source dir is not excluded by the
root binary rule, and document the new sync.interval field in config.yaml.example.
2026-07-19 22:48:31 +03:00
0635ca8a87 feat: wire main loop with periodic sync+scan and sync_interval config
Replace compute-only run() with an immediate sync+scan followed by a
ticker-driven periodic loop, add the sync.interval config field (default
6h) with defaults and validation, and add tests for the scheduling logic.
2026-07-19 22:27:06 +03:00
dc4bdcdab0 feat: add SyncAll periodic sync pipeline and wire Navidrome client into App 2026-07-19 22:22:46 +03:00
79a376a127 fix: address code review findings
- Strip standalone reissue keywords (remaster/remastered/remix/deluxe/
  expanded/edition/reissue/anniversary/bonus) regardless of brackets so
  non-parenthesized remasters still match the plain local title above the
  0.85 threshold (was falsely reported missing).
- Fix TestAppRun_ScanLogsMissingReleases to verify run() performs the
  scan itself (capture its log output) instead of re-running ScanAll
  independently, which passed even if run() were a no-op.
- Fix TestSimilarity misleading cases that downgraded to a <0.85 range
  check with dead want/epsilon fields; assert actual computed scores.
2026-07-19 21:33:52 +03:00
4da5ee5f8c fix: address code review findings 2026-07-19 20:06:41 +03:00
f1839ad9e7 fix: address code review findings 2026-07-19 19:11:22 +03:00
c70f46af27 fix: address code review findings 2026-07-19 19:07:20 +03:00
8a5b58a817 fix: address code review findings
- Fix artist-ID namespace mismatch in MusicBrainz provider: SyncArtistDiscography
  now stores the canonical Navidrome artist ID (artist_settings.id) as
  external_releases.artist_id instead of the MusicBrainz MBID. Previously the
  MBID was stored, which violated the FK to artist_settings and broke the
  scanner join (local_albums.artist_id is the Navidrome ID), causing every
  external release to be falsely reported as missing and the sync insert to
  fail at runtime. getArtistFilterOptions now also resolves by the Navidrome ID.
- Resolve threshold in FindMissingReleases so the exported primitive honors the
  same zero-means-default contract as ScanArtist/ScanAll.
- Remove dead maxLen==0 guard in scanner.Similarity.
- Inline trivial buildPath helper; drop unused url import in client.go.
- Replace hand-rolled itoa with strconv.Itoa in tests.
- Rewrite SyncArtistDiscography tests to seed artist_settings with the Navidrome
  ID (tests previously seeded the MBID to mask the FK mismatch).
- Fix TestFuzzySmoke to exercise the real dependency (fuzzy.LevenshteinDistance /
  scanner.Similarity) instead of an unused API.
- Fix TestAppRun_ScanLogsMissingReleases to run the scan against a live context
  and assert the missing release is found.
- Document cached_at column in Specification.md and note startup scan / required
  musicbrainz.user_agent in README.
- Stop tracking .serena/ tooling config; add it to .gitignore.
2026-07-19 18:41:18 +03:00
c2da8c2095 feat: add DB-backed scanner entrypoint and compute-only main hook
Implements ScanArtist/ScanAll in internal/scanner loading local/external
releases via the database layer with ctx-cancellation checks, plus a
compute-only run() hook that logs missing-release counts. Add table-driven
tests using in-memory SQLite fixtures.
2026-07-19 18:19:57 +03:00
c95c740cd5 feat: add fuzzysearch dependency for scanner engine 2026-07-19 18:09:41 +03:00
582202bb86 fix: address second code review findings
- Add context cancellation check between pagination pages in GetArtistReleaseGroups
  for responsive graceful shutdown during large discography fetches.
- Eliminate double DB query on cache hit by having GetCachedReleases return
  []ExternalRelease directly instead of just a count, avoiding a redundant
  second query in SyncArtistDiscography.
- Update cache_test.go to match new GetCachedReleases return type.
- Format main.go (pre-existing whitespace issue).
2026-05-26 15:58:40 +03:00
a5911c257c fix: address code review findings
Remove dead code: duplicate ExternalRelease/Artist/ParsedArtist structs in
model.go, ParseArtist/mbArtist/mbArtistData in client.go, ArtistTypeFilter
and related filtering functions in api.go, SyncArtistDiscographyWithFilter
in sync.go, and CacheStats/IsArtistCacheValid in cache.go.

Fix bugs: SaveExternalRelease now stores NULL instead of empty string for
zero CachedAt; sync upserts are now transactional with stale release cleanup;
getCachedReleases returns int instead of *CacheStats; doGet uses url.Values
for proper query encoding of MBID.

Fix tests: removed duplicate TestRun_GracefulShutdown, removed dead code
(_ = dbPath) from TestNewApp, fixed assertions in httptest handler goroutine
to avoid data race, increased rate limiter timing tolerance, removed
Client.Close() calls (no-op removed), fixed sync test cache expiry to use
UPDATE instead of 0 TTL races.

Fix formatting: cancel()}() formatting in main.go, error format string in sync.go.
2026-05-26 14:10:22 +03:00
da49d12bb6 feat: wire up MusicBrainz provider in application entry point
- Add App struct with Config, DB, and MusicBrainz client dependencies
- NewApp() initializes database and MusicBrainz client from config
- App.Close() cleans up resources (MB client + DB) on shutdown
- App.run() blocks until context cancelled (goroutine-ready for future tasks)
- Config validation already requires musicbrainz.user_agent
- Add 6 tests: graceful shutdown, config integration, NewApp creation,
  shutdown cleanup, app.run shutdown, UserAgent validation
- All tests pass (6/6 in cmd, full suite green)
2026-05-26 13:14:47 +03:00
735ff0828e feat: add foundation layer (Go module, config, database, Docker)
Squashed commits from foundation-layer branch:

- Initialize Go module and project skeleton (cmd/naviwatcher/main.go)
- Add configuration management with YAML parsing and validation
- Add database layer with schema migrations (artist_settings, external_releases, notifications_sent)
- Add CRUD operations for artist_settings, external_releases, notifications_sent
- Add Docker setup with multi-stage build and docker-compose
- Verify acceptance criteria (tests, vet, fmt)
- Update README.md with build/run/test instructions
- Fix: filter ignored releases in GetUnnotifiedReleases (spec compliance)
- Fix: add FK constraint on notifications_sent.rgid
- Fix: add config.yaml to .gitignore (security)
- Fix: run Docker container as non-root user
- Fix: pin alpine:3.21 instead of alpine:latest
- Fix: wrap migrations in transactions for atomicity

All 49 tests pass, go vet clean, Docker image builds successfully.
2026-05-20 16:11:11 +03:00