Add ability to watch an episode
- Migration for new backing table. - CRUD server functions, new components to display and interact.
This commit is contained in:
parent
75e388555b
commit
b2e55008dd
11 changed files with 317 additions and 43 deletions
69
Cargo.lock
generated
69
Cargo.lock
generated
|
@ -44,6 +44,21 @@ version = "0.2.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.15"
|
version = "0.6.15"
|
||||||
|
@ -338,6 +353,18 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"num-traits",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ciborium"
|
name = "ciborium"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -445,6 +472,12 @@ dependencies = [
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -1049,6 +1082,29 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ident_case"
|
name = "ident_case"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -2436,6 +2492,7 @@ dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
"crossbeam-queue",
|
"crossbeam-queue",
|
||||||
"either",
|
"either",
|
||||||
|
@ -2520,6 +2577,7 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
"digest",
|
"digest",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
@ -2562,6 +2620,7 @@ dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"etcetera",
|
"etcetera",
|
||||||
|
@ -2598,6 +2657,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680"
|
checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
|
"chrono",
|
||||||
"flume",
|
"flume",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -3218,6 +3278,15 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
|
|
|
@ -20,7 +20,7 @@ wasm-bindgen = "=0.2.93"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
tracing = { version = "0.1", optional = true }
|
tracing = { version = "0.1", optional = true }
|
||||||
http = "1"
|
http = "1"
|
||||||
sqlx = { version = "0.8.1", features = ["postgres", "runtime-tokio", "tls-rustls-ring", "uuid"], optional = true }
|
sqlx = { version = "0.8.1", features = ["postgres", "runtime-tokio", "tls-rustls-ring", "uuid", "chrono"], optional = true }
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
log = { version = "0.4.22", optional = true }
|
log = { version = "0.4.22", optional = true }
|
||||||
env_logger = { version = "0.11.5", optional = true }
|
env_logger = { version = "0.11.5", optional = true }
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
create table if not exists "public"."series" (
|
create table if not exists series (
|
||||||
"id" uuid default gen_random_uuid() not null primary key,
|
id uuid default gen_random_uuid() not null primary key,
|
||||||
"name" text not null,
|
created timestamptz default now() not null,
|
||||||
"created" timestamptz default now() not null,
|
updated timestamptz default now() not null,
|
||||||
"updated" timestamptz default now() not null
|
name text not null,
|
||||||
|
source text not null check (source in (
|
||||||
|
'Disney', 'Hulu', 'Paramount', 'Prime', 'Max', 'Netflix', 'Shudder'
|
||||||
|
)),
|
||||||
|
source_url text not null,
|
||||||
|
imdb text null,
|
||||||
|
wikipedia text null
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
alter table "public"."series" add column source text not null;
|
|
||||||
alter table "public"."series" add column source_url text not null;
|
|
||||||
alter table "public"."series" add column imdb text null;
|
|
||||||
alter table "public"."series" add column wikipedia text null
|
|
9
migrations/20240914205329_add-viewing.sql
Normal file
9
migrations/20240914205329_add-viewing.sql
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
create table if not exists viewing (
|
||||||
|
id uuid default gen_random_uuid() not null primary key,
|
||||||
|
series_id uuid not null references series(id),
|
||||||
|
created timestamptz default now() not null,
|
||||||
|
updated timestamptz default now() not null,
|
||||||
|
season smallint not null check (season > 0),
|
||||||
|
episode smallint not null check (episode > 0),
|
||||||
|
"comment" text null
|
||||||
|
);
|
1
sql/insert_viewing.sql
Normal file
1
sql/insert_viewing.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
insert into viewing (series_id, season, episode, "comment") values ($1, $2, $3, $4);
|
3
sql/select_viewings.sql
Normal file
3
sql/select_viewings.sql
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
select *
|
||||||
|
from viewing
|
||||||
|
where series_id = $1;
|
|
@ -79,12 +79,17 @@ pub async fn add_series(
|
||||||
imdb: String,
|
imdb: String,
|
||||||
wikipedia: String,
|
wikipedia: String,
|
||||||
) -> Result<(), ServerFnError> {
|
) -> Result<(), ServerFnError> {
|
||||||
use crate::db::load_statement;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
|
use crate::db::load_statement;
|
||||||
|
|
||||||
let pool = expect_context::<Pool<Postgres>>();
|
let pool = expect_context::<Pool<Postgres>>();
|
||||||
|
|
||||||
let s = load_statement("insert_series.sql");
|
static SQL: OnceLock<String> = OnceLock::new();
|
||||||
sqlx::query(&s)
|
let s = SQL.get_or_init(|| load_statement("insert_series.sql"));
|
||||||
|
sqlx::query(s)
|
||||||
.persistent(true)
|
.persistent(true)
|
||||||
.bind(name)
|
.bind(name)
|
||||||
.bind(source.to_string())
|
.bind(source.to_string())
|
||||||
|
|
162
src/app/series/history.rs
Normal file
162
src/app/series/history.rs
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
use leptos::*;
|
||||||
|
use leptos_router::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::format_errors;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn ViewingList(series_id: Uuid) -> impl IntoView {
|
||||||
|
let add = create_server_action::<AddViewing>();
|
||||||
|
let viewings = create_resource(
|
||||||
|
move || (add.version().get(), series_id),
|
||||||
|
|(_, id)| async move { get_viewings(id).await },
|
||||||
|
);
|
||||||
|
view! {
|
||||||
|
<h3 class="mb-3">Viewing History</h3>
|
||||||
|
<Suspense fallback=|| view!{ <div>Loading...</div> }>
|
||||||
|
{viewings().map(format_viewings)}
|
||||||
|
</Suspense>
|
||||||
|
<ActionForm action=add>
|
||||||
|
<input type="hidden" name="series_id" value=series_id.to_string() />
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col col-4 align-middle">
|
||||||
|
<input class="form-control btn btn-primary" type="submit" value="Watch Episode" />
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<label class="input-group-text" for="season">Season</label>
|
||||||
|
<input class="form-control" name="season" type="number" value="1" />
|
||||||
|
<label class="input-group-text" for="episode">Episode</label>
|
||||||
|
<input class="form-control" name="episode" type="number" value="1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="form-label" for="comment">Comment</label>
|
||||||
|
<textarea class="form-control" name="comment" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ActionForm>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_viewings(
|
||||||
|
result: Result<Vec<Viewing>, ServerFnError>,
|
||||||
|
) -> Result<impl IntoView, ServerFnError> {
|
||||||
|
Ok(view! {
|
||||||
|
<ErrorBoundary fallback=format_errors>
|
||||||
|
{result.map(|viewings| {
|
||||||
|
let empty = viewings.is_empty();
|
||||||
|
view! {
|
||||||
|
<Show when=move || empty fallback=|| ()>
|
||||||
|
<div class="mb-3">No viewings found.</div>
|
||||||
|
</Show>
|
||||||
|
<ul class="list-group mb-3">
|
||||||
|
{viewings.into_iter().map(|viewing| {
|
||||||
|
view! {
|
||||||
|
<li class="list-group-item">
|
||||||
|
Season {viewing.season},
|
||||||
|
Episode {viewing.episode}
|
||||||
|
on {viewing.created}
|
||||||
|
<Suspense fallback=|| ()>
|
||||||
|
{viewing.comment.as_ref()}
|
||||||
|
</Suspense>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}).collect_view()}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ErrorBoundary>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Viewing {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub created: String,
|
||||||
|
pub updated: String,
|
||||||
|
pub season: i16,
|
||||||
|
pub episode: i16,
|
||||||
|
pub comment: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
mod ssr {
|
||||||
|
use super::Viewing;
|
||||||
|
use sqlx::{
|
||||||
|
postgres::PgRow,
|
||||||
|
prelude::*,
|
||||||
|
types::chrono::{DateTime, Utc},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, PgRow> for Viewing {
|
||||||
|
fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
|
||||||
|
let id = row.try_get("id")?;
|
||||||
|
let created: DateTime<Utc> = row.try_get("created")?;
|
||||||
|
let created = created.format("%Y-%m-%d").to_string();
|
||||||
|
let updated: DateTime<Utc> = row.try_get("updated")?;
|
||||||
|
let updated = updated.format("%Y-%m-%d").to_string();
|
||||||
|
let season = row.try_get("season")?;
|
||||||
|
let episode = row.try_get("episode")?;
|
||||||
|
let comment = row.try_get("comment")?;
|
||||||
|
|
||||||
|
Ok(Viewing {
|
||||||
|
id,
|
||||||
|
created,
|
||||||
|
updated,
|
||||||
|
season,
|
||||||
|
episode,
|
||||||
|
comment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server(AddViewing)]
|
||||||
|
pub async fn add_viewing(
|
||||||
|
series_id: Uuid,
|
||||||
|
episode: i32,
|
||||||
|
season: i32,
|
||||||
|
comment: Option<String>,
|
||||||
|
) -> Result<(), ServerFnError> {
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
|
use crate::db::load_statement;
|
||||||
|
|
||||||
|
static SQL: OnceLock<String> = OnceLock::new();
|
||||||
|
|
||||||
|
let pool = expect_context::<Pool<Postgres>>();
|
||||||
|
let s = SQL.get_or_init(|| load_statement("insert_viewing.sql"));
|
||||||
|
sqlx::query(s)
|
||||||
|
.persistent(true)
|
||||||
|
.bind(series_id)
|
||||||
|
.bind(season)
|
||||||
|
.bind(episode)
|
||||||
|
.bind(comment)
|
||||||
|
.execute(&pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server(GetViewings)]
|
||||||
|
pub async fn get_viewings(series_id: Uuid) -> Result<Vec<Viewing>, ServerFnError> {
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
|
use crate::db::load_statement;
|
||||||
|
|
||||||
|
static SQL: OnceLock<String> = OnceLock::new();
|
||||||
|
|
||||||
|
let pool = expect_context::<Pool<Postgres>>();
|
||||||
|
let s = SQL.get_or_init(|| load_statement("select_viewings.sql"));
|
||||||
|
Ok(sqlx::query_as::<_, Viewing>(s)
|
||||||
|
.persistent(true)
|
||||||
|
.bind(series_id)
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await?)
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub mod add;
|
pub mod add;
|
||||||
|
mod history;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
@ -132,6 +133,8 @@ impl Display for Source {
|
||||||
pub struct Series {
|
pub struct Series {
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
name: String,
|
name: String,
|
||||||
|
created: String,
|
||||||
|
updated: String,
|
||||||
source: Source,
|
source: Source,
|
||||||
source_url: String,
|
source_url: String,
|
||||||
imdb: Option<String>,
|
imdb: Option<String>,
|
||||||
|
@ -141,11 +144,19 @@ pub struct Series {
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
mod ssr {
|
mod ssr {
|
||||||
use super::{Series, Source};
|
use super::{Series, Source};
|
||||||
use sqlx::{postgres::PgRow, prelude::*};
|
use sqlx::{
|
||||||
|
postgres::PgRow,
|
||||||
|
prelude::*,
|
||||||
|
types::chrono::{DateTime, Utc},
|
||||||
|
};
|
||||||
|
|
||||||
impl<'r> FromRow<'r, PgRow> for Series {
|
impl<'r> FromRow<'r, PgRow> for Series {
|
||||||
fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
|
fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
|
||||||
let id = row.try_get("id")?;
|
let id = row.try_get("id")?;
|
||||||
|
let created: DateTime<Utc> = row.try_get("created")?;
|
||||||
|
let created = created.format("%Y-%m-%d").to_string();
|
||||||
|
let updated: DateTime<Utc> = row.try_get("updated")?;
|
||||||
|
let updated = updated.format("%Y-%m-%d").to_string();
|
||||||
let name = row.try_get("name")?;
|
let name = row.try_get("name")?;
|
||||||
let source: String = row.try_get("source")?;
|
let source: String = row.try_get("source")?;
|
||||||
|
|
||||||
|
@ -162,6 +173,8 @@ mod ssr {
|
||||||
|
|
||||||
Ok(Series {
|
Ok(Series {
|
||||||
id,
|
id,
|
||||||
|
created,
|
||||||
|
updated,
|
||||||
name,
|
name,
|
||||||
source_url,
|
source_url,
|
||||||
imdb,
|
imdb,
|
||||||
|
@ -191,31 +204,36 @@ mod ssr {
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
pub async fn get_all() -> Result<Vec<Series>, ServerFnError> {
|
pub async fn get_all() -> Result<Vec<Series>, ServerFnError> {
|
||||||
use crate::db::load_statement;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
use tokio_stream::StreamExt;
|
|
||||||
|
use crate::db::load_statement;
|
||||||
|
|
||||||
|
static SQL: OnceLock<String> = OnceLock::new();
|
||||||
|
|
||||||
let pool: Pool<Postgres> = expect_context();
|
let pool: Pool<Postgres> = expect_context();
|
||||||
|
|
||||||
let s = load_statement("select_all_series.sql");
|
let s = SQL.get_or_init(|| load_statement("select_all_series.sql"));
|
||||||
let mut r = sqlx::query_as::<_, Series>(&s).fetch(&pool);
|
Ok(sqlx::query_as::<_, Series>(&s)
|
||||||
|
.persistent(true)
|
||||||
let mut found = Vec::new();
|
.fetch_all(&pool)
|
||||||
while let Some(series) = r.try_next().await? {
|
.await?)
|
||||||
found.push(series);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(found)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server(DeleteSeries)]
|
#[server(DeleteSeries)]
|
||||||
pub async fn delete(id: String) -> Result<(), ServerFnError> {
|
pub async fn delete(id: String) -> Result<(), ServerFnError> {
|
||||||
use crate::db::load_statement;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
|
use crate::db::load_statement;
|
||||||
|
|
||||||
|
static SQL: OnceLock<String> = OnceLock::new();
|
||||||
|
|
||||||
let pool: Pool<Postgres> = expect_context();
|
let pool: Pool<Postgres> = expect_context();
|
||||||
|
|
||||||
let s = load_statement("delete_series.sql");
|
let s = SQL.get_or_init(|| load_statement("delete_series.sql"));
|
||||||
sqlx::query(&s)
|
sqlx::query(&s)
|
||||||
.bind(id.parse::<Uuid>()?)
|
.bind(id.parse::<Uuid>()?)
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{format_errors, Series};
|
use super::{format_errors, history::ViewingList, Series};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -32,19 +32,19 @@ pub fn ViewSeries() -> impl IntoView {
|
||||||
|
|
||||||
fn loading() -> impl IntoView {
|
fn loading() -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
<div class="card-heading">
|
<div class="card-heading mb-3">
|
||||||
<A href="/"
|
<A href="/"
|
||||||
attr:class="btn btn-close"
|
attr:class="btn btn-close"
|
||||||
attr:style="float: right;"
|
attr:style="float: right;"
|
||||||
>
|
>
|
||||||
<span style="display: none;">x</span>
|
<span style="display: none;">x</span>
|
||||||
</A>
|
</A>
|
||||||
<h2><span class="placeholder col-4"></span></h2>
|
</div>
|
||||||
<div class="btn-group">
|
<h2><span class="placeholder col-4"></span></h2>
|
||||||
<button class="btn btn-primary"><span class="placeholder col-4"></span></button>
|
<div class="btn-group mb-3">
|
||||||
<button class="btn btn-primary">IMDB</button>
|
<button class="btn btn-primary"><span class="placeholder col-4"></span></button>
|
||||||
<button class="btn btn-primary">Wikipedia</button>
|
<button class="btn btn-primary">IMDB</button>
|
||||||
</div>
|
<button class="btn btn-primary">Wikipedia</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ fn format_series_details(
|
||||||
Ok(view! {
|
Ok(view! {
|
||||||
<ErrorBoundary fallback=format_errors>
|
<ErrorBoundary fallback=format_errors>
|
||||||
{details.map(|details| view! {
|
{details.map(|details| view! {
|
||||||
<div class="card-heading">
|
<div class="card-heading mb-3">
|
||||||
<A href="/"
|
<A href="/"
|
||||||
attr:class="btn btn-close"
|
attr:class="btn btn-close"
|
||||||
attr:style="float: right;"
|
attr:style="float: right;"
|
||||||
|
@ -63,8 +63,9 @@ fn format_series_details(
|
||||||
<span style="display: none;">x</span>
|
<span style="display: none;">x</span>
|
||||||
</A>
|
</A>
|
||||||
<h2>{details.name}</h2>
|
<h2>{details.name}</h2>
|
||||||
|
<small class="text-body-secondary">created {details.created}, last updated {details.updated}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
<div class="btn-group mb-3">
|
||||||
<a href={details.source_url}
|
<a href={details.source_url}
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
target="_new"
|
target="_new"
|
||||||
|
@ -74,6 +75,7 @@ fn format_series_details(
|
||||||
<OptionalUrlButton label="IMDB" opt_url=details.imdb.clone() />
|
<OptionalUrlButton label="IMDB" opt_url=details.imdb.clone() />
|
||||||
<OptionalUrlButton label="Wikipedia" opt_url=details.wikipedia.clone() />
|
<OptionalUrlButton label="Wikipedia" opt_url=details.wikipedia.clone() />
|
||||||
</div>
|
</div>
|
||||||
|
<ViewingList series_id=details.id.clone() />
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
@ -98,16 +100,19 @@ fn OptionalUrlButton(label: &'static str, opt_url: Option<String>) -> impl IntoV
|
||||||
|
|
||||||
#[server(GetSeriesDetails)]
|
#[server(GetSeriesDetails)]
|
||||||
pub async fn get_series_details(id: Uuid) -> Result<Series, ServerFnError> {
|
pub async fn get_series_details(id: Uuid) -> Result<Series, ServerFnError> {
|
||||||
use crate::db::load_statement;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
|
use crate::db::load_statement;
|
||||||
|
|
||||||
|
static SQL: OnceLock<String> = OnceLock::new();
|
||||||
|
|
||||||
let pool = expect_context::<Pool<Postgres>>();
|
let pool = expect_context::<Pool<Postgres>>();
|
||||||
let s = load_statement("select_series.sql");
|
let s = SQL.get_or_init(|| load_statement("select_series.sql"));
|
||||||
let r = sqlx::query_as::<_, Series>(&s)
|
Ok(sqlx::query_as::<_, Series>(&s)
|
||||||
.persistent(true)
|
.persistent(true)
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.fetch_one(&pool)
|
.fetch_one(&pool)
|
||||||
.await?;
|
.await?)
|
||||||
|
|
||||||
Ok(r)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue