Compare commits
No commits in common. "4bff55cc6b2c43006dfdd592e4e46ebcf4f2ba71" and "b95bad440b2862ea7957bdab1bec85c0e5667411" have entirely different histories.
4bff55cc6b
...
b95bad440b
5
build.rs
5
build.rs
|
@ -3,9 +3,6 @@ fn main() {
|
||||||
config.type_attribute(".", "#[derive(Serialize)]");
|
config.type_attribute(".", "#[derive(Serialize)]");
|
||||||
config.type_attribute(".", "#[serde(rename_all = \"camelCase\")]");
|
config.type_attribute(".", "#[serde(rename_all = \"camelCase\")]");
|
||||||
config
|
config
|
||||||
.compile_protos(
|
.compile_protos(&["proto/cofd.proto"], &["src/", "proto/"])
|
||||||
&["proto/cofd.proto", "proto/cofd_api.proto"],
|
|
||||||
&["src/", "proto/"],
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
import "cofd.proto";
|
|
||||||
|
|
||||||
package models.proto.cofd.api;
|
|
||||||
|
|
||||||
//Update basic information about a Chronicles of Darkness (or
|
|
||||||
//derivative system) character sheet. This is a straight overwrite of
|
|
||||||
//all basic information on the sheet.
|
|
||||||
message BasicInfo {
|
|
||||||
string name = 1;
|
|
||||||
string gender = 2;
|
|
||||||
string concept = 3;
|
|
||||||
string chronicle = 4;
|
|
||||||
int32 age = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Update all attributes in a Chronicles of Darkness character (or
|
|
||||||
//derivative system) character sheet. This is a straight overwrite of
|
|
||||||
//all basic information on the sheet.
|
|
||||||
message Attributes {
|
|
||||||
int32 strength = 1;
|
|
||||||
int32 dexterity = 2;
|
|
||||||
int32 stamina = 3;
|
|
||||||
|
|
||||||
int32 intelligence = 4;
|
|
||||||
int32 wits = 5;
|
|
||||||
int32 resolve = 6;
|
|
||||||
|
|
||||||
int32 presence = 7;
|
|
||||||
int32 manipulation = 8;
|
|
||||||
int32 composure = 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Attribute {
|
|
||||||
string name = 1;
|
|
||||||
int32 value = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Update skill entries in a Chronicles of Darkness character sheet.
|
|
||||||
//This is a straight overwrite of all skills in the sheet.
|
|
||||||
message Skills {
|
|
||||||
repeated CofdSheet.Skill physical_skills = 1;
|
|
||||||
repeated CofdSheet.Skill mental_skills = 2;
|
|
||||||
repeated CofdSheet.Skill social_skills = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Add a Condition to a Chronicles of Darkness character sheet.
|
|
||||||
message Condition {
|
|
||||||
string name = 1;
|
|
||||||
}
|
|
17
src/db.rs
17
src/db.rs
|
@ -13,15 +13,16 @@ pub(crate) trait Dao {
|
||||||
|
|
||||||
async fn load_user(&self, for_username: String) -> QueryResult<Option<User>>;
|
async fn load_user(&self, for_username: String) -> QueryResult<Option<User>>;
|
||||||
|
|
||||||
|
//async fn insert_user<'a>(&self, new_user: &'a NewUser<'a>) -> QueryResult<User>;
|
||||||
|
|
||||||
async fn insert_user(&self, new_user: NewUser) -> QueryResult<User>;
|
async fn insert_user(&self, new_user: NewUser) -> QueryResult<User>;
|
||||||
|
|
||||||
async fn load_character_list(&self, for_user_id: i32) -> QueryResult<Vec<StrippedCharacter>>;
|
async fn load_character_list(&self, for_user_id: i32) -> QueryResult<Vec<StrippedCharacter>>;
|
||||||
|
|
||||||
async fn load_character(&self, character_id: i32) -> QueryResult<Option<Character>>;
|
async fn load_character(&self, character_id: i32) -> QueryResult<Option<Character>>;
|
||||||
|
|
||||||
|
//async fn insert_character<'a>(&self, new_character: NewCharacter<'a>) -> QueryResult<()>;
|
||||||
async fn insert_character(&self, new_character: NewCharacter) -> QueryResult<()>;
|
async fn insert_character(&self, new_character: NewCharacter) -> QueryResult<()>;
|
||||||
|
|
||||||
async fn update_character_sheet(&self, character: Character) -> QueryResult<()>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type StrippedCharacterColumns = (
|
type StrippedCharacterColumns = (
|
||||||
|
@ -106,16 +107,4 @@ impl Dao for TenebrousDbConn {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_character_sheet(&self, character: Character) -> QueryResult<()> {
|
|
||||||
use crate::schema::characters::dsl::*;
|
|
||||||
self.run(move |conn| {
|
|
||||||
diesel::update(&character)
|
|
||||||
.set(data.eq(&character.data))
|
|
||||||
.execute(conn)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,6 @@ pub enum Error {
|
||||||
#[error("you do not have permission to access this")]
|
#[error("you do not have permission to access this")]
|
||||||
NoPermission,
|
NoPermission,
|
||||||
|
|
||||||
#[error("invalid input")]
|
|
||||||
InvalidInput,
|
|
||||||
|
|
||||||
#[error("query error: {0}")]
|
#[error("query error: {0}")]
|
||||||
QueryError(#[from] diesel::result::Error),
|
QueryError(#[from] diesel::result::Error),
|
||||||
|
|
||||||
|
@ -28,9 +25,6 @@ pub enum Error {
|
||||||
|
|
||||||
#[error("deserialization error: {0}")]
|
#[error("deserialization error: {0}")]
|
||||||
DeserializationError(#[from] prost::DecodeError),
|
DeserializationError(#[from] prost::DecodeError),
|
||||||
|
|
||||||
#[error("i/o error: {0}")]
|
|
||||||
IoError(#[from] std::io::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
@ -38,7 +32,6 @@ impl Error {
|
||||||
use Error::*;
|
use Error::*;
|
||||||
match self {
|
match self {
|
||||||
QueryError(_) => true,
|
QueryError(_) => true,
|
||||||
IoError(_) => true,
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@ async fn main() -> Result<(), rocket::error::Error> {
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
let api_routes = routes::api::routes();
|
|
||||||
let character_routes = routes::characters::routes();
|
let character_routes = routes::characters::routes();
|
||||||
let catchers = catchers::catchers();
|
let catchers = catchers::catchers();
|
||||||
|
|
||||||
|
@ -40,11 +39,6 @@ async fn main() -> Result<(), rocket::error::Error> {
|
||||||
.attach(db::TenebrousDbConn::fairing())
|
.attach(db::TenebrousDbConn::fairing())
|
||||||
.mount("/", root_routes)
|
.mount("/", root_routes)
|
||||||
.mount("/characters", character_routes)
|
.mount("/characters", character_routes)
|
||||||
.mount("/api", api_routes)
|
|
||||||
.mount(
|
|
||||||
"/scripts",
|
|
||||||
StaticFiles::from(concat!(env!("CARGO_MANIFEST_DIR"), "/static/scripts")),
|
|
||||||
)
|
|
||||||
.mount(
|
.mount(
|
||||||
"/protos",
|
"/protos",
|
||||||
StaticFiles::from(concat!(env!("CARGO_MANIFEST_DIR"), "/proto")),
|
StaticFiles::from(concat!(env!("CARGO_MANIFEST_DIR"), "/proto")),
|
||||||
|
|
|
@ -73,7 +73,7 @@ impl CharacterDataType {
|
||||||
|
|
||||||
/// 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, Identifiable, AsChangeset)]
|
#[derive(Serialize, Debug, Queryable)]
|
||||||
pub struct Character {
|
pub struct Character {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
|
@ -129,22 +129,8 @@ impl Character {
|
||||||
// fields.
|
// fields.
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the existing character with new serialized protobuf
|
|
||||||
/// data. Consumes the data.
|
|
||||||
pub fn update_data<T>(&mut self, data: T) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
T: prost::Message + std::default::Default,
|
|
||||||
{
|
|
||||||
let mut buf = BytesMut::with_capacity(std::mem::size_of_val(&data));
|
|
||||||
data.encode(&mut buf)?;
|
|
||||||
self.data = buf.to_vec();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as regular character type, but without the actual protobuf
|
|
||||||
/// data loaded into memory.
|
|
||||||
#[derive(Serialize, Debug, Queryable)]
|
#[derive(Serialize, Debug, Queryable)]
|
||||||
pub struct StrippedCharacter {
|
pub struct StrippedCharacter {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
|
|
@ -1,20 +1,8 @@
|
||||||
use crate::errors::Error;
|
|
||||||
use rocket::data::{Data, FromData, Outcome, ToByteUnit};
|
|
||||||
use rocket::request::Request;
|
|
||||||
use std::default::Default;
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
/// Contains the generated Chronicles of Darkness-related protocol
|
/// Contains the generated Chronicles of Darkness-related protocol
|
||||||
/// buffer types.
|
/// buffer types.
|
||||||
pub mod cofd {
|
pub mod cofd {
|
||||||
include!(concat!(env!("OUT_DIR"), "/models.proto.cofd.rs"));
|
include!(concat!(env!("OUT_DIR"), "/models.proto.cofd.rs"));
|
||||||
|
|
||||||
pub mod api {
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/models.proto.cofd.api.rs"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO these values are not available in tera templates, so how to
|
|
||||||
// handle?
|
|
||||||
pub(crate) trait DerivedStats {
|
pub(crate) trait DerivedStats {
|
||||||
fn speed(&self) -> i32;
|
fn speed(&self) -> i32;
|
||||||
}
|
}
|
||||||
|
@ -25,46 +13,3 @@ pub mod cofd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A struct wrapping a protobuf that allows it to be used as binary
|
|
||||||
/// data submitted via POST using fetch API. Can automatically be
|
|
||||||
/// dereferenced into its wrapped type.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct Proto<T>(T)
|
|
||||||
where
|
|
||||||
T: prost::Message + Default;
|
|
||||||
|
|
||||||
/// Converts the body of a POST request containing encoded protobuf
|
|
||||||
/// data into the wrapped type.
|
|
||||||
#[rocket::async_trait]
|
|
||||||
impl<T> FromData for Proto<T>
|
|
||||||
where
|
|
||||||
T: prost::Message + Default,
|
|
||||||
{
|
|
||||||
type Error = crate::errors::Error;
|
|
||||||
|
|
||||||
async fn from_data(_req: &Request<'_>, data: Data) -> Outcome<Self, Error> {
|
|
||||||
use rocket::http::Status;
|
|
||||||
|
|
||||||
let bytes: Vec<u8> = match data.open(2.mebibytes()).stream_to_vec().await {
|
|
||||||
Ok(read_bytes) => read_bytes,
|
|
||||||
Err(e) => return Outcome::Failure((Status::new(422, "invalid protobuf"), e.into())),
|
|
||||||
};
|
|
||||||
|
|
||||||
match T::decode(bytes.as_ref()) {
|
|
||||||
Ok(decoded) => Outcome::Success(Proto(decoded)),
|
|
||||||
Err(e) => Outcome::Failure((Status::new(422, "invalid protobuf"), e.into())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for Proto<T>
|
|
||||||
where
|
|
||||||
T: prost::Message + Default,
|
|
||||||
{
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &T {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
pub mod api;
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod characters;
|
pub mod characters;
|
||||||
pub mod common;
|
pub mod common;
|
||||||
|
|
|
@ -1,115 +0,0 @@
|
||||||
use crate::db::{Dao, TenebrousDbConn};
|
|
||||||
use crate::errors::Error;
|
|
||||||
use crate::models::characters::{Character, CharacterDataType, DynCharacterData, Visibility};
|
|
||||||
use crate::models::proto::{cofd::*, Proto};
|
|
||||||
use crate::models::users::User;
|
|
||||||
use rocket_contrib::templates::Template;
|
|
||||||
use serde::Serialize;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub(crate) fn routes() -> Vec<rocket::Route> {
|
|
||||||
routes![
|
|
||||||
cofd::update_basic_info,
|
|
||||||
cofd::update_attributes,
|
|
||||||
cofd::update_attribute,
|
|
||||||
cofd::update_skills,
|
|
||||||
cofd::add_condition,
|
|
||||||
cofd::remove_condition
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Protobuf-based REST endpoints for editing a character.
|
|
||||||
mod cofd {
|
|
||||||
use super::*;
|
|
||||||
use crate::models::proto::{cofd::api::*, cofd::*, Proto};
|
|
||||||
|
|
||||||
#[post("/cofd/<owner>/<character_id>/basic-info", data = "<info>")]
|
|
||||||
pub(super) fn update_basic_info<'a>(
|
|
||||||
owner: String,
|
|
||||||
character_id: i32,
|
|
||||||
info: Proto<BasicInfo>,
|
|
||||||
) -> &'a str {
|
|
||||||
"lol"
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/cofd/<owner>/<character_id>/attributes", data = "<info>")]
|
|
||||||
pub(super) fn update_attributes<'a>(
|
|
||||||
owner: String,
|
|
||||||
character_id: i32,
|
|
||||||
info: Proto<Attributes>,
|
|
||||||
) -> &'a str {
|
|
||||||
"lol"
|
|
||||||
}
|
|
||||||
|
|
||||||
#[patch("/cofd/<owner>/<character_id>/attributes", data = "<attr_update>")]
|
|
||||||
pub(super) async fn update_attribute<'a>(
|
|
||||||
owner: String,
|
|
||||||
character_id: i32,
|
|
||||||
attr_update: Proto<Attribute>,
|
|
||||||
conn: TenebrousDbConn,
|
|
||||||
logged_in_user: Option<&User>,
|
|
||||||
) -> Result<&'a str, Error> {
|
|
||||||
let logged_in_user = logged_in_user.ok_or(Error::NotLoggedIn)?;
|
|
||||||
let owner = conn.load_user(owner).await?.ok_or(Error::NotFound)?;
|
|
||||||
let mut character: Character = conn
|
|
||||||
.load_character(character_id)
|
|
||||||
.await?
|
|
||||||
.ok_or(Error::NotFound)?;
|
|
||||||
|
|
||||||
if logged_in_user != &owner {
|
|
||||||
return Err(Error::NoPermission);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut sheet: CofdSheet = character.try_deserialize()?;
|
|
||||||
|
|
||||||
match attr_update.name.to_lowercase().as_ref() {
|
|
||||||
"strength" => Ok(sheet.strength += attr_update.value),
|
|
||||||
"dexterity" => Ok(sheet.dexterity += attr_update.value),
|
|
||||||
"stamina" => Ok(sheet.stamina += attr_update.value),
|
|
||||||
"intelligence" => Ok(sheet.intelligence += attr_update.value),
|
|
||||||
"wits" => Ok(sheet.wits += attr_update.value),
|
|
||||||
"resolve" => Ok(sheet.resolve += attr_update.value),
|
|
||||||
"presence" => Ok(sheet.presence += attr_update.value),
|
|
||||||
"manipulation" => Ok(sheet.manipulation += attr_update.value),
|
|
||||||
"composure" => Ok(sheet.composure += attr_update.value),
|
|
||||||
_ => Err(Error::InvalidInput),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"updated {} attribute {} to {}",
|
|
||||||
character.character_name, attr_update.name, attr_update.value
|
|
||||||
);
|
|
||||||
|
|
||||||
character.update_data(sheet)?;
|
|
||||||
conn.update_character_sheet(character).await?;
|
|
||||||
Ok("lol")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/cofd/<owner>/<character_id>/skills", data = "<info>")]
|
|
||||||
pub(super) fn update_skills<'a>(
|
|
||||||
owner: String,
|
|
||||||
character_id: i32,
|
|
||||||
info: Proto<Skills>,
|
|
||||||
conn: TenebrousDbConn,
|
|
||||||
) -> &'a str {
|
|
||||||
"lol"
|
|
||||||
}
|
|
||||||
|
|
||||||
#[put("/cofd/<owner>/<character_id>/conditions", data = "<info>")]
|
|
||||||
pub(super) fn add_condition<'a>(
|
|
||||||
owner: String,
|
|
||||||
character_id: i32,
|
|
||||||
info: Proto<Condition>,
|
|
||||||
) -> &'a str {
|
|
||||||
"lol"
|
|
||||||
}
|
|
||||||
|
|
||||||
#[delete("/cofd/<owner>/<character_id>/conditions", data = "<info>")]
|
|
||||||
pub(super) fn remove_condition<'a>(
|
|
||||||
owner: String,
|
|
||||||
character_id: i32,
|
|
||||||
info: Proto<Condition>,
|
|
||||||
) -> &'a str {
|
|
||||||
"lol"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,8 +3,8 @@ use crate::errors::Error;
|
||||||
use crate::models::characters::{Character, CharacterDataType, DynCharacterData, Visibility};
|
use crate::models::characters::{Character, CharacterDataType, DynCharacterData, Visibility};
|
||||||
use crate::models::users::User;
|
use crate::models::users::User;
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_contrib::templates::Template;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
mod edit;
|
|
||||||
mod new;
|
mod new;
|
||||||
|
|
||||||
pub(crate) fn routes() -> Vec<rocket::Route> {
|
pub(crate) fn routes() -> Vec<rocket::Route> {
|
||||||
|
@ -13,13 +13,12 @@ pub(crate) fn routes() -> Vec<rocket::Route> {
|
||||||
new::new_character_page,
|
new::new_character_page,
|
||||||
new::new_character_submit,
|
new::new_character_submit,
|
||||||
new::new_character_not_logged_in,
|
new::new_character_not_logged_in,
|
||||||
edit::edit_character_page
|
edit_character
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ViewCharacterContext<'a> {
|
struct ViewCharacterTemplate<'a> {
|
||||||
pub id: i32,
|
|
||||||
pub name: &'a str,
|
pub name: &'a str,
|
||||||
pub username: &'a str,
|
pub username: &'a str,
|
||||||
pub data_type: &'a CharacterDataType,
|
pub data_type: &'a CharacterDataType,
|
||||||
|
@ -29,8 +28,7 @@ struct ViewCharacterContext<'a> {
|
||||||
fn view_character_template(user: &User, character: Character) -> Result<Template, Error> {
|
fn view_character_template(user: &User, character: Character) -> Result<Template, Error> {
|
||||||
let character = character.uprade()?;
|
let character = character.uprade()?;
|
||||||
|
|
||||||
let context = ViewCharacterContext {
|
let context = ViewCharacterTemplate {
|
||||||
id: character.id,
|
|
||||||
name: &character.character_name,
|
name: &character.character_name,
|
||||||
username: &user.username,
|
username: &user.username,
|
||||||
data_type: &character.data_type,
|
data_type: &character.data_type,
|
||||||
|
@ -64,3 +62,28 @@ async fn view_character(
|
||||||
let template = view_character_template(user, character)?;
|
let template = view_character_template(user, character)?;
|
||||||
Ok(template)
|
Ok(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/<owner>/<character_id>/edit")]
|
||||||
|
async fn edit_character(
|
||||||
|
character_id: i32,
|
||||||
|
owner: String,
|
||||||
|
logged_in_user: Option<&User>,
|
||||||
|
conn: TenebrousDbConn,
|
||||||
|
) -> Result<Template, Error> {
|
||||||
|
let owner = conn.load_user(owner).await?.ok_or(Error::NotFound)?;
|
||||||
|
|
||||||
|
let logged_in_user = logged_in_user.ok_or(Error::NotLoggedIn)?;
|
||||||
|
let character = conn
|
||||||
|
.load_character(character_id)
|
||||||
|
.await?
|
||||||
|
.ok_or(Error::NotFound)?;
|
||||||
|
|
||||||
|
if logged_in_user != &owner {
|
||||||
|
return Err(Error::NoPermission);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut context = HashMap::new();
|
||||||
|
context.insert("name", character.character_name);
|
||||||
|
context.insert("username", owner.username);
|
||||||
|
Ok(Template::render("view_character", context))
|
||||||
|
}
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
use crate::db::{Dao, TenebrousDbConn};
|
|
||||||
use crate::errors::Error;
|
|
||||||
use crate::models::characters::{Character, CharacterDataType, DynCharacterData, Visibility};
|
|
||||||
use crate::models::users::User;
|
|
||||||
use rocket_contrib::templates::Template;
|
|
||||||
use serde::Serialize;
|
|
||||||
use strum::IntoEnumIterator;
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct EditCharacterContext<'a> {
|
|
||||||
pub name: &'a str,
|
|
||||||
pub username: &'a str,
|
|
||||||
pub data_type: &'a CharacterDataType,
|
|
||||||
pub sheet: Box<DynCharacterData>,
|
|
||||||
pub state: FormStateContext<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct FormStateContext<'a> {
|
|
||||||
pub selected_system: &'a CharacterDataType,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/<owner>/<character_id>/edit")]
|
|
||||||
pub(super) async fn edit_character_page(
|
|
||||||
character_id: i32,
|
|
||||||
owner: String,
|
|
||||||
logged_in_user: Option<&User>,
|
|
||||||
conn: TenebrousDbConn,
|
|
||||||
) -> Result<Template, Error> {
|
|
||||||
let owner = conn.load_user(owner).await?.ok_or(Error::NotFound)?;
|
|
||||||
|
|
||||||
let logged_in_user = logged_in_user.ok_or(Error::NotLoggedIn)?;
|
|
||||||
let character = conn
|
|
||||||
.load_character(character_id)
|
|
||||||
.await?
|
|
||||||
.ok_or(Error::NotFound)?;
|
|
||||||
|
|
||||||
if logged_in_user != &owner {
|
|
||||||
return Err(Error::NoPermission);
|
|
||||||
}
|
|
||||||
|
|
||||||
let context = EditCharacterContext {
|
|
||||||
name: &character.character_name,
|
|
||||||
username: &owner.username,
|
|
||||||
data_type: &character.data_type,
|
|
||||||
sheet: character.dyn_deserialize()?,
|
|
||||||
state: FormStateContext {
|
|
||||||
selected_system: &character.data_type,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Template::render("characters/edit_character", context))
|
|
||||||
}
|
|
|
@ -7,7 +7,7 @@ use rocket_contrib::templates::Template;
|
||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
|
|
||||||
pub fn routes() -> Vec<rocket::Route> {
|
pub fn routes() -> Vec<rocket::Route> {
|
||||||
routes![index, user_index, proto_test]
|
routes![index, user_index]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information to display to the user on their home page.
|
/// Information to display to the user on their home page.
|
||||||
|
@ -39,12 +39,3 @@ async fn user_index(user: &User, conn: TenebrousDbConn) -> Result<Template, Erro
|
||||||
fn index() -> Redirect {
|
fn index() -> Redirect {
|
||||||
super::common::redirect_to_login()
|
super::common::redirect_to_login()
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::models::proto::{cofd::*, Proto};
|
|
||||||
|
|
||||||
#[post("/proto-test", data = "<buf>")]
|
|
||||||
async fn proto_test<'a>(buf: Proto<CofdSheet>) -> &'a str {
|
|
||||||
println!("buf is {:#?}", buf);
|
|
||||||
println!("str is: {}", buf.strength);
|
|
||||||
"lol"
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
function makeAPI(root) {
|
|
||||||
const Attribute = root.lookupType("models.proto.cofd.api.Attribute");
|
|
||||||
|
|
||||||
const attributesResource = (username, characterID) =>
|
|
||||||
'/api/cofd/' + username + '/' + characterID + '/attributes';
|
|
||||||
|
|
||||||
async function updateAttribute(params) {
|
|
||||||
const { username, characterID, attribute, newValue } = params;
|
|
||||||
|
|
||||||
let req = Attribute.create({
|
|
||||||
name: attribute,
|
|
||||||
value: parseInt(newValue)
|
|
||||||
});
|
|
||||||
|
|
||||||
const resource = attributesResource(username, characterID);
|
|
||||||
|
|
||||||
let resp = await fetch(resource, {
|
|
||||||
method: 'PATCH',
|
|
||||||
body: Attribute.encode(req).finish()
|
|
||||||
}).then(async resp => {
|
|
||||||
console.log("resp is", await resp.text());
|
|
||||||
}).catch(async err => {
|
|
||||||
console.log("err is", err.text());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
updateAttribute
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
(async () => {
|
|
||||||
//TODO start refactoring these into a separate script, and make API calls
|
|
||||||
//take all necessary info (e.g. username and character ID, plus other stuff)
|
|
||||||
//as object params.
|
|
||||||
const root = await protobuf.load("/protos/cofd_api.proto");
|
|
||||||
|
|
||||||
const [, , USERNAME, CHARACTER_ID] = window.location.pathname.split('/');
|
|
||||||
|
|
||||||
const api = makeAPI(root);
|
|
||||||
console.log("api is", api);
|
|
||||||
|
|
||||||
function setupAttributes() {
|
|
||||||
const attributeInputs = document.querySelectorAll('#attributes input[type="number"]');
|
|
||||||
|
|
||||||
Array.from(attributeInputs).forEach(input => {
|
|
||||||
input.addEventListener('change', async function(event) {
|
|
||||||
console.log("updating attr");
|
|
||||||
const attribute = event.target.id;
|
|
||||||
const newValue = parseInt(event.target.value);
|
|
||||||
const params = { username: USERNAME, characterID: CHARACTER_ID, attribute, newValue };
|
|
||||||
await api.updateAttribute(params);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setupAttributes();
|
|
||||||
})().catch(e => {
|
|
||||||
alert(e);
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
document.addEventListener('DOMContentLoaded', event => {
|
|
||||||
protobuf.load("/protos/cofd.proto").then(function(root) {
|
|
||||||
console.log("root is", root);
|
|
||||||
let CofdSheet = root.lookupType("models.proto.cofd.CofdSheet");
|
|
||||||
let sheet = CofdSheet.fromObject({ name: 'lol', strength: 100 });
|
|
||||||
let buffer = CofdSheet.encode(sheet).finish();
|
|
||||||
|
|
||||||
fetch('/proto-test', {
|
|
||||||
method: 'POST',
|
|
||||||
body: buffer
|
|
||||||
}).then(async resp => {
|
|
||||||
console.log("resp is", await resp.text());
|
|
||||||
}).catch(async err => {
|
|
||||||
console.log("err is", err.text());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,76 +0,0 @@
|
||||||
{% import "characters/edit_character_macros" as macros %}
|
|
||||||
{% extends "base" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<style type="text/css">
|
|
||||||
body {
|
|
||||||
font-family: Liberation Sans, Arial;
|
|
||||||
}
|
|
||||||
|
|
||||||
#attributes {
|
|
||||||
padding: 4px;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
#attributes .attributes-section {
|
|
||||||
border: 1px solid gray;
|
|
||||||
border-collapse: collapse;
|
|
||||||
display: table-cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attribute {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attribute label {
|
|
||||||
display: inline-block;
|
|
||||||
float: left;
|
|
||||||
clear: left;
|
|
||||||
width: 10em;
|
|
||||||
text-align: right;
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
padding: 8px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attribute input {
|
|
||||||
max-width: 4em;
|
|
||||||
display: inline-block;
|
|
||||||
float: left;
|
|
||||||
padding: 8px;
|
|
||||||
border: none;
|
|
||||||
background-color: lightgray;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/protobufjs@6.10.2/dist/protobuf.min.js"></script>
|
|
||||||
<script defer type="text/javascript" src="/scripts/api.js"></script>
|
|
||||||
<script defer type="text/javascript" src="/scripts/characters/edit.js"></script>
|
|
||||||
<h1>Core Sheet</h1>
|
|
||||||
<div>
|
|
||||||
<h1>Name: <input type="text" value="{{name}}" /></h1>
|
|
||||||
<p>System: {{data_type}}</p>
|
|
||||||
<div id="attributes">
|
|
||||||
<div class="attributes-section" id="mentalAttributes">
|
|
||||||
{{ macros::attribute(name="Intelligence", value=sheet.intelligence) }}
|
|
||||||
{{ macros::attribute(name="Wits", value=sheet.wits) }}
|
|
||||||
{{ macros::attribute(name="Resolve", value=sheet.resolve) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="attributes-section" id="physicalAttributes">
|
|
||||||
{{ macros::attribute(name="Strength", value=sheet.strength) }}
|
|
||||||
{{ macros::attribute(name="Dexterity", value=sheet.dexterity) }}
|
|
||||||
{{ macros::attribute(name="Stamina", value=sheet.stamina) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="attributes-section" id="socicalAttributes">
|
|
||||||
{{ macros::attribute(name="Presence", value=sheet.presence) }}
|
|
||||||
{{ macros::attribute(name="Manipulation", value=sheet.manipulation) }}
|
|
||||||
{{ macros::attribute(name="Composure", value=sheet.composure) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock content %}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{% macro attribute(name, value) %}
|
|
||||||
<div class="attribute">
|
|
||||||
<label for="{{name}}">{{name}}:</label>
|
|
||||||
<input id="{{name}}" name="{{name}}" type="number" min="0" value="{{value}}" />
|
|
||||||
</div>
|
|
||||||
{% endmacro input %}
|
|
|
@ -1,8 +1,6 @@
|
||||||
{% extends "base" %}
|
{% extends "base" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/protobufjs@6.10.2/dist/protobuf.min.js"></script>
|
|
||||||
<script type="text/javascript" src="/scripts/characters/new-character.js"></script>
|
|
||||||
<div>
|
<div>
|
||||||
New character page.
|
New character page.
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,4 @@
|
||||||
<p>System: {{data_type}}</h3>
|
<p>System: {{data_type}}</h3>
|
||||||
<p>Strength: {{sheet.strength}}</p>
|
<p>Strength: {{sheet.strength}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<a href="/characters/{{username}}/{{id}}/edit">Edit Character</a>
|
|
||||||
</div>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
Loading…
Reference in New Issue