Set up the user interface state model and set up the admin user onboarding #283
|
@ -240,6 +240,69 @@ impl DiskDb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
.prepare(
|
||||||
|
"UPDATE users SET name=?, password=?, admin=?, enbabled=? 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn character(&self, id: CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
|
fn character(&self, id: CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
|
||||||
let mut stmt = self
|
let mut stmt = self
|
||||||
.conn
|
.conn
|
||||||
|
@ -267,7 +330,7 @@ impl DiskDb {
|
||||||
fn save_character(
|
fn save_character(
|
||||||
&self,
|
&self,
|
||||||
char_id: Option<CharacterId>,
|
char_id: Option<CharacterId>,
|
||||||
game_type: String,
|
game: GameId,
|
||||||
character: serde_json::Value,
|
character: serde_json::Value,
|
||||||
) -> std::result::Result<CharacterId, Error> {
|
) -> std::result::Result<CharacterId, Error> {
|
||||||
match char_id {
|
match char_id {
|
||||||
|
@ -277,7 +340,7 @@ impl DiskDb {
|
||||||
.conn
|
.conn
|
||||||
.prepare("INSERT INTO characters VALUES (?, ?, ?)")
|
.prepare("INSERT INTO characters VALUES (?, ?, ?)")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
stmt.execute((char_id.as_str(), game_type, character.to_string()))
|
stmt.execute((char_id.as_str(), game.as_str(), character.to_string()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Ok(char_id)
|
Ok(char_id)
|
||||||
|
@ -373,16 +436,24 @@ mod test {
|
||||||
|
|
||||||
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." ] }"#;
|
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." ] }"#;
|
||||||
|
|
||||||
#[test]
|
fn setup_db() -> (DiskDb, GameId) {
|
||||||
fn it_can_retrieve_a_character() {
|
|
||||||
let no_path: Option<PathBuf> = None;
|
let no_path: Option<PathBuf> = None;
|
||||||
let db = DiskDb::new(no_path).unwrap();
|
let db = DiskDb::new(no_path).unwrap();
|
||||||
|
|
||||||
|
db.save_user(None, "admin", "abcdefg", true, true);
|
||||||
|
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));
|
assert_matches!(db.character(CharacterId::from("1")), Ok(None));
|
||||||
|
|
||||||
let js: serde_json::Value = serde_json::from_str(soren).unwrap();
|
let js: serde_json::Value = serde_json::from_str(soren).unwrap();
|
||||||
let soren_id = db
|
let soren_id = db
|
||||||
.save_character(None, "candela".to_owned(), js.clone())
|
.save_character(None, game_id, js.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_matches!(db.character(soren_id).unwrap(), Some(CharsheetRow{ data, .. }) => assert_eq!(js, data));
|
assert_matches!(db.character(soren_id).unwrap(), Some(CharsheetRow{ data, .. }) => assert_eq!(js, data));
|
||||||
}
|
}
|
||||||
|
@ -392,6 +463,9 @@ mod test {
|
||||||
let memory_db: Option<PathBuf> = None;
|
let memory_db: Option<PathBuf> = None;
|
||||||
let mut conn = DbConn::new(memory_db);
|
let mut conn = DbConn::new(memory_db);
|
||||||
|
|
||||||
assert_matches!(conn.character(CharacterId::from("1")).await, ResultExt::Ok(None));
|
assert_matches!(
|
||||||
|
conn.character(CharacterId::from("1")).await,
|
||||||
|
ResultExt::Ok(None)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue