Set up the user interface state model and set up the admin user onboarding #283

Merged
savanni merged 12 commits from visions-admin into main 2024-12-18 14:18:16 +00:00
2 changed files with 118 additions and 7 deletions
Showing only changes of commit afb510d92e - Show all commits

View File

@ -0,0 +1,16 @@
CREATE TABLE users(
uuid TEXT PRIMARY KEY,
name TEXT,
password TEXT,
admin BOOLEAN,
enabled BOOLEAN
);
CREATE TABLE roles(
user_id TEXT,
game_id TEXT,
role TEXT,
FOREIGN KEY(user_id) REFERENCES users(uuid),
FOREIGN KEY(game_id) REFERENCES games(uuid)
);

View File

@ -48,12 +48,62 @@ enum DatabaseResponse {
Charsheet(Option<CharsheetRow>), Charsheet(Option<CharsheetRow>),
} }
#[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)
}
}
#[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)
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct CharacterId(String); pub struct CharacterId(String);
impl CharacterId { impl CharacterId {
pub fn new() -> Self { pub fn new() -> Self {
CharacterId(format!("{}", Uuid::new_v4().hyphenated())) Self(format!("{}", Uuid::new_v4().hyphenated()))
} }
pub fn as_str<'a>(&'a self) -> &'a str { pub fn as_str<'a>(&'a self) -> &'a str {
@ -63,20 +113,36 @@ impl CharacterId {
impl From<&str> for CharacterId { impl From<&str> for CharacterId {
fn from(s: &str) -> Self { fn from(s: &str) -> Self {
CharacterId(s.to_owned()) Self(s.to_owned())
} }
} }
impl From<String> for CharacterId { impl From<String> for CharacterId {
fn from(s: String) -> Self { fn from(s: String) -> Self {
CharacterId(s) Self(s)
} }
} }
#[derive(Clone, Debug)]
pub struct UserRow {
id: String,
name: String,
password: String,
admin: bool,
enabled: bool,
}
#[derive(Clone, Debug)]
pub struct Role {
userid: String,
gameid: String,
role: String,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CharsheetRow { pub struct CharsheetRow {
id: String, id: String,
gametype: String, game: String,
pub data: serde_json::Value, pub data: serde_json::Value,
} }
@ -93,11 +159,20 @@ fn setup_test_database(conn: &Connection) {
let mut gamecount_stmt = conn.prepare("SELECT count(*) FROM games").unwrap(); let mut gamecount_stmt = conn.prepare("SELECT count(*) FROM games").unwrap();
let mut count = gamecount_stmt.query([]).unwrap(); let mut count = gamecount_stmt.query([]).unwrap();
if count.next().unwrap().unwrap().get::<usize, usize>(0) == Ok(0) { if count.next().unwrap().unwrap().get::<usize, usize>(0) == Ok(0) {
let admin_id = format!("{}", Uuid::new_v4());
let user_id = format!("{}", Uuid::new_v4());
let game_id = format!("{}", Uuid::new_v4()); let game_id = format!("{}", Uuid::new_v4());
let char_id = CharacterId::new(); let char_id = CharacterId::new();
let mut user_stmt = conn.prepare("INSERT INTO users VALUES (?, ?, ?, ?, ?)").unwrap();
user_stmt.execute((admin_id.clone(), "admin", "abcdefg", true, true)).unwrap();
user_stmt.execute((user_id.clone(), "savanni", "abcdefg", false, true)).unwrap();
let mut game_stmt = conn.prepare("INSERT INTO games VALUES (?, ?)").unwrap(); let mut game_stmt = conn.prepare("INSERT INTO games VALUES (?, ?)").unwrap();
game_stmt.execute((game_id.clone(), "Circle of Bluest Sky")); game_stmt.execute((game_id.clone(), "Circle of Bluest Sky")).unwrap();
let mut role_stmt = conn.prepare("INSERT INTO roles VALUES (?, ?, ?)").unwrap();
role_stmt.execute((user_id.clone(), game_id.clone(), "gm")).unwrap();
let mut sheet_stmt = conn let mut sheet_stmt = conn
.prepare("INSERT INTO characters VALUES (?, ?, ?)") .prepare("INSERT INTO characters VALUES (?, ?, ?)")
@ -124,17 +199,37 @@ impl DiskDb {
Ok(DiskDb { conn }) Ok(DiskDb { conn })
} }
fn user(&self, id: UserId) -> Result<Option<UserRow>, Error> {
let mut stmt = self
.conn
.prepare("SELECT uuid, name, password, admin, enabled WHERE uuid=?")
.unwrap();
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())),
_ => unimplemented!(),
}
}
fn charsheet(&self, id: CharacterId) -> Result<Option<CharsheetRow>, Error> { fn charsheet(&self, id: CharacterId) -> Result<Option<CharsheetRow>, Error> {
let mut stmt = self let mut stmt = self
.conn .conn
.prepare("SELECT uuid, gametype, data FROM charsheet WHERE uuid=?") .prepare("SELECT uuid, game, data FROM charsheet WHERE uuid=?")
.unwrap(); .unwrap();
let items: Vec<CharsheetRow> = stmt let items: Vec<CharsheetRow> = stmt
.query_map([id.as_str()], |row| { .query_map([id.as_str()], |row| {
let data: String = row.get(2).unwrap(); let data: String = row.get(2).unwrap();
Ok(CharsheetRow { Ok(CharsheetRow {
id: row.get(0).unwrap(), id: row.get(0).unwrap(),
gametype: row.get(1).unwrap(), game: row.get(1).unwrap(),
data: serde_json::from_str(&data).unwrap(), data: serde_json::from_str(&data).unwrap(),
}) })
}) })