Serialize characters as protobufs. Skeletal character creation.

Implements the absolute basics of the character creation flow, AKA
most things are missing. Integrates a method of storing character
data, support for that in the database, and a working character
creation page.

The only thing the page does at the moment is create a hardcoded basic
CofD character sheet and save it to the database. There is no ability
to change game system, fill in extra details, etc. There's also no
ability to edit anything.

Also added basic links to the registration and create new character
pages.
This commit is contained in:
jeff 2020-12-07 20:32:02 +00:00
parent ec5be56858
commit d6a80a7996
17 changed files with 430 additions and 43 deletions

149
Cargo.lock generated
View File

@ -79,6 +79,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "anyhow"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4"
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.6" version = "0.3.6"
@ -203,6 +209,12 @@ version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "bytes"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "0.1.10" version = "0.1.10"
@ -350,6 +362,12 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]] [[package]]
name = "error-chain" name = "error-chain"
version = "0.12.4" version = "0.12.4"
@ -378,6 +396,12 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "fixedbitset"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
[[package]] [[package]]
name = "fsevent" name = "fsevent"
version = "0.4.0" version = "0.4.0"
@ -466,6 +490,15 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.17" version = "0.1.17"
@ -585,6 +618,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "itertools"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "0.4.6" version = "0.4.6"
@ -742,6 +784,12 @@ dependencies = [
"ws2_32-sys", "ws2_32-sys",
] ]
[[package]]
name = "multimap"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1255076139a83bb467426e7f8d0134968a8118844faa755985e077cf31850333"
[[package]] [[package]]
name = "net2" name = "net2"
version = "0.2.36" version = "0.2.36"
@ -915,6 +963,16 @@ dependencies = [
"sha-1", "sha-1",
] ]
[[package]]
name = "petgraph"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.19" version = "0.3.19"
@ -955,6 +1013,57 @@ dependencies = [
"unicode-xid 0.2.1", "unicode-xid 0.2.1",
] ]
[[package]]
name = "prost"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce49aefe0a6144a45de32927c77bd2859a5f7677b55f220ae5b744e87389c212"
dependencies = [
"bytes",
"prost-derive",
]
[[package]]
name = "prost-build"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b10678c913ecbd69350e8535c3aef91a8676c0773fc1d7b95cdd196d7f2f26"
dependencies = [
"bytes",
"heck",
"itertools",
"log 0.4.11",
"multimap",
"petgraph",
"prost",
"prost-types",
"tempfile",
"which",
]
[[package]]
name = "prost-derive"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537aa19b95acde10a12fec4301466386f757403de4cd4e5b4fa78fb5ecb18f72"
dependencies = [
"anyhow",
"itertools",
"proc-macro2 1.0.24",
"quote 1.0.7",
"syn 1.0.53",
]
[[package]]
name = "prost-types"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1834f67c0697c001304b75be76f67add9c89742eda3a085ad8ee0bb38c3417aa"
dependencies = [
"bytes",
"prost",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "0.6.13" version = "0.6.13"
@ -1049,6 +1158,15 @@ version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi 0.3.9",
]
[[package]] [[package]]
name = "rocket" name = "rocket"
version = "0.4.6" version = "0.4.6"
@ -1299,12 +1417,28 @@ dependencies = [
"unicode-xid 0.2.1", "unicode-xid 0.2.1",
] ]
[[package]]
name = "tempfile"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
dependencies = [
"cfg-if 0.1.10",
"libc",
"rand",
"redox_syscall",
"remove_dir_all",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "tenebrous-sheets" name = "tenebrous-sheets"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"diesel", "diesel",
"log 0.4.11", "log 0.4.11",
"prost",
"prost-build",
"rand", "rand",
"rocket", "rocket",
"rocket_contrib", "rocket_contrib",
@ -1501,6 +1635,12 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-segmentation"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.1.0" version = "0.1.0"
@ -1575,6 +1715,15 @@ version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "which"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.2.8" version = "0.2.8"

View File

@ -3,10 +3,13 @@ name = "tenebrous-sheets"
version = "0.1.0" version = "0.1.0"
authors = ["jeff <jeff@agnos.is>"] authors = ["jeff <jeff@agnos.is>"]
edition = "2018" edition = "2018"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies]
prost-build = "0.6"
[dependencies] [dependencies]
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"

3
build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
prost_build::compile_protos(&["proto/cofd.proto"], &["proto/"]).unwrap();
}

View File

@ -3,5 +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,
character_data BLOB NUT NULL data_type TEXT NOT NULL,
data_version INTEGER NOT NULL,
data BLOB NOT NULL
); );

79
proto/cofd.proto Normal file
View File

@ -0,0 +1,79 @@
syntax = "proto3";
package models.proto.cofd;
//Base sheet for Chronicles of Darkness systems.
message CofdSheet {
message Merit {
int32 dots = 1;
string name = 2;
}
message Condition {
string name = 1;
}
///Entry for a skill
message Skill {
int32 dots = 1;
string name = 2;
sint32 untrained_penalty = 3;
repeated string specializations = 4;
}
//A generic item with a name, physical description, and rules text.
message Item {
string name = 1;
string description = 2;
string rules = 3;
}
//An entry for an attack. Usually a weapon.
message Attack {
string name = 1;
int32 dice_pool = 2;
int32 damage = 3;
int32 range = 4;
sint32 initiative_modifier = 5;
int32 size = 6;
}
string name = 1;
string player = 2;
string campaign = 3;
string description = 4;
int32 strength = 6;
int32 dexterity = 7;
int32 stamina = 8;
int32 intelligence = 9;
int32 wits = 10;
int32 resolve = 11;
int32 presence = 12;
int32 manipulation = 13;
int32 composure = 14;
map<string, Skill> physical_skills = 16;
map<string, Skill> mental_skills = 17;
map<string, Skill> social_skills = 18;
repeated Merit merits = 15;
repeated Condition conditions = 19;
int32 size = 20;
int32 health = 21;
int32 willpower = 22;
int32 experience_points = 23;
int32 beats = 24;
repeated Item items = 25;
repeated Attack attacks = 26;
map<string, string> other_data = 27;
}
message ChangelingSheet {
CofdSheet base = 1;
}

View File

@ -1,5 +1,6 @@
use crate::models::characters::{CharacterEntry, NewCharacter}; use crate::models::characters::{Character, NewCharacter, StrippedCharacter};
use crate::models::users::{NewUser, User}; use crate::models::users::{NewUser, User};
use crate::schema::characters;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::SqliteConnection; use diesel::SqliteConnection;
@ -8,21 +9,39 @@ pub(crate) struct TenebrousDbConn(SqliteConnection);
pub(crate) trait Dao { pub(crate) trait Dao {
fn load_user_by_id(&self, id: i32) -> QueryResult<Option<User>>; fn load_user_by_id(&self, id: i32) -> QueryResult<Option<User>>;
fn load_user(&self, for_username: &str) -> QueryResult<Option<User>>; fn load_user(&self, for_username: &str) -> QueryResult<Option<User>>;
fn insert_user(&self, new_user: &NewUser) -> QueryResult<User>; fn insert_user(&self, new_user: &NewUser) -> QueryResult<User>;
fn load_character_list(&self, for_user_id: i32) -> QueryResult<Vec<CharacterEntry>>; fn load_character_list(&self, for_user_id: i32) -> QueryResult<Vec<StrippedCharacter>>;
fn load_character(&self, character_id: i32) -> QueryResult<Option<CharacterEntry>>; fn load_character(&self, character_id: i32) -> QueryResult<Option<Character>>;
fn insert_character(&self, new_character: &NewCharacter) -> QueryResult<()>; fn insert_character(&self, new_character: NewCharacter) -> QueryResult<()>;
} }
type StrippedCharacterColumns = (
characters::id,
characters::user_id,
characters::viewable,
characters::character_name,
characters::data_type,
characters::data_version,
);
const STRIPPED_CHARACTER_COLUMNS: StrippedCharacterColumns = (
characters::id,
characters::user_id,
characters::viewable,
characters::character_name,
characters::data_type,
characters::data_version,
);
impl Dao for TenebrousDbConn { impl Dao for TenebrousDbConn {
fn load_user_by_id(&self, user_id: i32) -> QueryResult<Option<User>> { fn load_user_by_id(&self, user_id: i32) -> QueryResult<Option<User>> {
use crate::schema::users::dsl::*; use crate::schema::users::dsl::*;
users.filter(id.eq(user_id)).first(&self.0).optional() users.filter(id.eq(user_id)).first(&self.0).optional()
} }
@ -48,12 +67,15 @@ impl Dao for TenebrousDbConn {
.first(&self.0)?) .first(&self.0)?)
} }
fn load_character_list(&self, for_user_id: i32) -> QueryResult<Vec<CharacterEntry>> { fn load_character_list(&self, for_user_id: i32) -> QueryResult<Vec<StrippedCharacter>> {
use crate::schema::characters::dsl::*; use crate::schema::characters::dsl::*;
characters.filter(user_id.eq(for_user_id)).load(&self.0) characters
.filter(user_id.eq(for_user_id))
.select(STRIPPED_CHARACTER_COLUMNS)
.load(&self.0)
} }
fn load_character(&self, character_id: i32) -> QueryResult<Option<CharacterEntry>> { fn load_character(&self, character_id: i32) -> QueryResult<Option<Character>> {
use crate::schema::characters::dsl::*; use crate::schema::characters::dsl::*;
characters characters
@ -62,9 +84,7 @@ impl Dao for TenebrousDbConn {
.optional() .optional()
} }
fn insert_character(&self, new_character: &NewCharacter) -> QueryResult<()> { fn insert_character(&self, new_character: NewCharacter) -> QueryResult<()> {
use crate::schema::characters;
diesel::insert_into(characters::table) diesel::insert_into(characters::table)
.values(new_character) .values(new_character)
.execute(&self.0)?; .execute(&self.0)?;

View File

@ -19,6 +19,9 @@ pub enum Error {
#[error("query error: {0}")] #[error("query error: {0}")]
QueryError(#[from] diesel::result::Error), QueryError(#[from] diesel::result::Error),
#[error("serialization error: {0}")]
SerializationError(#[from] prost::EncodeError),
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]

View File

@ -1,2 +1,3 @@
pub mod characters; pub mod characters;
pub mod proto;
pub mod users; pub mod users;

View File

@ -2,34 +2,73 @@ use crate::models::users::User;
use crate::schema::characters; use crate::schema::characters;
use serde_derive::Serialize; use serde_derive::Serialize;
/// An entry that appears in a user's character list. Properties are /// Control system visibility of a character for a particular user.
/// in order of table columns. /// Implemented as a trait because there are multiple character
#[derive(Serialize, Debug, Queryable)] /// structs that need this.
pub struct CharacterEntry { pub(crate) trait Visibility {
pub id: i32, /// User ID that owns this character.
pub user_id: i32, fn user_id(&self) -> i32;
pub viewable: bool,
pub name: String, /// If the character is publicly visible.
//TODO don't need to carry around character data for this. fn viewable(&self) -> bool;
pub data: Option<Vec<u8>>,
}
impl CharacterEntry {
/// Transform to an Option that holds the character, if the /// Transform to an Option that holds the character, if the
/// character is viewable to a potentially existing user. A /// character is viewable to a potentially existing user. A
/// character is "visible" if the public viewable property is set /// character is "visible" if the public viewable property is set
/// to true, or the user is the owner of the character. Consumes /// to true, or the user is the owner of the character. Consumes
/// self. /// self.
pub fn as_visible_for(self, user: Option<&User>) -> Option<CharacterEntry> { fn as_visible_for(self, user: Option<&User>) -> Option<Self>
let character_is_visible = |c: CharacterEntry| { where
if c.viewable || user.map(|u| u.id) == Some(c.user_id) { Self: std::marker::Sized,
Some(c) {
if self.viewable() || user.map(|u| u.id) == Some(self.user_id()) {
Some(self)
} else { } else {
None None
} }
}; }
}
Some(self).and_then(character_is_visible) /// An entry that appears in a user's character list. Properties are
/// in order of table columns.
#[derive(Serialize, Debug, Queryable)]
pub struct Character {
pub id: i32,
pub user_id: i32,
pub viewable: bool,
pub character_name: String,
pub data_type: String,
pub data_version: i32,
pub data: Vec<u8>,
}
impl Visibility for Character {
fn user_id(&self) -> i32 {
self.user_id
}
fn viewable(&self) -> bool {
self.viewable
}
}
#[derive(Serialize, Debug, Queryable)]
pub struct StrippedCharacter {
pub id: i32,
pub user_id: i32,
pub viewable: bool,
pub character_name: String,
pub data_type: String,
pub data_version: i32,
}
impl Visibility for StrippedCharacter {
fn user_id(&self) -> i32 {
self.user_id
}
fn viewable(&self) -> bool {
self.viewable
} }
} }
@ -41,5 +80,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 character_data: &'a [u8], pub data_type: &'a str,
pub data_version: i32,
pub data: &'a [u8],
} }

15
src/models/proto.rs Normal file
View File

@ -0,0 +1,15 @@
/// Contains the generated Chronicles of Darkness-related protocol
/// buffer types.
pub mod cofd {
include!(concat!(env!("OUT_DIR"), "/models.proto.cofd.rs"));
pub(crate) trait DerivedStats {
fn speed(&self) -> i32;
}
impl DerivedStats for CofdSheet {
fn speed(&self) -> i32 {
self.size + self.stamina
}
}
}

View File

@ -1,6 +1,8 @@
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::users::User; use crate::models::users::User;
use rocket::request::Form;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket_contrib::templates::Template; use rocket_contrib::templates::Template;
use std::collections::HashMap; use std::collections::HashMap;
@ -9,11 +11,17 @@ pub(crate) fn routes() -> Vec<rocket::Route> {
routes![ routes![
view_character, view_character,
new_character, new_character,
create_new_character,
new_character_not_logged_in, new_character_not_logged_in,
edit_character edit_character
] ]
} }
#[derive(FromForm)]
struct NewCharacterForm {
name: String, //TODO add game system
}
#[get("/<username>/<character_id>")] #[get("/<username>/<character_id>")]
fn view_character( fn view_character(
character_id: i32, character_id: i32,
@ -29,7 +37,7 @@ fn view_character(
.ok_or(Error::NotFound)?; .ok_or(Error::NotFound)?;
let mut context = HashMap::new(); let mut context = HashMap::new();
context.insert("name", character.name); context.insert("name", character.character_name);
context.insert("username", user.username); context.insert("username", user.username);
Ok(Template::render("view_character", context)) Ok(Template::render("view_character", context))
} }
@ -40,6 +48,37 @@ fn new_character(logged_in_user: &User, conn: TenebrousDbConn) -> Result<Templat
Ok(Template::render("new_character", context)) 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)] #[get("/new", rank = 2)]
fn new_character_not_logged_in() -> Redirect { fn new_character_not_logged_in() -> Redirect {
super::common::redirect_to_login() super::common::redirect_to_login()
@ -61,7 +100,7 @@ fn edit_character(
} }
let mut context = HashMap::new(); let mut context = HashMap::new();
context.insert("name", character.name); context.insert("name", character.character_name);
context.insert("username", owner.username); context.insert("username", owner.username);
Ok(Template::render("view_character", context)) Ok(Template::render("view_character", context))
} }

View File

@ -4,3 +4,8 @@ use rocket::response::Redirect;
pub(super) fn redirect_to_login() -> Redirect { pub(super) fn redirect_to_login() -> Redirect {
Redirect::to(uri!(super::auth::login_page)) Redirect::to(uri!(super::auth::login_page))
} }
/// Common redirect to the index page.
pub(super) fn redirect_to_index() -> Redirect {
Redirect::to(uri!(super::root::index))
}

View File

@ -1,6 +1,7 @@
use crate::db::{Dao, TenebrousDbConn}; use crate::db::{Dao, TenebrousDbConn};
use crate::errors::Error; use crate::errors::Error;
use crate::models::{characters::CharacterEntry, users::User}; use crate::models::characters::Visibility;
use crate::models::{characters::StrippedCharacter, users::User};
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket_contrib::templates::Template; use rocket_contrib::templates::Template;
use serde_derive::Serialize; use serde_derive::Serialize;
@ -12,13 +13,18 @@ pub fn routes() -> Vec<rocket::Route> {
/// Information to display to the user on their home page. /// Information to display to the user on their home page.
#[derive(Serialize)] #[derive(Serialize)]
pub struct UserHomeContext<'a> { pub struct UserHomeContext<'a> {
pub characters: &'a [CharacterEntry], pub characters: &'a [StrippedCharacter],
pub user: &'a User, pub user: &'a User,
} }
#[get("/")] #[get("/")]
fn user_index(user: &User, conn: TenebrousDbConn) -> Result<Template, Error> { fn user_index(user: &User, conn: TenebrousDbConn) -> Result<Template, Error> {
let characters = conn.load_character_list(user.id)?; let characters: Vec<StrippedCharacter> = conn
.load_character_list(user.id)?
.into_iter()
.map(|c| c.as_visible_for(Some(user)))
.filter_map(|c| c)
.collect();
let context = UserHomeContext { let context = UserHomeContext {
characters: &characters, characters: &characters,

View File

@ -4,7 +4,9 @@ table! {
user_id -> Integer, user_id -> Integer,
viewable -> Bool, viewable -> Bool,
character_name -> Text, character_name -> Text,
character_data -> Nullable<Binary>, data_type -> Text,
data_version -> Integer,
data -> Binary,
} }
} }

View File

@ -8,12 +8,16 @@
{% for char in characters %} {% for char in characters %}
<li> <li>
<a href="characters/{{ user.username }}/{{char.id}}"> <a href="characters/{{ user.username }}/{{char.id}}">
{{ char.name }} {{ char.character_name }}
</a> </a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<div>
<a href="/characters/new">Create New</a>
</div>
<p> <p>
<form action="/logout" method="post"> <form action="/logout" method="post">
<input type="submit" value="Logout" /> <input type="submit" value="Logout" />

View File

@ -17,5 +17,9 @@
<input type="password" name="password" id="password" value="" /> <input type="password" name="password" id="password" value="" />
<p><input type="submit" value="Login"></p> <p><input type="submit" value="Login"></p>
</form> </form>
<div>
<a href="/register">Register</a>
</div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -3,5 +3,16 @@
{% block content %} {% block content %}
<div> <div>
New character page. 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> </div>
{% endblock content %} {% endblock content %}