1
0
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:
NG (Graham)
2026-02-07 12:27:48 -05:00
parent 514db6fd02
commit 31d7dd462f
7 changed files with 98 additions and 0 deletions

View File

@@ -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,
});

View File

@@ -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,
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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]