mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Allow promo codes to only be redeemed once
This commit is contained in:
@@ -544,6 +544,7 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
|
||||
bundle_id: val.bundle_id.to_owned().unwrap_or_else(|| key.to_owned()),
|
||||
promo_id: val.promo_id.to_owned().unwrap_or_else(|| key.to_owned()),
|
||||
is_serial: val.is_serial,
|
||||
is_repeatable: val.is_repeatable,
|
||||
value: val.value,
|
||||
transaction: tx,
|
||||
});
|
||||
|
||||
@@ -498,6 +498,7 @@ pub struct PromoCode {
|
||||
pub bundle_id: String,
|
||||
pub promo_id: String,
|
||||
pub is_serial: bool,
|
||||
pub is_repeatable: bool,
|
||||
pub value: f32,
|
||||
pub transaction: ShopAction,
|
||||
}
|
||||
|
||||
@@ -213,6 +213,8 @@ pub struct ItemCode {
|
||||
#[serde(default)]
|
||||
pub is_serial: bool,
|
||||
#[serde(default)]
|
||||
pub is_repeatable: bool,
|
||||
#[serde(default)]
|
||||
pub value: f32,
|
||||
pub gives: Vec<ItemPurchase>,
|
||||
}
|
||||
@@ -389,6 +391,16 @@ pub fn default_codes() -> std::collections::HashMap<String, ItemCode> {
|
||||
bundle_id: None,
|
||||
promo_id: None,
|
||||
is_serial: false,
|
||||
is_repeatable: true,
|
||||
value: 1.5,
|
||||
gives: vec![]
|
||||
});
|
||||
map.insert("TEST-ONCE".to_owned(), ItemCode {
|
||||
message: Some("Test passed".to_owned()),
|
||||
bundle_id: None,
|
||||
promo_id: None,
|
||||
is_serial: false,
|
||||
is_repeatable: false,
|
||||
value: 1.5,
|
||||
gives: vec![]
|
||||
});
|
||||
@@ -397,6 +409,7 @@ pub fn default_codes() -> std::collections::HashMap<String, ItemCode> {
|
||||
bundle_id: None,
|
||||
promo_id: None,
|
||||
is_serial: false,
|
||||
is_repeatable: false,
|
||||
value: 1.5,
|
||||
gives: vec![
|
||||
ItemPurchase::Experience {
|
||||
|
||||
@@ -1398,6 +1398,71 @@ impl <C: Clone + Send> super::User<C> for UserData {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn mark_code_redeemed(&self, code: String) -> Result<bool, polariton_server::operations::SimpleOpError> {
|
||||
// TODO support serial (single global use) codes
|
||||
// FIXME this should probably be a transaction
|
||||
let codes_opt = self.db.user_aux_by_user_id_and_descriptor(self.account.id, oj_rc_database::schema::user_aux::Descriptor::RedeemedPromoCodes).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve RedeemedPromoCodes (user_aux) for user_id {}: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::WebServicesError::DatabaseError as i16,
|
||||
"Failed to retrieve RedeemedPromoCodes".to_owned(),
|
||||
)
|
||||
})?;
|
||||
if let Some(codes_entity) = codes_opt {
|
||||
match serde_json::from_str::<Vec<String>>(&codes_entity.data) {
|
||||
Ok(mut codes) => {
|
||||
if codes.contains(&code) {
|
||||
Ok(false)
|
||||
} else {
|
||||
codes.push(code);
|
||||
let active_model = oj_rc_database::schema::user_aux::ActiveModel {
|
||||
data: oj_rc_database::sea_orm::ActiveValue::Set(serde_json::to_string(&codes).unwrap()),
|
||||
..Default::default()
|
||||
};
|
||||
self.db.update_user_aux_by_user_id_and_descriptor(
|
||||
active_model,
|
||||
self.account.id,
|
||||
oj_rc_database::schema::user_aux::Descriptor::RedeemedPromoCodes,
|
||||
).await.map_err(|e| {
|
||||
log::error!("Failed to update RedeemedPromoCodes (user_aux id {}) for user_id {}: {}", codes_entity.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::WebServicesError::DatabaseError as i16,
|
||||
"Failed to update RedeemedPromoCodes".to_owned(),
|
||||
)
|
||||
})?;
|
||||
Ok(true)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to parse RedeemedPromoCodes (user_aux id {}) for user_id {}: {}", self.account.id, codes_entity.id, e);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::WebServicesError::DatabaseError as i16,
|
||||
"Failed to parse RedeemedPromoCodes JSON".to_owned(),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// user_aux entry needs to be created
|
||||
let new_codes = oj_rc_database::schema::user_aux::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
user_id: oj_rc_database::sea_orm::ActiveValue::Set(self.account.id),
|
||||
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(chrono::Utc::now().timestamp()),
|
||||
descriptor: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::user_aux::Descriptor::RedeemedPromoCodes),
|
||||
data: oj_rc_database::sea_orm::ActiveValue::Set(serde_json::to_string(&vec![code]).unwrap()),
|
||||
};
|
||||
self.db.insert_user_aux(vec![new_codes]).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to insert RedeemedPromoCodes (user_aux) for user_id {}: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::WebServicesError::DatabaseError as i16,
|
||||
"Failed to insert updated RedeemedPromoCodes".to_owned(),
|
||||
)
|
||||
})?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GameEventSetterImpl {
|
||||
|
||||
@@ -85,6 +85,7 @@ pub trait User<C>: ChatUser + SocialUser + LobbyUser + MultiplayerUser + Singlep
|
||||
fn current_game_event_setter(&self) -> Box<dyn GameEventSetter>;
|
||||
async fn apply_purchase(&self, action: &crate::persist::config::ShopAction) -> Result<PurchaseResult, polariton_server::operations::SimpleOpError>;
|
||||
async fn currency_debit(&self, ty: CurrencyType, to_sub: u64) -> Result<(), polariton_server::operations::SimpleOpError>;
|
||||
async fn mark_code_redeemed(&self, code: String) -> Result<bool, polariton_server::operations::SimpleOpError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
||||
@@ -43,4 +43,5 @@ pub enum Descriptor {
|
||||
LastSeen, // u64, seconds since Unix epoch
|
||||
SubscribedChannels, // Vec<String>, JSON
|
||||
AvatarId, // u32, u32::MAX means custom avatar
|
||||
RedeemedPromoCodes, // Vec<String>, JSON
|
||||
}
|
||||
|
||||
@@ -41,6 +41,22 @@ impl SimpleOperation<()> for PromoCodeApplier {
|
||||
if let Some(Typed::Str(promo_code)) = params.remove(&CODE_NAME_PARAM_KEY) {
|
||||
let user_info = user.user()?;
|
||||
if let Some(code_info) = self.code_map.get(&promo_code.string) {
|
||||
if !code_info.is_repeatable {
|
||||
if !user_info.mark_code_redeemed(promo_code.string.clone()).await? {
|
||||
params.insert(SUCCESS_PARAM_KEY, Typed::Bool(false));
|
||||
params.insert(RESULT_CODE_PARAM_KEY, Typed::Int(PromoResultCode::AlreadyAwarded as _));
|
||||
params.insert(IS_SERIAL_PARAM_KEY, Typed::Bool(false));
|
||||
params.insert(VALUE_PARAM_KEY, Typed::Float(0.0));
|
||||
params.insert(PROMO_ID_PARAM_KEY, Typed::Str(promo_code.clone()));
|
||||
params.insert(CUBES_AWARDED_PARAM_KEY, Typed::Str("{}".into()));
|
||||
params.insert(MSG_PARAM_KEY, Typed::Str("".into()));
|
||||
params.insert(BUNDLE_ID_PARAM_KEY, Typed::Str(code_info.bundle_id.clone().into()));
|
||||
params.insert(ROBOPASS_PARAM_KEY, Typed::Bool(false));
|
||||
params.insert(PAID_CURRENCY_PARAM_KEY, Typed::Long(0));
|
||||
log::debug!("Code \"{}\" not redeemed by {} (code already redeemed)", promo_code.string, user_info.public_id());
|
||||
return Ok(params);
|
||||
}
|
||||
}
|
||||
let result = user_info.apply_purchase(&code_info.transaction).await?;
|
||||
params.insert(SUCCESS_PARAM_KEY, Typed::Bool(result.success));
|
||||
params.insert(RESULT_CODE_PARAM_KEY, Typed::Int(PromoResultCode::Success as _));
|
||||
|
||||
Reference in New Issue
Block a user