Basic albeit mostly broken flow for creating new character.

This commit is contained in:
jeff 2020-12-08 22:26:47 +00:00
parent 92301fb1d4
commit 0751a783dc
6 changed files with 195 additions and 81 deletions

View File

@ -92,6 +92,17 @@ impl Character {
Ok(decoded) 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<Character, Error> {
// 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)] #[derive(Serialize, Debug, Queryable)]

View File

@ -1,35 +1,49 @@
use crate::db::{Dao, TenebrousDbConn}; use crate::db::{Dao, TenebrousDbConn};
use crate::errors::Error; 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 crate::models::users::User;
use rocket::request::Form;
use rocket::response::Redirect;
use rocket_contrib::templates::Template; use rocket_contrib::templates::Template;
use serde::Serialize;
use std::collections::HashMap; use std::collections::HashMap;
mod new;
pub(crate) fn routes() -> Vec<rocket::Route> { pub(crate) fn routes() -> Vec<rocket::Route> {
routes![ routes![
view_character, view_character,
new_character, new::new_character,
create_new_character, new::create_new_character,
new_character_not_logged_in, new::new_character_not_logged_in,
edit_character edit_character
] ]
} }
#[derive(FromForm)]
struct NewCharacterForm {
name: String, //TODO add game system
}
#[derive(Serialize)] #[derive(Serialize)]
struct ViewCharacterTemplate<'a> { struct ViewCharacterTemplate<'a> {
pub name: &'a str, pub name: &'a str,
pub username: &'a str, pub username: &'a str,
pub data_type: &'a CharacterDataType,
pub sheet: Box<DynCharacterData>, pub sheet: Box<DynCharacterData>,
} }
fn view_character_template(user: &User, character: Character) -> Result<Template, Error> {
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("/<username>/<character_id>")] #[get("/<username>/<character_id>")]
fn view_character( fn view_character(
character_id: i32, character_id: i32,
@ -37,63 +51,15 @@ fn view_character(
conn: TenebrousDbConn, conn: TenebrousDbConn,
logged_in_user: Option<&User>, logged_in_user: Option<&User>,
) -> Result<Template, Error> { ) -> Result<Template, Error> {
let user = conn.load_user(&username)?.ok_or(Error::NotFound)?; let user = &conn.load_user(&username)?.ok_or(Error::NotFound)?;
let character = conn let character = conn
.load_character(character_id)? .load_character(character_id)?
.and_then(|c| c.as_visible_for(logged_in_user)) .and_then(|c| c.as_visible_for(logged_in_user))
.ok_or(Error::NotFound)?; .ok_or(Error::NotFound)?;
let context = ViewCharacterTemplate { let template = view_character_template(user, character)?;
name: &character.character_name, Ok(template)
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<Template, Error> {
let context = HashMap::<String, String>::new();
Ok(Template::render("new_character", context))
}
#[post("/new", data = "<form>")]
fn create_new_character(
form: Form<NewCharacterForm>,
logged_in_user: &User,
conn: TenebrousDbConn,
) -> Result<Redirect, Error> {
//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()
} }
#[get("/<owner>/<character_id>/edit")] #[get("/<owner>/<character_id>/edit")]

View File

@ -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<CharacterDataType, &'v RawStr> {
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<Template, Error> {
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<BytesMut, Error> {
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<NewCharacterForm>,
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 = "<form>")]
pub(super) fn create_new_character(
form: Result<Form<NewCharacterForm>, FormError>,
logged_in_user: &User,
conn: TenebrousDbConn,
) -> Result<Redirect, Template> {
//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::<String, String>::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()
}

View File

@ -0,0 +1,26 @@
{% extends "base" %}
{% block content %}
<div>
New character page.
<form action="/characters/new" method="post">
<div>
<label for="name">Name:</label>
<input id="name" name="name" type="text" value="{{ form.name }}" />
</div>
<div>
<label for="system">System:</label>
<select id="system" name="system">
<option value="cofd">Chronicles of Darkness</option>
<option value="changeling">Changeling</option>
</select>
</div>
<div>
<input type="submit" value="Create Character" />
</div>
</form>
</div>
{% endblock content %}

View File

@ -4,6 +4,7 @@
<div> <div>
<h1>Character {{name}}</h1> <h1>Character {{name}}</h1>
<h3>User: {{username}}</h3> <h3>User: {{username}}</h3>
<p>System: {{data_type}}</h3>
<p>Strength: {{sheet.strength}}</p> <p>Strength: {{sheet.strength}}</p>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -1,18 +0,0 @@
{% extends "base" %}
{% block content %}
<div>
New character page.
<form action="/characters/new" method="post">
<div>
<label for="name">Name:</label>
<input id="name" name="name" type="text" />
</div>
<div>
<input type="submit" value="Create Character" />
</div>
</form>
</div>
{% endblock content %}