Skip to content

1.0#68

Open
skjaere wants to merge 62 commits into
mainfrom
1.0
Open

1.0#68
skjaere wants to merge 62 commits into
mainfrom
1.0

Conversation

@skjaere
Copy link
Copy Markdown
Owner

@skjaere skjaere commented Apr 25, 2026

No description provided.

skjaere and others added 30 commits February 28, 2026 10:30
…roperty source

Introduces a @ConfigProperty annotation that flags properties as
user-configurable, combined with a DatabasePropertySource that lets
runtime overrides persist to postgres and refresh Spring beans without
a restart.

Includes:
- @ConfigProperty annotations + advanced flag + property type metadata
- File browser API behind the config API
- Per-provider test / verification endpoints
- Migration of application.properties to YAML
- Conversion of @ConfigurationProperties from data class to mutable class
- spring-cloud-context for runtime rebinding
- Removal of @ConditionalOnExpression from debrid client beans

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lback

Migrates the NNTP config from a flat single-server shape to a pools
list, each with host/port/credentials/TLS/maxConnections and a
priority for fill/fallback. Adds CRUD endpoints for pools plus a test
endpoint, and plumbs runtime updates through so pool changes applied
via the config API take effect without a restart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a JWT-based authentication layer that can protect the API and the
qBittorrent / SABnzbd / actuator endpoints behind opt-in flags. Also
generates short-lived tokens for temporary file access (used by the
frontend file browser). Excludes Spring's default UserDetailsService
auto-config so the generated-password warning no longer appears at
startup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extracts NZB import tracking into a dedicated NzbImportRecord entity
so the import pipeline can be observed and paginated. Adds a
sortable/searchable /api/v1/imports history endpoint, exposes the
current queue status with an ARTICLES_MISSING terminal state, and
bumps nzb-streamer through 0.7.0 → 0.7.3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bundles several small fixes surfaced via glitchtip / observability:
- Premiumize returning errors inside 200 responses now parses cleanly
- Wrapped ClientAbortException no longer escalates to ERROR
- ObjectOptimisticLockingFailureException on concurrent updates
- DataIntegrityViolationException on duplicate-key conflicts
- EOFException on abruptly closed streams

Plus housekeeping:
- Downgrade ktor from 3.5.0-eap-1584 to stable 3.4.1
- Extract FileController.detail() into focused private methods
- Suppress noisy detekt warnings across the codebase

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related fixes to how runtime config overrides take effect:
- NNTP pools now re-sync with NzbStreamer after DB overrides are loaded
  on startup (previously pools stored in overrides were ignored until
  the next save)
- HttpClient connect-timeout now reads the current config at request
  time instead of at bean creation, so overrides actually apply

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a file moves in the Arr, the queue response used to return stale
paths cached at enqueue time. Resolves paths lazily via a dedicated
endpoint that queries db_item directly, so queue responses always
reflect the current on-disk layout. Also derives SABnzbd history
storage path from the actual file location and tightens a few test
isolation issues the changes surfaced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a full health-check and repair pipeline for torrents on top of
PGMQ: periodic re-verification of cached links, short-circuit on the
first healthy debrid provider, and automatic Arr-side
blocklist + search on failure. Migrates the health check surface to a
dedicated REST API, adds scheduled PGMQ archive cleanup, and retires
ARTICLES_MISSING in favour of a single FAILED terminal state.

Bumps nzb-streamer to 0.7.4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Improves Real-Debrid integration reliability around link-not-found /
stale-link scenarios and sync consistency. Adds reference CLAUDE.md
sections documenting both the Real-Debrid and NNTP/Usenet integrations
so future work has a contentful starting point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously WebDAV was mounted at the root, which meant any non-API
path had to be disambiguated between WebDAV and the Spring MVC
controllers. Moving it under /webdav cleanly separates the WebDAV
surface from REST / static resources.

BREAKING CHANGE: WebDAV clients (rclone, media servers) must update
their URL from / to /webdav.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
debridav-frontend is now tracked as a git submodule and its built
`dist/` gets copied into Spring Boot's static resources at build time.
Users no longer need to deploy the frontend as a separate container —
a single jib-built image serves both the API and the UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the ad-hoc example/ with a curated docker-compose example
that layers a minimal stack, an arrs override, and a full monitoring
override (Prometheus + Grafana + dashboards + scraparr for
Sonarr/Radarr metrics + postgres-exporter).

Includes:
- Security scope section in the example README
- Rclone and NNTP dashboards ported from debridav-ci
- Dracula palette for threshold colors, NNTP streams panel, platform
  tidy-up
- Iterative compose fixes (network labels, healthcheck, Grafana iframe
  embedding, rclone AppArmor/userns/mount defaults, scraparr image)
- UI feature-flag endpoint for the frontend to branch on
- Runtime NNTP pool empty-list + blank-host handling
- NNTP treated as implicitly enabled when any pool is configured;
  NNTP_ENABLED env var dropped

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
debridav now pushes VFS cache invalidation to rclone via its RC API
whenever files are created / moved / deleted. This means the mounted
filesystem picks up changes without waiting for rclone's dir-cache-time
to expire, so a 2-minute dir-cache becomes safe.

DatabaseFileService emits FileSystemChangedEvent, the rclone RC
invalidator consumes it, refreshes all ancestor dirs (so new subdirs
become visible), and omits the special dir name when refreshing the
VFS root.

Bumps debridav-frontend to 12d9b4c.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an overview page that surfaces live activity without requiring the
Grafana stack, powered by a new /api/v1/summary endpoint:
- active streams with per-file bitrate + source
- library file counts by source
- import / health-check queue depths
- basic CPU + memory

Also migrates StreamingService and LibraryMetricsService from the
Prometheus native client to Micrometer, and bridges
Metrics.globalRegistry to Spring's MeterRegistry so meters registered
by libraries that reach for the static registry (notably nzb-streamer)
are visible via both the summary endpoint and /actuator/prometheus.

Other odds and ends:
- Auto-generate a random JWT key when none is configured (with a
  warning that tokens won't survive restarts)
- Expose the backend version via /api/v1/ui-config
- Dashboards updated to use the new nzb_streams_bitrate gauge

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Real-Debrid now skips the scheduled torrent sync unless REAL_DEBRID is
in debrid-clients and an API key is set. Premiumize drops its init-time
requirement that an API key be present so the bean can be created when
the provider is disabled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Keys.hmacShaKeyFor (per RFC 7518 §3.2) requires HS256 keys to be ≥ 32
bytes and throws WeakKeyException otherwise. Previously only
isBlank() was checked at startup and the key was built lazily, so a
short secret like DEBRIDAV_AUTH_JWT_SECRET=mypassword passed boot and
then caused HTTP 500 on /api/v1/auth/login (WeakKeyException escapes
generateToken) plus silent 401s on every protected endpoint
(WeakKeyException is a JwtException subtype, swallowed by the
validator's catch). No log line pointed at the cause.

Adds a @PostConstruct validator that fails fast with a clear message
and the openssl command to generate a compliant secret.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…onsumer

Previously PgmqConsumer's catch block only logged on failure — the
message was never archived and read_ct was never inspected. Poisoned
messages (malformed JSON, schema drift, deterministic handler
failures) got redelivered every visibility-timeout and pinned workers
forever.

This commit:
- Archives immediately on JsonProcessingException (retry can't fix a
  deserialization error).
- For any other exception, compares entry.readCounter against a new
  pgmq.max-read-count config (default 5). If exhausted, dead-letters;
  otherwise lets the message redeliver for another attempt.
- Dead-lettering wraps the original payload plus failure metadata
  (failureClass, failureMessage, readCount, enqueued/failed
  timestamps) into an envelope sent to <queueName>_dlq, then archives
  from the main queue so a worker slot is freed.
- V20__create_pgmq_dlq_queues.sql provisions the five DLQs alongside
  the existing main queues.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings in the mobile fix for the repair history table — shows the
Result column on narrow viewports (was previously hidden in favour of
the less useful Reads column).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the static DEBRIDAV_UI_GRAFANA_DASHBOARDS_* env wiring in
favour of runtime discovery: GrafanaDashboardService queries Grafana's
/api/search?type=dash-db, filters to the `debridav` folder, and
returns title + url pairs. New endpoint /api/v1/grafana/dashboards
surfaces that list to the frontend.

Auth: anonymous if Grafana allows it (the example stack does),
otherwise set debridav.ui.grafana.api-key / DEBRIDAV_UI_GRAFANA_APIKEY.

Organise a dashboard under Grafana's `debridav` folder and it shows
up as a tab on next page load — no redeploy needed.

Bumps debridav-frontend to 8de2824.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mobile fix: horizontally scroll Grafana dashboard tabs when the row is
wider than the viewport (previously clipped off-screen).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spring MVC bridges suspend functions via kotlinx-coroutines-reactor's
Mono adapter, which isn't on the classpath, so the call failed with
NoClassDefFoundError: kotlinx/coroutines/reactor/MonoKt at runtime
(500 on every dashboard fetch).

The endpoint is infrequent and cheap, so wrapping the suspend call in
runBlocking is simpler than pulling in coroutines-reactor just for
this one bridge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a Logs page to the frontend that tails /actuator/logfile via
Range headers, plus a FrontendRoutingConfiguration that forwards the
SPA's client-side routes to /index.html so deep links resolve on hard
refresh. Backend exposes a file appender + the logfile endpoint.

Housekeeping in the same push:
- Removes the long-unused FileSystemImportService + debridav.root-path
  (plus its V1 tables via V19__drop_import_registry.sql)
- Consolidates health-check/repair settings under a single health-check.*
  config group (replaces debridav.torrent-health-check-*,
  nntp.health-check-*, and repair.enabled)
- Hard-codes the NNTP forward-threshold-bytes constant inside
  nzb-streamer; exposes read-ahead-segments as a @ConfigProperty
- Rclone dir-cache-time bumped to 120s now that cache invalidation is
  pushed; DEBRIDAV_UI_GRAFANA_BASEURL made opt-in
- Bumps debridav-frontend to e054619 (Logs page + Health settings tab)

BREAKING CHANGE: debridav.root-path and related keys are gone; repair.enabled
→ health-check.repair-enabled; nntp.forward-threshold-bytes removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Was pinned to a commit SHA (af14aba) that got squashed out of
nzb-streamer's history. v0.8.0 is the properly-tagged release
containing the same changes — plus the ref-counted bitrate gauge fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract magic numbers, drop unused parameters, suppress TooManyFunctions
on the cohesive DatabaseFileService, fold StreamingService.streamContents'
catch chain into a helper, and tighten up minor style issues so :detekt
passes and the test phase can run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SabNzbdService now picks the easynews-by-name path when the easynews
debrid client is enabled and no NNTP pool is configured, and falls
through to the async PGMQ import path otherwise. This restores the
behaviour easynews-only users expect — the addfile call is synchronous
and doesn't try to parse the NZB body.

Test wiring follows: TestContextInitializer no longer pre-populates
nntp.pools, NZB-path tests opt in via the new @MockServerNntpTest meta-
annotation (which adds NntpPoolInitializer alongside the existing one),
and the duplicated waitForCompletion polling loops fold into a shared
Awaitility-based awaitSabImportCompletion helper. The /webdav/ prefix
gets applied across the integration tests that hit WebDAV directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The fk_nzb_contents_document constraint was ON DELETE NO ACTION, so any
attempt to delete an NzbDocument required callers to manually unlink
the contents first. That made cleanup brittle in production and broke
integration-test teardown in three ITs.

Migration V21 swaps the constraint to ON DELETE CASCADE, and the
matching @onDelete annotation keeps Hibernate's DDL generation in
agreement.

Also bump the test postgres container's max_connections to 300 — with
~12 cached @SpringBootTest contexts each holding a Hikari pool, the
default 100 limit was exhausted and randomly broke whichever context
loaded last.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
getStreamableLink returned the DB-cached download URL without checking
that it was still alive — once Real-Debrid expired or rotated a link,
we kept handing it out and streams failed with HTTP errors instead of
recovering. The unrestrict-on-dead-link path was only ever reached
when no DB record existed at all.

Now: HEAD the cached download URL; if RD has retired it, delete the
stale RealDebridDownloadEntity (locally and from the RD account) and
re-unrestrict the share link to mint a fresh download.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Most of these were carried over from the audit memory. None changes
behaviour today, but each removes a foot-gun that would surface as
LazyInitializationException, dangling transactions, or surprising
collection equality once the codebase grows.

- Switch @onetomany on Torrent.files and UsenetDownload.debridFiles
  from EAGER to LAZY. The collection is loaded only when callers ask
  for it. The qBittorrent /api/v2/torrents/files endpoint now goes
  through a new TorrentService.getTorrentFilesByHash that initializes
  the collection inside a read-only transaction.
- Drop @transactional from suspend fun getChildren — Spring binds the
  transaction to a thread, so it was a no-op for a function that
  resumes on a different thread anyway.
- Drop @transactional from the UsenetRepository interface and put it
  back on the specific write-derived methods that actually need it.
  The interface-level annotation wrapped reads in unnecessary
  transactions.
- Add business-key equals/hashCode to Torrent, UsenetDownload,
  Category, and DbDirectory so they survive Hibernate-proxy
  comparison and Set/Map membership.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A second pass on audit items:

- Drop CascadeType.ALL from RemotelyCachedEntity.contents,
  LocalEntity.blob, and RealDebridTorrentEntity.files in favour of
  PERSIST + MERGE + REMOVE. The omitted REFRESH and DETACH paths were
  the audit's actual concern; lifecycle cascade is what we want.

- Bound SabNzbdService.history(): the unconditional findAll() returned
  every UsenetDownload ever, which would OOM as the table grows. The
  per-category branch was just as unbounded. Both now use
  PageRequest.of(0, 500) and sort newest-first.

- Add idx_db_item_directory_id, idx_torrent_category_id, and
  idx_usenet_download_category_id via migration V22, plus matching
  @Index annotations on the entities. Postgres does not auto-index
  FK columns, so deletes against the parent and joins against the
  child were doing sequential scans.

- Drop the entity_copy_observer=allow override from application.yaml
  — Hibernate's safety check belongs on by default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mobile fix for the NNTP pool editor header — stacks the name above
the Test/Remove buttons on narrow viewports and truncates long
hostnames instead of overflowing the card.
skjaere and others added 6 commits April 25, 2026 12:40
The published rapidyenc-kotlin-wrapper jar (v0.1.3 on jitpack) only
bundles linux-x86-64/librapidyenc.so, so the arm64 layer of our
multi-platform Docker image had no native binary to load. Wiring the
local wrapper as an includeBuild lets us bundle linux-aarch64/
alongside linux-x86-64/ without waiting on a wrapper release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RateLimiterRegistry.ofDefaults() doesn't wire metrics automatically,
so resilience4j_ratelimiter_available_permissions and
_waiting_threads weren't being exposed — the 'Rate limiters' panel
on the platform dashboard went empty. Pulls in
resilience4j-micrometer and calls TaggedRateLimiterMetrics.bindTo
(same for retries).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rapidyenc-kotlin-wrapper v0.2.0 ships both linux-x86-64 and
linux-aarch64 binaries, so the local-source workaround is no
longer needed. ktor-nntp-client v0.5.0 already pulls the new
version through its libs.versions.toml.
Pulls in ktor-nntp-client v0.5.1 and rapidyenc-kotlin-wrapper v0.2.0,
the latter of which now bundles linux-aarch64/librapidyenc.so so
multi-arch container images work without local-build overrides.
The /webdav/ URL prefix tipped these two sardine calls over the 120-char
MaxLineLength threshold.
LibraryMetricsService only queried debrid_cached_torrent_content for
the per-provider/type breakdown, so debridav_library_metrics reported
zero for usenet-sourced files. Adds a matching query against
debrid_cached_usenet_release_content and tags rows with
source=torrent|usenet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 25, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 55.00936% with 961 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...jaere/debridav/health/PgmqHealthQueueRepository.kt 1.83% 107 Missing ⚠️
...in/kotlin/io/skjaere/debridav/fs/FileController.kt 4.81% 79 Missing ⚠️
...kotlin/io/skjaere/debridav/ui/SummaryController.kt 10.00% 54 Missing ⚠️
.../kotlin/io/skjaere/debridav/fs/StreamController.kt 10.20% 44 Missing ⚠️
...kjaere/debridav/usenet/queue/UsenetQueueService.kt 29.62% 31 Missing and 7 partials ⚠️
...o/skjaere/debridav/config/ConfigOverrideService.kt 76.58% 15 Missing and 22 partials ⚠️
...lin/io/skjaere/debridav/stream/StreamingService.kt 54.32% 34 Missing and 3 partials ⚠️
...in/kotlin/io/skjaere/debridav/pgmq/PgmqConsumer.kt 14.28% 30 Missing ⚠️
...ridav/debrid/client/realdebrid/RealDebridClient.kt 34.09% 21 Missing and 8 partials ⚠️
...tlin/io/skjaere/debridav/config/auth/JwtService.kt 47.16% 26 Missing and 2 partials ⚠️
... and 52 more
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Files with missing lines Coverage Δ Complexity Δ
.../kotlin/io/skjaere/debridav/DebriDavApplication.kt 50.00% <100.00%> (ø) 1.00 <1.00> (ø)
...otlin/io/skjaere/debridav/DebridavConfiguration.kt 100.00% <100.00%> (ø) 9.00 <2.00> (ø)
...o/skjaere/debridav/FrontendRoutingConfiguration.kt 100.00% <100.00%> (ø) 2.00 <2.00> (?)
...n/io/skjaere/debridav/Resilience4jConfiguration.kt 100.00% <100.00%> (ø) 3.00 <2.00> (ø)
...otlin/io/skjaere/debridav/arrs/ArrConfiguration.kt 100.00% <ø> (ø) 1.00 <0.00> (ø)
...main/kotlin/io/skjaere/debridav/arrs/ArrService.kt 90.00% <100.00%> (+1.11%) 5.00 <0.00> (ø)
...ere/debridav/arrs/RadarrConfigurationProperties.kt 100.00% <100.00%> (+11.11%) 7.00 <6.00> (+5.00)
...ere/debridav/arrs/SonarrConfigurationProperties.kt 100.00% <100.00%> (+11.11%) 7.00 <6.00> (+1.00)
...in/io/skjaere/debridav/config/ConfigOverrideDto.kt 100.00% <100.00%> (ø) 11.00 <11.00> (?)
.../skjaere/debridav/config/DatabasePropertySource.kt 100.00% <100.00%> (ø) 2.00 <2.00> (?)
... and 95 more

... and 5 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

skjaere and others added 23 commits April 25, 2026 14:34
The log line literally ended 'could not unrestrict link: X because'
with nothing after 'because' — the parsed error map was being
discarded. Now includes HTTP status + error + error_code (e.g.
hoster_unavailable / 19) so operators can tell RD-side outages apart
from client bugs without shelling into the pod.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two streams were observed stuck at 0 bitrate for 5+ hours — the
consumer (NzbFileResource's out.write() / StreamingService's
sendBytesFromHttpStream) was blocked on a socket for a client that
had silently disconnected without a clean TCP close. Coroutine
cancellation can't preempt blocking Java I/O, so the only reliable
unblock is to close the OutputStream itself.

Adds a lightweight AtomicBoolean-based watchdog in both paths:
writer flips it to true on each chunk; a coroutine ticks once per
STALL_TIMEOUT_MS and calls getAndSet(false) — if the result was
already false, no bytes moved in a full interval and the output is
closed, which throws IOException inside the blocked write and lets
cleanup + metric removal proceed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With OSIV on (Spring's default) the Hibernate session stays open for
the entire HTTP request, which for a WebDAV GET that streams for
hours meant a Hikari connection stayed pinned the whole time
(visible in hikari_connections_active climbing with active streams).

Streaming paths already pre-materialize the entities they need
(NzbFileResource via toNzbDocument(); DebridFileResource via the
captured contents), so sendContent no longer touches lazy state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The test still hit bare /testfile.mp4, which now routes to Spring's
static-resource handler (404) because WebDAV moved to /webdav/* in
1.0. Updates the GET and DELETE URIs accordingly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- default.yaml: folder 'Debridav' -> 'debridav' so
  GrafanaDashboardService's folderTitle filter matches (same case
  fix that went out to debridav-ci)
- platform.json: title 'DebriDav: Platform' -> 'DebriDAV'
- health.json (new): full Health dashboard ported from k8s —
  throughput, latency percentiles, queue depth, DLQ count, oldest
  message age

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each call site is reached via runBlocking on a Tomcat worker thread,
so the worker is parked the whole time anyway — bouncing the JDBC or
output-stream write to an IO pool just adds a hop without freeing the
caller. DatabaseFileService.getChildren keeps its withContext because
the two parallel async branches actually need the dispatcher.
Five call sites used fully-qualified names where a regular import
would have worked: @onDelete on NzbContents, the Torrent and
NzbDocumentEntity parameter types in the two health-repair handlers,
kotlinx.io.IOException in StreamingService, and Delay.milliseconds
in ContentStubbingService. The Spring vs MockServer MediaType pair
in two ITs stays FQCN-qualified because both are imported.
Spring Data does not auto-wrap interface-level derived delete methods
in a transaction (unlike CrudRepository.delete which is annotated on
SimpleJpaRepository). Without an outer @transactional caller this
fails with "TransactionRequiredException ... cannot reliably process
'remove' call".

RealDebridTorrentRepository.deleteByTorrentIdIgnoreCase was the
trigger — it's reached from the streaming path via getCachedFiles ->
deleteTorrent (when RD reports a torrent has no links left), and that
path has no transactional ancestor. TorrentRepository.deleteByHash
and ConfigOverrideRepository.deleteAllByPropKeyStartingWith get the
same defensive annotation; their callers all happen to be transactional
today, but the trap shouldn't sit there waiting for a future caller.
- Drop 'coming in 0.12.0' on NNTP streaming; it's shipped.
- Expand Features with the bundled UI, runtime config API, JWT auth,
  and observability story.
- New 'Migrating from 0.11 to 1.0' section covering the WebDAV URL
  move, NNTP pool-list reshape, health-check config consolidation,
  and removed keys.
- Replace the flat NNTP config table with the new NNTP_POOLS_0_*
  form and add a dedicated Health check & repair table.
- Regroup the main Configuration table into Core / Database /
  Authentication / Debrid providers / Arrs / UI sections; remove
  long-dead entries (DEBRIDAV_ROOTPATH, chunk cache knobs,
  DEBRIDAV_ENABLEFILEIMPORTONSTARTUP) and add the new ones
  (DEBRIDAV_AUTH_*, DEBRIDAV_WEBDAV-*, DEBRIDAV_UI_GRAFANA_*).
- Fix docker tag :v0 → :v1.
- Fix REAL-DEBRID_SYNCPOLLRATE default (PT24H, was PT4H).
- Fix broken links: example/QUICKSTART.md → example/README.md,
  docker-compose.yaml → docker-compose.yml.
- Rewrite Monitoring section to match today's compose layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pulls in the hardcoded forward-threshold-bytes constant (SeekConfig
removed) and the drop of the nzb_streams_bytes_total counter (the
nzb_streams_bitrate gauge covers the same ground).
Adding a torrent caused a CPU spike that scaled with the number of
files. Three pieces overlapped:

1. TorrentService.createTorrent re-parsed the magnet for every file
   to recover the hash. Hoisted into a single val.

2. DatabaseFileService.createDebridFile was called once per file and
   each call ran getOrCreateDirectory — global ReentrantLock + ~depth
   queries + Base58 encoding per segment per call. New
   createDebridFiles batch entry pre-resolves the unique parent
   directories once and reuses them for every file.

3. The per-file findByDirectoryAndName triggers a JPQL auto-flush,
   which would have undermined the new hibernate.jdbc.batch_size=50.
   The batch entry replaces it with a single
   findAllByDirectoryInAndNameIn that prefetches all potential
   collisions, lets us skip the per-iteration query, and lets the
   ordered inserts at commit actually batch.

SabNzbdService.createDebridFilesFromDebridResponse and
NzbImportService.executeImport switch to the new batch entry too —
same hot path for NZB imports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Jib produces the image end-to-end from Gradle; the hand-maintained
Dockerfile hadn't tracked Spring Boot layout changes in ages.

The dev/ directory's docker-compose.yml + rclone.conf were an old
development convenience superseded by example/docker-compose.yml —
same thing, cleaner layering, actually maintained.
Adds a 'Accessing the UI' subsection covering the URL/port, the auth
prompt when DEBRIDAV_AUTH_ENABLED is on, and the distinction between
the UI at / and WebDAV at /webdav/.
New feature in 1.0: debridav pushes cache invalidations to rclone's
RC endpoint on file changes, so long --dir-cache-time values stay
safe. Adds a dedicated section with the four config knobs and a note
about enabling rclone's RC server.
Was buried as a subsection under 'NNTP / Usenet streaming', which
wrongly implied it was NZB-only — the pipeline covers torrents too.
Moves the config table up a level and adds prose explaining what
gets checked, how repair interacts with Sonarr/Radarr, and the
PGMQ-backed queue + UI Health page.
The old docker-compose example was superseded by the current
example/ directory ages ago. Removing it from git history keeps
repo weight down while leaving the local copy in place for any
lingering references.
The version is tracked in gradle.properties (managed by release-please).
version.properties was a leftover from the simon-release plugin days
and isn't referenced anywhere in the build.
getTorrentInfo logged non-2xx responses but then still called
resp.body<TorrentsInfo>() on the error body. kotlinx.serialization
threw a SerializationException with no message and no cause, which
bubbled all the way up to DebridCachedContentService's catch — that's
where the "error getting cached files from REAL_DEBRID : null : null"
log came from. Funnel non-2xx through throwDebridProviderException
like the other RD endpoints so the actual status + body reach the
caller.

Also tighten the catch logger so future swallowed exceptions are
diagnosable: include the exception class name and full stack trace
instead of just .cause and .message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NNTP streaming and the health-check + repair pipelines are brand-new
in 1.0 — they don't have a previous release to migrate from, so
documenting 'rename NNTP_HOST → NNTP_POOLS_0_HOST' is meaningless
(no one was setting NNTP_HOST in 0.11.x; the feature didn't ship).
Pares the migration section back to the genuine 0.11 → 1.0 breaks.
Move @transactional off addMagnet/addTorrent and onto createTorrent so
the suspending debrid HTTP call runs outside the transaction. Swap the
kotlinx.coroutines Mutex in DatabaseFileService for a ReentrantLock,
which removes the runBlocking shim that wrapped purely-blocking JDBC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants