Compare commits
3 Commits
b467c32acb
...
76c67425b1
Author | SHA1 | Date |
---|---|---|
jeff | 76c67425b1 | |
jeff | 9968cc4a6e | |
jeff | d1e29b40ed |
|
@ -31,9 +31,12 @@ message Attributes {
|
|||
int32 composure = 9;
|
||||
}
|
||||
|
||||
message Attribute {
|
||||
string name = 1;
|
||||
int32 value = 2;
|
||||
//Update an attribute's dot amount. TODO rename to AttributesUpdate.
|
||||
message UpdateAttributeRequest {
|
||||
string character_username = 1;
|
||||
int32 character_id = 2;
|
||||
string attribute_name = 3;
|
||||
int32 attribute_value = 4;
|
||||
}
|
||||
|
||||
//Update skill entries in a Chronicles of Darkness character sheet.
|
||||
|
@ -44,12 +47,33 @@ 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 UpdateSkillValueRequest {
|
||||
string character_username = 1;
|
||||
int32 character_id = 2;
|
||||
string skill_name = 3;
|
||||
int32 skill_value = 4;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
service CofdApi {
|
||||
rpc UpdateSkillValue(UpdateSkillValueRequest) returns (CofdSheet.Skill);
|
||||
}
|
|
@ -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,28 @@ 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>()) {
|
||||
println!("message type is {:?}", message_type);
|
||||
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
|
||||
]
|
||||
|
@ -26,7 +27,7 @@ pub(crate) fn routes() -> Vec<rocket::Route> {
|
|||
async fn load_character(
|
||||
conn: &TenebrousDbConn<'_>,
|
||||
logged_in_user: Option<&User>,
|
||||
owner: String,
|
||||
owner: &str,
|
||||
character_id: i32,
|
||||
) -> Result<Character, Error> {
|
||||
let logged_in_user = logged_in_user.ok_or(Error::NotLoggedIn)?;
|
||||
|
@ -36,7 +37,7 @@ async fn load_character(
|
|||
.await?
|
||||
.ok_or(Error::NotFound)?;
|
||||
|
||||
if logged_in_user.username != owner {
|
||||
if &logged_in_user.username != owner {
|
||||
return Err(Error::NoPermission);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -67,41 +97,45 @@ mod cofd {
|
|||
"lol"
|
||||
}
|
||||
|
||||
#[patch("/cofd/<owner>/<character_id>/attributes", data = "<attr_update>")]
|
||||
#[post("/rpc/cofd/update_attribute", data = "<req>")]
|
||||
pub(super) async fn update_attribute<'a>(
|
||||
owner: String,
|
||||
character_id: i32,
|
||||
attr_update: Proto<Attribute>,
|
||||
req: Proto<UpdateAttributeRequest>,
|
||||
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 mut character = load_character(
|
||||
&conn,
|
||||
logged_in_user,
|
||||
&req.character_username,
|
||||
req.character_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
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),
|
||||
let mut sheet: CofdSheet = character.try_deserialize()?;
|
||||
let value = req.attribute_value;
|
||||
match req.attribute_name.to_lowercase().as_ref() {
|
||||
"strength" => Ok(sheet.strength = value),
|
||||
"dexterity" => Ok(sheet.dexterity = value),
|
||||
"stamina" => Ok(sheet.stamina = value),
|
||||
"intelligence" => Ok(sheet.intelligence = value),
|
||||
"wits" => Ok(sheet.wits = value),
|
||||
"resolve" => Ok(sheet.resolve = value),
|
||||
"presence" => Ok(sheet.presence = value),
|
||||
"manipulation" => Ok(sheet.manipulation = value),
|
||||
"composure" => Ok(sheet.composure = 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")
|
||||
}
|
||||
|
||||
#[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,
|
||||
|
@ -109,30 +143,13 @@ mod cofd {
|
|||
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 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,35 @@ mod cofd {
|
|||
Ok("lol")
|
||||
}
|
||||
|
||||
#[post("/rpc/cofd/update_skill_value", data = "<request>")]
|
||||
pub(super) async fn update_skill_value<'a>(
|
||||
request: Proto<UpdateSkillValueRequest>,
|
||||
conn: TenebrousDbConn<'_>,
|
||||
logged_in_user: Option<&User>,
|
||||
) -> Result<&'a str, Error> {
|
||||
println!("{:#?}", request);
|
||||
let mut character = load_character(
|
||||
&conn,
|
||||
logged_in_user,
|
||||
&request.character_username,
|
||||
request.character_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut sheet: CofdSheet = character.try_deserialize()?;
|
||||
let skill: Option<&mut Skill> = find_skill(&mut sheet, &request.skill_name);
|
||||
|
||||
skill
|
||||
.map(|s| s.dots = request.skill_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,22 +1,65 @@
|
|||
function makeAPI(root) {
|
||||
const Attribute = root.lookupType("models.proto.cofd.api.Attribute");
|
||||
//Protobuf types
|
||||
const UpdateAttributeRequestType = 'models.proto.cofd.api.UpdateAttributeRequest';
|
||||
const UpdateAttributeRequest = root.lookupType(UpdateAttributeRequestType);
|
||||
|
||||
const UpdateSkillValueRequestType = 'models.proto.cofd.api.UpdateSkillValueRequest';
|
||||
const UpdateSkillValueRequest = root.lookupType(UpdateSkillValueRequestType);
|
||||
|
||||
//TODO rpc-ify
|
||||
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';
|
||||
|
||||
function verifyAndCreate(protobufType, payload) {
|
||||
let err = protobufType.verify(payload);
|
||||
if (err) throw err;
|
||||
return protobufType.create(payload);
|
||||
}
|
||||
|
||||
async function updateAttribute(params) {
|
||||
const { username, characterID, attribute, newValue } = params;
|
||||
|
||||
let req = Attribute.create({
|
||||
name: attribute,
|
||||
value: parseInt(newValue)
|
||||
let req = verifyAndCreate(UpdateAttributeRequest, {
|
||||
characterUsername: username,
|
||||
characterId: parseInt(characterID),
|
||||
attributeName: attribute,
|
||||
attributeValue: parseInt(newValue)
|
||||
});
|
||||
|
||||
const resource = attributesResource(username, characterID);
|
||||
let resp = await fetch('/api/rpc/cofd/update_attribute', {
|
||||
method: 'POST',
|
||||
headers: { ... protobufContentType(UpdateAttributeRequestType) },
|
||||
body: UpdateAttributeRequest.encode(req).finish()
|
||||
}).then(async resp => {
|
||||
console.log("resp is", await resp.text());
|
||||
}).catch(async err => {
|
||||
console.log("err is", err.text());
|
||||
});
|
||||
}
|
||||
|
||||
let resp = await fetch(resource, {
|
||||
method: 'PATCH',
|
||||
body: Attribute.encode(req).finish()
|
||||
async function updateSkillValue(params) {
|
||||
const { username, characterID, skillName, newValue } = params;
|
||||
|
||||
let req = verifyAndCreate(UpdateSkillValueRequest, {
|
||||
characterUsername: username,
|
||||
characterId: parseInt(characterID),
|
||||
skillName: skillName,
|
||||
skillValue: parseInt(newValue)
|
||||
});
|
||||
|
||||
let resp = await fetch('/api/rpc/cofd/update_skill_value', {
|
||||
method: 'POST',
|
||||
headers: { ... protobufContentType(UpdateSkillValueRequestType) },
|
||||
body: UpdateSkillValueRequest.encode(req).finish()
|
||||
}).then(async resp => {
|
||||
console.log("resp is", await resp.text());
|
||||
}).catch(async err => {
|
||||
|
@ -25,6 +68,7 @@ function makeAPI(root) {
|
|||
}
|
||||
|
||||
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