Change the data types of the internal interfaces, and switch password expiration to an AccountState

This commit is contained in:
Savanni D'Gerinel 2025-01-11 11:43:32 -05:00
parent d0ba8d921d
commit e19d97663d
7 changed files with 264 additions and 255 deletions

View File

@ -13,6 +13,10 @@ use crate::{
asset_db::{self, AssetId, Assets}, asset_db::{self, AssetId, Assets},
database::{CharacterId, Database, GameId, SessionId, UserId}, database::{CharacterId, Database, GameId, SessionId, UserId},
types::{AppError, FatalError, GameOverview, Message, Rgb, Tabletop, User, UserOverview}, types::{AppError, FatalError, GameOverview, Message, Rgb, Tabletop, User, UserOverview},
types::{
AccountState, AppError, FatalError, GameOverview, Message, Rgb, Tabletop, User,
UserOverview,
},
}; };
const DEFAULT_BACKGROUND_COLOR: Rgb = Rgb { const DEFAULT_BACKGROUND_COLOR: Rgb = Rgb {
@ -171,7 +175,7 @@ impl Core {
Some(_) => error(AppError::UsernameUnavailable), Some(_) => error(AppError::UsernameUnavailable),
None => match state None => match state
.db .db
.save_user(None, username, "", false, true, Utc::now()) .create_user(username, "", false, AccountState::PasswordReset(Utc::now()))
.await .await
{ {
Ok(user_id) => ok(user_id), Ok(user_id) => ok(user_id),
@ -180,7 +184,7 @@ impl Core {
} }
} }
pub async fn disable_user(&self, userid: UserId) -> ResultExt<(), AppError, FatalError> { pub async fn disable_user(&self, _userid: UserId) -> ResultExt<(), AppError, FatalError> {
unimplemented!(); unimplemented!();
} }
@ -188,7 +192,14 @@ impl Core {
let games = self.0.read().await.db.games().await; let games = self.0.read().await.db.games().await;
match games { match games {
// Ok(games) => ok(games.into_iter().map(|g| Game::from(g)).collect()), // Ok(games) => ok(games.into_iter().map(|g| Game::from(g)).collect()),
Ok(games) => ok(games.into_iter().map(GameOverview::from).collect()), Ok(games) => ok(games.into_iter().map(|game|
GameOverview{
id: game.id,
game_type: "".to_owned(),
game_name: game.name,
gm: game.gm,
players: game.players,
}).collect::<Vec<GameOverview>>()),
Err(err) => fatal(err), Err(err) => fatal(err),
} }
} }
@ -200,7 +211,7 @@ impl Core {
game_name: &str, game_name: &str,
) -> ResultExt<GameId, AppError, FatalError> { ) -> ResultExt<GameId, AppError, FatalError> {
let state = self.0.read().await; let state = self.0.read().await;
match state.db.save_game(None, gm, game_type, game_name).await { match state.db.create_game(gm, game_type, game_name).await {
Ok(game_id) => ok(game_id), Ok(game_id) => ok(game_id),
Err(err) => fatal(err), Err(err) => fatal(err),
} }
@ -283,16 +294,22 @@ impl Core {
pub async fn save_user( pub async fn save_user(
&self, &self,
uuid: Option<UserId>, id: UserId,
username: &str, name: &str,
password: &str, password: &str,
admin: bool, admin: bool,
enabled: bool, account_state: AccountState,
) -> ResultExt<UserId, AppError, FatalError> { ) -> ResultExt<UserId, AppError, FatalError> {
let state = self.0.read().await; let state = self.0.read().await;
match state match state
.db .db
.save_user(uuid, username, password, admin, enabled, Utc::now()) .save_user(User {
id,
name: name.to_owned(),
password: password.to_owned(),
admin,
state: account_state,
})
.await .await
{ {
Ok(uuid) => ok(uuid), Ok(uuid) => ok(uuid),
@ -313,14 +330,7 @@ impl Core {
}; };
match state match state
.db .db
.save_user( .save_user(User{ password, state: AccountState::Normal, ..user })
Some(uuid),
&user.name,
&password,
user.admin,
user.enabled,
Utc::now(),
)
.await .await
{ {
Ok(_) => ok(()), Ok(_) => ok(()),
@ -330,9 +340,10 @@ impl Core {
pub async fn auth( pub async fn auth(
&self, &self,
username: &str, _username: &str,
password: &str, _password: &str,
) -> ResultExt<AuthResponse, AppError, FatalError> { ) -> ResultExt<AuthResponse, AppError, FatalError> {
/*
let now = Utc::now(); let now = Utc::now();
let state = self.0.read().await; let state = self.0.read().await;
match state.db.user_by_username(username).await { match state.db.user_by_username(username).await {
@ -355,6 +366,8 @@ impl Core {
Ok(_) => error(AppError::AuthFailed), Ok(_) => error(AppError::AuthFailed),
Err(err) => fatal(err), Err(err) => fatal(err),
} }
*/
unimplemented!();
} }
pub async fn session( pub async fn session(
@ -414,19 +427,22 @@ mod test {
]); ]);
let memory_db: Option<PathBuf> = None; let memory_db: Option<PathBuf> = None;
let conn = DbConn::new(memory_db); let conn = DbConn::new(memory_db);
conn.save_user( conn.create_user(
Some(UserId::from("admin")),
"admin", "admin",
"aoeu", "aoeu",
true, true,
true, AccountState::PasswordReset(Utc::now()),
Utc::now(), )
.await
.unwrap();
conn.create_user(
"gm_1",
"aoeu",
false,
AccountState::PasswordReset(Utc::now()),
) )
.await .await
.unwrap(); .unwrap();
conn.save_user(None, "gm_1", "aoeu", false, true, Utc::now())
.await
.unwrap();
Core::new(assets, conn) Core::new(assets, conn)
} }

View File

@ -1,7 +1,6 @@
use std::path::Path; use std::path::Path;
use async_std::channel::Receiver; use async_std::channel::Receiver;
use chrono::Utc;
use include_dir::{include_dir, Dir}; use include_dir::{include_dir, Dir};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use rusqlite::Connection; use rusqlite::Connection;
@ -9,13 +8,10 @@ use rusqlite_migration::Migrations;
use crate::{ use crate::{
database::{DatabaseResponse, Request}, database::{DatabaseResponse, Request},
types::FatalError, types::{AccountState, FatalError, Game, User},
}; };
use super::{ use super::{types::GameId, CharacterId, CharsheetRow, DatabaseRequest, SessionId, UserId};
types::{DateTime, GameId},
CharacterId, CharsheetRow, DatabaseRequest, GameRow, SessionId, UserId, UserRow,
};
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/migrations"); static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/migrations");
@ -47,24 +43,23 @@ impl DiskDb {
Ok(DiskDb { conn }) Ok(DiskDb { conn })
} }
pub fn user(&self, id: &UserId) -> Result<Option<UserRow>, FatalError> { pub fn user(&self, id: &UserId) -> Result<Option<User>, FatalError> {
let mut stmt = self let mut stmt = self
.conn .conn
.prepare("SELECT * FROM users WHERE uuid=?") .prepare("SELECT * FROM users WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?; .map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<UserRow> = stmt let items: Vec<User> = stmt
.query_map([id.as_str()], |row| { .query_map([id.as_str()], |row| {
Ok(UserRow { Ok(User {
id: row.get(0).unwrap(), id: row.get(0).unwrap(),
name: row.get(1).unwrap(), name: row.get(1).unwrap(),
password: row.get(2).unwrap(), password: row.get(2).unwrap(),
admin: row.get(3).unwrap(), admin: row.get(3).unwrap(),
enabled: row.get(4).unwrap(), state: row.get(4).unwrap(),
password_expires: row.get(5).unwrap(),
}) })
}) })
.unwrap() .unwrap()
.collect::<Result<Vec<UserRow>, rusqlite::Error>>() .collect::<Result<Vec<User>, rusqlite::Error>>()
.unwrap(); .unwrap();
match &items[..] { match &items[..] {
[] => Ok(None), [] => Ok(None),
@ -73,24 +68,23 @@ impl DiskDb {
} }
} }
pub fn user_by_username(&self, username: &str) -> Result<Option<UserRow>, FatalError> { pub fn user_by_username(&self, username: &str) -> Result<Option<User>, FatalError> {
let mut stmt = self let mut stmt = self
.conn .conn
.prepare("SELECT * FROM users WHERE name=?") .prepare("SELECT * FROM users WHERE name=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?; .map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<UserRow> = stmt let items: Vec<User> = stmt
.query_map([username], |row| { .query_map([username], |row| {
Ok(UserRow { Ok(User {
id: row.get(0).unwrap(), id: row.get(0).unwrap(),
name: row.get(1).unwrap(), name: row.get(1).unwrap(),
password: row.get(2).unwrap(), password: row.get(2).unwrap(),
admin: row.get(3).unwrap(), admin: row.get(3).unwrap(),
enabled: row.get(4).unwrap(), state: row.get(4).unwrap(),
password_expires: row.get(5).unwrap(),
}) })
}) })
.unwrap() .unwrap()
.collect::<Result<Vec<UserRow>, rusqlite::Error>>() .collect::<Result<Vec<User>, rusqlite::Error>>()
.unwrap(); .unwrap();
match &items[..] { match &items[..] {
[] => Ok(None), [] => Ok(None),
@ -99,81 +93,72 @@ impl DiskDb {
} }
} }
pub fn save_user( pub fn create_user(
&self, &self,
user_id: Option<UserId>,
name: &str, name: &str,
password: &str, password: &str,
admin: bool, admin: bool,
enabled: bool, state: AccountState,
expiration: chrono::DateTime<Utc>,
) -> Result<UserId, FatalError> { ) -> Result<UserId, FatalError> {
match user_id { let user_id = UserId::default();
None => { let mut stmt = self
let user_id = UserId::default(); .conn
let mut stmt = self .prepare("INSERT INTO users VALUES (?, ?, ?, ?, ?)")
.conn .map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
.prepare("INSERT INTO users VALUES (?, ?, ?, ?, ?, ?)") stmt.execute((user_id.as_str(), name, password, admin, state))
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?; .unwrap();
stmt.execute(( Ok(user_id)
user_id.as_str(),
name,
password,
admin,
enabled,
format!("{}", expiration.format("%Y-%m-%d %H:%M:%S")),
))
.unwrap();
Ok(user_id)
}
Some(user_id) => {
let mut stmt = self
.conn
.prepare("UPDATE users SET name=?, password=?, admin=?, enabled=?, password_expires=? WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((
name,
password,
admin,
enabled,
format!("{}", expiration.format("%Y-%m-%d %H:%M:%S")),
user_id.as_str(),
))
.unwrap();
Ok(user_id)
}
}
} }
pub fn users(&self) -> Result<Vec<UserRow>, FatalError> { pub fn save_user(&self, user: User) -> Result<UserId, FatalError> {
let mut stmt = self
.conn
.prepare("UPDATE users SET name=?, password=?, admin=?, state=? WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((
user.name,
user.password,
user.admin,
user.state,
// format!("{}", expiration.format("%Y-%m-%d %H:%M:%S")),
user.id.as_str(),
))
.unwrap();
Ok(user.id)
}
pub fn users(&self) -> Result<Vec<User>, FatalError> {
let mut stmt = self let mut stmt = self
.conn .conn
.prepare("SELECT * FROM users") .prepare("SELECT * FROM users")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?; .map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items = stmt let items = stmt
.query_map([], |row| { .query_map([], |row| {
Ok(UserRow { Ok(User {
id: row.get(0).unwrap(), id: row.get(0).unwrap(),
name: row.get(1).unwrap(), name: row.get(1).unwrap(),
password: row.get(2).unwrap(), password: row.get(2).unwrap(),
admin: row.get(3).unwrap(), admin: row.get(3).unwrap(),
enabled: row.get(4).unwrap(), state: row.get(4).unwrap(),
password_expires: row.get(5).unwrap(),
}) })
}) })
.unwrap() .unwrap()
.collect::<Result<Vec<UserRow>, rusqlite::Error>>() .collect::<Result<Vec<User>, rusqlite::Error>>()
.unwrap(); .unwrap();
Ok(items) Ok(items)
} }
pub fn save_game( pub fn create_game(
&self, &self,
game_id: Option<GameId>, _gm: &UserId,
gm: &UserId, _game_type: &str,
game_type: &str, _name: &str,
name: &str,
) -> Result<GameId, FatalError> { ) -> Result<GameId, FatalError> {
unimplemented!();
}
pub fn save_game(&self, _game: Game) -> Result<(), FatalError> {
/*
match game_id { match game_id {
None => { None => {
let game_id = GameId::new(); let game_id = GameId::new();
@ -195,46 +180,47 @@ impl DiskDb {
Ok(game_id) Ok(game_id)
} }
} }
*/
unimplemented!()
} }
pub fn games(&self) -> Result<Vec<GameRow>, FatalError> { pub fn games(&self) -> Result<Vec<Game>, FatalError> {
let mut stmt = self let mut stmt = self
.conn .conn
.prepare("SELECT * FROM games") .prepare("SELECT * FROM games")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?; .map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items = stmt let items = stmt
.query_map([], |row| { .query_map([], |row| {
Ok(GameRow { Ok(Game {
id: row.get(0).unwrap(), id: row.get(0).unwrap(),
gm: row.get(1).unwrap(), gm: row.get(1).unwrap(),
game_type: row.get(2).unwrap(),
name: row.get(3).unwrap(), name: row.get(3).unwrap(),
players: vec![],
}) })
}) })
.unwrap() .unwrap()
.collect::<Result<Vec<GameRow>, rusqlite::Error>>() .collect::<Result<Vec<Game>, rusqlite::Error>>()
.unwrap(); .unwrap();
Ok(items) Ok(items)
} }
pub fn session(&self, session_id: &SessionId) -> Result<Option<UserRow>, FatalError> { pub fn session(&self, session_id: &SessionId) -> Result<Option<User>, FatalError> {
let mut stmt = self.conn 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 = ?") .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)))?; .map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<UserRow> = stmt let items: Vec<User> = stmt
.query_map([session_id.as_str()], |row| { .query_map([session_id.as_str()], |row| {
Ok(UserRow { Ok(User {
id: row.get(0).unwrap(), id: row.get(0).unwrap(),
name: row.get(1).unwrap(), name: row.get(1).unwrap(),
password: row.get(2).unwrap(), password: row.get(2).unwrap(),
admin: row.get(3).unwrap(), admin: row.get(3).unwrap(),
enabled: row.get(4).unwrap(), state: row.get(4).unwrap(),
password_expires: row.get(5).unwrap(),
}) })
}) })
.unwrap() .unwrap()
.collect::<Result<Vec<UserRow>, rusqlite::Error>>() .collect::<Result<Vec<User>, rusqlite::Error>>()
.unwrap(); .unwrap();
match &items[..] { match &items[..] {
[] => Ok(None), [] => Ok(None),
@ -331,6 +317,8 @@ pub async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
_ => unimplemented!("errors for Charsheet"), _ => unimplemented!("errors for Charsheet"),
} }
} }
Request::CreateUser(_, _, _, _) => {}
Request::CreateGame(_, _, _) => {}
Request::CreateSession(id) => { Request::CreateSession(id) => {
let session_id = db.create_session(&id).unwrap(); let session_id = db.create_session(&id).unwrap();
tx.send(DatabaseResponse::CreateSession(session_id)) tx.send(DatabaseResponse::CreateSession(session_id))
@ -344,11 +332,12 @@ pub async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
Request::Game(_game_id) => { Request::Game(_game_id) => {
unimplemented!("Request::Game handler"); unimplemented!("Request::Game handler");
} }
Request::SaveGame(game_id, user_id, game_type, game_name) => { Request::SaveGame(game) => {
let game_id = db.save_game(game_id, &user_id, &game_type, &game_name); let id = game.id.clone();
match game_id { let save_result = db.save_game(game);
Ok(game_id) => { match save_result {
tx.send(DatabaseResponse::SaveGame(game_id)).await.unwrap(); Ok(_) => {
tx.send(DatabaseResponse::SaveGame(id)).await.unwrap();
} }
err => panic!("{:?}", err), err => panic!("{:?}", err),
} }
@ -369,15 +358,8 @@ pub async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
err => panic!("{:?}", err), err => panic!("{:?}", err),
} }
} }
Request::SaveUser(user_id, username, password, admin, enabled, expiration) => { Request::SaveUser(user) => {
let user_id = db.save_user( let user_id = db.save_user(user);
user_id,
username.as_ref(),
password.as_ref(),
admin,
enabled,
expiration,
);
match user_id { match user_id {
Ok(user_id) => { Ok(user_id) => {
tx.send(DatabaseResponse::SaveUser(user_id)).await.unwrap(); tx.send(DatabaseResponse::SaveUser(user_id)).await.unwrap();

View File

@ -5,20 +5,21 @@ use std::path::Path;
use async_std::channel::{bounded, Sender}; use async_std::channel::{bounded, Sender};
use async_trait::async_trait; use async_trait::async_trait;
use chrono::{DateTime, Utc};
use disk_db::{db_handler, DiskDb}; use disk_db::{db_handler, DiskDb};
pub use types::{CharacterId, CharsheetRow, GameId, GameRow, SessionId, UserId, UserRow}; pub use types::{CharacterId, CharsheetRow, GameId, SessionId, UserId};
use crate::types::FatalError; use crate::types::{AccountState, FatalError, Game, User};
#[derive(Debug)] #[derive(Debug)]
enum Request { enum Request {
Charsheet(CharacterId), Charsheet(CharacterId),
CreateGame(UserId, String, String),
CreateSession(UserId), CreateSession(UserId),
Games, CreateUser(String, String, bool, AccountState),
Game(GameId), Game(GameId),
SaveGame(Option<GameId>, UserId, String, String), Games,
SaveUser(Option<UserId>, String, String, bool, bool, DateTime<Utc>), SaveGame(Game),
SaveUser(User),
Session(SessionId), Session(SessionId),
User(UserId), User(UserId),
UserByUsername(String), UserByUsername(String),
@ -35,50 +36,43 @@ struct DatabaseRequest {
enum DatabaseResponse { enum DatabaseResponse {
Charsheet(Option<CharsheetRow>), Charsheet(Option<CharsheetRow>),
CreateSession(SessionId), CreateSession(SessionId),
Games(Vec<GameRow>), Games(Vec<Game>),
Game(Option<GameRow>), Game(Option<Game>),
SaveGame(GameId), SaveGame(GameId),
SaveUser(UserId), SaveUser(UserId),
Session(Option<UserRow>), Session(Option<User>),
User(Option<UserRow>), User(Option<User>),
Users(Vec<UserRow>), Users(Vec<User>),
} }
#[async_trait] #[async_trait]
pub trait Database: Send + Sync { pub trait Database: Send + Sync {
async fn users(&self) -> Result<Vec<UserRow>, FatalError>; async fn create_session(&self, id: &UserId) -> Result<SessionId, FatalError>;
async fn session(&self, id: &SessionId) -> Result<Option<User>, FatalError>;
async fn user(&self, _: &UserId) -> Result<Option<UserRow>, FatalError>;
async fn user_by_username(&self, _: &str) -> Result<Option<UserRow>, FatalError>;
async fn save_user(
&self,
user_id: Option<UserId>,
name: &str,
password: &str,
admin: bool,
enabled: bool,
expiration: DateTime<Utc>,
) -> Result<UserId, FatalError>;
async fn games(&self) -> Result<Vec<GameRow>, FatalError>;
async fn game(&self, _: &GameId) -> Result<Option<GameRow>, FatalError>;
async fn save_game(
&self,
game_id: Option<GameId>,
gm: &UserId,
game_type: &str,
game_name: &str,
) -> Result<GameId, FatalError>;
async fn character(&self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError>; async fn character(&self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError>;
async fn session(&self, id: &SessionId) -> Result<Option<UserRow>, FatalError>; async fn create_game(
&self,
gm: &UserId,
game_type: &str,
name: &str,
) -> Result<GameId, FatalError>;
async fn save_game(&self, game: Game) -> Result<GameId, FatalError>;
async fn game(&self, _: &GameId) -> Result<Option<Game>, FatalError>;
async fn games(&self) -> Result<Vec<Game>, FatalError>;
async fn create_session(&self, id: &UserId) -> Result<SessionId, FatalError>; async fn create_user(
&self,
name: &str,
password: &str,
admin: bool,
state: AccountState,
) -> Result<UserId, FatalError>;
async fn save_user(&self, user: User) -> Result<UserId, FatalError>;
async fn user(&self, _: &UserId) -> Result<Option<User>, FatalError>;
async fn user_by_username(&self, _: &str) -> Result<Option<User>, FatalError>;
async fn users(&self) -> Result<Vec<User>, FatalError>;
} }
pub struct DbConn { pub struct DbConn {
@ -125,67 +119,58 @@ macro_rules! send_request {
#[async_trait] #[async_trait]
impl Database for DbConn { impl Database for DbConn {
async fn users(&self) -> Result<Vec<UserRow>, FatalError> { async fn create_session(&self, id: &UserId) -> Result<SessionId, FatalError> {
send_request!(self, Request::Users, DatabaseResponse::Users(lst) => Ok(lst)) send_request!(self, Request::CreateSession(id.to_owned()), DatabaseResponse::CreateSession(session_id) => Ok(session_id))
} }
async fn session(&self, id: &SessionId) -> Result<Option<User>, FatalError> {
async fn user(&self, uid: &UserId) -> Result<Option<UserRow>, FatalError> { send_request!(self, Request::Session(id.to_owned()), DatabaseResponse::Session(row) => Ok(row))
send_request!(self, Request::User(uid.clone()), DatabaseResponse::User(user) => Ok(user))
} }
async fn user_by_username(&self, username: &str) -> Result<Option<UserRow>, FatalError> {
send_request!(self, Request::UserByUsername(username.to_owned()), DatabaseResponse::User(user) => Ok(user))
}
async fn save_user(
&self,
user_id: Option<UserId>,
name: &str,
password: &str,
admin: bool,
enabled: bool,
expiration: DateTime<Utc>,
) -> Result<UserId, FatalError> {
send_request!(self,
Request::SaveUser(
user_id,
name.to_owned(),
password.to_owned(),
admin,
enabled,
expiration,
),
DatabaseResponse::SaveUser(user_id) => Ok(user_id))
}
async fn games(&self) -> Result<Vec<GameRow>, FatalError> {
send_request!(self, Request::Games, DatabaseResponse::Games(lst) => Ok(lst))
}
async fn game(&self, game_id: &GameId) -> Result<Option<GameRow>, FatalError> {
send_request!(self, Request::Game(game_id.clone()), DatabaseResponse::Game(game) => Ok(game))
}
async fn save_game(
&self,
game_id: Option<GameId>,
user_id: &UserId,
game_type: &str,
game_name: &str,
) -> Result<GameId, FatalError> {
send_request!(self, Request::SaveGame(game_id, user_id.to_owned(), game_type.to_owned(), game_name.to_owned()), DatabaseResponse::SaveGame(game_id) => Ok(game_id))
}
async fn character(&self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError> { async fn character(&self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
send_request!(self, Request::Charsheet(id.to_owned()), DatabaseResponse::Charsheet(row) => Ok(row)) send_request!(self, Request::Charsheet(id.to_owned()), DatabaseResponse::Charsheet(row) => Ok(row))
} }
async fn session(&self, id: &SessionId) -> Result<Option<UserRow>, FatalError> { async fn create_game(
send_request!(self, Request::Session(id.to_owned()), DatabaseResponse::Session(row) => Ok(row)) &self,
user_id: &UserId,
game_type: &str,
game_name: &str,
) -> Result<GameId, FatalError> {
send_request!(self, Request::CreateGame(user_id.to_owned(), game_type.to_owned(), game_name.to_owned()), DatabaseResponse::SaveGame(game_id) => Ok(game_id))
}
async fn save_game(&self, game: Game) -> Result<GameId, FatalError> {
send_request!(self, Request::SaveGame(game), DatabaseResponse::SaveGame(game_id) => Ok(game_id))
}
async fn game(&self, game_id: &GameId) -> Result<Option<Game>, FatalError> {
send_request!(self, Request::Game(game_id.clone()), DatabaseResponse::Game(game) => Ok(game))
}
async fn games(&self) -> Result<Vec<Game>, FatalError> {
send_request!(self, Request::Games, DatabaseResponse::Games(lst) => Ok(lst))
} }
async fn create_session(&self, id: &UserId) -> Result<SessionId, FatalError> { async fn create_user(
send_request!(self, Request::CreateSession(id.to_owned()), DatabaseResponse::CreateSession(session_id) => Ok(session_id)) &self,
name: &str,
password: &str,
admin: bool,
state: AccountState,
) -> Result<UserId, FatalError> {
send_request!(self,
Request::CreateUser(name.to_owned(), password.to_owned(), admin, state),
DatabaseResponse::SaveUser(user_id) => Ok(user_id))
}
async fn save_user(&self, user: User) -> Result<UserId, FatalError> {
send_request!(self,
Request::SaveUser(user),
DatabaseResponse::SaveUser(user_id) => Ok(user_id))
}
async fn user(&self, uid: &UserId) -> Result<Option<User>, FatalError> {
send_request!(self, Request::User(uid.clone()), DatabaseResponse::User(user) => Ok(user))
}
async fn user_by_username(&self, username: &str) -> Result<Option<User>, FatalError> {
send_request!(self, Request::UserByUsername(username.to_owned()), DatabaseResponse::User(user) => Ok(user))
}
async fn users(&self) -> Result<Vec<User>, FatalError> {
send_request!(self, Request::Users, DatabaseResponse::Users(lst) => Ok(lst))
} }
} }
@ -205,11 +190,15 @@ mod test {
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();
let now = Utc::now(); db.create_user("admin", "abcdefg", true, AccountState::Normal)
.unwrap();
db.save_user(Some(UserId::from("admin")), "admin", "abcdefg", true, true, now) let game_id = db
.create_game(
&UserId::from("admin"),
"Candela",
"Circle of the Winter Solstice",
)
.unwrap(); .unwrap();
let game_id = db.save_game(None, &UserId::from("admin"), "Candela", "Circle of the Winter Solstice").unwrap();
(db, game_id) (db, game_id)
} }

View File

@ -160,16 +160,6 @@ impl FromSql for CharacterId {
} }
} }
#[derive(Clone, Debug)]
pub struct UserRow {
pub id: UserId,
pub name: String,
pub password: String,
pub admin: bool,
pub enabled: bool,
pub password_expires: DateTime,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Role { pub struct Role {
userid: UserId, userid: UserId,
@ -177,6 +167,7 @@ pub struct Role {
role: String, role: String,
} }
/*
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct GameRow { pub struct GameRow {
pub id: GameId, pub id: GameId,
@ -184,6 +175,7 @@ pub struct GameRow {
pub game_type: String, pub game_type: String,
pub name: String, pub name: String,
} }
*/
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CharsheetRow { pub struct CharsheetRow {

View File

@ -1,9 +1,6 @@
use axum::{ use axum::{http::HeaderMap, Json};
http::HeaderMap,
Json,
};
use futures::Future; use futures::Future;
use result_extended::{error, fatal, ok, return_error, ResultExt}; use result_extended::{error, ok, return_error, ResultExt};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use typeshare::typeshare; use typeshare::typeshare;
@ -97,12 +94,15 @@ where
} }
pub async fn check_password( pub async fn check_password(
core: Core, _core: Core,
req: Json<AuthRequest>, req: Json<AuthRequest>,
) -> ResultExt<AuthResponse, AppError, FatalError> { ) -> ResultExt<AuthResponse, AppError, FatalError> {
let Json(AuthRequest { username, password }) = req; let Json(AuthRequest {
username: _,
password: _,
}) = req;
unimplemented!() unimplemented!()
/* /*
match core.auth(&username, &password).await { match core.auth(&username, &password).await {
} }
*/ */
@ -118,7 +118,8 @@ pub async fn get_user(
Some(user_id) => core.user(user_id).await, Some(user_id) => core.user(user_id).await,
None => core.user(user.id).await, None => core.user(user.id).await,
} }
}).await })
.await
} }
pub async fn get_users( pub async fn get_users(
@ -127,7 +128,8 @@ pub async fn get_users(
) -> ResultExt<Vec<UserOverview>, AppError, FatalError> { ) -> ResultExt<Vec<UserOverview>, AppError, FatalError> {
auth_required(core.clone(), headers, |_user| async move { auth_required(core.clone(), headers, |_user| async move {
core.list_users().await core.list_users().await
}).await })
.await
} }
pub async fn create_user( pub async fn create_user(
@ -137,7 +139,8 @@ pub async fn create_user(
) -> ResultExt<UserId, AppError, FatalError> { ) -> ResultExt<UserId, AppError, FatalError> {
admin_required(core.clone(), headers, |_admin| async { admin_required(core.clone(), headers, |_admin| async {
core.create_user(&req.username).await core.create_user(&req.username).await
}).await })
.await
} }
pub async fn set_password( pub async fn set_password(
@ -151,5 +154,6 @@ pub async fn set_password(
} else { } else {
error(AppError::BadRequest) error(AppError::BadRequest)
} }
}).await })
.await
} }

View File

@ -128,7 +128,7 @@ mod test {
use crate::{ use crate::{
asset_db::FsAssets, asset_db::FsAssets,
core::{AuthResponse, Core}, core::{AuthResponse, Core},
database::{Database, DbConn, GameId, SessionId, UserId}, database::{DbConn, GameId, SessionId, UserId},
handlers::CreateGameRequest, handlers::CreateGameRequest,
types::UserOverview, types::UserOverview,
}; };
@ -240,13 +240,13 @@ mod test {
#[tokio::test] #[tokio::test]
async fn it_refuses_to_authenticate_a_disabled_user() { async fn it_refuses_to_authenticate_a_disabled_user() {
let (_core, server) = setup_with_disabled_user().await; let (_core, _server) = setup_with_disabled_user().await;
unimplemented!() unimplemented!()
} }
#[tokio::test] #[tokio::test]
async fn it_forces_changing_expired_password() { async fn it_forces_changing_expired_password() {
let (_core, server) = setup_with_user().await; let (_core, _server) = setup_with_user().await;
unimplemented!() unimplemented!()
} }

View File

@ -1,4 +1,5 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use rusqlite::{types::{FromSql, FromSqlError, ToSqlOutput, ValueRef}, ToSql};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
@ -6,7 +7,7 @@ use typeshare::typeshare;
use crate::{ use crate::{
asset_db::AssetId, asset_db::AssetId,
database::{GameId, GameRow, UserId, UserRow}, database::{GameId, UserId},
}; };
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -71,6 +72,43 @@ pub struct Rgb {
pub blue: u32, pub blue: u32,
} }
#[derive(Clone, Debug, Deserialize, Serialize)]
#[typeshare]
pub enum AccountState {
Normal,
PasswordReset(DateTime<Utc>),
Locked,
}
impl FromSql for AccountState {
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
if let ValueRef::Text(text) = value {
let text = String::from_utf8(text.to_vec()).unwrap();
if text.starts_with("Normal") {
Ok(AccountState::Normal)
} else if text.starts_with("PasswordReset") {
unimplemented!()
} else if text.starts_with("Locked") {
Ok(AccountState::Locked)
} else {
Err(FromSqlError::InvalidType)
}
} else {
Err(FromSqlError::InvalidType)
}
}
}
impl ToSql for AccountState {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
match self {
AccountState::Normal => Ok(ToSqlOutput::Borrowed(ValueRef::Text("Normal".as_bytes()))),
AccountState::PasswordReset(_expiration) => unimplemented!(),
AccountState::Locked => Ok(ToSqlOutput::Borrowed(ValueRef::Text("Locked".as_bytes()))),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[typeshare] #[typeshare]
@ -79,21 +117,7 @@ pub struct User {
pub name: String, pub name: String,
pub password: String, pub password: String,
pub admin: bool, pub admin: bool,
pub enabled: bool, pub state: AccountState,
pub expiration: DateTime<Utc>,
}
impl From<UserRow> for User {
fn from(row: UserRow) -> Self {
Self {
id: row.id,
name: row.name.to_owned(),
password: row.password.to_owned(),
admin: row.admin,
enabled: row.enabled,
expiration: row.password_expires.0,
}
}
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
@ -115,7 +139,7 @@ pub struct Player {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[typeshare] #[typeshare]
pub struct Game { pub struct Game {
pub id: String, pub id: GameId,
pub name: String, pub name: String,
pub gm: UserId, pub gm: UserId,
pub players: Vec<UserId>, pub players: Vec<UserId>,
@ -155,6 +179,7 @@ pub struct GameOverview {
pub players: Vec<UserId>, pub players: Vec<UserId>,
} }
/*
impl From<GameRow> for GameOverview { impl From<GameRow> for GameOverview {
fn from(row: GameRow) -> Self { fn from(row: GameRow) -> Self {
Self { Self {
@ -166,3 +191,4 @@ impl From<GameRow> for GameOverview {
} }
} }
} }
*/