From 4bff55cc6b2c43006dfdd592e4e46ebcf4f2ba71 Mon Sep 17 00:00:00 2001 From: jeff Date: Sun, 27 Dec 2020 21:49:08 +0000 Subject: [PATCH] Implement editing of attributes. --- src/db.rs | 17 +++++-- src/errors.rs | 4 ++ src/models/characters.rs | 16 ++++++- src/routes/api.rs | 48 ++++++++++++++++--- src/routes/characters.rs | 6 ++- static/scripts/api.js | 8 ++-- templates/characters/view_character.html.tera | 4 ++ 7 files changed, 86 insertions(+), 17 deletions(-) diff --git a/src/db.rs b/src/db.rs index 34f2e6f..069573f 100644 --- a/src/db.rs +++ b/src/db.rs @@ -13,16 +13,15 @@ pub(crate) trait Dao { async fn load_user(&self, for_username: String) -> QueryResult>; - //async fn insert_user<'a>(&self, new_user: &'a NewUser<'a>) -> QueryResult; - async fn insert_user(&self, new_user: NewUser) -> QueryResult; async fn load_character_list(&self, for_user_id: i32) -> QueryResult>; async fn load_character(&self, character_id: i32) -> QueryResult>; - //async fn insert_character<'a>(&self, new_character: NewCharacter<'a>) -> QueryResult<()>; async fn insert_character(&self, new_character: NewCharacter) -> QueryResult<()>; + + async fn update_character_sheet(&self, character: Character) -> QueryResult<()>; } type StrippedCharacterColumns = ( @@ -107,4 +106,16 @@ impl Dao for TenebrousDbConn { 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(()) + } } diff --git a/src/errors.rs b/src/errors.rs index 788932b..79b32ec 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,6 +17,9 @@ pub enum Error { #[error("you do not have permission to access this")] NoPermission, + #[error("invalid input")] + InvalidInput, + #[error("query error: {0}")] QueryError(#[from] diesel::result::Error), @@ -35,6 +38,7 @@ impl Error { use Error::*; match self { QueryError(_) => true, + IoError(_) => true, _ => false, } } diff --git a/src/models/characters.rs b/src/models/characters.rs index 7ae9050..ca0bd75 100644 --- a/src/models/characters.rs +++ b/src/models/characters.rs @@ -73,7 +73,7 @@ impl CharacterDataType { /// An entry that appears in a user's character list. Properties are /// in order of table columns. -#[derive(Serialize, Debug, Queryable)] +#[derive(Serialize, Debug, Queryable, Identifiable, AsChangeset)] pub struct Character { pub id: i32, pub user_id: i32, @@ -129,8 +129,22 @@ impl Character { // fields. Ok(self) } + + /// Update the existing character with new serialized protobuf + /// data. Consumes the data. + pub fn update_data(&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)] pub struct StrippedCharacter { pub id: i32, diff --git a/src/routes/api.rs b/src/routes/api.rs index 621b84f..d19b5a9 100644 --- a/src/routes/api.rs +++ b/src/routes/api.rs @@ -41,15 +41,48 @@ mod cofd { "lol" } - #[post("/cofd///attribute/", data = "")] - pub(super) fn update_attribute<'a>( + #[patch("/cofd///attributes", data = "")] + pub(super) async fn update_attribute<'a>( owner: String, character_id: i32, - attribute: String, - info: Proto, - ) -> &'a str { - println!("incoming request is {:#?}", info); - "lol" + attr_update: Proto, + 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///skills", data = "")] @@ -57,6 +90,7 @@ mod cofd { owner: String, character_id: i32, info: Proto, + conn: TenebrousDbConn, ) -> &'a str { "lol" } diff --git a/src/routes/characters.rs b/src/routes/characters.rs index 18383e5..d28a0ad 100644 --- a/src/routes/characters.rs +++ b/src/routes/characters.rs @@ -18,7 +18,8 @@ pub(crate) fn routes() -> Vec { } #[derive(Serialize)] -struct ViewCharacterTemplate<'a> { +struct ViewCharacterContext<'a> { + pub id: i32, pub name: &'a str, pub username: &'a str, pub data_type: &'a CharacterDataType, @@ -28,7 +29,8 @@ struct ViewCharacterTemplate<'a> { fn view_character_template(user: &User, character: Character) -> Result { let character = character.uprade()?; - let context = ViewCharacterTemplate { + let context = ViewCharacterContext { + id: character.id, name: &character.character_name, username: &user.username, data_type: &character.data_type, diff --git a/static/scripts/api.js b/static/scripts/api.js index 922261c..39cff6d 100644 --- a/static/scripts/api.js +++ b/static/scripts/api.js @@ -1,8 +1,8 @@ function makeAPI(root) { const Attribute = root.lookupType("models.proto.cofd.api.Attribute"); - const attributeResource = (username, characterID, attribute) => - '/api/cofd/' + username + '/' + characterID + '/attribute/' + attribute; + const attributesResource = (username, characterID) => + '/api/cofd/' + username + '/' + characterID + '/attributes'; async function updateAttribute(params) { const { username, characterID, attribute, newValue } = params; @@ -12,10 +12,10 @@ function makeAPI(root) { value: parseInt(newValue) }); - const resource = attributeResource(username, characterID, attribute); + const resource = attributesResource(username, characterID); let resp = await fetch(resource, { - method: 'POST', + method: 'PATCH', body: Attribute.encode(req).finish() }).then(async resp => { console.log("resp is", await resp.text()); diff --git a/templates/characters/view_character.html.tera b/templates/characters/view_character.html.tera index 163a5b2..c37ff75 100644 --- a/templates/characters/view_character.html.tera +++ b/templates/characters/view_character.html.tera @@ -8,4 +8,8 @@

System: {{data_type}}

Strength: {{sheet.strength}}

+ + {% endblock content %}