Implement editing of attributes.

This commit is contained in:
jeff 2020-12-27 21:49:08 +00:00
parent c0a48245b1
commit 4bff55cc6b
7 changed files with 86 additions and 17 deletions

View File

@ -13,16 +13,15 @@ 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 = (
@ -107,4 +106,16 @@ 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(())
}
} }

View File

@ -17,6 +17,9 @@ 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),
@ -35,6 +38,7 @@ impl Error {
use Error::*; use Error::*;
match self { match self {
QueryError(_) => true, QueryError(_) => true,
IoError(_) => true,
_ => false, _ => false,
} }
} }

View File

@ -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)] #[derive(Serialize, Debug, Queryable, Identifiable, AsChangeset)]
pub struct Character { pub struct Character {
pub id: i32, pub id: i32,
pub user_id: i32, pub user_id: i32,
@ -129,8 +129,22 @@ 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,

View File

@ -41,15 +41,48 @@ mod cofd {
"lol" "lol"
} }
#[post("/cofd/<owner>/<character_id>/attribute/<attribute>", data = "<info>")] #[patch("/cofd/<owner>/<character_id>/attributes", data = "<attr_update>")]
pub(super) fn update_attribute<'a>( pub(super) async fn update_attribute<'a>(
owner: String, owner: String,
character_id: i32, character_id: i32,
attribute: String, attr_update: Proto<Attribute>,
info: Proto<Attribute>, conn: TenebrousDbConn,
) -> &'a str { logged_in_user: Option<&User>,
println!("incoming request is {:#?}", info); ) -> Result<&'a str, Error> {
"lol" 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>")] #[post("/cofd/<owner>/<character_id>/skills", data = "<info>")]
@ -57,6 +90,7 @@ mod cofd {
owner: String, owner: String,
character_id: i32, character_id: i32,
info: Proto<Skills>, info: Proto<Skills>,
conn: TenebrousDbConn,
) -> &'a str { ) -> &'a str {
"lol" "lol"
} }

View File

@ -18,7 +18,8 @@ pub(crate) fn routes() -> Vec<rocket::Route> {
} }
#[derive(Serialize)] #[derive(Serialize)]
struct ViewCharacterTemplate<'a> { struct ViewCharacterContext<'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,
@ -28,7 +29,8 @@ struct ViewCharacterTemplate<'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 = ViewCharacterTemplate { let context = ViewCharacterContext {
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,

View File

@ -1,8 +1,8 @@
function makeAPI(root) { function makeAPI(root) {
const Attribute = root.lookupType("models.proto.cofd.api.Attribute"); const Attribute = root.lookupType("models.proto.cofd.api.Attribute");
const attributeResource = (username, characterID, attribute) => const attributesResource = (username, characterID) =>
'/api/cofd/' + username + '/' + characterID + '/attribute/' + attribute; '/api/cofd/' + username + '/' + characterID + '/attributes';
async function updateAttribute(params) { async function updateAttribute(params) {
const { username, characterID, attribute, newValue } = params; const { username, characterID, attribute, newValue } = params;
@ -12,10 +12,10 @@ function makeAPI(root) {
value: parseInt(newValue) value: parseInt(newValue)
}); });
const resource = attributeResource(username, characterID, attribute); const resource = attributesResource(username, characterID);
let resp = await fetch(resource, { let resp = await fetch(resource, {
method: 'POST', method: 'PATCH',
body: Attribute.encode(req).finish() body: Attribute.encode(req).finish()
}).then(async resp => { }).then(async resp => {
console.log("resp is", await resp.text()); console.log("resp is", await resp.text());

View File

@ -8,4 +8,8 @@
<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 %}