Extension filter via a releases.extensions column
Test and deploy / test (push) Successful in 1m28s
Test and deploy / deploy (push) Successful in 58m9s

The first version matched extensions in search_text, which was wrong and
slow: file names enter search_text as stems without extension (so plainly
posted .mkv files were missed), and matching the TOASTed text took 3-4 s
for common types.

- Migration …130: releases.extensions text[] (two to four characters with
  a letter; .7z.001 and .r00 count as 7z and rar), kept current by triggers
  on files (name, release) and on releases (archive listing), filled with
  one sequential pass over files, with a GIN index.
- Search: ext matches with `extensions && ?`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-04 19:54:38 +02:00
1 parent e1392608d8
commit 7ca338d4f3
4 files changed
+126 -6

No files matched your search

+4 -5
View File
@@ -48,7 +48,7 @@ export interface SearchParams {
/** Size bounds in bytes (releases without a known size never match). */
minSize?: number
maxSize?: number
/** File extensions (without the dot); a release matches if a file or archive entry has one of them. */
/** File extensions (without the dot); a release matches if a file or archive entry has one of them (`releases.extensions`). */
extensions?: string[]
/** Count matches up to this many past the page (default COUNT_LIMIT); `total` is capped there. */
countLimit?: number
@@ -125,12 +125,11 @@ class SearchService {
where.push('r.size <= ?')
bindings.push(params.maxSize)
}
// search_text holds file and entry names with dots as spaces, so an
// extension is a whole word there ("iso" but not "isolation").
// File types from file names and archive listings (migration …130), via their GIN index.
const extensions = (params.extensions ?? []).map((e) => e.toLowerCase().replace(/^\./, '')).filter((e) => /^[a-z0-9]{1,8}$/.test(e)).slice(0, 8)
if (extensions.length > 0) {
where.push(`r.search_text ~ ?`)
bindings.push(`(^| )(${extensions.join('|')})( |$)`)
where.push('r.extensions && ?::text[]')
bindings.push(extensions)
}
if (params.hideEncrypted) where.push('(r.encrypted IS NOT TRUE OR r.password IS NOT NULL)')
if (params.hideContainers) where.push('r.container IS NULL')