This commit is contained in:
2026-05-01 16:08:19 +08:00
parent dce84f677d
commit 14208ccb64
15 changed files with 752 additions and 175 deletions

View File

@@ -29,7 +29,8 @@ use shared_contracts::runtime::{
ProfileRechargeOrderResponse, ProfileRechargeProductResponse, ProfileRedeemCodeAdminResponse,
ProfileReferralInviteCenterResponse, ProfileWalletLedgerEntryResponse,
ProfileWalletLedgerResponse, RedeemProfileReferralInviteCodeRequest,
RedeemProfileRewardCodeRequest, RedeemProfileRewardCodeResponse,
RedeemProfileReferralInviteCodeResponse, RedeemProfileRewardCodeRequest,
RedeemProfileRewardCodeResponse,
};
use spacetime_client::SpacetimeClientError;
use time::OffsetDateTime;
@@ -216,14 +217,27 @@ pub async fn get_profile_referral_invite_center(
}
pub async fn redeem_profile_referral_invite_code(
State(_state): State<AppState>,
State(state): State<AppState>,
Extension(request_context): Extension<RequestContext>,
Extension(_authenticated): Extension<AuthenticatedAccessToken>,
Json(_payload): Json<RedeemProfileReferralInviteCodeRequest>,
Extension(authenticated): Extension<AuthenticatedAccessToken>,
Json(payload): Json<RedeemProfileReferralInviteCodeRequest>,
) -> Result<Json<Value>, Response> {
Err(runtime_profile_error_response(
&request_context,
AppError::from_status(StatusCode::BAD_REQUEST).with_message("邀请码仅注册时填写"),
let user_id = authenticated.claims().user_id().to_string();
let updated_at_micros = OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000;
let record = state
.spacetime_client()
.redeem_profile_referral_invite_code(user_id, payload.invite_code, updated_at_micros as i64)
.await
.map_err(|error| {
runtime_profile_error_response(
&request_context,
map_runtime_profile_client_error(error),
)
})?;
Ok(json_success_body(
Some(&request_context),
build_redeem_profile_referral_invite_code_response(record),
))
}
@@ -507,6 +521,18 @@ fn build_profile_referral_invite_center_response(
}
}
fn build_redeem_profile_referral_invite_code_response(
record: module_runtime::RuntimeReferralRedeemRecord,
) -> RedeemProfileReferralInviteCodeResponse {
RedeemProfileReferralInviteCodeResponse {
center: build_profile_referral_invite_center_response(record.center),
invitee_reward_granted: record.invitee_reward_granted,
inviter_reward_granted: record.inviter_reward_granted,
invitee_balance_after: record.invitee_balance_after,
inviter_balance_after: record.inviter_balance_after,
}
}
fn build_redeem_profile_reward_code_response(
record: RuntimeProfileRewardCodeRedeemRecord,
) -> RedeemProfileRewardCodeResponse {
@@ -603,6 +629,7 @@ mod tests {
AccessTokenClaims, AccessTokenClaimsInput, AuthProvider, BindingStatus, sign_access_token,
};
use serde_json::Value;
use std::time::Duration;
use time::OffsetDateTime;
use tower::ServiceExt;
@@ -759,7 +786,7 @@ mod tests {
}
#[tokio::test]
async fn profile_referral_redeem_code_rejects_authenticated_manual_fill() {
async fn profile_referral_redeem_code_calls_spacetime_for_authenticated_user() {
let state = seed_authenticated_state().await;
let token = issue_access_token(&state);
let app = build_router(state);
@@ -777,7 +804,7 @@ mod tests {
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
assert_eq!(response.status(), StatusCode::BAD_GATEWAY);
let body = response
.into_body()
.collect()
@@ -787,8 +814,8 @@ mod tests {
let payload: Value =
serde_json::from_slice(&body).expect("response body should be valid json");
assert_eq!(
payload["error"]["message"],
Value::String("邀请码仅注册时填写".to_string())
payload["error"]["details"]["provider"],
Value::String("spacetimedb".to_string())
);
}
@@ -900,7 +927,7 @@ mod tests {
}
async fn seed_authenticated_state() -> AppState {
let state = AppState::new(AppConfig::default()).expect("state should build");
let state = AppState::new(fast_spacetime_timeout_config()).expect("state should build");
state
.seed_test_phone_user_with_password("13800138104", "secret123")
.await
@@ -908,6 +935,13 @@ mod tests {
state
}
fn fast_spacetime_timeout_config() -> AppConfig {
AppConfig {
spacetime_procedure_timeout: Duration::from_secs(1),
..AppConfig::default()
}
}
fn issue_access_token(state: &AppState) -> String {
let claims = AccessTokenClaims::from_input(
AccessTokenClaimsInput {