Implement updating skills.
This commit is contained in:
parent
0ca23b46c6
commit
ee8f7e58e8
|
@ -31,6 +31,7 @@ message Attributes {
|
|||
int32 composure = 9;
|
||||
}
|
||||
|
||||
//Update an attribute's dot amount. TODO rename to AttributesUpdate.
|
||||
message Attribute {
|
||||
string name = 1;
|
||||
int32 value = 2;
|
||||
|
@ -44,11 +45,26 @@ message Skills {
|
|||
repeated CofdSheet.Skill social_skills = 3;
|
||||
}
|
||||
|
||||
//Full update of a single skill
|
||||
message SkillUpdate {
|
||||
string name = 1;
|
||||
CofdSheet.Skill skill = 2;
|
||||
}
|
||||
|
||||
|
||||
//Partial update of a single skill dot amount.
|
||||
message SkillValueUpdate {
|
||||
string name = 1;
|
||||
int32 value = 2;
|
||||
}
|
||||
|
||||
//Partial update of only a skill's specializations. The
|
||||
//specializations will be overwritten with the new values.
|
||||
message SkillSpecializationsUpdate {
|
||||
string name = 1;
|
||||
repeated string specializations = 2;
|
||||
}
|
||||
|
||||
//Add a Condition to a Chronicles of Darkness character sheet.
|
||||
message Condition {
|
||||
string name = 1;
|
||||
|
|
|
@ -6,6 +6,15 @@ use std::ops::Deref;
|
|||
|
||||
pub mod cofd;
|
||||
|
||||
const CRATE_NAME: &'static str = env!("CARGO_BIN_NAME");
|
||||
|
||||
/// Convert an incoming protobuf content-type to the equivalent type
|
||||
/// name produced by std::any::type_name(). Currently does NOT work
|
||||
/// with nested types due to how prost generates the module names.
|
||||
fn convert_to_rust_name(proto_type: &str) -> String {
|
||||
format!("{}::{}", CRATE_NAME, proto_type.replace(".", "::"))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
@ -23,8 +32,27 @@ where
|
|||
{
|
||||
type Error = crate::errors::Error;
|
||||
|
||||
async fn from_data(_req: &Request<'_>, data: Data) -> Outcome<Self, Error> {
|
||||
async fn from_data(req: &Request<'_>, data: Data) -> Outcome<Self, Error> {
|
||||
use rocket::http::Status;
|
||||
let content_type = req.content_type();
|
||||
|
||||
let is_protobuf = content_type
|
||||
.map(|ct| ct.top() == "application" && ct.sub() == "x-protobuf")
|
||||
.unwrap_or(false);
|
||||
|
||||
let message_type: Option<String> = content_type.and_then(|ct| {
|
||||
ct.params()
|
||||
.find(|&(name, _)| name == "messageType")
|
||||
.map(|(_, message_type)| convert_to_rust_name(message_type))
|
||||
});
|
||||
|
||||
if !is_protobuf {
|
||||
return Outcome::Failure((Status::new(422, "invalid protobuf"), Error::InvalidInput));
|
||||
}
|
||||
|
||||
if message_type.as_ref().map(String::as_str) != Some(std::any::type_name::<T>()) {
|
||||
return Outcome::Forward(data);
|
||||
}
|
||||
|
||||
let bytes: Vec<u8> = match data.open(2.mebibytes()).stream_to_vec().await {
|
||||
Ok(read_bytes) => read_bytes,
|
||||
|
|
|
@ -14,6 +14,7 @@ pub(crate) fn routes() -> Vec<rocket::Route> {
|
|||
cofd::update_attributes,
|
||||
cofd::update_attribute,
|
||||
cofd::update_skills,
|
||||
cofd::update_skill_value,
|
||||
cofd::add_condition,
|
||||
cofd::remove_condition
|
||||
]
|
||||
|
@ -49,6 +50,35 @@ mod cofd {
|
|||
use crate::models::proto::cofd::cofd_sheet::Skill;
|
||||
use crate::models::proto::{cofd::api::*, cofd::*, Proto};
|
||||
|
||||
fn find_skill_entry<'a>(
|
||||
sheet: &'a mut CofdSheet,
|
||||
skill_name: &'a str,
|
||||
) -> Option<OccupiedEntry<'a, String, Skill>> {
|
||||
let all_skills = vec![
|
||||
&mut sheet.mental_skills,
|
||||
&mut sheet.physical_skills,
|
||||
&mut sheet.social_skills,
|
||||
];
|
||||
|
||||
// Search all skill lists for this value using "workaround" to
|
||||
// break value from for loops.
|
||||
let skill: Option<OccupiedEntry<_, _>> = 'l: loop {
|
||||
for skill_map in all_skills {
|
||||
if let Entry::Occupied(entry) = skill_map.entry(skill_name.to_owned()) {
|
||||
break 'l Some(entry);
|
||||
}
|
||||
}
|
||||
|
||||
break None;
|
||||
};
|
||||
|
||||
skill
|
||||
}
|
||||
|
||||
fn find_skill<'a>(sheet: &'a mut CofdSheet, skill_name: &'a str) -> Option<&'a mut Skill> {
|
||||
find_skill_entry(sheet, skill_name).map(|entry| entry.into_mut())
|
||||
}
|
||||
|
||||
#[post("/cofd/<owner>/<character_id>/basic-info", data = "<info>")]
|
||||
pub(super) fn update_basic_info<'a>(
|
||||
owner: String,
|
||||
|
@ -101,7 +131,11 @@ mod cofd {
|
|||
Ok("lol")
|
||||
}
|
||||
|
||||
#[patch("/cofd/<owner>/<character_id>/skills", data = "<skill_update>")]
|
||||
#[patch(
|
||||
"/cofd/<owner>/<character_id>/skills",
|
||||
data = "<skill_update>",
|
||||
rank = 1
|
||||
)]
|
||||
pub(super) async fn update_skills<'a>(
|
||||
owner: String,
|
||||
character_id: i32,
|
||||
|
@ -111,28 +145,11 @@ mod cofd {
|
|||
) -> Result<&'a str, Error> {
|
||||
let mut character = load_character(&conn, logged_in_user, owner, character_id).await?;
|
||||
let mut sheet: CofdSheet = character.try_deserialize()?;
|
||||
let skill: &Skill = skill_update.skill.as_ref().ok_or(Error::InvalidInput)?;
|
||||
|
||||
let all_skills = vec![
|
||||
&mut sheet.mental_skills,
|
||||
&mut sheet.physical_skills,
|
||||
&mut sheet.social_skills,
|
||||
];
|
||||
|
||||
// Search all skill lists for this value using "workaround" to
|
||||
// break value from for loops.
|
||||
let skill_entry: Option<OccupiedEntry<_, _>> = 'l: loop {
|
||||
for skill_map in all_skills {
|
||||
if let Entry::Occupied(entry) = skill_map.entry(skill_update.name.clone()) {
|
||||
break 'l Some(entry);
|
||||
}
|
||||
}
|
||||
|
||||
break None;
|
||||
};
|
||||
let updated_skill: &Skill = skill_update.skill.as_ref().ok_or(Error::InvalidInput)?;
|
||||
let skill_entry = find_skill_entry(&mut sheet, &skill_update.name);
|
||||
|
||||
skill_entry
|
||||
.map(|mut entry| entry.insert(skill.clone()))
|
||||
.map(|mut entry| entry.insert(updated_skill.clone()))
|
||||
.ok_or(Error::InvalidInput)?;
|
||||
|
||||
println!(
|
||||
|
@ -145,6 +162,33 @@ mod cofd {
|
|||
Ok("lol")
|
||||
}
|
||||
|
||||
#[patch(
|
||||
"/cofd/<owner>/<character_id>/skills",
|
||||
data = "<value_update>",
|
||||
rank = 2
|
||||
)]
|
||||
pub(super) async fn update_skill_value<'a>(
|
||||
owner: String,
|
||||
character_id: i32,
|
||||
value_update: Proto<SkillValueUpdate>,
|
||||
conn: TenebrousDbConn<'_>,
|
||||
logged_in_user: Option<&User>,
|
||||
) -> Result<&'a str, Error> {
|
||||
let mut character = load_character(&conn, logged_in_user, owner, character_id).await?;
|
||||
let mut sheet: CofdSheet = character.try_deserialize()?;
|
||||
let skill: Option<&mut Skill> = find_skill(&mut sheet, &value_update.name);
|
||||
|
||||
skill
|
||||
.map(|s| s.dots = value_update.value)
|
||||
.ok_or(Error::InvalidInput)?;
|
||||
|
||||
println!("updated skill value",);
|
||||
|
||||
character.update_data(sheet)?;
|
||||
conn.update_character_sheet(&character).await?;
|
||||
Ok("lol")
|
||||
}
|
||||
|
||||
#[put("/cofd/<owner>/<character_id>/conditions", data = "<info>")]
|
||||
pub(super) fn add_condition<'a>(
|
||||
owner: String,
|
||||
|
|
|
@ -1,9 +1,21 @@
|
|||
function makeAPI(root) {
|
||||
const Attribute = root.lookupType("models.proto.cofd.api.Attribute");
|
||||
//Protobuf types
|
||||
const AttributeType = 'models.proto.cofd.api.Attribute';
|
||||
const Attribute = root.lookupType(AttributeType);
|
||||
const SkillValueUpdateType = 'models.proto.cofd.api.SkillValueUpdate';
|
||||
const SkillValueUpdate = root.lookupType(SkillValueUpdateType);
|
||||
const SkillSpecializationUpdateType = 'models.proto.cofd.api.SkillSpecializationsUpdate';
|
||||
const SkillSpecializationsUpdate = root.lookupType(SkillSpecializationUpdateType);
|
||||
|
||||
const protobufContentType = (messageType) =>
|
||||
({ 'Content-Type': 'application/x-protobuf; messageType="' + messageType + '"' });
|
||||
|
||||
const attributesResource = (username, characterID) =>
|
||||
'/api/cofd/' + username + '/' + characterID + '/attributes';
|
||||
|
||||
const skillResource = (username, characterID, skillName) =>
|
||||
'/api/cofd/' + username + '/' + characterID + '/skills';
|
||||
|
||||
async function updateAttribute(params) {
|
||||
const { username, characterID, attribute, newValue } = params;
|
||||
|
||||
|
@ -16,6 +28,7 @@ function makeAPI(root) {
|
|||
|
||||
let resp = await fetch(resource, {
|
||||
method: 'PATCH',
|
||||
headers: { ... protobufContentType(AttributeType) },
|
||||
body: Attribute.encode(req).finish()
|
||||
}).then(async resp => {
|
||||
console.log("resp is", await resp.text());
|
||||
|
@ -24,7 +37,29 @@ function makeAPI(root) {
|
|||
});
|
||||
}
|
||||
|
||||
async function updateSkillValue(params) {
|
||||
const { username, characterID, skillName, newValue } = params;
|
||||
|
||||
let req = SkillValueUpdate.create({
|
||||
name: skillName,
|
||||
value: parseInt(newValue)
|
||||
});
|
||||
|
||||
const resource = skillResource(username, characterID);
|
||||
|
||||
let resp = await fetch(resource, {
|
||||
method: 'PATCH',
|
||||
headers: { ... protobufContentType(SkillValueUpdateType) },
|
||||
body: SkillValueUpdate.encode(req).finish()
|
||||
}).then(async resp => {
|
||||
console.log("resp is", await resp.text());
|
||||
}).catch(async err => {
|
||||
console.log("err is", err.text());
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
updateAttribute
|
||||
updateAttribute,
|
||||
updateSkillValue
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,18 +12,37 @@
|
|||
function setupAttributes() {
|
||||
const attributeInputs = document.querySelectorAll('#attributes input[type="number"]');
|
||||
|
||||
async function attributeHandler(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);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
input.addEventListener('change', attributeHandler);
|
||||
});
|
||||
}
|
||||
|
||||
function setupSkills() {
|
||||
const attributeInputs = document.querySelectorAll('#skills input[type="number"]');
|
||||
|
||||
async function skillValueHandler(event) {
|
||||
console.log("updating skill value");
|
||||
const skillName = event.target.id;
|
||||
const newValue = parseInt(event.target.value);
|
||||
const params = { username: USERNAME, characterID: CHARACTER_ID, skillName, newValue };
|
||||
await api.updateSkillValue(params);
|
||||
}
|
||||
|
||||
Array.from(attributeInputs).forEach(input => {
|
||||
input.addEventListener('change', skillValueHandler);
|
||||
});
|
||||
}
|
||||
|
||||
setupAttributes();
|
||||
setupSkills();
|
||||
})().catch(e => {
|
||||
alert(e);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue