Skip to content

fix(swagger): respect base URL for API docs routes - #758

Merged
s0up4200 merged 3 commits into
mainfrom
fix/swagger-base-url
Dec 12, 2025
Merged

fix(swagger): respect base URL for API docs routes#758
s0up4200 merged 3 commits into
mainfrom
fix/swagger-base-url

Conversation

@s0up4200

@s0up4200 s0up4200 commented Dec 12, 2025

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes swagger docs not loading when using a custom base URL (e.g., /qui/ on seedboxes)
  • Routes were hardcoded at /api/docs and /api/openapi.json, ignoring the configured baseURL
  • Now prefixes routes with baseURL so /qui/api/docs works correctly

Test plan

  • Verify /api/docs still works with default config (no baseURL)
  • Verify /qui/api/docs loads correctly when baseUrl: "/qui/" is configured

Summary by CodeRabbit

  • Chores
    • Refactored API documentation routes to use a normalized base path, ensuring consistent doc and OpenAPI endpoints.
    • Adjusted development server routing patterns to more accurately match API and proxy paths, improving request routing during local development.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 12, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Routes for Swagger UI and OpenAPI now use a normalized base URL (via httphelpers.NormalizeBasePath) when registered, and the Vite navigateFallback denylist regex for API routes was adjusted to match trailing-slash or end-of-path cases and its order was swapped.

Changes

Cohort / File(s) Summary
Swagger base‑URL normalization & route registration
internal/web/swagger/swagger.go
Added httphelpers import and replaced manual trimming with httphelpers.NormalizeBasePath to normalize baseURL (ensures leading slash, no trailing slash, or "" for root). RegisterRoutes now prepends the normalized base URL to both /api/docs and /api/openapi.json endpoints.
Vite navigateFallback denylist regex
web/vite.config.ts
Updated navigateFallbackDenylist by changing the first regex from /^\/api/ to `//api(?:/

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

  • Verify httphelpers.NormalizeBasePath behavior for root ("") and nested base paths and ensure both Swagger UI and OpenAPI endpoints resolve correctly.
  • Confirm no routing conflicts arise when the normalized base URL is empty or /.
  • Validate the updated Vite regex matches intended API routes and doesn't unintentionally exclude valid client routes.

Poem

🐰 I hopped through code with careful paws,
I fixed the slashes, tweaked the laws.
Swagger now walks with baseURL pride,
Vite’s denylist hops in stride.
A little rabbit’s routing guide. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly summarizes the main change: making swagger API docs routes respect the configured base URL.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/swagger-base-url

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0fe48ae and d0dadb9.

📒 Files selected for processing (1)
  • internal/web/swagger/swagger.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/web/swagger/swagger.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
internal/web/swagger/openapi.yaml (1)

3743-3750: OpenAPI nullable: true for indexerName doesn’t match the Go shape (string + omitempty).
If the API never returns null (only omits or returns a string), drop nullable: true. Otherwise, make the Go field *string so null is representable.

internal/models/crossseed.go (1)

160-170: IndexerName field has inconsistent null/omit/required representation across Go, OpenAPI, and TypeScript types.

OpenAPI spec defines indexerName as nullable: true, but:

  • Go struct uses omitempty (field is omitted if empty, not sent as null)
  • TypeScript interface treats it as a required field (no ? optional marker)

Align the three to use consistent representation: either all nullable, all optional, or all required.

internal/services/crossseed/service.go (4)

1619-1666: Use the resolved sourceIndexer for IndexerName (currently uses result.Indexer). You already map result.IndexerID → friendly name into sourceIndexer (Lines 1620-1623), but the run results here still use the raw result.Indexer, which can diverge from the resolved name.

 run.Results = append(run.Results, models.CrossSeedRunResult{
   InstanceName: result.Indexer,
-  IndexerName:  result.Indexer,
+  IndexerName:  sourceIndexer,
   Success:      false,
   Status:       "no_match",
   Message:      fmt.Sprintf("No matching torrents for %s", result.Title),
 })

 run.Results = append(run.Results, models.CrossSeedRunResult{
   InstanceName: result.Indexer,
-  IndexerName:  result.Indexer,
+  IndexerName:  sourceIndexer,
   Success:      true,
   Status:       "dry-run",
   Message:      fmt.Sprintf("Dry run: %d viable candidates", candidateCount),
 })

1694-1723: Same IndexerName inconsistency in “exists” pre-check results. These blocks also set IndexerName: result.Indexer but should likely use sourceIndexer for consistency with the resolved indexer name.

 existingResults = append(existingResults, models.CrossSeedRunResult{
   InstanceID:   candidate.InstanceID,
   InstanceName: candidate.InstanceName,
-  IndexerName:  result.Indexer,
+  IndexerName:  sourceIndexer,
   Success:      false,
   Status:       "exists",
   Message:      "Torrent already exists (infohash pre-check)",
   ...
 })

 existingResults = append(existingResults, models.CrossSeedRunResult{
   InstanceID:   candidate.InstanceID,
   InstanceName: candidate.InstanceName,
-  IndexerName:  result.Indexer,
+  IndexerName:  sourceIndexer,
   Success:      false,
   Status:       "exists",
   Message:      "Torrent already exists (comment URL pre-check)",
   ...
 })

Also applies to: 1752-1780


1833-1893: Propagate IndexerName consistently for “no_result” and per-instance mapped results. Same issue: IndexerName uses result.Indexer instead of sourceIndexer.

 run.Results = append(run.Results, models.CrossSeedRunResult{
   InstanceName: result.Indexer,
-  IndexerName:  result.Indexer,
+  IndexerName:  sourceIndexer,
   Success:      false,
   Status:       "no_result",
   Message:      fmt.Sprintf("Cross-seed returned no actionable instances for %s", result.Title),
 })

 mapped := models.CrossSeedRunResult{
   InstanceID:   instanceResult.InstanceID,
   InstanceName: instanceResult.InstanceName,
-  IndexerName:  result.Indexer,
+  IndexerName:  sourceIndexer,
   Success:      instanceResult.Success,
   Status:       instanceResult.Status,
   Message:      instanceResult.Message,
 }

6521-6545: Tag matching uses exact case-sensitive comparison, creating a breaking change if configs historically used different casing. The code at lines 6521-6545 and 6547-6554 matches tags with exact equality (tag == allowed, tag == excluded, tag == "cross-seed") rather than case-insensitive comparison. Other services in the codebase (reannounce, trackerrules) consistently use strings.EqualFold for tag matching, but crossseed does not. Users with tags like "Cross-Seed" or "TAG1" will silently fail to match, since splitTags does not normalize casing before comparison. Confirm this breaking behavior is intentional or apply case-insensitive matching to align with existing patterns.

🧹 Nitpick comments (7)
web/src/types/index.ts (1)

1404-1417: Consider backward-compat: required rssSource* fields can break older API responses.
If the UI can connect to older qui versions, make these optional (or ensure the fetch/normalization layer defaults missing fields to []). If server/UI are always deployed together, current typing is fine.

Also applies to: 1437-1450

internal/api/handlers/crossseed.go (1)

198-210: Patch application for rssSource* is straightforward and correct.
Optional follow-up: consider normalizing (trim/dedupe) before persisting if you want consistent matching behavior.

internal/models/crossseed.go (1)

415-431: Store write-path wiring for rss_source_* looks consistent (columns/placeholders/args).
Optional: normalize (trim/dedupe) RSSSource* before encoding to keep stored settings canonical (there’s already normalizeStringSlice in this file).

Also applies to: 451-494, 507-535

internal/services/crossseed/service.go (1)

1919-2002: RSS source filtering integration looks good; consider normalizing filters to avoid whitespace surprises. splitTags() trims torrent tags, but settings-provided tags/categories may include whitespace and won’t match as expected. A lightweight fix is to normalize the settings slices once when hasRSSSourceFilters is true (either here or at settings-validation time).

web/src/pages/CrossSeedPage.tsx (3)

384-395: Normalize rssSource lists before persisting (avoid dupes / whitespace variants).*
Search filters are typically “set-like”; saving duplicates can lead to confusing UI and non-deterministic backend behavior.

       setAutomationForm({
         enabled: settings.enabled,
         runIntervalMinutes: settings.runIntervalMinutes,
         targetInstanceIds: settings.targetInstanceIds,
         targetIndexerIds: settings.targetIndexerIds,
-        rssSourceCategories: settings.rssSourceCategories ?? [],
-        rssSourceTags: settings.rssSourceTags ?? [],
-        rssSourceExcludeCategories: settings.rssSourceExcludeCategories ?? [],
-        rssSourceExcludeTags: settings.rssSourceExcludeTags ?? [],
+        rssSourceCategories: normalizeStringList(settings.rssSourceCategories ?? []),
+        rssSourceTags: normalizeStringList(settings.rssSourceTags ?? []),
+        rssSourceExcludeCategories: normalizeStringList(settings.rssSourceExcludeCategories ?? []),
+        rssSourceExcludeTags: normalizeStringList(settings.rssSourceExcludeTags ?? []),
       })
     return {
       enabled: automationSource.enabled,
       runIntervalMinutes: automationSource.runIntervalMinutes,
       targetInstanceIds: automationSource.targetInstanceIds,
       targetIndexerIds: automationSource.targetIndexerIds,
-      rssSourceCategories: automationSource.rssSourceCategories,
-      rssSourceTags: automationSource.rssSourceTags,
-      rssSourceExcludeCategories: automationSource.rssSourceExcludeCategories,
-      rssSourceExcludeTags: automationSource.rssSourceExcludeTags,
+      rssSourceCategories: normalizeStringList(automationSource.rssSourceCategories),
+      rssSourceTags: normalizeStringList(automationSource.rssSourceTags),
+      rssSourceExcludeCategories: normalizeStringList(automationSource.rssSourceExcludeCategories),
+      rssSourceExcludeTags: normalizeStringList(automationSource.rssSourceExcludeTags),
     }

Also applies to: 482-507


835-874: Select option derivation looks good; consider guarding against include/exclude overlap.
Right now the UI can select the same category/tag in both include and exclude lists; if backend precedence is “exclude wins”, consider enforcing that in UI (or surfacing a warning).


989-998: Defensive reduce for stats (avoid NaN if backend ever sends null/undefined).

 return {
-  totalAdded: runs.reduce((sum, run) => sum + run.torrentsAdded, 0),
-  totalFailed: runs.reduce((sum, run) => sum + run.torrentsFailed, 0),
+  totalAdded: runs.reduce((sum, run) => sum + (run.torrentsAdded ?? 0), 0),
+  totalFailed: runs.reduce((sum, run) => sum + (run.torrentsFailed ?? 0), 0),
   totalRuns: runs.length,
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d44058f and 3d2f141.

📒 Files selected for processing (8)
  • internal/api/handlers/crossseed.go (3 hunks)
  • internal/database/migrations/032_add_rss_source_filters.sql (1 hunks)
  • internal/models/crossseed.go (11 hunks)
  • internal/services/crossseed/service.go (15 hunks)
  • internal/web/swagger/openapi.yaml (1 hunks)
  • internal/web/swagger/swagger.go (1 hunks)
  • web/src/pages/CrossSeedPage.tsx (9 hunks)
  • web/src/types/index.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-11-28T20:32:30.126Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go:209-212
Timestamp: 2025-11-28T20:32:30.126Z
Learning: Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go
Learning: The cross-seed recheck-resume worker intentionally runs for the process lifetime and keys pending entries by hash only. This is acceptable under the current constraint that background seeded-search runs operate on a single instance at a time; graceful shutdown and instanceID|hash keying are deferred by design.

Applied to files:

  • internal/models/crossseed.go
  • internal/services/crossseed/service.go
📚 Learning: 2025-11-28T22:21:20.730Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go:2415-2457
Timestamp: 2025-11-28T22:21:20.730Z
Learning: Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go
Learning: The determineSavePath function intentionally includes a contentLayout string parameter for future/content-layout branching and API consistency. Its presence is by design even if unused in the current body; do not flag as an issue in reviews.

Applied to files:

  • internal/models/crossseed.go
  • internal/services/crossseed/service.go
📚 Learning: 2025-11-27T15:33:39.007Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.

Applied to files:

  • internal/services/crossseed/service.go
🧬 Code graph analysis (2)
internal/models/crossseed.go (1)
web/src/types/index.ts (2)
  • CrossSeedAutomationSettings (1404-1435)
  • Category (427-430)
internal/services/crossseed/service.go (3)
pkg/prowlarr/client.go (1)
  • Indexer (76-84)
internal/models/crossseed.go (1)
  • CrossSeedAutomationSettings (20-64)
web/src/types/index.ts (3)
  • CrossSeedAutomationSettings (1404-1435)
  • Torrent (226-283)
  • Category (427-430)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests
🔇 Additional comments (10)
internal/database/migrations/032_add_rss_source_filters.sql (1)

1-8: LGTM: safe defaults for new filter columns.
Default '[]' is a good compatibility choice for existing rows and for JSON-slice decoding.

web/src/types/index.ts (1)

1375-1384: indexerName?: string aligns with Go omitempty.
No concerns here.

internal/api/handlers/crossseed.go (1)

135-162: isEmpty() correctly accounts for the new RSS source filter fields.

internal/models/crossseed.go (3)

31-36: Good: rssSource fields are explicit and documented on the settings model.*


76-92: Good defaults: empty slices avoid nulls and simplify client logic.


273-314: Store read-path wiring for rss_source_* looks correct.
Given the migration defaults to '[]', decode should be stable for both new and existing rows.

Also applies to: 357-369

internal/services/crossseed/service.go (2)

1539-1542: Signature update usage looks correct. buildAutomationSnapshots is now called with settings, enabling RSS source filtering during snapshot construction.


6434-6463: The original review comment is incorrect. Tags are already normalized upstream before reaching matchesSearchFilters. At line 1374 in validateSearchRunOptions, opts.Tags = normalizeStringSlice(opts.Tags) trims whitespace and removes duplicates from all input tags. This normalization happens during validation (line 1172) before state is created at line 1235, so the concern about spacing mismatches silently filtering torrents is unfounded. Case sensitivity is by design, as noted in comments ("case-sensitive to match qBittorrent behavior"), not a bug.

web/src/pages/CrossSeedPage.tsx (2)

39-66: Nice, cohesive wiring for the new RSS source filter fields (types + icons used later).
The AutomationFormState additions look consistent with the rest of the form model.


104-113: Defaults correctly include the new rssSource arrays.*

Comment thread internal/api/handlers/crossseed.go
Comment thread internal/services/crossseed/service.go Outdated
Comment thread internal/web/swagger/swagger.go
Comment thread web/src/pages/CrossSeedPage.tsx Outdated
Comment thread web/src/pages/CrossSeedPage.tsx Outdated
Comment thread web/src/pages/CrossSeedPage.tsx Outdated
Swagger docs were registered at hardcoded /api/docs and /api/openapi.json
paths, ignoring the configured baseURL. This caused docs to be inaccessible
when using a custom base URL (e.g., /qui/ on seedboxes).

The handler already received baseURL but didn't use it in RegisterRoutes().
Now routes are prefixed with baseURL so /qui/api/docs works correctly.
The navigateFallbackDenylist regex only matched paths starting with /api,
causing the service worker to serve the SPA shell for /qui/api/docs when
using a custom base URL. Remove the ^ anchor so /api is matched anywhere
in the path.
@s0up4200
s0up4200 force-pushed the fix/swagger-base-url branch from 6ae9888 to 0fe48ae Compare December 12, 2025 22:46
Use the existing httphelpers.NormalizeBasePath function instead of manual
TrimSuffix. This ensures baseURL always has a leading slash (e.g., "qui"
becomes "/qui"), preventing malformed routes like "qui/api/docs".
@s0up4200 s0up4200 added this to the v1.10.0 milestone Dec 12, 2025
@s0up4200
s0up4200 merged commit 55d0ccc into main Dec 12, 2025
11 checks passed
@s0up4200
s0up4200 deleted the fix/swagger-base-url branch December 12, 2025 23:05
alexlebens pushed a commit to alexlebens/infrastructure that referenced this pull request Dec 18, 2025
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.9.1` -> `v1.10.0` |

---

### Release Notes

<details>
<summary>autobrr/qui (ghcr.io/autobrr/qui)</summary>

### [`v1.10.0`](https://github.com/autobrr/qui/releases/tag/v1.10.0)

[Compare Source](autobrr/qui@v1.9.1...v1.10.0)

#### Changelog

##### New Features

- [`f2b17e6`](autobrr/qui@f2b17e6): feat(config): add SESSION\_SECRET\_FILE env var ([#&#8203;661](autobrr/qui#661)) ([@&#8203;undefined-landmark](https://github.com/undefined-landmark))
- [`f5ede56`](autobrr/qui@f5ede56): feat(crossseed): add RSS source filters for categories and tags ([#&#8203;757](autobrr/qui#757)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`9dee7bb`](autobrr/qui@9dee7bb): feat(crossseed): add Unicode normalization for title and file matching ([#&#8203;742](autobrr/qui#742)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`d44058f`](autobrr/qui@d44058f): feat(crossseed): add skip auto-resume settings per mode ([#&#8203;755](autobrr/qui#755)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`9e3534a`](autobrr/qui@9e3534a): feat(crossseed): add webhook source filters for categories and tags ([#&#8203;763](autobrr/qui#763)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`c8bbe07`](autobrr/qui@c8bbe07): feat(crossseed): only poll status endpoints when features are enabled ([#&#8203;738](autobrr/qui#738)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`fda8101`](autobrr/qui@fda8101): feat(sidebar): add size tooltips and deduplicate cross-seed sizes ([#&#8203;724](autobrr/qui#724)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e4c0556`](autobrr/qui@e4c0556): feat(torrent): add sequential download toggles ([#&#8203;776](autobrr/qui#776)) ([@&#8203;rare-magma](https://github.com/rare-magma))
- [`2a43f15`](autobrr/qui@2a43f15): feat(torrents): autocomplete paths ([#&#8203;634](autobrr/qui#634)) ([@&#8203;rare-magma](https://github.com/rare-magma))
- [`1c07b33`](autobrr/qui@1c07b33): feat(torrents): replace filtered speeds with global ([#&#8203;745](autobrr/qui#745)) ([@&#8203;jabloink](https://github.com/jabloink))
- [`cd0deee`](autobrr/qui@cd0deee): feat(tracker): add per-domain stats inclusion toggle for merged trackers ([#&#8203;781](autobrr/qui#781)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`b6a6200`](autobrr/qui@b6a6200): feat(web): add Size column to Tracker Breakdown table ([#&#8203;770](autobrr/qui#770)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`560071b`](autobrr/qui@560071b): feat(web): add zebra striping to torrent table ([#&#8203;726](autobrr/qui#726)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`f8f65a8`](autobrr/qui@f8f65a8): feat(web): improve auto-search on completion UX ([#&#8203;743](autobrr/qui#743)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e36312f`](autobrr/qui@e36312f): feat(web): improve torrent selection UX with unified click and escape behavior ([#&#8203;782](autobrr/qui#782)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`27c1daa`](autobrr/qui@27c1daa): feat(web): napster theme ([#&#8203;728](autobrr/qui#728)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e3950de`](autobrr/qui@e3950de): feat(web): new torrent details panel for desktop ([#&#8203;760](autobrr/qui#760)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`6c66ba5`](autobrr/qui@6c66ba5): feat(web): persist tab state in URL for CrossSeed and Settings pages ([#&#8203;775](autobrr/qui#775)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`59884a9`](autobrr/qui@59884a9): feat(web): share tracker customizations with filtersidebar ([#&#8203;717](autobrr/qui#717)) ([@&#8203;s0up4200](https://github.com/s0up4200))

##### Bug Fixes

- [`fafd278`](autobrr/qui@fafd278): fix(api): add webhook source filter fields to PATCH settings endpoint ([#&#8203;774](autobrr/qui#774)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`bdf0339`](autobrr/qui@bdf0339): fix(api): support apikey query param with custom base URL ([#&#8203;748](autobrr/qui#748)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`c3c8d66`](autobrr/qui@c3c8d66): fix(crossseed): compare Site and Sum fields for anime releases ([#&#8203;769](autobrr/qui#769)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`cb4c965`](autobrr/qui@cb4c965): fix(crossseed): detect file name differences and fix hasExtraSourceFiles ([#&#8203;741](autobrr/qui#741)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`fd9e054`](autobrr/qui@fd9e054): fix(crossseed): fix batch completion searches and remove legacy settings ([#&#8203;744](autobrr/qui#744)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`26706a0`](autobrr/qui@26706a0): fix(crossseed): normalize punctuation in title matching ([#&#8203;718](autobrr/qui#718)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`db30566`](autobrr/qui@db30566): fix(crossseed): rename files before folder to avoid path conflicts ([#&#8203;752](autobrr/qui#752)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`8886ac4`](autobrr/qui@8886ac4): fix(crossseed): resolve category creation race condition and relax autoTMM ([#&#8203;767](autobrr/qui#767)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`f8f2a05`](autobrr/qui@f8f2a05): fix(crossseed): support game scene releases with RAR files ([#&#8203;768](autobrr/qui#768)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`918adee`](autobrr/qui@918adee): fix(crossseed): treat x264/H.264/H264/AVC as equivalent codecs ([#&#8203;766](autobrr/qui#766)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`c4b1f0a`](autobrr/qui@c4b1f0a): fix(dashboard): merge tracker customizations with duplicate displayName ([#&#8203;751](autobrr/qui#751)) ([@&#8203;jabloink](https://github.com/jabloink))
- [`3c6e0f9`](autobrr/qui@3c6e0f9): fix(license): remove redundant validation call after activation ([#&#8203;749](autobrr/qui#749)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`a9c7754`](autobrr/qui@a9c7754): fix(reannounce): simplify tracker detection to match qbrr logic ([#&#8203;746](autobrr/qui#746)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`3baa007`](autobrr/qui@3baa007): fix(rss): skip download when torrent already exists by infohash ([#&#8203;715](autobrr/qui#715)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`55d0ccc`](autobrr/qui@55d0ccc): fix(swagger): respect base URL for API docs routes ([#&#8203;758](autobrr/qui#758)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`47695fd`](autobrr/qui@47695fd): fix(web): add height constraint to filter sidebar wrapper for proper scrolling ([#&#8203;778](autobrr/qui#778)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`4b3bfea`](autobrr/qui@4b3bfea): fix(web): default torrent format to v1 in creator dialog ([#&#8203;723](autobrr/qui#723)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`2d54b79`](autobrr/qui@2d54b79): fix(web): pin submit button in Services sheet footer ([#&#8203;756](autobrr/qui#756)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`2bcd6a3`](autobrr/qui@2bcd6a3): fix(web): preserve folder collapse state during file tree sync ([#&#8203;740](autobrr/qui#740)) ([@&#8203;ewenjo](https://github.com/ewenjo))
- [`57f3f1d`](autobrr/qui@57f3f1d): fix(web): sort Peers column by total peers instead of connected ([#&#8203;759](autobrr/qui#759)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`53a8818`](autobrr/qui@53a8818): fix(web): sort Seeds column by total seeds instead of connected ([#&#8203;747](autobrr/qui#747)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`d171915`](autobrr/qui@d171915): fix(web): sort folders before files in torrent file tree ([#&#8203;764](autobrr/qui#764)) ([@&#8203;s0up4200](https://github.com/s0up4200))

##### Other Changes

- [`172b4aa`](autobrr/qui@172b4aa): chore(assets): replace napster.svg with napster.png for logo update ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`dc83102`](autobrr/qui@dc83102): chore(deps): bump the github group with 3 updates ([#&#8203;761](autobrr/qui#761)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`75357d3`](autobrr/qui@75357d3): chore: fix napster logo ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`206c4b2`](autobrr/qui@206c4b2): refactor(web): extract CrossSeed completion to accordion component ([#&#8203;762](autobrr/qui#762)) ([@&#8203;s0up4200](https://github.com/s0up4200))

**Full Changelog**: <autobrr/qui@v1.9.1...v1.10.0>

#### Docker images

- `docker pull ghcr.io/autobrr/qui:v1.10.0`
- `docker pull ghcr.io/autobrr/qui:latest`

#### What to do next?

- Join our [Discord server](https://discord.autobrr.com/qui)

Thank you for using qui!

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4zOS4xIiwidXBkYXRlZEluVmVyIjoiNDIuMzkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW1hZ2UiXX0=-->

Reviewed-on: https://gitea.alexlebens.dev/alexlebens/infrastructure/pulls/2664
Co-authored-by: Renovate Bot <renovate-bot@alexlebens.net>
Co-committed-by: Renovate Bot <renovate-bot@alexlebens.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant