Compare commits
3 Commits
b467c32acb
...
76c67425b1
Author | SHA1 | Date |
---|---|---|
jeff | 76c67425b1 | |
jeff | 9968cc4a6e | |
jeff | d1e29b40ed |
|
@ -31,9 +31,12 @@ message Attributes {
|
||||||
int32 composure = 9;
|
int32 composure = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Attribute {
|
//Update an attribute's dot amount. TODO rename to AttributesUpdate.
|
||||||
string name = 1;
|
message UpdateAttributeRequest {
|
||||||
int32 value = 2;
|
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.
|
//Update skill entries in a Chronicles of Darkness character sheet.
|
||||||
|
@ -44,12 +47,33 @@ message Skills {
|
||||||
repeated CofdSheet.Skill social_skills = 3;
|
repeated CofdSheet.Skill social_skills = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Full update of a single skill
|
||||||
message SkillUpdate {
|
message SkillUpdate {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
CofdSheet.Skill skill = 2;
|
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.
|
//Add a Condition to a Chronicles of Darkness character sheet.
|
||||||
message Condition {
|
message Condition {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service CofdApi {
|
||||||
|
rpc UpdateSkillValue(UpdateSkillValueRequest) returns (CofdSheet.Skill);
|
||||||
|
}
|
|
@ -6,6 +6,15 @@ use std::ops::Deref;
|
||||||
|
|
||||||
pub mod cofd;
|
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
|
/// A struct wrapping a protobuf that allows it to be used as binary
|
||||||
/// data submitted via POST using fetch API. Can automatically be
|
/// data submitted via POST using fetch API. Can automatically be
|
||||||
/// dereferenced into its wrapped type.
|
/// dereferenced into its wrapped type.
|
||||||
|
@ -23,8 +32,28 @@ where
|
||||||
{
|
{
|
||||||
type Error = crate::errors::Error;
|
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;
|
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 {
|
let bytes: Vec<u8> = match data.open(2.mebibytes()).stream_to_vec().await {
|
||||||
Ok(read_bytes) => read_bytes,
|
Ok(read_bytes) => read_bytes,
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub(crate) fn routes() -> Vec<rocket::Route> {
|
||||||
cofd::update_attributes,
|
cofd::update_attributes,
|
||||||
cofd::update_attribute,
|
cofd::update_attribute,
|
||||||
cofd::update_skills,
|
cofd::update_skills,
|
||||||
|
cofd::update_skill_value,
|
||||||
cofd::add_condition,
|
cofd::add_condition,
|
||||||
cofd::remove_condition
|
cofd::remove_condition
|
||||||
]
|
]
|
||||||
|
@ -26,7 +27,7 @@ pub(crate) fn routes() -> Vec<rocket::Route> {
|
||||||
async fn load_character(
|
async fn load_character(
|
||||||
conn: &TenebrousDbConn<'_>,
|
conn: &TenebrousDbConn<'_>,
|
||||||
logged_in_user: Option<&User>,
|
logged_in_user: Option<&User>,
|
||||||
owner: String,
|
owner: &str,
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
) -> Result<Character, Error> {
|
) -> Result<Character, Error> {
|
||||||
let logged_in_user = logged_in_user.ok_or(Error::NotLoggedIn)?;
|
let logged_in_user = logged_in_user.ok_or(Error::NotLoggedIn)?;
|
||||||
|
@ -36,7 +37,7 @@ async fn load_character(
|
||||||
.await?
|
.await?
|
||||||
.ok_or(Error::NotFound)?;
|
.ok_or(Error::NotFound)?;
|
||||||
|
|
||||||
if logged_in_user.username != owner {
|
if &logged_in_user.username != owner {
|
||||||
return Err(Error::NoPermission);
|
return Err(Error::NoPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +50,35 @@ mod cofd {
|
||||||
use crate::models::proto::cofd::cofd_sheet::Skill;
|
use crate::models::proto::cofd::cofd_sheet::Skill;
|
||||||
use crate::models::proto::{cofd::api::*, cofd::*, Proto};
|
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>")]
|
#[post("/cofd/<owner>/<character_id>/basic-info", data = "<info>")]
|
||||||
pub(super) fn update_basic_info<'a>(
|
pub(super) fn update_basic_info<'a>(
|
||||||
owner: String,
|
owner: String,
|
||||||
|
@ -67,41 +97,45 @@ mod cofd {
|
||||||
"lol"
|
"lol"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[patch("/cofd/<owner>/<character_id>/attributes", data = "<attr_update>")]
|
#[post("/rpc/cofd/update_attribute", data = "<req>")]
|
||||||
pub(super) async fn update_attribute<'a>(
|
pub(super) async fn update_attribute<'a>(
|
||||||
owner: String,
|
req: Proto<UpdateAttributeRequest>,
|
||||||
character_id: i32,
|
|
||||||
attr_update: Proto<Attribute>,
|
|
||||||
conn: TenebrousDbConn<'_>,
|
conn: TenebrousDbConn<'_>,
|
||||||
logged_in_user: Option<&User>,
|
logged_in_user: Option<&User>,
|
||||||
) -> Result<&'a str, Error> {
|
) -> Result<&'a str, Error> {
|
||||||
let mut character = load_character(&conn, logged_in_user, owner, character_id).await?;
|
let mut character = load_character(
|
||||||
let mut sheet: CofdSheet = character.try_deserialize()?;
|
&conn,
|
||||||
|
logged_in_user,
|
||||||
|
&req.character_username,
|
||||||
|
req.character_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
match attr_update.name.to_lowercase().as_ref() {
|
let mut sheet: CofdSheet = character.try_deserialize()?;
|
||||||
"strength" => Ok(sheet.strength = attr_update.value),
|
let value = req.attribute_value;
|
||||||
"dexterity" => Ok(sheet.dexterity = attr_update.value),
|
match req.attribute_name.to_lowercase().as_ref() {
|
||||||
"stamina" => Ok(sheet.stamina = attr_update.value),
|
"strength" => Ok(sheet.strength = value),
|
||||||
"intelligence" => Ok(sheet.intelligence = attr_update.value),
|
"dexterity" => Ok(sheet.dexterity = value),
|
||||||
"wits" => Ok(sheet.wits = attr_update.value),
|
"stamina" => Ok(sheet.stamina = value),
|
||||||
"resolve" => Ok(sheet.resolve = attr_update.value),
|
"intelligence" => Ok(sheet.intelligence = value),
|
||||||
"presence" => Ok(sheet.presence = attr_update.value),
|
"wits" => Ok(sheet.wits = value),
|
||||||
"manipulation" => Ok(sheet.manipulation = attr_update.value),
|
"resolve" => Ok(sheet.resolve = value),
|
||||||
"composure" => Ok(sheet.composure = attr_update.value),
|
"presence" => Ok(sheet.presence = value),
|
||||||
|
"manipulation" => Ok(sheet.manipulation = value),
|
||||||
|
"composure" => Ok(sheet.composure = value),
|
||||||
_ => Err(Error::InvalidInput),
|
_ => Err(Error::InvalidInput),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
println!(
|
|
||||||
"updated {} attribute {} to {}",
|
|
||||||
character.character_name, attr_update.name, attr_update.value
|
|
||||||
);
|
|
||||||
|
|
||||||
character.update_data(sheet)?;
|
character.update_data(sheet)?;
|
||||||
conn.update_character_sheet(&character).await?;
|
conn.update_character_sheet(&character).await?;
|
||||||
Ok("lol")
|
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>(
|
pub(super) async fn update_skills<'a>(
|
||||||
owner: String,
|
owner: String,
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
|
@ -109,30 +143,13 @@ mod cofd {
|
||||||
conn: TenebrousDbConn<'_>,
|
conn: TenebrousDbConn<'_>,
|
||||||
logged_in_user: Option<&User>,
|
logged_in_user: Option<&User>,
|
||||||
) -> Result<&'a str, Error> {
|
) -> 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 mut sheet: CofdSheet = character.try_deserialize()?;
|
||||||
let skill: &Skill = skill_update.skill.as_ref().ok_or(Error::InvalidInput)?;
|
let updated_skill: &Skill = skill_update.skill.as_ref().ok_or(Error::InvalidInput)?;
|
||||||
|
let skill_entry = find_skill_entry(&mut sheet, &skill_update.name);
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
skill_entry
|
skill_entry
|
||||||
.map(|mut entry| entry.insert(skill.clone()))
|
.map(|mut entry| entry.insert(updated_skill.clone()))
|
||||||
.ok_or(Error::InvalidInput)?;
|
.ok_or(Error::InvalidInput)?;
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
|
@ -145,6 +162,35 @@ mod cofd {
|
||||||
Ok("lol")
|
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>")]
|
#[put("/cofd/<owner>/<character_id>/conditions", data = "<info>")]
|
||||||
pub(super) fn add_condition<'a>(
|
pub(super) fn add_condition<'a>(
|
||||||
owner: String,
|
owner: String,
|
||||||
|
|
|
@ -1,22 +1,65 @@
|
||||||
function makeAPI(root) {
|
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) =>
|
const attributesResource = (username, characterID) =>
|
||||||
'/api/cofd/' + username + '/' + characterID + '/attributes';
|
'/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) {
|
async function updateAttribute(params) {
|
||||||
const { username, characterID, attribute, newValue } = params;
|
const { username, characterID, attribute, newValue } = params;
|
||||||
|
|
||||||
let req = Attribute.create({
|
let req = verifyAndCreate(UpdateAttributeRequest, {
|
||||||
name: attribute,
|
characterUsername: username,
|
||||||
value: parseInt(newValue)
|
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, {
|
async function updateSkillValue(params) {
|
||||||
method: 'PATCH',
|
const { username, characterID, skillName, newValue } = params;
|
||||||
body: Attribute.encode(req).finish()
|
|
||||||
|
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 => {
|
}).then(async resp => {
|
||||||
console.log("resp is", await resp.text());
|
console.log("resp is", await resp.text());
|
||||||
}).catch(async err => {
|
}).catch(async err => {
|
||||||
|
@ -25,6 +68,7 @@ function makeAPI(root) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updateAttribute
|
updateAttribute,
|
||||||
|
updateSkillValue
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,18 +12,37 @@
|
||||||
function setupAttributes() {
|
function setupAttributes() {
|
||||||
const attributeInputs = document.querySelectorAll('#attributes input[type="number"]');
|
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 => {
|
Array.from(attributeInputs).forEach(input => {
|
||||||
input.addEventListener('change', async function(event) {
|
input.addEventListener('change', attributeHandler);
|
||||||
console.log("updating attr");
|
});
|
||||||
const attribute = event.target.id;
|
}
|
||||||
const newValue = parseInt(event.target.value);
|
|
||||||
const params = { username: USERNAME, characterID: CHARACTER_ID, attribute, newValue };
|
function setupSkills() {
|
||||||
await api.updateAttribute(params);
|
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();
|
setupAttributes();
|
||||||
|
setupSkills();
|
||||||
})().catch(e => {
|
})().catch(e => {
|
||||||
alert(e);
|
alert(e);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue