2024-12-27 19:29:07 +00:00
mod disk_db ;
mod types ;
2024-12-01 05:51:08 +00:00
use std ::path ::Path ;
2024-11-30 04:14:52 +00:00
2024-12-27 19:29:07 +00:00
use async_std ::channel ::{ bounded , Sender } ;
2024-11-30 16:48:35 +00:00
use async_trait ::async_trait ;
2024-12-27 19:29:07 +00:00
use disk_db ::{ db_handler , DiskDb } ;
pub use types ::{ CharacterId , CharsheetRow , GameRow , SessionId , UserId , UserRow } ;
2024-11-30 04:14:52 +00:00
2024-12-01 05:51:08 +00:00
use crate ::types ::FatalError ;
2024-11-30 16:48:35 +00:00
#[ derive(Debug) ]
enum Request {
Charsheet ( CharacterId ) ,
2024-12-27 19:02:43 +00:00
CreateSession ( UserId ) ,
2024-12-11 03:43:15 +00:00
Games ,
2024-12-27 19:02:43 +00:00
SaveUser ( Option < UserId > , String , String , bool , bool ) ,
Session ( SessionId ) ,
2024-12-16 05:27:55 +00:00
User ( UserId ) ,
2024-12-22 14:17:00 +00:00
UserByUsername ( String ) ,
2024-12-01 19:07:37 +00:00
Users ,
2024-11-30 16:48:35 +00:00
}
#[ derive(Debug) ]
struct DatabaseRequest {
2024-11-30 17:05:31 +00:00
tx : Sender < DatabaseResponse > ,
2024-11-30 16:48:35 +00:00
req : Request ,
}
#[ derive(Debug) ]
enum DatabaseResponse {
Charsheet ( Option < CharsheetRow > ) ,
2024-12-27 19:02:43 +00:00
CreateSession ( SessionId ) ,
2024-12-11 03:43:15 +00:00
Games ( Vec < GameRow > ) ,
2024-12-27 19:02:43 +00:00
SaveUser ( UserId ) ,
Session ( Option < UserRow > ) ,
2024-12-16 05:27:55 +00:00
User ( Option < UserRow > ) ,
2024-12-11 03:43:15 +00:00
Users ( Vec < UserRow > ) ,
2024-11-30 04:14:52 +00:00
}
2024-11-30 16:48:35 +00:00
#[ async_trait ]
pub trait Database : Send + Sync {
2024-12-27 19:29:07 +00:00
async fn user ( & self , _ : & UserId ) -> Result < Option < UserRow > , FatalError > ;
2024-12-18 04:43:36 +00:00
2024-12-27 19:02:43 +00:00
async fn user_by_username ( & self , _ : & str ) -> Result < Option < UserRow > , FatalError > ;
2024-12-22 14:17:00 +00:00
2024-12-18 04:43:36 +00:00
async fn save_user (
2024-12-27 19:02:43 +00:00
& self ,
2024-12-18 04:43:36 +00:00
user_id : Option < UserId > ,
name : & str ,
password : & str ,
admin : bool ,
enabled : bool ,
) -> Result < UserId , FatalError > ;
2024-12-16 05:27:55 +00:00
2024-12-18 04:43:36 +00:00
async fn users ( & mut self ) -> Result < Vec < UserRow > , FatalError > ;
2024-12-11 03:43:15 +00:00
2024-12-18 04:43:36 +00:00
async fn games ( & mut self ) -> Result < Vec < GameRow > , FatalError > ;
2024-12-01 19:07:37 +00:00
2024-12-31 19:09:21 +00:00
async fn character ( & mut self , id : & CharacterId ) -> Result < Option < CharsheetRow > , FatalError > ;
2024-12-27 19:02:43 +00:00
2024-12-31 19:09:21 +00:00
async fn session ( & self , id : & SessionId ) -> Result < Option < UserRow > , FatalError > ;
2024-12-27 19:02:43 +00:00
2024-12-31 19:09:21 +00:00
async fn create_session ( & self , id : & UserId ) -> Result < SessionId , FatalError > ;
2024-11-30 04:14:52 +00:00
}
2024-11-30 16:48:35 +00:00
pub struct DbConn {
2024-11-30 17:05:31 +00:00
conn : Sender < DatabaseRequest > ,
2024-11-30 16:48:35 +00:00
handle : tokio ::task ::JoinHandle < ( ) > ,
2024-11-30 04:14:52 +00:00
}
2024-11-30 16:48:35 +00:00
impl DbConn {
pub fn new < P > ( path : Option < P > ) -> Self
where
P : AsRef < Path > ,
{
2024-11-30 17:05:31 +00:00
let ( tx , rx ) = bounded ::< DatabaseRequest > ( 5 ) ;
2024-11-30 16:48:35 +00:00
let db = DiskDb ::new ( path ) . unwrap ( ) ;
2024-11-30 04:14:52 +00:00
2024-11-30 17:20:38 +00:00
let handle = tokio ::spawn ( async move {
db_handler ( db , rx ) . await ;
} ) ;
2024-11-30 16:48:35 +00:00
Self { conn : tx , handle }
2024-11-30 04:14:52 +00:00
}
2024-12-27 19:59:24 +00:00
}
2024-11-30 04:14:52 +00:00
2024-12-27 19:59:24 +00:00
macro_rules ! send_request {
( $s :expr , $req :expr , $resp_h :pat = > $block :expr ) = > { {
2024-12-16 05:27:55 +00:00
let ( tx , rx ) = bounded ::< DatabaseResponse > ( 1 ) ;
2024-12-27 19:59:24 +00:00
let request = DatabaseRequest { tx , req : $req } ;
match $s . conn . send ( request ) . await {
2024-12-16 05:27:55 +00:00
Ok ( ( ) ) = > ( ) ,
2024-12-18 04:43:36 +00:00
Err ( _ ) = > return Err ( FatalError ::DatabaseConnectionLost ) ,
2024-12-16 05:27:55 +00:00
} ;
2024-12-27 19:59:24 +00:00
match rx
. recv ( )
2024-12-27 19:29:07 +00:00
. await
. map_err ( | _ | FatalError ::DatabaseConnectionLost )
2024-12-27 19:59:24 +00:00
{
Ok ( $resp_h ) = > $block ,
Ok ( _ ) = > Err ( FatalError ::MessageMismatch ) ,
Err ( _ ) = > Err ( FatalError ::DatabaseConnectionLost ) ,
}
} } ;
2024-12-27 19:29:07 +00:00
}
#[ async_trait ]
impl Database for DbConn {
async fn user ( & self , uid : & UserId ) -> Result < Option < UserRow > , FatalError > {
2024-12-27 19:59:24 +00:00
send_request! ( self , Request ::User ( uid . clone ( ) ) , DatabaseResponse ::User ( user ) = > Ok ( user ) )
2024-12-16 05:27:55 +00:00
}
2024-12-27 19:02:43 +00:00
async fn user_by_username ( & self , username : & str ) -> Result < Option < UserRow > , FatalError > {
2024-12-27 19:59:24 +00:00
send_request! ( self , Request ::UserByUsername ( username . to_owned ( ) ) , DatabaseResponse ::User ( user ) = > Ok ( user ) )
2024-12-22 14:17:00 +00:00
}
2024-12-18 04:43:36 +00:00
async fn save_user (
2024-12-27 19:02:43 +00:00
& self ,
2024-12-18 04:43:36 +00:00
user_id : Option < UserId > ,
name : & str ,
password : & str ,
admin : bool ,
enabled : bool ,
) -> Result < UserId , FatalError > {
2024-12-27 19:59:24 +00:00
send_request! ( self ,
Request ::SaveUser (
2024-12-18 04:43:36 +00:00
user_id ,
name . to_owned ( ) ,
password . to_owned ( ) ,
admin ,
enabled ,
) ,
2024-12-27 19:59:24 +00:00
DatabaseResponse ::SaveUser ( user_id ) = > Ok ( user_id ) )
2024-12-18 04:43:36 +00:00
}
async fn users ( & mut self ) -> Result < Vec < UserRow > , FatalError > {
2024-12-27 19:59:24 +00:00
send_request! ( self , Request ::Users , DatabaseResponse ::Users ( lst ) = > Ok ( lst ) )
2024-12-01 19:07:37 +00:00
}
2024-12-18 04:43:36 +00:00
async fn games ( & mut self ) -> Result < Vec < GameRow > , FatalError > {
2024-12-27 19:59:24 +00:00
send_request! ( self , Request ::Games , DatabaseResponse ::Games ( lst ) = > Ok ( lst ) )
2024-12-11 03:43:15 +00:00
}
2024-12-31 19:09:21 +00:00
async fn character ( & mut self , id : & CharacterId ) -> Result < Option < CharsheetRow > , FatalError > {
send_request! ( self , Request ::Charsheet ( id . to_owned ( ) ) , DatabaseResponse ::Charsheet ( row ) = > Ok ( row ) )
2024-11-30 04:14:52 +00:00
}
2024-12-27 19:02:43 +00:00
2024-12-31 19:09:21 +00:00
async fn session ( & self , id : & SessionId ) -> Result < Option < UserRow > , FatalError > {
send_request! ( self , Request ::Session ( id . to_owned ( ) ) , DatabaseResponse ::Session ( row ) = > Ok ( row ) )
2024-12-27 19:02:43 +00:00
}
2024-12-31 19:09:21 +00:00
async fn create_session ( & self , id : & UserId ) -> Result < SessionId , FatalError > {
send_request! ( self , Request ::CreateSession ( id . to_owned ( ) ) , DatabaseResponse ::CreateSession ( session_id ) = > Ok ( session_id ) )
2024-12-27 19:02:43 +00:00
}
2024-11-30 04:14:52 +00:00
}
#[ cfg(test) ]
mod test {
2024-12-01 05:51:08 +00:00
use std ::path ::PathBuf ;
2024-11-30 04:14:52 +00:00
use cool_asserts ::assert_matches ;
2024-12-27 19:29:07 +00:00
use disk_db ::DiskDb ;
use types ::GameId ;
2024-11-30 04:14:52 +00:00
use super ::* ;
2024-12-27 19:29:07 +00:00
const SOREN : & 'static str = r # "{ "type_": "Candela", "name": "Soren Jensen", "pronouns": "he/him", "circle": "Circle of the Bluest Sky", "style": "dapper gentleman", "catalyst": "a cursed book", "question": "What were the contents of that book?", "nerve": { "type_": "nerve", "drives": { "current": 1, "max": 2 }, "resistances": { "current": 0, "max": 3 }, "move": { "gilded": false, "score": 2 }, "strike": { "gilded": false, "score": 1 }, "control": { "gilded": true, "score": 0 } }, "cunning": { "type_": "cunning", "drives": { "current": 1, "max": 1 }, "resistances": { "current": 0, "max": 3 }, "sway": { "gilded": false, "score": 0 }, "read": { "gilded": false, "score": 0 }, "hide": { "gilded": false, "score": 0 } }, "intuition": { "type_": "intuition", "drives": { "current": 0, "max": 0 }, "resistances": { "current": 0, "max": 3 }, "survey": { "gilded": false, "score": 0 }, "focus": { "gilded": false, "score": 0 }, "sense": { "gilded": false, "score": 0 } }, "role": "Slink", "role_abilities": [ "Scout: If you have time to observe a location, you can spend 1 Intuition to ask a question: What do I notice here that others do not see? What in this place might be of use to us? What path should we follow?" ], "specialty": "Detective", "specialty_abilities": [ "Mind Palace: When you want to figure out how two clues might relate or what path they should point you towards, burn 1 Intution resistance. The GM will give you the information you have deduced." ] }"# ;
2024-11-30 04:14:52 +00:00
2024-12-01 16:14:28 +00:00
fn setup_db ( ) -> ( DiskDb , GameId ) {
2024-11-30 16:48:35 +00:00
let no_path : Option < PathBuf > = None ;
let db = DiskDb ::new ( no_path ) . unwrap ( ) ;
2024-11-30 04:14:52 +00:00
2024-12-31 17:56:05 +00:00
db . save_user ( Some ( UserId ::from ( " admin " ) ) , " admin " , " abcdefg " , true , true ) . unwrap ( ) ;
2024-12-01 16:14:28 +00:00
let game_id = db . save_game ( None , " Candela " ) . unwrap ( ) ;
( db , game_id )
}
#[ test ]
fn it_can_retrieve_a_character ( ) {
let ( db , game_id ) = setup_db ( ) ;
2024-12-01 05:51:08 +00:00
assert_matches! ( db . character ( CharacterId ::from ( " 1 " ) ) , Ok ( None ) ) ;
2024-11-30 04:14:52 +00:00
2024-12-27 19:29:07 +00:00
let js : serde_json ::Value = serde_json ::from_str ( SOREN ) . unwrap ( ) ;
2024-12-16 05:27:55 +00:00
let soren_id = db . save_character ( None , game_id , js . clone ( ) ) . unwrap ( ) ;
2024-12-01 05:51:08 +00:00
assert_matches! ( db . character ( soren_id ) . unwrap ( ) , Some ( CharsheetRow { data , .. } ) = > assert_eq! ( js , data ) ) ;
2024-11-30 04:14:52 +00:00
}
2024-11-30 16:48:35 +00:00
#[ tokio::test ]
2024-12-01 05:51:08 +00:00
async fn it_can_retrieve_a_character_through_conn ( ) {
2024-11-30 16:48:35 +00:00
let memory_db : Option < PathBuf > = None ;
let mut conn = DbConn ::new ( memory_db ) ;
2024-12-31 19:09:21 +00:00
assert_matches! ( conn . character ( & CharacterId ::from ( " 1 " ) ) . await , Ok ( None ) ) ;
2024-11-30 16:48:35 +00:00
}
2024-11-30 04:14:52 +00:00
}