srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/server/src')
-rw-r--r--crates/server/src/billing.rs17
-rw-r--r--crates/server/src/cosmetics.rs22
2 files changed, 27 insertions, 12 deletions
diff --git a/crates/server/src/billing.rs b/crates/server/src/billing.rs
index fda42c7..2852b0d 100644
--- a/crates/server/src/billing.rs
+++ b/crates/server/src/billing.rs
@@ -80,6 +80,15 @@ async fn create_session(
amount_cents: i32,
) -> Result<String, AppError> {
let stripe = require_stripe(state)?;
+
+ // Nothing is ever sold for nothing. A zero here would mean a price that
+ // failed to decode or a catalogue row that was never given one, and the
+ // result would be a session that grants the item without charging.
+ if amount_cents <= 0 {
+ tracing::error!("refusing to create a checkout session for {name} at {amount_cents} cents");
+ return Err(AppError::Internal(anyhow::anyhow!("could not start checkout")));
+ }
+
let purchase_id = uuid::Uuid::new_v4().to_string();
let success = format!("{}/?purchase=done", state.frontend_origin);
@@ -161,8 +170,8 @@ async fn checkout_cosmetic(
.fetch_optional(&state.db)
.await?
.ok_or(AppError::NotFound)?;
- let name: String = row.try_get("name").unwrap_or_default();
- let price: i32 = row.try_get("price_cents").unwrap_or(0);
+ let name: String = row.try_get("name")?;
+ let price: i32 = row.try_get("price_cents")?;
// Already owned: charging again would be taking money for nothing.
let owned = sqlx::query("SELECT 1 FROM user_cosmetics WHERE user_id = $1 AND cosmetic_id = $2")
@@ -190,8 +199,8 @@ async fn checkout_bundle(
.fetch_optional(&state.db)
.await?
.ok_or(AppError::NotFound)?;
- let name: String = row.try_get("name").unwrap_or_default();
- let price: i32 = row.try_get("price_cents").unwrap_or(0);
+ let name: String = row.try_get("name")?;
+ let price: i32 = row.try_get("price_cents")?;
// A bundle whose every item is already owned has nothing to sell.
let remaining: i64 = sqlx::query_scalar(
diff --git a/crates/server/src/cosmetics.rs b/crates/server/src/cosmetics.rs
index b4532a8..de70950 100644
--- a/crates/server/src/cosmetics.rs
+++ b/crates/server/src/cosmetics.rs
@@ -25,7 +25,7 @@ struct Cosmetic {
id: String,
name: String,
category: String,
- price_cents: i64,
+ price_cents: i32,
value: String,
}
@@ -33,16 +33,22 @@ async fn list_catalog(State(state): State<Arc<AppState>>) -> Result<impl IntoRes
let rows = sqlx::query("SELECT id, name, category, price_cents, value FROM cosmetics ORDER BY category, price_cents")
.fetch_all(&state.db)
.await?;
+ // Decode errors are returned, not defaulted away. price_cents was typed
+ // i64 against an INTEGER column, so sqlx refused every decode and
+ // unwrap_or_default turned the whole catalogue into $0.00 with nothing
+ // logged. A wrong price is worse than an error page.
let items: Vec<Cosmetic> = rows
.into_iter()
- .map(|row| Cosmetic {
- id: row.try_get("id").unwrap_or_default(),
- name: row.try_get("name").unwrap_or_default(),
- category: row.try_get("category").unwrap_or_default(),
- price_cents: row.try_get("price_cents").unwrap_or_default(),
- value: row.try_get("value").unwrap_or_default(),
+ .map(|row| {
+ Ok(Cosmetic {
+ id: row.try_get("id")?,
+ name: row.try_get("name")?,
+ category: row.try_get("category")?,
+ price_cents: row.try_get("price_cents")?,
+ value: row.try_get("value")?,
+ })
})
- .collect();
+ .collect::<Result<_, sqlx::Error>>()?;
Ok(Json(items))
}