diff --git a/migrations/20230426221940_init.down.sql b/migrations/20230426221940_init.down.sql deleted file mode 100644 index 334e449..0000000 --- a/migrations/20230426221940_init.down.sql +++ /dev/null @@ -1,16 +0,0 @@ --- indices -drop index if exists user_username_dex; -drop index if exists user_email_dex; -drop index if exists watch_title_dex; -drop index if exists witch_added_by_dex; -drop index if exists quests_user_dex; -drop index if exists quests_watch_dex; -drop index if exists note_user_dex; -drop index if exists note_watch_dex; --- tables -drop table if exists watch_quests; -drop table if exists watch_notes; -drop table if exists follows; -drop table if exists users; -drop table if exists watches; - diff --git a/migrations/20230427212229_update_triggers.down.sql b/migrations/20230427212229_update_triggers.down.sql deleted file mode 100644 index ffab8ee..0000000 --- a/migrations/20230427212229_update_triggers.down.sql +++ /dev/null @@ -1,5 +0,0 @@ -drop trigger if exists update_last_updated_users; -drop trigger if exists update_last_updated_watches; -drop trigger if exists update_last_updated_watch_quests; -drop trigger if exists update_last_updated_follows; -drop trigger if exists update_last_updated_watch_notes; diff --git a/migrations/20230427212229_update_triggers.up.sql b/migrations/20230427212229_update_triggers.up.sql deleted file mode 100644 index 8ff1523..0000000 --- a/migrations/20230427212229_update_triggers.up.sql +++ /dev/null @@ -1,34 +0,0 @@ -create trigger if not exists update_last_updated_users - after update on users - when OLD.last_updated = NEW.last_updated or OLD.last_updated is null -BEGIN - update users set last_updated = (select unixepoch()) where id=NEW.id; -END; - -create trigger if not exists update_last_updated_invites - after update on invites - when OLD.last_updated = NEW.last_updated or OLD.last_updated is null -BEGIN - update invites set last_updated = (select unixepoch()) where id=NEW.id; -END; - -create trigger if not exists update_last_updated_watches - after update on watches - when OLD.last_updated = NEW.last_updated or OLD.last_updated is null -BEGIN - update watches set last_updated = (select unixepoch()) where id=NEW.id; -END; - -create trigger if not exists update_last_updated_watch_quests - after update on watch_quests - when OLD.last_updated = NEW.last_updated or OLD.last_updated is null -BEGIN - update watch_quests set last_updated = (select unixepoch()) where watch=NEW.watch and user=NEW.user; -END; - -create trigger if not exists update_last_updated_watch_notes - after update on watch_notes - when OLD.last_updated = NEW.last_updated or OLD.last_updated is null -BEGIN - update watch_notes set last_updated = (select unixepoch()) where id=NEW.id; -END; diff --git a/migrations/20240116054222_users_and_invites.down.sql b/migrations/20240116054222_users_and_invites.down.sql new file mode 100644 index 0000000..0baecb1 --- /dev/null +++ b/migrations/20240116054222_users_and_invites.down.sql @@ -0,0 +1,2 @@ +drop table if exists users; +drop table if exists invites; diff --git a/migrations/20240116054222_users_and_invites.up.sql b/migrations/20240116054222_users_and_invites.up.sql new file mode 100644 index 0000000..b6b34c7 --- /dev/null +++ b/migrations/20240116054222_users_and_invites.up.sql @@ -0,0 +1,41 @@ +create table if not exists users ( + id blob not null primary key default (julid_new()), + username text not null unique, + displayname text, + email text, + last_seen int, + pwhash blob not null, + invited_by blob not null, + is_active boolean not null default true, + last_updated int not null default (unixepoch()), + foreign key (invited_by) references users (id) +); +create index if not exists users_username_dex on users (lower(username)); +create index if not exists users_email_dex on users (lower(email)); +create index if not exists users_invited_by_dex on users (invited_by); + +create trigger if not exists update_last_updated_users + after update on users + when OLD.last_updated = NEW.last_updated or OLD.last_updated is null +BEGIN + update users set last_updated = (select unixepoch()) where id=NEW.id; +END; + +-- invitations +create table if not exists invites ( + id blob not null primary key default (julid_new()), + owner blob not null, + expires_at int, + remaining int not null default 1, + last_updated int not null default (unixepoch()), + foreign key (owner) references users (id) on delete cascade on update no action +); +create index if not exists invites_owner_dex on invites (owner); + +create trigger if not exists update_last_updated_invites + after update on invites + when OLD.last_updated = NEW.last_updated or OLD.last_updated is null +BEGIN + update invites set last_updated = (select unixepoch()) where id=NEW.id; +END; + diff --git a/migrations/20240116054243_watches_and_quests.down.sql b/migrations/20240116054243_watches_and_quests.down.sql new file mode 100644 index 0000000..44cc722 --- /dev/null +++ b/migrations/20240116054243_watches_and_quests.down.sql @@ -0,0 +1,3 @@ +drop table if exists watches; +drop table if exists watch_quests; +drop table if exists watch_notes; diff --git a/migrations/20230426221940_init.up.sql b/migrations/20240116054243_watches_and_quests.up.sql similarity index 51% rename from migrations/20230426221940_init.up.sql rename to migrations/20240116054243_watches_and_quests.up.sql index 3fb4847..a449f8a 100644 --- a/migrations/20230426221940_init.up.sql +++ b/migrations/20240116054243_watches_and_quests.up.sql @@ -1,37 +1,3 @@ --- note: sqlite-specific migration due to the types of the columns --- When used for an ID, a blob is a UUID in byte form, or a vector of those like for friends list. --- Otherwise, for content, a blob is just binary data, possibly representing UTF-8 text. --- Dates are ints, unix epoch style - --- users -create table if not exists users ( - id blob not null primary key default (julid_new()), - username text not null unique, - displayname text, - email text, - last_seen int, - pwhash blob not null, - invited_by blob not null, - is_active int not null default 1, - last_updated int not null default (unixepoch()), - foreign key (invited_by) references users (id) -); -create index if not exists users_username_dex on users (lower(username)); -create index if not exists users_email_dex on users (lower(email)); -create index if not exists users_invited_by_dex on users (invited_by); - --- invitations -create table if not exists invites ( - id blob not null primary key default (julid_new()), - owner blob not null, - expires_at int, - remaining int not null default 1, - last_updated int not null default (unixepoch()), - foreign key (owner) references users (id) on delete cascade on update no action -); -create index if not exists invites_owner_dex on invites (owner); - - -- table of things to watch create table if not exists watches ( id blob not null primary key default (julid_new()), @@ -46,6 +12,13 @@ create table if not exists watches ( ); create index if not exists watches_title_dex on watches (lower(title)); +create trigger if not exists update_last_updated_watches + after update on watches + when OLD.last_updated = NEW.last_updated or OLD.last_updated is null +BEGIN + update watches set last_updated = (select unixepoch()) where id=NEW.id; +END; + -- table of what people want to watch create table if not exists watch_quests ( user blob not null, @@ -63,16 +36,14 @@ create table if not exists watch_quests ( create index if not exists quests_user_dex on watch_quests (user); create index if not exists quests_watch_dex on watch_quests (watch); -create table if not exists follows ( - follower blob not null, - followee blob not null, - created_at int not null default (unixepoch()), - foreign key (follower) references users (id) on delete cascade on update no action - foreign key (followee) references users (id) on delete cascade on update no action -); -create index if not exists follows_follower_dex on follows (follower); -create index if not exists follows_followee_dex on follows (followee); +create trigger if not exists update_last_updated_watch_quests + after update on watch_quests + when OLD.last_updated = NEW.last_updated or OLD.last_updated is null +BEGIN + update watch_quests set last_updated = (select unixepoch()) where watch=NEW.watch and user=NEW.user; +END; +-- notes on stuff to watch create table if not exists watch_notes ( id blob not null primary key default (julid_new()), -- a user can have multiple notes about the same thing user blob not null, @@ -86,4 +57,9 @@ create table if not exists watch_notes ( create index if not exists notes_user_dex on watch_notes (user); create index if not exists notes_watch_dex on watch_notes (watch); --- indices +create trigger if not exists update_last_updated_watch_notes + after update on watch_notes + when OLD.last_updated = NEW.last_updated or OLD.last_updated is null +BEGIN + update watch_notes set last_updated = (select unixepoch()) where id=NEW.id; +END; diff --git a/migrations/20240116054253_stars_and_credits.down.sql b/migrations/20240116054253_stars_and_credits.down.sql new file mode 100644 index 0000000..da6e0a8 --- /dev/null +++ b/migrations/20240116054253_stars_and_credits.down.sql @@ -0,0 +1,2 @@ +drop table if exists credits; -- must be first +drop table if exists stars; diff --git a/migrations/20240116054253_stars_and_credits.up.sql b/migrations/20240116054253_stars_and_credits.up.sql new file mode 100644 index 0000000..d685661 --- /dev/null +++ b/migrations/20240116054253_stars_and_credits.up.sql @@ -0,0 +1,20 @@ +create table if not exists stars ( + id blob not null primary key default (julid_new()), + name text not null, + metadata_url text, + born int, + died int +); +create index if not exists stars_name_dex on stars (lower(name)); + +-- as in screen credits for a movie +create table if not exists credits ( + star blob not null, + watch blob not null, + credit text, -- "actor", "director", whatevs + unique (star, watch, credit), + foreign key (star) references stars (id), + foreign key (watch) references watches (id) +); +create index if not exists credits_star_dex on credits (star); +create index if not exists credits_watch_dex on credits (watch); diff --git a/migrations/20240116054301_follows.down.sql b/migrations/20240116054301_follows.down.sql new file mode 100644 index 0000000..d92e292 --- /dev/null +++ b/migrations/20240116054301_follows.down.sql @@ -0,0 +1 @@ +drop table if exists follows; diff --git a/migrations/20240116054301_follows.up.sql b/migrations/20240116054301_follows.up.sql new file mode 100644 index 0000000..35a84aa --- /dev/null +++ b/migrations/20240116054301_follows.up.sql @@ -0,0 +1,11 @@ +create table if not exists follows ( + follower blob not null, + followee blob not null, + created_at int not null default (unixepoch()), + foreign key (follower) references users (id) on delete cascade on update no action, + foreign key (followee) references users (id) on delete cascade on update no action, + unique (follower, followee) +); +create index if not exists follows_follower_dex on follows (follower); +create index if not exists follows_followee_dex on follows (followee); + diff --git a/src/bin/import_imdb.rs b/src/bin/import_imdb.rs index 696f2a3..2096efb 100644 --- a/src/bin/import_imdb.rs +++ b/src/bin/import_imdb.rs @@ -1,8 +1,15 @@ -use std::{ffi::OsString, time::Duration}; +use std::{ + ffi::{OsStr, OsString}, + path::Path, + time::Duration, +}; use clap::Parser; -use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; -use what2watch::{get_db_pool, import_utils::add_imdb_movies}; +use sqlx::{ + sqlite::{SqliteConnectOptions, SqlitePoolOptions}, + Connection, SqliteConnection, SqlitePool, +}; +use what2watch::{get_db_pool, imdb_utils::*}; #[derive(Debug, Parser)] struct Cli { @@ -18,49 +25,62 @@ struct Cli { } fn main() { + let w2w_db = get_db_pool(); + + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + let cli = Cli::parse(); - let path = cli.db_path; + let ids = rt.block_on(import_watches(&w2w_db, &cli)); + rt.block_on(save_ids(&cli.db_path, &ids)); +} + +async fn import_watches(w2w_db: &SqlitePool, cli: &Cli) -> IdMap { + let path = &cli.db_path; let num = cli.number; let opts = SqliteConnectOptions::new().filename(path).read_only(true); - let movie_db = { - let rt = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); - rt.block_on( - SqlitePoolOptions::new() - .idle_timeout(Duration::from_secs(90)) - .connect_with(opts), - ) - .expect("could not open movies db") - }; + let movie_db = SqlitePoolOptions::new() + .idle_timeout(Duration::from_secs(90)) + .connect_with(opts) + .await + .unwrap(); - let w2w_db = get_db_pool(); + let _ = what2watch::import_utils::ensure_omega(w2w_db).await; - let (dur, rows) = { - let rt = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); + let mut map = IdMap::new(); - rt.block_on(async { - let dur = add_imdb_movies(&w2w_db, &movie_db, num).await.unwrap(); - let rows: i32 = sqlx::query_scalar("select count(*) from watches") - .fetch_one(&w2w_db) - .await - .unwrap(); + import_imdb_data(w2w_db, &movie_db, &mut map, num).await; + w2w_db.close().await; - w2w_db.close().await; - (dur, rows) - }) - }; - - println!( - "Added {rows} movies in {} seconds ({}ms, {}us)", - dur.as_secs_f64(), - dur.as_millis(), - dur.as_micros() - ); + map +} + +async fn save_ids(path: &OsStr, ids: &IdMap) { + let path = Path::new(path); + let file = path.file_name().unwrap(); + let file = file.to_str().unwrap(); + let path = format!("{}/w2w-{file}", path.parent().unwrap().to_str().unwrap()); + + let conn_opts = SqliteConnectOptions::new() + .filename(path) + .create_if_missing(true); + let mut conn = SqliteConnection::connect_with(&conn_opts).await.unwrap(); + + let create = + "create table if not exists id_map (imdb text not null primary key, w2w blob not null)"; + let _ = sqlx::query(create).execute(&mut conn).await.unwrap(); + + for (imdb, w2w) in ids.iter() { + sqlx::query("insert into id_map (imdb, w2w) values (?, ?)") + .bind(imdb) + .bind(w2w) + .execute(&mut conn) + .await + .unwrap(); + } + conn.close().await.unwrap(); } diff --git a/src/imdb_utils.rs b/src/imdb_utils.rs new file mode 100644 index 0000000..b124e87 --- /dev/null +++ b/src/imdb_utils.rs @@ -0,0 +1,149 @@ +use julid::Julid; +use sqlx::{Sqlite, SqlitePool}; + +use crate::{ + import_utils::{insert_credit, insert_star, insert_watch}, + misc_util::year_to_epoch, + Credit, ShowKind, Star, Watch, +}; + +pub type IdMap = std::collections::BTreeMap; + +const OMEGA_ID: Julid = Julid::omega(); + +#[derive(Debug, sqlx::FromRow, Clone)] +pub struct ImportImdbMovie { + pub title: String, + pub year: Option, + pub length: Option, + pub id: String, + pub kind: Option, +} + +impl From for Watch { + fn from(value: ImportImdbMovie) -> Self { + Watch { + id: OMEGA_ID, // this is ignored by the inserter + title: value.title, + release_date: year_to_epoch(value.year.as_deref()), + length: value.length.and_then(|v| v.parse::().ok()), + kind: ShowKind::Movie, + metadata_url: Some(format!("https://imdb.com/title/{}/", &value.id)), + added_by: OMEGA_ID, + } + } +} + +impl From<&ImportImdbMovie> for Watch { + fn from(value: &ImportImdbMovie) -> Self { + Watch { + id: OMEGA_ID, + title: value.title.to_string(), + release_date: year_to_epoch(value.year.as_deref()), + length: value.length.as_ref().and_then(|v| v.parse::().ok()), + kind: ShowKind::Movie, + metadata_url: Some(format!("https://imdb.com/title/{}/", value.id)), + added_by: OMEGA_ID, + } + } +} + +#[derive(Debug, sqlx::FromRow, Clone)] +pub struct ImdbStar { + pub nconst: String, + #[sqlx(rename = "primaryName")] + pub name: String, + #[sqlx(rename = "birthYear")] + pub born: Option, + #[sqlx(rename = "deathYear")] + pub died: Option, +} + +impl From<&ImdbStar> for Star { + fn from(value: &ImdbStar) -> Self { + let id = &value.nconst; + let metadata_url = Some(format!("https://imdb.com/name/{id}/")); + Self { + name: value.name.clone(), + metadata_url, + born: year_to_epoch(value.born.as_deref()), + died: year_to_epoch(value.died.as_deref()), + ..Default::default() + } + } +} + +pub async fn import_imdb_data(w2w_db: &SqlitePool, imdb: &SqlitePool, ids: &mut IdMap, num: u32) { + const IMDB_QUERY: &str = "select * from watches order by year, title asc limit ?"; + + let iwatches: Vec = sqlx::query_as(IMDB_QUERY) + .bind(num) + .fetch_all(imdb) + .await + .unwrap(); + + for iwatch in iwatches { + let aid = iwatch.id.clone(); + let kind = show_kind(iwatch.kind.as_ref().unwrap()); + let mut watch: Watch = iwatch.into(); + watch.kind = kind; + let watch_id: Julid = insert_watch(watch, w2w_db).await; + add_imdb_stars(w2w_db, imdb, &aid, watch_id, ids).await; + ids.insert(aid, watch_id); + } +} + +async fn add_imdb_stars( + w2w_db: &SqlitePool, + imdb: &SqlitePool, + iwatch: &str, + watch: Julid, + ids: &mut IdMap, +) { + let principals_query = "select nconst, category from principals where tconst = ?"; + let principals = sqlx::query_as::(principals_query) + .bind(iwatch) + .fetch_all(imdb) + .await + .unwrap(); + + for row in principals { + let (name_id, cat) = row; + let name_query = + "select nconst, primaryName, birthYear, deathYear from names where nconst = ?"; + let istar: Option = sqlx::query_as(name_query) + .bind(&name_id) + .fetch_optional(imdb) + .await + .unwrap(); + if let Some(star) = istar { + let star = (&star).into(); + let star_id = insert_star(&star, w2w_db).await; + ids.insert(name_id, star_id); + let credit = Credit { + star: star_id, + watch, + credit: Some(cat.to_string()), + }; + insert_credit(&credit, w2w_db).await; + } + } +} + +fn show_kind(kind: &str) -> ShowKind { + /* + tvSeries + tvMiniSeries + tvMovie + tvShort + tvSpecial + */ + match &kind[0..4] { + "tvSe" => ShowKind::Series, + "tvSh" => ShowKind::Short, + "tvMi" => ShowKind::LimitedSeries, + "tvSp" => ShowKind::Other, + "tvMo" | "movi" => ShowKind::Movie, + _ => ShowKind::Unknown, + } +} diff --git a/src/import_utils.rs b/src/import_utils.rs index 3cf3ec5..603fa5f 100644 --- a/src/import_utils.rs +++ b/src/import_utils.rs @@ -1,131 +1,53 @@ -use std::time::{Duration, Instant}; - use julid::Julid; use sqlx::{query_scalar, SqlitePool}; -use crate::{util::year_to_epoch, ShowKind, User, Watch, WatchQuest}; - -const USER_EXISTS_QUERY: &str = "select count(*) from users where id = $1"; +use crate::{Credit, Star, User, Watch}; //-************************************************************************ // the omega user is the system ID, but has no actual power in the app //-************************************************************************ const OMEGA_ID: Julid = Julid::omega(); -const BULK_INSERT: usize = 2_000; - -#[derive(Debug, sqlx::FromRow, Clone)] -pub struct ImportImdbMovie { - pub title: String, - pub year: Option, - pub runtime: Option, - pub aid: String, -} - -impl From for Watch { - fn from(value: ImportImdbMovie) -> Self { - Watch { - id: OMEGA_ID, // this is ignored by the inserter - title: value.title, - release_date: year_to_epoch(value.year.as_deref()), - length: value.runtime.and_then(|v| v.parse::().ok()), - kind: ShowKind::Movie, - metadata_url: Some(format!("https://imdb.com/title/{}/", &value.aid)), - added_by: OMEGA_ID, - } - } -} - -impl From<&ImportImdbMovie> for Watch { - fn from(value: &ImportImdbMovie) -> Self { - Watch { - id: OMEGA_ID, - title: value.title.to_string(), - release_date: year_to_epoch(value.year.as_deref()), - length: value.runtime.as_ref().and_then(|v| v.parse::().ok()), - kind: ShowKind::Movie, - metadata_url: Some(format!("https://imdb.com/title/{}/", value.aid)), - added_by: OMEGA_ID, - } - } -} //-************************************************************************ // utility functions for building CLI tools, currently just for benchmarking //-************************************************************************ -pub async fn add_watch_quests(pool: &SqlitePool, quests: &[WatchQuest]) -> Result<(), ()> { - let mut builder = sqlx::QueryBuilder::new("insert into watch_quests (user, watch) "); - builder.push_values(quests, |mut b, quest| { - let user = quest.user; - let watch = quest.watch; - //eprintln!("{user}, {watch}"); - b.push_bind(user).push_bind(watch); - }); - let q = builder.build(); - q.execute(pool).await.map_err(|e| { - dbg!(e); - })?; - - Ok(()) +pub async fn insert_watch(watch: Watch, db: &SqlitePool) -> Julid { + let q = "insert into watches (kind, title, length, release_date, added_by, metadata_url) values (?, ?, ?, ?, ?, ?) returning id"; + sqlx::query_scalar(q) + .bind(watch.kind) + .bind(watch.title) + .bind(watch.length) + .bind(watch.release_date) + .bind(watch.added_by) + .bind(watch.metadata_url) + .fetch_one(db) + .await + .unwrap() } -pub async fn add_users(db_pool: &SqlitePool, users: &[User]) -> Result { - let mut builder = - sqlx::QueryBuilder::new("insert into users (username, displayname, email, pwhash)"); - - let start = Instant::now(); - builder.push_values(users.iter(), |mut b, user| { - b.push_bind(&user.username) - .push_bind(&user.displayname) - .push_bind(&user.email) - .push_bind(&user.pwhash); - }); - let q = builder.build(); - q.execute(db_pool).await.map_err(|_| ())?; - let end = Instant::now(); - let dur = end - start; - - Ok(dur) +pub async fn insert_star(star: &Star, db: &SqlitePool) -> Julid { + let q = "insert into stars (name, metadata_url, born, died) values (?, ?, ?, ?) returning id"; + sqlx::query_scalar(q) + .bind(&star.name) + .bind(&star.metadata_url) + .bind(star.born) + .bind(star.died) + .fetch_one(db) + .await + .unwrap() } -pub async fn add_imdb_movies( - w2w_db: &SqlitePool, - movie_db: &SqlitePool, - num: u32, -) -> Result { - const IMDB_MOVIE_QUERY: &str = - "select * from movie_titles where porn = 0 order by year, title asc limit ?"; +pub async fn insert_credit(credit: &Credit, db: &SqlitePool) { + let q = "insert into credits (star, watch, credit) values (?, ?, ?)"; - let omega = ensure_omega(w2w_db).await; - - let movies: Vec = sqlx::query_as(IMDB_MOVIE_QUERY) - .bind(num) - .fetch_all(movie_db) + sqlx::query(q) + .bind(credit.star) + .bind(credit.watch) + .bind(credit.credit.as_deref()) + .execute(db) .await .unwrap(); - - let start = Instant::now(); - for movies in movies.as_slice().chunks(BULK_INSERT) { - let mut builder = sqlx::QueryBuilder::new( - "insert into watches (kind, title, length, release_date, added_by, metadata_url) ", - ); - - builder.push_values(movies, |mut b, movie| { - let movie: Watch = movie.into(); - b.push_bind(ShowKind::Movie) - .push_bind(movie.title) - .push_bind(movie.length) - .push_bind(movie.release_date) - .push_bind(omega) - .push_bind(movie.metadata_url); - }); - let q = builder.build(); - q.execute(w2w_db).await.map_err(|_| ())?; - } - let end = Instant::now(); - let dur = end - start; - - Ok(dur) } pub async fn ensure_omega(db_pool: &SqlitePool) -> Julid { @@ -136,6 +58,7 @@ pub async fn ensure_omega(db_pool: &SqlitePool) -> Julid { } async fn check_omega_exists(db_pool: &SqlitePool) -> bool { + const USER_EXISTS_QUERY: &str = "select count(*) from users where id = $1"; let count = query_scalar(USER_EXISTS_QUERY) .bind(OMEGA_ID) .fetch_one(db_pool) diff --git a/src/lib.rs b/src/lib.rs index 58550f4..b7a9704 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,9 +11,12 @@ extern crate justerror; /// Some public interfaces for interacting with the database outside of the web /// app pub use db::get_db_pool; +pub mod imdb_utils; pub mod import_utils; +pub mod misc_util; pub use signup::Invitation; +pub use stars::*; pub use users::User; pub use watches::{ShowKind, Watch, WatchQuest}; @@ -25,9 +28,9 @@ mod db; mod generic_handlers; mod login; mod signup; +mod stars; mod templates; mod users; -mod util; mod watches; // things we want in the crate namespace diff --git a/src/util.rs b/src/misc_util.rs similarity index 90% rename from src/util.rs rename to src/misc_util.rs index 375a8f1..21dd34e 100644 --- a/src/util.rs +++ b/src/misc_util.rs @@ -2,7 +2,7 @@ use std::{error::Error, ops::Range}; use unicode_segmentation::UnicodeSegmentation; -pub fn validate_optional_length( +pub(crate) fn validate_optional_length( opt: &Option, len_range: Range, err: E, @@ -21,7 +21,7 @@ pub fn validate_optional_length( } /// Serde deserialization decorator to map empty Strings to None -pub fn empty_string_as_none<'de, D, T>(de: D) -> Result, D::Error> +pub(crate) fn empty_string_as_none<'de, D, T>(de: D) -> Result, D::Error> where D: serde::Deserializer<'de>, T: std::str::FromStr, diff --git a/src/signup/handlers.rs b/src/signup/handlers.rs index 24f86e5..c34f7bd 100644 --- a/src/signup/handlers.rs +++ b/src/signup/handlers.rs @@ -13,7 +13,7 @@ use sqlx::{query_as, Sqlite, SqlitePool}; use unicode_segmentation::UnicodeSegmentation; use super::{templates::*, Invitation}; -use crate::{util::empty_string_as_none, User}; +use crate::{misc_util::empty_string_as_none, User}; //-************************************************************************ // Error types for user creation @@ -94,7 +94,7 @@ pub async fn post_create_user( State(pool): State, Form(signup): Form, ) -> Result { - use crate::util::validate_optional_length; + use crate::misc_util::validate_optional_length; let username = signup.username.trim(); let password = signup.password.trim(); let verify = signup.pw_verify.trim(); diff --git a/src/stars.rs b/src/stars.rs new file mode 100644 index 0000000..1593b08 --- /dev/null +++ b/src/stars.rs @@ -0,0 +1,19 @@ +use julid::Julid; +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; + +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, FromRow)] +pub struct Star { + pub id: Julid, + pub name: String, + pub metadata_url: Option, + pub born: Option, + pub died: Option, +} + +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, FromRow)] +pub struct Credit { + pub star: Julid, + pub watch: Julid, + pub credit: Option, +} diff --git a/src/watches/handlers.rs b/src/watches/handlers.rs index b95958e..2f8098f 100644 --- a/src/watches/handlers.rs +++ b/src/watches/handlers.rs @@ -10,7 +10,7 @@ use sqlx::{query, query_as, query_scalar, SqlitePool}; use super::templates::{AddNewWatchPage, AddWatchButton, GetWatchPage, SearchWatchesPage}; use crate::{ - util::{empty_string_as_none, year_to_epoch}, + misc_util::{empty_string_as_none, year_to_epoch}, AuthSession, MyWatchesPage, ShowKind, Watch, WatchQuest, };