diff --git a/src/models/characters.rs b/src/models/characters.rs index f0c2ae0..4cc72d6 100644 --- a/src/models/characters.rs +++ b/src/models/characters.rs @@ -92,6 +92,17 @@ impl Character { Ok(decoded) } + + /// Upgrade the stored protobuf character data to its newest + /// iteration, if new types have been created. Consumes self. + pub fn uprade(self) -> Result { + // Currently, this just returns itself because there are no + // iterations. But we could for example go from CofdSheet v1 + // to CofdSheet v2 by deserializing v1, applying a migration + // to v2, then reserializing, and copying over all other + // fields. + Ok(self) + } } #[derive(Serialize, Debug, Queryable)] diff --git a/src/routes/characters.rs b/src/routes/characters.rs index 5e2644d..4956953 100644 --- a/src/routes/characters.rs +++ b/src/routes/characters.rs @@ -1,35 +1,49 @@ use crate::db::{Dao, TenebrousDbConn}; use crate::errors::Error; -use crate::models::characters::{CharacterDataType, DynCharacterData, Visibility}; +use crate::models::characters::{Character, CharacterDataType, DynCharacterData, Visibility}; use crate::models::users::User; -use rocket::request::Form; -use rocket::response::Redirect; use rocket_contrib::templates::Template; -use serde::Serialize; use std::collections::HashMap; +mod new; + pub(crate) fn routes() -> Vec { routes![ view_character, - new_character, - create_new_character, - new_character_not_logged_in, + new::new_character, + new::create_new_character, + new::new_character_not_logged_in, edit_character ] } -#[derive(FromForm)] -struct NewCharacterForm { - name: String, //TODO add game system -} - #[derive(Serialize)] struct ViewCharacterTemplate<'a> { pub name: &'a str, pub username: &'a str, + pub data_type: &'a CharacterDataType, pub sheet: Box, } +fn view_character_template(user: &User, character: Character) -> Result { + let character = character.uprade()?; + + let context = ViewCharacterTemplate { + name: &character.character_name, + username: &user.username, + data_type: &character.data_type, + sheet: character.dyn_deserialize()?, + }; + + use CharacterDataType::*; + let template = match character.data_type { + ChroniclesOfDarknessV1 => Template::render("characters/view_character", context), + ChangelingV1 => Template::render("characters/view_character", context), + }; + + Ok(template) +} + #[get("//")] fn view_character( character_id: i32, @@ -37,63 +51,15 @@ fn view_character( conn: TenebrousDbConn, logged_in_user: Option<&User>, ) -> Result { - let user = conn.load_user(&username)?.ok_or(Error::NotFound)?; + let user = &conn.load_user(&username)?.ok_or(Error::NotFound)?; let character = conn .load_character(character_id)? .and_then(|c| c.as_visible_for(logged_in_user)) .ok_or(Error::NotFound)?; - let context = ViewCharacterTemplate { - name: &character.character_name, - username: &user.username, - sheet: character.dyn_deserialize()?, - }; - - Ok(Template::render("view_character", context)) -} - -#[get("/new")] -fn new_character(logged_in_user: &User, conn: TenebrousDbConn) -> Result { - let context = HashMap::::new(); - Ok(Template::render("new_character", context)) -} - -#[post("/new", data = "
")] -fn create_new_character( - form: Form, - logged_in_user: &User, - conn: TenebrousDbConn, -) -> Result { - //TODO redirect to character edit page - //TODO redirect back to new character page with an error and filled-out form if validation errors. - //TODO add game system. - use crate::models::characters::NewCharacter; - use crate::models::proto::cofd::CofdSheet; - use prost::bytes::BytesMut; - use prost::Message; - - let mut new_character = CofdSheet::default(); - new_character.strength = 100; - let mut buf = BytesMut::with_capacity(std::mem::size_of_val(&new_character)); - new_character.encode(&mut buf)?; - - let insert = NewCharacter { - user_id: logged_in_user.id, - viewable: true, - character_name: &form.name, - data_type: CharacterDataType::ChroniclesOfDarknessV1, - data_version: 1, - data: &buf, - }; - - conn.insert_character(insert)?; - Ok(super::common::redirect_to_index()) -} - -#[get("/new", rank = 2)] -fn new_character_not_logged_in() -> Redirect { - super::common::redirect_to_login() + let template = view_character_template(user, character)?; + Ok(template) } #[get("///edit")] diff --git a/src/routes/characters/new.rs b/src/routes/characters/new.rs new file mode 100644 index 0000000..d7908c9 --- /dev/null +++ b/src/routes/characters/new.rs @@ -0,0 +1,128 @@ +use crate::db::{Dao, TenebrousDbConn}; +use crate::errors::Error; +use crate::models::{ + characters::{CharacterDataType, NewCharacter}, + proto::cofd::*, + users::User, +}; +use prost::{bytes::BytesMut, Message}; +use rocket::http::RawStr; +use rocket::request::{Form, FormError, FromFormValue}; +use rocket::response::Redirect; +use rocket_contrib::templates::Template; +use std::collections::HashMap; + +#[derive(FromForm, Serialize)] +pub(super) struct NewCharacterForm { + name: String, + system: CharacterDataType, +} + +#[derive(Serialize)] +pub(super) struct RawNewCharacterForm { + name: String, + system: String, +} + +impl<'v> FromFormValue<'v> for CharacterDataType { + type Error = &'v RawStr; + + fn from_form_value(form_value: &'v RawStr) -> Result { + let system = form_value.url_decode().or(Err("bad input"))?; + match system.as_ref() { + "cofd" => Ok(CharacterDataType::ChroniclesOfDarknessV1), + "changeling" => Ok(CharacterDataType::ChangelingV1), + _ => Err(form_value), + } + } +} + +#[get("/new")] +pub(super) fn new_character( + logged_in_user: &User, + conn: TenebrousDbConn, +) -> Result { + let mut context = HashMap::new(); + let form = NewCharacterForm { + name: "".to_string(), + system: CharacterDataType::ChroniclesOfDarknessV1, + }; + + context.insert("form", form); + Ok(Template::render("characters/new_character", context)) +} + +fn new_sheet(system: &CharacterDataType) -> Result { + let sheet = match system { + CharacterDataType::ChroniclesOfDarknessV1 => { + let mut new_character = CofdSheet::default(); + new_character.strength = 100; + let mut buf = BytesMut::with_capacity(std::mem::size_of_val(&new_character)); + new_character.encode(&mut buf)?; + buf + } + CharacterDataType::ChangelingV1 => { + let mut new_character = ChangelingSheet::default(); + new_character.base = Some(CofdSheet::default()); + new_character.base.as_mut().unwrap().strength = 100; + let mut buf = BytesMut::with_capacity(std::mem::size_of_val(&new_character)); + new_character.encode(&mut buf)?; + buf + } + }; + + Ok(sheet) +} + +fn do_new_character( + form: Form, + user_id: i32, + conn: TenebrousDbConn, +) -> Result<(), Error> { + let sheet = new_sheet(&form.system)?; + + let insert = NewCharacter { + user_id: user_id, + viewable: true, + character_name: &form.name, + data_type: form.system, + data_version: 1, + data: &sheet, + }; + + conn.insert_character(insert)?; + Ok(()) +} + +#[post("/new", data = "")] +pub(super) fn create_new_character( + form: Result, FormError>, + logged_in_user: &User, + conn: TenebrousDbConn, +) -> Result { + //TODO redirect to character edit page + //TODO redirect back to new character page with an error and filled-out form if validation errors. + if let Err(e) = form { + //Not sure how to repopulate the form. + let mut context = HashMap::new(); + let form = NewCharacterForm { + name: "".to_string(), + system: CharacterDataType::ChroniclesOfDarknessV1, + }; + context.insert("form", form); + return Err(Template::render("characters/new_character", context)); + } + + match do_new_character(form.unwrap(), logged_in_user.id, conn) { + Ok(_) => Ok(crate::routes::common::redirect_to_index()), + Err(e) => { + let context = HashMap::::new(); + return Err(Template::render("characters/new_character", context)); + } + } +} + +#[get("/new", rank = 2)] +pub(super) fn new_character_not_logged_in() -> Redirect { + crate::routes::common::redirect_to_login() +} diff --git a/templates/characters/new_character.html.tera b/templates/characters/new_character.html.tera new file mode 100644 index 0000000..1894fb3 --- /dev/null +++ b/templates/characters/new_character.html.tera @@ -0,0 +1,26 @@ +{% extends "base" %} + +{% block content %} +
+ New character page. + + +
+ + +
+ +
+ + +
+ +
+ +
+ +
+{% endblock content %} diff --git a/templates/view_character.html.tera b/templates/characters/view_character.html.tera similarity index 84% rename from templates/view_character.html.tera rename to templates/characters/view_character.html.tera index 8a4a5c8..2b58961 100644 --- a/templates/view_character.html.tera +++ b/templates/characters/view_character.html.tera @@ -4,6 +4,7 @@

Character {{name}}

User: {{username}}

+

System: {{data_type}}

Strength: {{sheet.strength}}

{% endblock content %} diff --git a/templates/new_character.html.tera b/templates/new_character.html.tera deleted file mode 100644 index 9049600..0000000 --- a/templates/new_character.html.tera +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base" %} - -{% block content %} -
- New character page. - -
-
- - -
- -
- -
-
-
-{% endblock content %}