feat(sbc): make submissions atomic and durable
CI / Build, lint & test (push) Failing after 52s

This commit is contained in:
funman300
2026-08-18 18:26:23 +00:00
parent 637a21eac1
commit 271c3639ed
7 changed files with 1264 additions and 85 deletions
+5
View File
@@ -251,8 +251,13 @@ pub async fn build(pool: Pool, cfg: Config) -> Result<Router> {
.route("/matches/opponent", get(routes::matches::get_opponent))
.route("/matches/result", post(routes::matches::post_match_result))
.route("/sbc", get(routes::sbc::get_sbcs))
.route("/sbc/status", get(routes::sbc::get_sbc_status))
.route("/sbc/submit", post(routes::sbc::post_sbc_submit))
.route("/sbc/:sbc_id", get(routes::sbc::get_sbc))
.route(
"/sbc/:sbc_id/squad",
get(routes::sbc::get_sbc_squad).put(routes::sbc::put_sbc_squad),
)
.route("/market", get(routes::market::get_market))
.route("/market/buy", post(routes::market::post_market_buy))
.route("/market/sell", post(routes::market::post_market_sell))
+14 -2
View File
@@ -33,16 +33,17 @@ pub struct SbcReward {
pub pack_id: Option<String>,
}
/// DB record of a completed submission
#[allow(dead_code)]
/// DB record of a completed submission.
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct SbcSubmission {
pub id: String,
pub profile_id: String,
pub club_id: Option<String>,
pub sbc_id: String,
pub submitted_card_ids: String,
pub passed: bool,
pub submitted_at: String,
pub repeatable: bool,
}
#[derive(Debug, Deserialize)]
@@ -51,6 +52,17 @@ pub struct SubmitSbcRequest {
pub owned_card_ids: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct SaveSbcSquadRequest {
pub owned_card_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SbcSquadState {
pub sbc_id: String,
pub owned_card_ids: Vec<String>,
}
#[derive(Debug, Serialize)]
pub struct SbcResult {
pub passed: bool,
+52 -12
View File
@@ -8,7 +8,7 @@ use serde_json::{json, Value};
use crate::{
app::AppState,
error::{AppError, AppResult},
models::sbc::{SbcResult, SubmitSbcRequest},
models::sbc::{SaveSbcSquadRequest, SbcResult, SubmitSbcRequest},
services::{club as club_svc, profile as profile_svc, sbc as sbc_svc},
};
@@ -28,6 +28,49 @@ pub async fn get_sbc(
Ok(Json(json!({ "sbc": sbc })))
}
pub async fn get_sbc_status(State(state): State<AppState>, game: GameId) -> AppResult<Json<Value>> {
let profile = profile_svc::get_active_profile(&state.pool, game.as_str()).await?;
let completions = sbc_svc::completion_counts(&state.pool, &profile.id).await?;
Ok(Json(json!({ "completions": completions })))
}
pub async fn get_sbc_squad(
State(state): State<AppState>,
game: GameId,
Path(sbc_id): Path<String>,
) -> AppResult<Json<Value>> {
if !state
.sbc_defs
.iter()
.any(|definition| definition.id == sbc_id)
{
return Err(AppError::NotFound(format!("SBC '{sbc_id}' not found")));
}
let profile = profile_svc::get_active_profile(&state.pool, game.as_str()).await?;
let squad = sbc_svc::load_sbc_squad(&state.pool, &profile.id, &sbc_id).await?;
Ok(Json(json!({ "squad": squad })))
}
pub async fn put_sbc_squad(
State(state): State<AppState>,
game: GameId,
Path(sbc_id): Path<String>,
Json(req): Json<SaveSbcSquadRequest>,
) -> AppResult<Json<Value>> {
let profile = profile_svc::get_active_profile(&state.pool, game.as_str()).await?;
let club = club_svc::get_club_by_profile(&state.pool, &profile.id).await?;
let squad = sbc_svc::save_sbc_squad(
&state.pool,
&state.sbc_defs,
&profile.id,
&club.id,
&sbc_id,
&req.owned_card_ids,
)
.await?;
Ok(Json(json!({ "squad": squad })))
}
pub async fn post_sbc_submit(
State(state): State<AppState>,
game: GameId,
@@ -38,20 +81,17 @@ pub async fn post_sbc_submit(
let result = sbc_svc::submit_sbc(
&state.pool,
&state.card_db,
&state.sbc_defs,
&state.obj_defs,
&profile.id,
&club.id,
sbc_svc::SbcSubmissionContext {
card_db: &state.card_db,
sbc_defs: &state.sbc_defs,
objective_defs: &state.obj_defs,
achievement_defs: &state.achievement_defs,
profile_id: &profile.id,
club_id: &club.id,
},
&req,
)
.await?;
if result.passed {
let _ = crate::services::achievement::check_and_unlock(
&state.pool, &state.achievement_defs, &profile.id, &club.id,
).await;
}
Ok(Json(result))
}
+1169 -71
View File
File diff suppressed because it is too large Load Diff