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:
parent
ec5be56858
commit
d6a80a7996
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
prost_build::compile_protos(&["proto/cofd.proto"], &["proto/"]).unwrap();
|
||||||
|
}
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
42
src/db.rs
42
src/db.rs
|
@ -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)?;
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod characters;
|
pub mod characters;
|
||||||
|
pub mod proto;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
|
@ -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],
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
Loading…
Reference in New Issue