use sqlx::{query_scalar, SqlitePool}; use crate::{util::year_to_epoch, DbId, ShowKind, User, Watch, WatchQuest}; const USER_EXISTS_QUERY: &str = "select count(*) from users where id = $1"; const MOVIE_QUERY: &str = "select * from movies order by random() limit 10000"; //-************************************************************************ // the omega user is the system ID, but has no actual power in the app //-************************************************************************ const OMEGA_ID: u128 = u128::MAX; const BULK_INSERT: usize = 2_000; #[derive(Debug, sqlx::FromRow, Clone)] pub struct ImportMovieOmega { pub title: String, pub year: Option, pub length: Option, } impl From for Watch { fn from(value: ImportMovieOmega) -> Self { Watch { title: value.title, release_date: year_to_epoch(value.year.as_deref()), length: value.length.and_then(|v| v.parse::().ok()), id: DbId::new(), kind: ShowKind::Movie, metadata_url: None, added_by: OMEGA_ID.into(), } } } impl From<&ImportMovieOmega> for Watch { fn from(value: &ImportMovieOmega) -> Self { Watch { 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()), id: DbId::new(), kind: ShowKind::Movie, metadata_url: None, added_by: OMEGA_ID.into(), } } } //-************************************************************************ // 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 add_users(db_pool: &SqlitePool, users: &[User]) -> Result<(), ()> { let mut builder = sqlx::QueryBuilder::new("insert into users (id, username, displayname, email, pwhash) "); builder.push_values(users.iter(), |mut b, user| { b.push_bind(user.id) .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(|_| ())?; Ok(()) } pub async fn add_omega_watches( w2w_db: &SqlitePool, movie_db: &SqlitePool, ) -> Result, ()> { ensure_omega(w2w_db).await; let movies: Vec = sqlx::query_as(MOVIE_QUERY) .fetch_all(movie_db) .await .unwrap(); let mut ids = Vec::with_capacity(10_000); let omega: DbId = OMEGA_ID.into(); for movies in movies.as_slice().chunks(BULK_INSERT) { let mut builder = sqlx::QueryBuilder::new( "insert into watches (id, kind, title, length, release_date, added_by) ", ); builder.push_values(movies, |mut b, movie| { let id = DbId::new(); ids.push(id); let title = &movie.title; b.push_bind(id) .push_bind(ShowKind::Movie) .push_bind(title) .push_bind(movie.length.as_ref().and_then(|l| l.parse::().ok())) .push_bind(year_to_epoch(movie.year.as_deref())) .push_bind(omega); }); let q = builder.build(); q.execute(w2w_db).await.map_err(|_| ())?; } Ok(ids) } pub async fn ensure_omega(db_pool: &SqlitePool) -> DbId { if !check_omega_exists(db_pool).await { let omega = User { id: OMEGA_ID.into(), username: "The Omega User".to_string(), displayname: Some("I am the end of all watches".to_string()), email: None, last_seen: None, pwhash: "you shall not password".to_string(), }; add_users(db_pool, &[omega]).await.unwrap(); } OMEGA_ID.into() } async fn check_omega_exists(db_pool: &SqlitePool) -> bool { let id: DbId = OMEGA_ID.into(); let count = query_scalar(USER_EXISTS_QUERY) .bind(id) .fetch_one(db_pool) .await .unwrap_or(0); count > 0 } //-************************************************************************ //tests //-************************************************************************ #[cfg(test)] mod test { use tokio::runtime::Runtime; use super::*; #[test] fn ensure_omega_user() { let p = crate::db::get_db_pool(); let rt = Runtime::new().unwrap(); rt.block_on(async { assert!(!check_omega_exists(&p).await); ensure_omega(&p).await; assert!(check_omega_exists(&p).await); }); } }