use crate::models::characters::{Character, NewCharacter, StrippedCharacter}; use crate::models::users::{NewUser, User}; use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions}; use sqlx::ConnectOptions; use std::str::FromStr; /// Type alias for the Rocket-managed singleton database connection. pub type TenebrousDbConn<'a> = rocket::State<'a, SqlitePool>; /// Create a connection pool to the database. pub(crate) async fn create_pool(db_path: &str) -> Result { //Create database if missing. let conn = SqliteConnectOptions::from_str(&format!("sqlite://{}", db_path))? .create_if_missing(true) .connect() .await?; drop(conn); //Return actual conncetion pool. SqlitePoolOptions::new() .max_connections(5) .connect(db_path) .await .map_err(|e| e.into()) } #[rocket::async_trait] pub(crate) trait Dao { async fn load_user_by_id(&self, id: i32) -> sqlx::Result>; async fn load_user(&self, for_username: &str) -> sqlx::Result>; async fn insert_user(&self, new_user: NewUser<'_>) -> sqlx::Result; async fn load_character_list(&self, for_user_id: i32) -> sqlx::Result>; async fn load_character(&self, character_id: i32) -> sqlx::Result>; async fn insert_character(&self, new_character: NewCharacter<'_>) -> sqlx::Result<()>; async fn update_character<'a>(&self, character: &'a Character) -> sqlx::Result<()>; async fn update_character_sheet<'a>(&self, character: &'a Character) -> sqlx::Result<()>; } #[rocket::async_trait] impl Dao for SqlitePool { async fn load_user_by_id(&self, user_id: i32) -> sqlx::Result> { sqlx::query_as!( User, r#"SELECT id as "id: _", username, password FROM users WHERE id = ?"#, user_id ) .fetch_optional(self) .await } async fn load_user(&self, for_username: &str) -> sqlx::Result> { sqlx::query_as!( User, r#"SELECT id as "id: _", username, password FROM users WHERE username = ?"#, for_username ) .fetch_optional(self) .await } async fn insert_user(&self, new_user: NewUser<'_>) -> sqlx::Result { sqlx::query("INSERT INTO users (username, password) values (?, ?)") .bind(new_user.username) .bind(new_user.password) .execute(self) .await?; self.load_user(new_user.username) .await .and_then(|user| user.ok_or(sqlx::Error::RowNotFound)) } async fn load_character_list(&self, for_user_id: i32) -> sqlx::Result> { sqlx::query_as!( StrippedCharacter, r#"SELECT id as "id: _", user_id as "user_id: _", data_type as "data_type: _", data_version as "data_version: _", viewable, character_name FROM characters WHERE user_id = ?"#, for_user_id ) .fetch_all(self) .await } async fn load_character(&self, character_id: i32) -> sqlx::Result> { sqlx::query_as!( Character, r#"SELECT id as "id: _", user_id as "user_id: _", viewable, character_name, data, data_type as "data_type: _", data_version as "data_version: _" FROM characters WHERE id = ?"#, character_id ) .fetch_optional(self) .await } async fn insert_character(&self, new_character: NewCharacter<'_>) -> sqlx::Result<()> { sqlx::query( "INSERT INTO characters (user_id, viewable, character_name, data_type, data_version, data) values (?, ?, ?, ?, ?, ?)", ) .bind(new_character.user_id) .bind(new_character.viewable) .bind(new_character.character_name) .bind(new_character.data_type) .bind(new_character.data_version) .bind(new_character.data) .execute(self) .await?; Ok(()) } async fn update_character<'a>(&self, character: &'a Character) -> sqlx::Result<()> { sqlx::query( "UPDATE characters set user_id = ?, viewable = ?, character_name = ?, data_type = ?, data_version = ?, data = ? where id = ?", ) .bind(character.user_id) .bind(character.viewable) .bind(&character.character_name) .bind(character.data_type) .bind(character.data_version) .bind(&character.data) .bind(character.id) .execute(self) .await?; Ok(()) } async fn update_character_sheet<'a>(&self, character: &'a Character) -> sqlx::Result<()> { sqlx::query("UPDATE characters set data = ? where id = ?") .bind(&character.data) .bind(character.id) .execute(self) .await?; Ok(()) } }