Add the ability to create a game
This commit is contained in:
parent
792437af44
commit
f6eb942371
@ -15,7 +15,11 @@ CREATE TABLE sessions(
|
|||||||
|
|
||||||
CREATE TABLE games(
|
CREATE TABLE games(
|
||||||
uuid TEXT PRIMARY KEY,
|
uuid TEXT PRIMARY KEY,
|
||||||
name TEXT
|
gm TEXT,
|
||||||
|
game_type TEXT,
|
||||||
|
name TEXT,
|
||||||
|
|
||||||
|
FOREIGN KEY(gm) REFERENCES users(uuid)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE characters(
|
CREATE TABLE characters(
|
||||||
|
@ -10,7 +10,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
asset_db::{self, AssetId, Assets},
|
asset_db::{self, AssetId, Assets},
|
||||||
database::{CharacterId, Database, SessionId, UserId},
|
database::{CharacterId, Database, GameId, SessionId, UserId},
|
||||||
types::{AppError, FatalError, Game, Message, Rgb, Tabletop, User},
|
types::{AppError, FatalError, Game, Message, Rgb, Tabletop, User},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -131,12 +131,12 @@ impl Core {
|
|||||||
ok(users.into_iter().find(|user| user.id == user_id))
|
ok(users.into_iter().find(|user| user.id == user_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_user(&self, username: &str) -> ResultExt<(), AppError, FatalError> {
|
pub async fn create_user(&self, username: &str) -> ResultExt<UserId, AppError, FatalError> {
|
||||||
let state = self.0.read().await;
|
let state = self.0.read().await;
|
||||||
match return_error!(self.user_by_username(username).await) {
|
match return_error!(self.user_by_username(username).await) {
|
||||||
Some(_) => error(AppError::UsernameUnavailable),
|
Some(_) => error(AppError::UsernameUnavailable),
|
||||||
None => match state.db.save_user(None, username, "", false, true).await {
|
None => match state.db.save_user(None, username, "", false, true).await {
|
||||||
Ok(_) => ok(()),
|
Ok(user_id) => ok(user_id),
|
||||||
Err(err) => fatal(err),
|
Err(err) => fatal(err),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -151,6 +151,14 @@ impl Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn create_game(&self, gm: &UserId, game_type: &str, game_name: &str) -> ResultExt<GameId, AppError, FatalError> {
|
||||||
|
let state = self.0.read().await;
|
||||||
|
match state.db.save_game(None, gm, game_type, game_name).await {
|
||||||
|
Ok(game_id) => ok(game_id),
|
||||||
|
Err(err) => fatal(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn tabletop(&self) -> Tabletop {
|
pub async fn tabletop(&self) -> Tabletop {
|
||||||
self.0.read().await.tabletop.clone()
|
self.0.read().await.tabletop.clone()
|
||||||
}
|
}
|
||||||
|
@ -142,23 +142,23 @@ impl DiskDb {
|
|||||||
Ok(items)
|
Ok(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_game(&self, game_id: Option<GameId>, name: &str) -> Result<GameId, FatalError> {
|
pub fn save_game(&self, game_id: Option<GameId>, gm: &UserId, game_type: &str, name: &str) -> Result<GameId, FatalError> {
|
||||||
match game_id {
|
match game_id {
|
||||||
None => {
|
None => {
|
||||||
let game_id = GameId::new();
|
let game_id = GameId::new();
|
||||||
let mut stmt = self
|
let mut stmt = self
|
||||||
.conn
|
.conn
|
||||||
.prepare("INSERT INTO games VALUES (?, ?)")
|
.prepare("INSERT INTO games VALUES (?, ?, ?, ?)")
|
||||||
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
||||||
stmt.execute((game_id.as_str(), name)).unwrap();
|
stmt.execute((game_id.as_str(), gm.as_str(), game_type, name)).unwrap();
|
||||||
Ok(game_id)
|
Ok(game_id)
|
||||||
}
|
}
|
||||||
Some(game_id) => {
|
Some(game_id) => {
|
||||||
let mut stmt = self
|
let mut stmt = self
|
||||||
.conn
|
.conn
|
||||||
.prepare("UPDATE games SET name=? WHERE uuid=?")
|
.prepare("UPDATE games SET gm=? game_type=? name=? WHERE uuid=?")
|
||||||
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
||||||
stmt.execute((name, game_id.as_str())).unwrap();
|
stmt.execute((gm.as_str(), game_type, name, game_id.as_str())).unwrap();
|
||||||
Ok(game_id)
|
Ok(game_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,6 +286,16 @@ pub async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
|
|||||||
Request::Games => {
|
Request::Games => {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
Request::Game(_game_id) => {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
Request::SaveGame(game_id, user_id, game_type, game_name) => {
|
||||||
|
let game_id = db.save_game(game_id, &user_id, &game_type, &game_name);
|
||||||
|
match game_id {
|
||||||
|
Ok(game_id) => { tx.send(DatabaseResponse::SaveGame(game_id)).await.unwrap(); }
|
||||||
|
err => panic!("{:?}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
Request::User(uid) => {
|
Request::User(uid) => {
|
||||||
let user = db.user(&uid);
|
let user = db.user(&uid);
|
||||||
match user {
|
match user {
|
||||||
|
@ -6,7 +6,7 @@ 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 disk_db::{db_handler, DiskDb};
|
use disk_db::{db_handler, DiskDb};
|
||||||
pub use types::{CharacterId, CharsheetRow, GameRow, SessionId, UserId, UserRow};
|
pub use types::{CharacterId, CharsheetRow, GameId, GameRow, SessionId, UserId, UserRow};
|
||||||
|
|
||||||
use crate::types::FatalError;
|
use crate::types::FatalError;
|
||||||
|
|
||||||
@ -15,6 +15,8 @@ enum Request {
|
|||||||
Charsheet(CharacterId),
|
Charsheet(CharacterId),
|
||||||
CreateSession(UserId),
|
CreateSession(UserId),
|
||||||
Games,
|
Games,
|
||||||
|
Game(GameId),
|
||||||
|
SaveGame(Option<GameId>, UserId, String, String),
|
||||||
SaveUser(Option<UserId>, String, String, bool, bool),
|
SaveUser(Option<UserId>, String, String, bool, bool),
|
||||||
Session(SessionId),
|
Session(SessionId),
|
||||||
User(UserId),
|
User(UserId),
|
||||||
@ -33,6 +35,8 @@ enum DatabaseResponse {
|
|||||||
Charsheet(Option<CharsheetRow>),
|
Charsheet(Option<CharsheetRow>),
|
||||||
CreateSession(SessionId),
|
CreateSession(SessionId),
|
||||||
Games(Vec<GameRow>),
|
Games(Vec<GameRow>),
|
||||||
|
Game(Option<GameRow>),
|
||||||
|
SaveGame(GameId),
|
||||||
SaveUser(UserId),
|
SaveUser(UserId),
|
||||||
Session(Option<UserRow>),
|
Session(Option<UserRow>),
|
||||||
User(Option<UserRow>),
|
User(Option<UserRow>),
|
||||||
@ -41,6 +45,8 @@ enum DatabaseResponse {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Database: Send + Sync {
|
pub trait Database: Send + Sync {
|
||||||
|
async fn users(&mut self) -> Result<Vec<UserRow>, FatalError>;
|
||||||
|
|
||||||
async fn user(&self, _: &UserId) -> Result<Option<UserRow>, FatalError>;
|
async fn user(&self, _: &UserId) -> Result<Option<UserRow>, FatalError>;
|
||||||
|
|
||||||
async fn user_by_username(&self, _: &str) -> Result<Option<UserRow>, FatalError>;
|
async fn user_by_username(&self, _: &str) -> Result<Option<UserRow>, FatalError>;
|
||||||
@ -54,10 +60,18 @@ pub trait Database: Send + Sync {
|
|||||||
enabled: bool,
|
enabled: bool,
|
||||||
) -> Result<UserId, FatalError>;
|
) -> Result<UserId, FatalError>;
|
||||||
|
|
||||||
async fn users(&mut self) -> Result<Vec<UserRow>, FatalError>;
|
|
||||||
|
|
||||||
async fn games(&mut self) -> Result<Vec<GameRow>, FatalError>;
|
async fn games(&mut 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(&mut self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError>;
|
async fn character(&mut self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError>;
|
||||||
|
|
||||||
async fn session(&self, id: &SessionId) -> Result<Option<UserRow>, FatalError>;
|
async fn session(&self, id: &SessionId) -> Result<Option<UserRow>, FatalError>;
|
||||||
@ -109,6 +123,10 @@ macro_rules! send_request {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Database for DbConn {
|
impl Database for DbConn {
|
||||||
|
async fn users(&mut self) -> Result<Vec<UserRow>, FatalError> {
|
||||||
|
send_request!(self, Request::Users, DatabaseResponse::Users(lst) => Ok(lst))
|
||||||
|
}
|
||||||
|
|
||||||
async fn user(&self, uid: &UserId) -> Result<Option<UserRow>, FatalError> {
|
async fn user(&self, uid: &UserId) -> Result<Option<UserRow>, FatalError> {
|
||||||
send_request!(self, Request::User(uid.clone()), DatabaseResponse::User(user) => Ok(user))
|
send_request!(self, Request::User(uid.clone()), DatabaseResponse::User(user) => Ok(user))
|
||||||
}
|
}
|
||||||
@ -136,14 +154,24 @@ impl Database for DbConn {
|
|||||||
DatabaseResponse::SaveUser(user_id) => Ok(user_id))
|
DatabaseResponse::SaveUser(user_id) => Ok(user_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn users(&mut self) -> Result<Vec<UserRow>, FatalError> {
|
|
||||||
send_request!(self, Request::Users, DatabaseResponse::Users(lst) => Ok(lst))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn games(&mut self) -> Result<Vec<GameRow>, FatalError> {
|
async fn games(&mut self) -> Result<Vec<GameRow>, FatalError> {
|
||||||
send_request!(self, Request::Games, DatabaseResponse::Games(lst) => Ok(lst))
|
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(&mut self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
|
async fn character(&mut 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))
|
||||||
}
|
}
|
||||||
@ -173,8 +201,9 @@ 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();
|
||||||
|
|
||||||
db.save_user(Some(UserId::from("admin")), "admin", "abcdefg", true, true).unwrap();
|
db.save_user(Some(UserId::from("admin")), "admin", "abcdefg", true, true)
|
||||||
let game_id = db.save_game(None, "Candela").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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +172,9 @@ pub struct Role {
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct GameRow {
|
pub struct GameRow {
|
||||||
pub id: UserId,
|
pub id: GameId,
|
||||||
|
pub gm: UserId,
|
||||||
|
pub game_type: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,3 +191,5 @@ pub struct SessionRow {
|
|||||||
user_id: SessionId,
|
user_id: SessionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
33
visions/server/src/handlers/game_management.rs
Normal file
33
visions/server/src/handlers/game_management.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use axum::{http::{HeaderMap, StatusCode}, Json};
|
||||||
|
use result_extended::ResultExt;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{database::GameId, core::Core};
|
||||||
|
|
||||||
|
use super::auth_required;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct CreateGameRequest {
|
||||||
|
pub game_type: String,
|
||||||
|
pub game_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_game(
|
||||||
|
core: Core,
|
||||||
|
headers: HeaderMap,
|
||||||
|
req: CreateGameRequest,
|
||||||
|
) -> (StatusCode, Json<Option<GameId>>) {
|
||||||
|
println!("create game handler");
|
||||||
|
auth_required(core.clone(), headers, |user| async move {
|
||||||
|
let game = core.create_game(&user.id, &req.game_type, &req.game_name).await;
|
||||||
|
println!("create_game completed: {:?}", game);
|
||||||
|
match game {
|
||||||
|
ResultExt::Ok(game_id) => (StatusCode::OK, Json(Some(game_id))),
|
||||||
|
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
||||||
|
ResultExt::Fatal(fatal) => panic!("{}", fatal),
|
||||||
|
}
|
||||||
|
}).await
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,6 @@
|
|||||||
|
pub mod game_management;
|
||||||
|
pub use game_management::CreateGameRequest;
|
||||||
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
@ -20,8 +23,6 @@ async fn check_session(
|
|||||||
core: &Core,
|
core: &Core,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
) -> ResultExt<Option<User>, AppError, FatalError> {
|
) -> ResultExt<Option<User>, AppError, FatalError> {
|
||||||
println!("headers: {:?}", headers);
|
|
||||||
println!("auth_header: {:?}", headers.get("Authorization"));
|
|
||||||
match headers.get("Authorization") {
|
match headers.get("Authorization") {
|
||||||
Some(token) => {
|
Some(token) => {
|
||||||
match token
|
match token
|
||||||
|
@ -11,8 +11,7 @@ use crate::{
|
|||||||
core::Core,
|
core::Core,
|
||||||
database::UserId,
|
database::UserId,
|
||||||
handlers::{
|
handlers::{
|
||||||
check_password, create_user, get_user, healthcheck, set_password, AuthRequest,
|
check_password, create_user, game_management::create_game, get_user, healthcheck, set_password, AuthRequest, CreateGameRequest, CreateUserRequest, SetPasswordRequest
|
||||||
CreateUserRequest, SetPasswordRequest,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,6 +66,16 @@ pub fn routes(core: Core) -> Router {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/api/v1/games",
|
||||||
|
put({
|
||||||
|
let core = core.clone();
|
||||||
|
move |headers: HeaderMap, req: Json<CreateGameRequest>| {
|
||||||
|
let Json(req) = req;
|
||||||
|
create_game(core, headers, req)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -82,8 +91,8 @@ mod test {
|
|||||||
use crate::{
|
use crate::{
|
||||||
asset_db::FsAssets,
|
asset_db::FsAssets,
|
||||||
core::Core,
|
core::Core,
|
||||||
database::{Database, DbConn, SessionId, UserId},
|
database::{Database, DbConn, GameId, SessionId, UserId},
|
||||||
handlers::UserProfile,
|
handlers::{CreateGameRequest, UserProfile},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn setup_without_admin() -> (Core, TestServer) {
|
fn setup_without_admin() -> (Core, TestServer) {
|
||||||
@ -127,7 +136,15 @@ mod test {
|
|||||||
username: "savanni".to_owned(),
|
username: "savanni".to_owned(),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
|
||||||
|
let response = server
|
||||||
|
.put("/api/v1/user")
|
||||||
|
.add_header("Authorization", format!("Bearer {}", session_id))
|
||||||
|
.json(&CreateUserRequest {
|
||||||
|
username: "shephard".to_owned(),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
response.assert_status_ok();
|
response.assert_status_ok();
|
||||||
|
|
||||||
(core, server)
|
(core, server)
|
||||||
@ -305,7 +322,7 @@ mod test {
|
|||||||
response.assert_status(StatusCode::BAD_REQUEST);
|
response.assert_status(StatusCode::BAD_REQUEST);
|
||||||
|
|
||||||
let response = server
|
let response = server
|
||||||
.put(&format!("/api/v1/user/password"))
|
.put("/api/v1/user/password")
|
||||||
.add_header("Authorization", format!("Bearer {}", session_id))
|
.add_header("Authorization", format!("Bearer {}", session_id))
|
||||||
.json(&SetPasswordRequest {
|
.json(&SetPasswordRequest {
|
||||||
password_1: "abcdefg".to_owned(),
|
password_1: "abcdefg".to_owned(),
|
||||||
@ -315,16 +332,28 @@ mod test {
|
|||||||
response.assert_status(StatusCode::OK);
|
response.assert_status(StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ignore]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn a_user_cannot_change_another_users_password() {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn a_user_can_create_a_game() {
|
async fn a_user_can_create_a_game() {
|
||||||
unimplemented!();
|
let (_core, server) = setup_with_user().await;
|
||||||
|
|
||||||
|
let response = server
|
||||||
|
.post("/api/v1/auth")
|
||||||
|
.json(&AuthRequest {
|
||||||
|
username: "savanni".to_owned(),
|
||||||
|
password: "".to_owned(),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
let session_id = response.json::<Option<SessionId>>().unwrap();
|
||||||
|
|
||||||
|
let response = server
|
||||||
|
.put("/api/v1/games")
|
||||||
|
.add_header("Authorization", format!("Bearer {}", session_id))
|
||||||
|
.json(&CreateGameRequest {
|
||||||
|
game_type: "Candela".to_owned(),
|
||||||
|
game_name: "Circle of the Winter Solstice".to_owned(),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
let _game_id = response.json::<Option<GameId>>().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ignore]
|
#[ignore]
|
||||||
|
@ -33,6 +33,9 @@ pub enum AppError {
|
|||||||
#[error("invalid request")]
|
#[error("invalid request")]
|
||||||
BadRequest,
|
BadRequest,
|
||||||
|
|
||||||
|
#[error("could not create an object")]
|
||||||
|
CouldNotCreateObject,
|
||||||
|
|
||||||
#[error("something wasn't found {0}")]
|
#[error("something wasn't found {0}")]
|
||||||
NotFound(String),
|
NotFound(String),
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user