Compare commits
3 Commits
93ed679946
...
0751a783dc
Author | SHA1 | Date |
---|---|---|
jeff | 0751a783dc | |
jeff | 92301fb1d4 | |
jeff | 44dded7f28 |
|
@ -342,6 +342,18 @@ dependencies = [
|
||||||
"r2d2",
|
"r2d2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diesel-derive-enum"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "703e71c268ea2d8da9c0ab0b40d8b217179ee622209c170875d24443193a0dfb"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2 1.0.24",
|
||||||
|
"quote 1.0.7",
|
||||||
|
"syn 1.0.53",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "diesel_derives"
|
name = "diesel_derives"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
|
@ -368,6 +380,15 @@ version = "1.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "erased-serde"
|
||||||
|
version = "0.3.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ca8b296792113e1500fd935ae487be6e00ce318952a6880555554824d6ebf38"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "error-chain"
|
name = "error-chain"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
|
@ -1436,6 +1457,8 @@ name = "tenebrous-sheets"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"diesel",
|
"diesel",
|
||||||
|
"diesel-derive-enum",
|
||||||
|
"erased-serde",
|
||||||
"log 0.4.11",
|
"log 0.4.11",
|
||||||
"prost",
|
"prost",
|
||||||
"prost-build",
|
"prost-build",
|
||||||
|
|
|
@ -13,7 +13,9 @@ prost = "0.6"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
erased-serde = "0.3"
|
||||||
diesel = "1.4"
|
diesel = "1.4"
|
||||||
|
diesel-derive-enum = { version = "1", features = ["sqlite"] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
rust-argon2 = "0.8"
|
rust-argon2 = "0.8"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -3,3 +3,5 @@
|
||||||
|
|
||||||
[print_schema]
|
[print_schema]
|
||||||
file = "src/schema.rs"
|
file = "src/schema.rs"
|
||||||
|
import_types = ["diesel::sql_types::*", "crate::models::characters::*"]
|
||||||
|
patch_file = "src/schema.patch"
|
|
@ -3,7 +3,7 @@ CREATE TABLE characters(
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
viewable BOOLEAN NOT NULL,
|
viewable BOOLEAN NOT NULL,
|
||||||
character_name TEXT NOT NULL,
|
character_name TEXT NOT NULL,
|
||||||
data_type TEXT NOT NULL,
|
data_type TEXT CHECK(data_type IN ('chronicles_of_darkness_v1', 'changeling_v1')) NOT NULL,
|
||||||
data_version INTEGER NOT NULL,
|
data_version INTEGER NOT NULL,
|
||||||
data BLOB NOT NULL
|
data BLOB NOT NULL
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,17 @@
|
||||||
|
use crate::errors::Error;
|
||||||
|
use crate::models::proto::cofd::*;
|
||||||
use crate::models::users::User;
|
use crate::models::users::User;
|
||||||
use crate::schema::characters;
|
use crate::schema::characters;
|
||||||
|
use diesel_derive_enum::DbEnum;
|
||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
|
|
||||||
|
/// Dynamic character data is an opaque container type that holds
|
||||||
|
/// successfully deserialized character data protobuf object of any
|
||||||
|
/// type. It does not know what kind of type it has. This is a
|
||||||
|
/// semantically more appropriate name for what is returned from the
|
||||||
|
/// dyn_deserialize function.
|
||||||
|
pub(crate) type DynCharacterData = dyn erased_serde::Serialize;
|
||||||
|
|
||||||
/// Control system visibility of a character for a particular user.
|
/// Control system visibility of a character for a particular user.
|
||||||
/// Implemented as a trait because there are multiple character
|
/// Implemented as a trait because there are multiple character
|
||||||
/// structs that need this.
|
/// structs that need this.
|
||||||
|
@ -29,6 +39,12 @@ pub(crate) trait Visibility {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(DbEnum, Debug, Serialize, PartialEq)]
|
||||||
|
pub enum CharacterDataType {
|
||||||
|
ChroniclesOfDarknessV1,
|
||||||
|
ChangelingV1,
|
||||||
|
}
|
||||||
|
|
||||||
/// An entry that appears in a user's character list. Properties are
|
/// An entry that appears in a user's character list. Properties are
|
||||||
/// in order of table columns.
|
/// in order of table columns.
|
||||||
#[derive(Serialize, Debug, Queryable)]
|
#[derive(Serialize, Debug, Queryable)]
|
||||||
|
@ -37,7 +53,7 @@ pub struct Character {
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
pub viewable: bool,
|
pub viewable: bool,
|
||||||
pub character_name: String,
|
pub character_name: String,
|
||||||
pub data_type: String,
|
pub data_type: CharacterDataType,
|
||||||
pub data_version: i32,
|
pub data_version: i32,
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
@ -52,13 +68,50 @@ impl Visibility for Character {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Character {
|
||||||
|
/// Attempt to deserialize the character's data into the given
|
||||||
|
/// type, which must be one of the protobuf types.
|
||||||
|
pub fn try_deserialize<T>(&self) -> Result<T, Error>
|
||||||
|
where
|
||||||
|
T: prost::Message + std::default::Default,
|
||||||
|
{
|
||||||
|
let decoded = T::decode(self.data.as_ref())?;
|
||||||
|
Ok(decoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to deserialize the character's data based on its
|
||||||
|
/// stored type, but return the deserialized protobuf type as a
|
||||||
|
/// trait object. Primarily used for passing character sheets to
|
||||||
|
/// templates or other places (like a REST API).
|
||||||
|
pub fn dyn_deserialize(&self) -> Result<Box<DynCharacterData>, Error> {
|
||||||
|
use CharacterDataType::*;
|
||||||
|
let decoded: Box<dyn erased_serde::Serialize> = match self.data_type {
|
||||||
|
ChroniclesOfDarknessV1 => Box::new(self.try_deserialize::<CofdSheet>()?),
|
||||||
|
ChangelingV1 => Box::new(self.try_deserialize::<ChangelingSheet>()?),
|
||||||
|
};
|
||||||
|
|
||||||
|
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)]
|
||||||
pub struct StrippedCharacter {
|
pub struct StrippedCharacter {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
pub viewable: bool,
|
pub viewable: bool,
|
||||||
pub character_name: String,
|
pub character_name: String,
|
||||||
pub data_type: String,
|
pub data_type: CharacterDataType,
|
||||||
pub data_version: i32,
|
pub data_version: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +133,7 @@ pub struct NewCharacter<'a> {
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
pub viewable: bool,
|
pub viewable: bool,
|
||||||
pub character_name: &'a str,
|
pub character_name: &'a str,
|
||||||
pub data_type: &'a str,
|
pub data_type: CharacterDataType,
|
||||||
pub data_version: i32,
|
pub data_version: i32,
|
||||||
pub data: &'a [u8],
|
pub data: &'a [u8],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,47 @@
|
||||||
use crate::db::{Dao, TenebrousDbConn};
|
use crate::db::{Dao, TenebrousDbConn};
|
||||||
use crate::errors::Error;
|
use crate::errors::Error;
|
||||||
use crate::models::characters::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 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 ViewCharacterTemlate<'a, T> {
|
struct ViewCharacterTemplate<'a> {
|
||||||
pub name: &'a str,
|
pub name: &'a str,
|
||||||
pub username: &'a str,
|
pub username: &'a str,
|
||||||
pub sheet: T,
|
pub data_type: &'a CharacterDataType,
|
||||||
|
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>")]
|
||||||
|
@ -36,67 +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)?;
|
||||||
|
|
||||||
//TODO determine sheet type and deserialize based on that.
|
let template = view_character_template(user, character)?;
|
||||||
use crate::models::proto::cofd::CofdSheet;
|
Ok(template)
|
||||||
use prost::Message;
|
|
||||||
let sheet = CofdSheet::decode(character.data.as_ref())?;
|
|
||||||
|
|
||||||
let context = ViewCharacterTemlate {
|
|
||||||
name: &character.character_name,
|
|
||||||
username: &user.username,
|
|
||||||
sheet: sheet,
|
|
||||||
};
|
|
||||||
|
|
||||||
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 new_character = CofdSheet::default();
|
|
||||||
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: std::any::type_name::<CofdSheet>(),
|
|
||||||
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")]
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
This patch converts the generated schema to an enum type.
|
||||||
|
--- a/src/schema.rs
|
||||||
|
+++ b/src/schema.rs
|
||||||
|
@@ -4,13 +4,13 @@ table! {
|
||||||
|
|
||||||
|
characters (id) {
|
||||||
|
id -> Integer,
|
||||||
|
user_id -> Integer,
|
||||||
|
viewable -> Bool,
|
||||||
|
character_name -> Text,
|
||||||
|
- data_type -> Text,
|
||||||
|
+ data_type -> CharacterDataTypeMapping,
|
||||||
|
data_version -> Integer,
|
||||||
|
data -> Binary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
|
@ -1,16 +1,22 @@
|
||||||
table! {
|
table! {
|
||||||
|
use diesel::sql_types::*;
|
||||||
|
use crate::models::characters::*;
|
||||||
|
|
||||||
characters (id) {
|
characters (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
user_id -> Integer,
|
user_id -> Integer,
|
||||||
viewable -> Bool,
|
viewable -> Bool,
|
||||||
character_name -> Text,
|
character_name -> Text,
|
||||||
data_type -> Text,
|
data_type -> CharacterDataTypeMapping,
|
||||||
data_version -> Integer,
|
data_version -> Integer,
|
||||||
data -> Binary,
|
data -> Binary,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
|
use diesel::sql_types::*;
|
||||||
|
use crate::models::characters::*;
|
||||||
|
|
||||||
users (id) {
|
users (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
username -> Text,
|
username -> Text,
|
||||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
Loading…
Reference in New Issue