UI/UX списка и карточки загрузки: серверные фильтр/поиск/пагинация, матч-ссылка, имя раздачи (web-ui-list-detail)
- Список: серверные фильтр по группе состояний, поиск и пагинация (GET
f/q/page/all, по 25), сортировка по времени добавления в qBittorrent
(added_on) с фолбеком на created_at и tie-break по id.
- Заголовок загрузки = имя раздачи (display_name) → распознанное название →
усечённый источник; сырой magnet вынесен в блок «Информация о торренте».
- Матч метабазы показан ссылкой на запись (страница загрузки и ревью);
URL берётся у выбранного кандидата либо строится по provider+id и типу.
- Полировка вёрстки; клиентская JS-фильтрация убрана (всё серверное, без JS).
- Миграция 0005 (display_name, source_added_at); воркер однократно
фиксирует source_added_at при поллинге/усыновлении; ER-схема обновлена.
- OpenSpec: дельты влиты в specs/{web-ui,ingest}, change заархивирован.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,7 @@ type reviewView struct {
|
||||
Year int
|
||||
Provider string
|
||||
ProviderID string
|
||||
MatchURL string // ссылка на подтверждённую запись метабазы (пусто — текстом)
|
||||
Confidence string
|
||||
Reasons []string
|
||||
Hints []string
|
||||
@@ -102,6 +103,7 @@ func (s *server) handleReview(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
view.Provider = rd.Provider
|
||||
view.ProviderID = rd.ProviderID
|
||||
view.MatchURL = matchURL(rd, string(rd.Plan.Type))
|
||||
}
|
||||
if rec.Confidence.Valid {
|
||||
view.Confidence = strconv.FormatFloat(rec.Confidence.Float64, 'f', 2, 64)
|
||||
@@ -253,6 +255,47 @@ func (s *server) reviewAction(w http.ResponseWriter, r *http.Request, fn func(co
|
||||
redirectReview(w, r, id, "")
|
||||
}
|
||||
|
||||
// matchURL выбирает ссылку на подтверждённую запись метабазы. Приоритет — URL
|
||||
// выбранного кандидата, но только если его provider+id совпадают с эффективными
|
||||
// (человек мог выбрать кандидата, затем вручную переопределить id — тогда
|
||||
// кандидат указывает на другую запись). Иначе строим канонический URL; если не
|
||||
// удаётся — возвращаем пусто (транспорт покажет матч текстом).
|
||||
func matchURL(rd *worker.ReviewData, mediaType string) string {
|
||||
for _, c := range rd.Candidates {
|
||||
if c.Chosen && c.Provider == rd.Provider && c.ProviderID == rd.ProviderID &&
|
||||
c.URL.Valid && c.URL.String != "" {
|
||||
return c.URL.String
|
||||
}
|
||||
}
|
||||
return providerURL(rd.Provider, rd.ProviderID, mediaType)
|
||||
}
|
||||
|
||||
// providerURL строит канонический URL записи метабазы с учётом типа медиа.
|
||||
// Пустой id или неизвестный провайдер → пусто.
|
||||
func providerURL(provider, id, mediaType string) string {
|
||||
if id == "" {
|
||||
return ""
|
||||
}
|
||||
switch provider {
|
||||
case "tmdb":
|
||||
kind := "movie"
|
||||
if mediaType == "series" {
|
||||
kind = "tv"
|
||||
}
|
||||
return "https://www.themoviedb.org/" + kind + "/" + id
|
||||
case "tvdb":
|
||||
kind := "series"
|
||||
if mediaType == "movie" {
|
||||
kind = "movie"
|
||||
}
|
||||
return "https://www.thetvdb.com/dereferrer/" + kind + "/" + id
|
||||
case "imdb":
|
||||
return "https://www.imdb.com/title/" + id
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func redirectReview(w http.ResponseWriter, r *http.Request, id int64, msg string) {
|
||||
u := "/review/" + strconv.FormatInt(id, 10)
|
||||
if msg != "" {
|
||||
|
||||
Reference in New Issue
Block a user