feat: better typing on a series page

This commit is contained in:
FrenchGithubUser
2025-12-09 13:17:07 +01:00
parent 026169d0a6
commit 343aa39a68
13 changed files with 263 additions and 48 deletions

View File

@@ -5,7 +5,12 @@ use actix_web::{
};
use arcadia_common::error::Result;
use arcadia_storage::{
models::series::SeriesAndTitleGroupHierarchyLite, redis::RedisPoolInterface,
models::{
common::OrderByDirection,
series::SeriesAndTitleGroupHierarchyLite,
torrent::{TorrentSearch, TorrentSearchOrderByColumn},
},
redis::RedisPoolInterface,
};
use serde::Deserialize;
use utoipa::IntoParams;
@@ -31,5 +36,25 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
) -> Result<HttpResponse> {
let series = arc.pool.find_series(&query.id).await?;
Ok(HttpResponse::Ok().json(series))
let search_form = TorrentSearch {
series_id: Some(query.id),
page: 1,
page_size: i64::MAX,
order_by_column: TorrentSearchOrderByColumn::TitleGroupOriginalReleaseDate,
order_by_direction: OrderByDirection::Desc,
title_group_include_empty_groups: true,
title_group_name: None,
torrent_reported: None,
torrent_staff_checked: None,
torrent_created_by_id: None,
torrent_snatched_by_id: None,
artist_id: None,
collage_id: None,
};
let title_groups_in_series = arc.pool.search_torrents(&search_form, None).await?;
Ok(HttpResponse::Ok().json(SeriesAndTitleGroupHierarchyLite {
series,
title_groups: title_groups_in_series.results,
}))
}

View File

@@ -44,6 +44,7 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
order_by_direction: OrderByDirection::Desc,
artist_id: None,
collage_id: None,
series_id: None,
};
let uploaded_torrents = arc
.pool

View File

@@ -53,6 +53,7 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
order_by_direction: OrderByDirection::Desc,
artist_id: None,
collage_id: None,
series_id: None,
};
let uploaded_torrents = arc
.pool

View File

@@ -188,6 +188,7 @@ async fn test_find_torrents_by_external_link(pool: PgPool) {
page_size: 50,
order_by_column: TorrentSearchOrderByColumn::TorrentCreatedAt,
order_by_direction: OrderByDirection::Desc,
series_id: None,
};
let query = serde_urlencoded::to_string(query).unwrap();
@@ -244,6 +245,7 @@ async fn test_find_torrents_by_name(pool: PgPool) {
page_size: 50,
order_by_column: TorrentSearchOrderByColumn::TorrentCreatedAt,
order_by_direction: OrderByDirection::Desc,
series_id: None,
};
let query = serde_urlencoded::to_string(query).unwrap();
@@ -300,6 +302,7 @@ async fn test_find_torrents_no_link_or_name_provided(pool: PgPool) {
page_size: 50,
order_by_column: TorrentSearchOrderByColumn::TorrentCreatedAt,
order_by_direction: OrderByDirection::Desc,
series_id: None,
};
let query = serde_urlencoded::to_string(query).unwrap();

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n jsonb_build_object(\n 'series', to_jsonb(s),\n 'title_groups', COALESCE(\n jsonb_agg(tgd.title_group_data),\n '[]'::jsonb\n )\n ) AS series_and_title_groups\n FROM\n series s\n LEFT JOIN\n title_groups tg ON s.id = tg.series_id\n LEFT JOIN\n get_title_groups_and_edition_group_and_torrents_lite AS tgd ON tg.id = tgd.title_group_id\n WHERE\n s.id = $1\n GROUP BY\n s.id, s.*;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "series_and_title_groups",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
null
]
},
"hash": "09337987d244feeb7b38251eac5791f9d499388dd01ab4d369e963ed3ecf3f95"
}

View File

@@ -0,0 +1,70 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT * FROM series\n WHERE series.id = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "description",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "tags",
"type_info": "TextArray"
},
{
"ordinal": 4,
"name": "covers",
"type_info": "TextArray"
},
{
"ordinal": 5,
"name": "banners",
"type_info": "TextArray"
},
{
"ordinal": 6,
"name": "created_by_id",
"type_info": "Int4"
},
{
"ordinal": 7,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 8,
"name": "updated_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "9bd01434dd614efca467327891d8038b8f431cf6188a9ed81ee9a925f7c32208"
}

View File

@@ -0,0 +1,145 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT title_group_id AS \"id!\", title_group_name AS \"name!\", title_group_covers AS \"covers!\",\n title_group_category AS \"category!: _\", title_group_content_type AS \"content_type!: _\", title_group_tag_names AS \"tags!\",\n title_group_original_release_date AS \"original_release_date!\", title_group_platform AS \"platform!: _\",\n '[]'::jsonb AS \"edition_groups!: _\",\n '[]'::jsonb AS \"affiliated_artists!: _\"\n\n FROM title_group_hierarchy_lite tgh\n\n WHERE ($4::BOOLEAN IS NULL OR tgh.torrent_staff_checked = $4)\n AND ($5::BOOLEAN IS NULL OR tgh.torrent_reported = $5)\n AND (\n $7::INT IS NULL OR\n -- don't return torrents created as anonymous\n -- unless the requesting user is the uploader\n (tgh.torrent_created_by_id = $7 AND (\n tgh.torrent_created_by_id = $8 OR\n NOT tgh.torrent_uploaded_as_anonymous)\n )\n )\n AND (\n $9::BIGINT IS NULL OR\n EXISTS (SELECT 1 FROM affiliated_artists aa WHERE aa.title_group_id = tgh.title_group_id AND aa.artist_id = $9)\n )\n -- name filter (partial match) or external link match or series name match\n AND (\n $10::TEXT IS NULL OR\n tgh.title_group_name ILIKE '%' || $10 || '%' ESCAPE '\\' OR\n tgh.title_group_series_name ILIKE '%' || $10 || '%' ESCAPE '\\'\n )\n AND ($11::TEXT IS NULL OR $11 = ANY(tgh.title_group_external_links))\n AND ($12::BOOLEAN IS TRUE OR tgh.torrent_id IS NOT NULL)\n AND ($13::BIGINT IS NULL OR tgh.title_group_series_id = $13)\n\n GROUP BY title_group_id, title_group_name, title_group_covers, title_group_category,\n title_group_content_type, title_group_tag_names, title_group_original_release_date, title_group_platform\n\n ORDER BY\n CASE WHEN $1 = 'title_group_original_release_date' AND $6 = 'asc' THEN title_group_original_release_date END ASC,\n CASE WHEN $1 = 'title_group_original_release_date' AND $6 = 'desc' THEN title_group_original_release_date END DESC,\n CASE WHEN $1 = 'torrent_size' AND $6 = 'asc' THEN MAX(torrent_size) END ASC,\n CASE WHEN $1 = 'torrent_size' AND $6 = 'desc' THEN MAX(torrent_size) END DESC,\n CASE WHEN $1 = 'torrent_created_at' AND $6 = 'asc' THEN MAX(torrent_created_at) END ASC,\n CASE WHEN $1 = 'torrent_created_at' AND $6 = 'desc' THEN MAX(torrent_created_at) END DESC,\n title_group_original_release_date ASC\n\n LIMIT $2 OFFSET $3\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "name!",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "covers!",
"type_info": "TextArray"
},
{
"ordinal": 3,
"name": "category!: _",
"type_info": {
"Custom": {
"name": "title_group_category_enum",
"kind": {
"Enum": [
"Ep",
"Album",
"Single",
"Soundtrack",
"Anthology",
"Compilation",
"Remix",
"Bootleg",
"Mixtape",
"ConcertRecording",
"DjMix",
"FeatureFilm",
"ShortFilm",
"Game",
"Program",
"Illustrated",
"Periodical",
"Book",
"Article",
"Manual",
"Other"
]
}
}
}
},
{
"ordinal": 4,
"name": "content_type!: _",
"type_info": {
"Custom": {
"name": "content_type_enum",
"kind": {
"Enum": [
"movie",
"video",
"tv_show",
"music",
"podcast",
"software",
"book",
"collection"
]
}
}
}
},
{
"ordinal": 5,
"name": "tags!",
"type_info": "VarcharArray"
},
{
"ordinal": 6,
"name": "original_release_date!",
"type_info": "Timestamptz"
},
{
"ordinal": 7,
"name": "platform!: _",
"type_info": {
"Custom": {
"name": "platform_enum",
"kind": {
"Enum": [
"Linux",
"MacOS",
"Windows",
"Xbox"
]
}
}
}
},
{
"ordinal": 8,
"name": "edition_groups!: _",
"type_info": "Jsonb"
},
{
"ordinal": 9,
"name": "affiliated_artists!: _",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Text",
"Int8",
"Int8",
"Bool",
"Bool",
"Text",
"Int4",
"Int4",
"Int8",
"Text",
"Text",
"Bool",
"Int8"
]
},
"nullable": [
true,
true,
true,
true,
true,
true,
true,
true,
null,
null
]
},
"hash": "baa37b12c8a0b2ff7ac0419faac8bbe6c2763b894727e823d6b343941efcd160"
}

View File

@@ -457,6 +457,7 @@ pub struct TorrentSearch {
// link to other tables
pub artist_id: Option<i64>,
pub collage_id: Option<i32>,
pub series_id: Option<i64>,
// pagination and ordering
pub page: i64,
pub page_size: i64,

View File

@@ -160,6 +160,7 @@ impl ConnectionPool {
torrent_reported: None,
torrent_snatched_by_id: None,
torrent_staff_checked: None,
series_id: None,
order_by_direction: OrderByDirection::Desc,
order_by_column: TorrentSearchOrderByColumn::TitleGroupOriginalReleaseDate,
collage_id: None,

View File

@@ -5,7 +5,6 @@ use crate::{
},
};
use arcadia_common::error::{Error, Result};
use serde_json::Value;
use sqlx::{query_as_unchecked, query_scalar};
use std::borrow::Borrow;
@@ -32,27 +31,12 @@ impl ConnectionPool {
Ok(created_series)
}
pub async fn find_series(&self, series_id: &i64) -> Result<Value> {
let found_series = sqlx::query!(
pub async fn find_series(&self, series_id: &i64) -> Result<Series> {
let series = sqlx::query_as!(
Series,
r#"
SELECT
jsonb_build_object(
'series', to_jsonb(s),
'title_groups', COALESCE(
jsonb_agg(tgd.title_group_data),
'[]'::jsonb
)
) AS series_and_title_groups
FROM
series s
LEFT JOIN
title_groups tg ON s.id = tg.series_id
LEFT JOIN
get_title_groups_and_edition_group_and_torrents_lite AS tgd ON tg.id = tgd.title_group_id
WHERE
s.id = $1
GROUP BY
s.id, s.*;
SELECT * FROM series
WHERE series.id = $1
"#,
series_id
)
@@ -60,8 +44,7 @@ impl ConnectionPool {
.await
.map_err(|_| Error::SeriesWithIdNotFound(*series_id))?;
// Ok(serde_json::from_value(found_series.series_and_groups.unwrap()).unwrap())
Ok(found_series.series_and_title_groups.unwrap())
Ok(series)
}
pub async fn search_series(&self, form: &SearchSeriesQuery) -> Result<SeriesSearchResponse> {

View File

@@ -454,6 +454,7 @@ impl ConnectionPool {
)
AND ($11::TEXT IS NULL OR $11 = ANY(tgh.title_group_external_links))
AND ($12::BOOLEAN IS TRUE OR tgh.torrent_id IS NOT NULL)
AND ($13::BIGINT IS NULL OR tgh.title_group_series_id = $13)
GROUP BY title_group_id, title_group_name, title_group_covers, title_group_category,
title_group_content_type, title_group_tag_names, title_group_original_release_date, title_group_platform
@@ -480,7 +481,8 @@ impl ConnectionPool {
form.artist_id,
name_filter,
external_link_filter,
form.title_group_include_empty_groups
form.title_group_include_empty_groups,
form.series_id
)
.fetch_all(self.borrow())
.await

View File

@@ -84,6 +84,11 @@ const router = createRouter({
},
component: () => import('../views/torrent_request/TorrentRequestView.vue'),
},
{
path: '/new-series',
name: 'CreateSeries',
component: () => import('../views/series/CreateOrEditSeriesView.vue'),
},
{
path: '/series/:id',
name: 'Series',