monorepo/visions/server/src/database/mod.rs

807 lines
25 KiB
Rust
Raw Normal View History

use std::path::Path;
2024-11-30 17:20:38 +00:00
use async_std::channel::{bounded, Receiver, Sender};
use async_trait::async_trait;
use include_dir::{include_dir, Dir};
use lazy_static::lazy_static;
use rusqlite::{
types::{FromSql, FromSqlResult, ValueRef},
Connection,
};
use rusqlite_migration::Migrations;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::types::FatalError;
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/migrations");
lazy_static! {
static ref MIGRATIONS: Migrations<'static> =
Migrations::from_directory(&MIGRATIONS_DIR).unwrap();
}
#[derive(Debug)]
enum Request {
Charsheet(CharacterId),
2024-12-27 19:02:43 +00:00
CreateSession(UserId),
Games,
2024-12-27 19:02:43 +00:00
SaveUser(Option<UserId>, String, String, bool, bool),
Session(SessionId),
User(UserId),
2024-12-22 14:17:00 +00:00
UserByUsername(String),
Users,
}
#[derive(Debug)]
struct DatabaseRequest {
2024-11-30 17:05:31 +00:00
tx: Sender<DatabaseResponse>,
req: Request,
}
#[derive(Debug)]
enum DatabaseResponse {
Charsheet(Option<CharsheetRow>),
2024-12-27 19:02:43 +00:00
CreateSession(SessionId),
Games(Vec<GameRow>),
2024-12-27 19:02:43 +00:00
SaveUser(UserId),
Session(Option<UserRow>),
User(Option<UserRow>),
Users(Vec<UserRow>),
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct UserId(String);
impl UserId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str<'a>(&'a self) -> &'a str {
&self.0
}
}
impl From<&str> for UserId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for UserId {
fn from(s: String) -> Self {
Self(s)
}
}
impl FromSql for UserId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(text) => Ok(Self::from(String::from_utf8(text.to_vec()).unwrap())),
_ => unimplemented!(),
}
}
}
2024-12-27 19:02:43 +00:00
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct SessionId(String);
impl SessionId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str<'a>(&'a self) -> &'a str {
&self.0
}
}
impl From<&str> for SessionId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for SessionId {
fn from(s: String) -> Self {
Self(s)
}
}
impl FromSql for SessionId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(text) => Ok(Self::from(String::from_utf8(text.to_vec()).unwrap())),
_ => unimplemented!(),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct GameId(String);
impl GameId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str<'a>(&'a self) -> &'a str {
&self.0
}
}
impl From<&str> for GameId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for GameId {
fn from(s: String) -> Self {
Self(s)
}
}
impl FromSql for GameId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(text) => Ok(Self::from(String::from_utf8(text.to_vec()).unwrap())),
_ => unimplemented!(),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct CharacterId(String);
impl CharacterId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str<'a>(&'a self) -> &'a str {
&self.0
}
}
impl From<&str> for CharacterId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for CharacterId {
fn from(s: String) -> Self {
Self(s)
}
}
impl FromSql for CharacterId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(text) => Ok(Self::from(String::from_utf8(text.to_vec()).unwrap())),
_ => unimplemented!(),
}
}
}
#[derive(Clone, Debug)]
pub struct UserRow {
pub id: UserId,
pub name: String,
pub password: String,
pub admin: bool,
pub enabled: bool,
}
#[derive(Clone, Debug)]
pub struct Role {
userid: UserId,
gameid: GameId,
role: String,
}
#[derive(Clone, Debug)]
pub struct GameRow {
pub id: UserId,
pub name: String,
}
#[derive(Clone, Debug)]
pub struct CharsheetRow {
id: String,
game: GameId,
2024-11-30 20:24:57 +00:00
pub data: serde_json::Value,
}
2024-12-27 19:02:43 +00:00
#[derive(Clone, Debug)]
pub struct SessionRow {
id: SessionId,
user_id: SessionId,
}
#[async_trait]
pub trait Database: Send + Sync {
2024-12-27 19:02:43 +00:00
async fn user(&mut self, _: &UserId) -> Result<Option<UserRow>, FatalError>;
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
async fn save_user(
2024-12-27 19:02:43 +00:00
&self,
user_id: Option<UserId>,
name: &str,
password: &str,
admin: bool,
enabled: bool,
) -> Result<UserId, FatalError>;
async fn users(&mut self) -> Result<Vec<UserRow>, FatalError>;
async fn games(&mut self) -> Result<Vec<GameRow>, FatalError>;
async fn character(&mut self, id: CharacterId) -> Result<Option<CharsheetRow>, FatalError>;
2024-12-27 19:02:43 +00:00
async fn session(&self, id: SessionId) -> Result<Option<UserRow>, FatalError>;
async fn create_session(&self, id: UserId) -> Result<SessionId, FatalError>;
}
pub struct DiskDb {
conn: Connection,
}
impl DiskDb {
pub fn new<P>(path: Option<P>) -> Result<Self, FatalError>
where
P: AsRef<Path>,
{
let mut conn = match path {
None => Connection::open(":memory:").expect("to create a memory connection"),
Some(path) => Connection::open(path).expect("to create connection"),
};
2024-11-30 17:20:38 +00:00
MIGRATIONS
.to_latest(&mut conn)
.map_err(|err| FatalError::DatabaseMigrationFailure(format!("{}", err)))?;
// setup_test_database(&conn)?;
2024-11-30 17:20:38 +00:00
Ok(DiskDb { conn })
}
2024-12-27 19:02:43 +00:00
fn user(&self, id: &UserId) -> Result<Option<UserRow>, FatalError> {
let mut stmt = self
.conn
.prepare("SELECT uuid, name, password, admin, enabled FROM users WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<UserRow> = stmt
.query_map([id.as_str()], |row| {
Ok(UserRow {
id: row.get(0).unwrap(),
name: row.get(1).unwrap(),
password: row.get(2).unwrap(),
admin: row.get(3).unwrap(),
enabled: row.get(4).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<UserRow>, rusqlite::Error>>()
.unwrap();
match &items[..] {
[] => Ok(None),
[item] => Ok(Some(item.clone())),
_ => Err(FatalError::NonUniqueDatabaseKey(id.as_str().to_owned())),
}
}
2024-12-27 19:02:43 +00:00
fn user_by_username(&self, username: &str) -> Result<Option<UserRow>, FatalError> {
2024-12-22 14:17:00 +00:00
let mut stmt = self
.conn
.prepare("SELECT uuid, name, password, admin, enabled FROM users WHERE name=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<UserRow> = stmt
2024-12-27 19:02:43 +00:00
.query_map([username], |row| {
2024-12-22 14:17:00 +00:00
Ok(UserRow {
id: row.get(0).unwrap(),
name: row.get(1).unwrap(),
password: row.get(2).unwrap(),
admin: row.get(3).unwrap(),
enabled: row.get(4).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<UserRow>, rusqlite::Error>>()
.unwrap();
match &items[..] {
[] => Ok(None),
[item] => Ok(Some(item.clone())),
_ => Err(FatalError::NonUniqueDatabaseKey(username.to_owned())),
}
}
fn users(&self) -> Result<Vec<UserRow>, FatalError> {
let mut stmt = self
.conn
.prepare("SELECT * FROM users")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items = stmt
.query_map([], |row| {
Ok(UserRow {
id: row.get(0).unwrap(),
name: row.get(1).unwrap(),
password: row.get(2).unwrap(),
admin: row.get(3).unwrap(),
enabled: row.get(4).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<UserRow>, rusqlite::Error>>()
.unwrap();
Ok(items)
}
fn save_user(
&self,
user_id: Option<UserId>,
name: &str,
password: &str,
admin: bool,
enabled: bool,
) -> Result<UserId, FatalError> {
match user_id {
None => {
let user_id = UserId::new();
let mut stmt = self
.conn
.prepare("INSERT INTO users VALUES (?, ?, ?, ?, ?)")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((user_id.as_str(), name, password, admin, enabled))
.unwrap();
Ok(user_id)
}
Some(user_id) => {
let mut stmt = self
.conn
2024-12-27 19:02:43 +00:00
.prepare("UPDATE users SET name=?, password=?, admin=?, enabled=? WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((name, password, admin, enabled, user_id.as_str()))
.unwrap();
Ok(user_id)
}
}
}
fn save_game(&self, game_id: Option<GameId>, name: &str) -> Result<GameId, FatalError> {
match game_id {
None => {
let game_id = GameId::new();
let mut stmt = self
.conn
.prepare("INSERT INTO games VALUES (?, ?)")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((game_id.as_str(), name)).unwrap();
Ok(game_id)
}
Some(game_id) => {
let mut stmt = self
.conn
.prepare("UPDATE games SET name=? WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((name, game_id.as_str())).unwrap();
Ok(game_id)
}
}
}
2024-12-27 19:02:43 +00:00
fn session(&self, session_id: &SessionId) -> Result<Option<UserRow>, FatalError> {
let mut stmt = self.conn
.prepare("SELECT u.uuid, u.name, u.password, u.admin, u.enabled FROM sessions s INNER JOIN users u ON u.uuid = s.user_id WHERE s.id = ?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<UserRow> = stmt
.query_map([session_id.as_str()], |row| {
Ok(UserRow {
id: row.get(0).unwrap(),
name: row.get(1).unwrap(),
password: row.get(2).unwrap(),
admin: row.get(3).unwrap(),
enabled: row.get(4).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<UserRow>, rusqlite::Error>>()
.unwrap();
match &items[..] {
[] => Ok(None),
[item] => Ok(Some(item.clone())),
_ => Err(FatalError::NonUniqueDatabaseKey(
session_id.as_str().to_owned(),
)),
}
}
fn create_session(&self, user_id: &UserId) -> Result<SessionId, FatalError> {
match self.user(user_id) {
Ok(Some(_)) => {
let mut stmt = self
.conn
.prepare("INSERT INTO sessions VALUES (?, ?)")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let session_id = SessionId::new();
stmt.execute((session_id.as_str(), user_id.as_str()))
.unwrap();
Ok(session_id)
}
Ok(None) => Err(FatalError::DatabaseKeyMissing),
Err(err) => Err(err),
}
}
fn character(&self, id: CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
let mut stmt = self
.conn
.prepare("SELECT uuid, game, data FROM characters WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<CharsheetRow> = stmt
.query_map([id.as_str()], |row| {
let data: String = row.get(2).unwrap();
Ok(CharsheetRow {
id: row.get(0).unwrap(),
game: row.get(1).unwrap(),
data: serde_json::from_str(&data).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<CharsheetRow>, rusqlite::Error>>()
.unwrap();
match &items[..] {
[] => Ok(None),
[item] => Ok(Some(item.clone())),
_ => Err(FatalError::NonUniqueDatabaseKey(id.as_str().to_owned())),
}
}
fn save_character(
&self,
char_id: Option<CharacterId>,
game: GameId,
character: serde_json::Value,
) -> std::result::Result<CharacterId, FatalError> {
match char_id {
None => {
let char_id = CharacterId::new();
let mut stmt = self
.conn
.prepare("INSERT INTO characters VALUES (?, ?, ?)")
.unwrap();
stmt.execute((char_id.as_str(), game.as_str(), character.to_string()))
.unwrap();
Ok(char_id)
}
Some(char_id) => {
let mut stmt = self
.conn
.prepare("UPDATE characters SET data=? WHERE uuid=?")
.unwrap();
stmt.execute((character.to_string(), char_id.as_str()))
.unwrap();
Ok(char_id)
}
}
}
}
2024-11-30 20:24:57 +00:00
async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
2024-11-30 17:20:38 +00:00
while let Ok(DatabaseRequest { tx, req }) = requestor.recv().await {
println!("Request received: {:?}", req);
match req {
Request::Charsheet(id) => {
let sheet = db.character(id);
match sheet {
2024-11-30 17:05:31 +00:00
Ok(sheet) => {
tx.send(DatabaseResponse::Charsheet(sheet)).await.unwrap();
2024-11-30 17:20:38 +00:00
}
_ => unimplemented!(),
}
}
2024-12-27 19:02:43 +00:00
Request::CreateSession(id) => {
let session_id = db.create_session(&id).unwrap();
tx.send(DatabaseResponse::CreateSession(session_id))
.await
.unwrap();
}
Request::Games => {
unimplemented!();
}
Request::User(uid) => {
2024-12-27 19:02:43 +00:00
let user = db.user(&uid);
match user {
Ok(user) => {
tx.send(DatabaseResponse::User(user)).await.unwrap();
}
err => panic!("{:?}", err),
}
}
2024-12-22 14:17:00 +00:00
Request::UserByUsername(username) => {
2024-12-27 19:02:43 +00:00
let user = db.user_by_username(&username);
2024-12-22 14:17:00 +00:00
match user {
Ok(user) => tx.send(DatabaseResponse::User(user)).await.unwrap(),
err => panic!("{:?}", err),
}
}
Request::SaveUser(user_id, username, password, admin, enabled) => {
2024-12-27 19:02:43 +00:00
let user_id = db.save_user(
user_id,
username.as_ref(),
password.as_ref(),
admin,
enabled,
);
match user_id {
Ok(user_id) => {
tx.send(DatabaseResponse::SaveUser(user_id)).await.unwrap();
}
err => panic!("{:?}", err),
}
}
2024-12-27 19:02:43 +00:00
Request::Session(session_id) => {
let user = db.session(&session_id);
match user {
Ok(user) => tx.send(DatabaseResponse::Session(user)).await.unwrap(),
err => panic!("{:?}", err),
}
}
Request::Users => {
let users = db.users();
match users {
Ok(users) => {
tx.send(DatabaseResponse::Users(users)).await.unwrap();
}
_ => unimplemented!(),
}
}
}
}
println!("ending db_handler");
}
pub struct DbConn {
2024-11-30 17:05:31 +00:00
conn: Sender<DatabaseRequest>,
handle: tokio::task::JoinHandle<()>,
}
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);
let db = DiskDb::new(path).unwrap();
2024-11-30 17:20:38 +00:00
let handle = tokio::spawn(async move {
db_handler(db, rx).await;
});
Self { conn: tx, handle }
}
}
#[async_trait]
impl Database for DbConn {
2024-12-27 19:02:43 +00:00
async fn user(&mut self, uid: &UserId) -> Result<Option<UserRow>, FatalError> {
let (tx, rx) = bounded::<DatabaseResponse>(1);
let request = DatabaseRequest {
tx,
2024-12-27 19:02:43 +00:00
req: Request::User(uid.clone()),
};
match self.conn.send(request).await {
Ok(()) => (),
Err(_) => return Err(FatalError::DatabaseConnectionLost),
};
match rx.recv().await {
Ok(DatabaseResponse::User(user)) => Ok(user),
Ok(_) => Err(FatalError::MessageMismatch),
Err(_) => Err(FatalError::DatabaseConnectionLost),
}
}
2024-12-27 19:02:43 +00:00
async fn user_by_username(&self, username: &str) -> Result<Option<UserRow>, FatalError> {
2024-12-22 14:17:00 +00:00
let (tx, rx) = bounded::<DatabaseResponse>(1);
let request = DatabaseRequest {
tx,
2024-12-27 19:02:43 +00:00
req: Request::UserByUsername(username.to_owned()),
2024-12-22 14:17:00 +00:00
};
match self.conn.send(request).await {
Ok(()) => (),
Err(_) => return Err(FatalError::DatabaseConnectionLost),
};
match rx.recv().await {
Ok(DatabaseResponse::User(user)) => Ok(user),
Ok(_) => Err(FatalError::MessageMismatch),
Err(_) => Err(FatalError::DatabaseConnectionLost),
}
}
async fn save_user(
2024-12-27 19:02:43 +00:00
&self,
user_id: Option<UserId>,
name: &str,
password: &str,
admin: bool,
enabled: bool,
) -> Result<UserId, FatalError> {
let (tx, rx) = bounded::<DatabaseResponse>(1);
let request = DatabaseRequest {
tx,
req: Request::SaveUser(
user_id,
name.to_owned(),
password.to_owned(),
admin,
enabled,
),
};
match self.conn.send(request).await {
Ok(()) => (),
Err(_) => return Err(FatalError::DatabaseConnectionLost),
};
match rx.recv().await {
Ok(DatabaseResponse::SaveUser(user_id)) => Ok(user_id),
Ok(_) => Err(FatalError::MessageMismatch),
Err(_) => Err(FatalError::DatabaseConnectionLost),
}
}
async fn users(&mut self) -> Result<Vec<UserRow>, FatalError> {
let (tx, rx) = bounded::<DatabaseResponse>(1);
let request = DatabaseRequest {
tx,
req: Request::Users,
};
match self.conn.send(request).await {
Ok(()) => (),
Err(_) => return Err(FatalError::DatabaseConnectionLost),
};
match rx.recv().await {
Ok(DatabaseResponse::Users(lst)) => Ok(lst),
Ok(_) => Err(FatalError::MessageMismatch),
Err(_) => Err(FatalError::DatabaseConnectionLost),
}
}
async fn games(&mut self) -> Result<Vec<GameRow>, FatalError> {
let (tx, rx) = bounded::<DatabaseResponse>(1);
let request = DatabaseRequest {
tx,
req: Request::Games,
};
match self.conn.send(request).await {
Ok(()) => (),
Err(_) => return Err(FatalError::DatabaseConnectionLost),
};
match rx.recv().await {
Ok(DatabaseResponse::Games(lst)) => Ok(lst),
Ok(_) => Err(FatalError::MessageMismatch),
Err(_) => Err(FatalError::DatabaseConnectionLost),
}
}
async fn character(&mut self, id: CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
2024-11-30 17:05:31 +00:00
let (tx, rx) = bounded::<DatabaseResponse>(1);
2024-11-30 17:20:38 +00:00
let request = DatabaseRequest {
tx,
req: Request::Charsheet(id),
};
match self.conn.send(request).await {
Ok(()) => (),
Err(_) => return Err(FatalError::DatabaseConnectionLost),
};
2024-11-30 17:05:31 +00:00
match rx.recv().await {
Ok(DatabaseResponse::Charsheet(row)) => Ok(row),
Ok(_) => Err(FatalError::MessageMismatch),
Err(_) => Err(FatalError::DatabaseConnectionLost),
}
}
2024-12-27 19:02:43 +00:00
async fn session(&self, id: SessionId) -> Result<Option<UserRow>, FatalError> {
let (tx, rx) = bounded::<DatabaseResponse>(1);
let request = DatabaseRequest {
tx,
req: Request::Session(id),
};
match self.conn.send(request).await {
Ok(()) => (),
Err(_) => return Err(FatalError::DatabaseConnectionLost),
};
match rx.recv().await {
Ok(DatabaseResponse::Session(row)) => Ok(row),
Ok(_) => Err(FatalError::MessageMismatch),
Err(_) => Err(FatalError::DatabaseConnectionLost),
}
}
async fn create_session(&self, id: UserId) -> Result<SessionId, FatalError> {
let (tx, rx) = bounded::<DatabaseResponse>(1);
let request = DatabaseRequest {
tx,
req: Request::CreateSession(id),
};
match self.conn.send(request).await {
Ok(()) => (),
Err(_) => return Err(FatalError::DatabaseConnectionLost),
};
match rx.recv().await {
Ok(DatabaseResponse::CreateSession(session_id)) => Ok(session_id),
Ok(_) => Err(FatalError::MessageMismatch),
Err(_) => Err(FatalError::DatabaseConnectionLost),
}
}
}
#[cfg(test)]
mod test {
use std::path::PathBuf;
use cool_asserts::assert_matches;
use super::*;
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." ] }"#;
fn setup_db() -> (DiskDb, GameId) {
let no_path: Option<PathBuf> = None;
let db = DiskDb::new(no_path).unwrap();
2024-12-27 19:02:43 +00:00
db.save_user(None, "admin", "abcdefg", true, true).unwrap();
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();
assert_matches!(db.character(CharacterId::from("1")), Ok(None));
let js: serde_json::Value = serde_json::from_str(soren).unwrap();
let soren_id = db.save_character(None, game_id, js.clone()).unwrap();
assert_matches!(db.character(soren_id).unwrap(), Some(CharsheetRow{ data, .. }) => assert_eq!(js, data));
}
#[tokio::test]
async fn it_can_retrieve_a_character_through_conn() {
let memory_db: Option<PathBuf> = None;
let mut conn = DbConn::new(memory_db);
2024-12-27 19:02:43 +00:00
assert_matches!(conn.character(CharacterId::from("1")).await, Ok(None));
}
}