1
This commit is contained in:
@@ -4,13 +4,21 @@ use axum::{
|
||||
http::StatusCode,
|
||||
response::Response,
|
||||
};
|
||||
use module_runtime::{
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_MOCK, RuntimeProfileMembershipBenefitRecord,
|
||||
RuntimeProfileRechargeCenterRecord, RuntimeProfileRechargeOrderRecord,
|
||||
RuntimeProfileRechargeProductRecord,
|
||||
};
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::runtime::{
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_SNAPSHOT_SYNC, ProfileDashboardSummaryResponse,
|
||||
ProfilePlayStatsResponse, ProfilePlayedWorkSummaryResponse, ProfileWalletLedgerEntryResponse,
|
||||
CreateProfileRechargeOrderRequest, CreateProfileRechargeOrderResponse,
|
||||
ProfileDashboardSummaryResponse, ProfileMembershipBenefitResponse, ProfileMembershipResponse,
|
||||
ProfilePlayStatsResponse, ProfilePlayedWorkSummaryResponse, ProfileRechargeCenterResponse,
|
||||
ProfileRechargeOrderResponse, ProfileRechargeProductResponse, ProfileWalletLedgerEntryResponse,
|
||||
ProfileWalletLedgerResponse,
|
||||
};
|
||||
use spacetime_client::SpacetimeClientError;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{
|
||||
api_response::json_success_body, auth::AuthenticatedAccessToken, http_error::AppError,
|
||||
@@ -71,11 +79,7 @@ pub async fn get_profile_wallet_ledger(
|
||||
id: entry.wallet_ledger_id,
|
||||
amount_delta: entry.amount_delta,
|
||||
balance_after: entry.balance_after,
|
||||
source_type: match entry.source_type {
|
||||
module_runtime::RuntimeProfileWalletLedgerSourceType::SnapshotSync => {
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_SNAPSHOT_SYNC.to_string()
|
||||
}
|
||||
},
|
||||
source_type: entry.source_type.as_str().to_string(),
|
||||
created_at: entry.created_at,
|
||||
})
|
||||
.collect(),
|
||||
@@ -83,6 +87,65 @@ pub async fn get_profile_wallet_ledger(
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_profile_recharge_center(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let user_id = authenticated.claims().user_id().to_string();
|
||||
let record = state
|
||||
.spacetime_client()
|
||||
.get_profile_recharge_center(user_id)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
map_runtime_profile_client_error(error),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
build_profile_recharge_center_response(record),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn create_profile_recharge_order(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
Json(payload): Json<CreateProfileRechargeOrderRequest>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let user_id = authenticated.claims().user_id().to_string();
|
||||
let payment_channel = payload
|
||||
.payment_channel
|
||||
.unwrap_or_else(|| PROFILE_RECHARGE_PAYMENT_CHANNEL_MOCK.to_string());
|
||||
let created_at_micros = OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000;
|
||||
let (center, order) = state
|
||||
.spacetime_client()
|
||||
.create_profile_recharge_order(
|
||||
user_id,
|
||||
payload.product_id,
|
||||
payment_channel,
|
||||
created_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),
|
||||
CreateProfileRechargeOrderResponse {
|
||||
order: build_profile_recharge_order_response(order),
|
||||
center: build_profile_recharge_center_response(center),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_profile_play_stats(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
@@ -140,6 +203,87 @@ fn runtime_profile_error_response(request_context: &RequestContext, error: AppEr
|
||||
error.into_response_with_context(Some(request_context))
|
||||
}
|
||||
|
||||
fn build_profile_recharge_center_response(
|
||||
record: RuntimeProfileRechargeCenterRecord,
|
||||
) -> ProfileRechargeCenterResponse {
|
||||
ProfileRechargeCenterResponse {
|
||||
wallet_balance: record.wallet_balance,
|
||||
membership: ProfileMembershipResponse {
|
||||
status: record.membership.status.as_str().to_string(),
|
||||
tier: record.membership.tier.as_str().to_string(),
|
||||
started_at: record.membership.started_at,
|
||||
expires_at: record.membership.expires_at,
|
||||
updated_at: record.membership.updated_at,
|
||||
},
|
||||
point_products: record
|
||||
.point_products
|
||||
.into_iter()
|
||||
.map(build_profile_recharge_product_response)
|
||||
.collect(),
|
||||
membership_products: record
|
||||
.membership_products
|
||||
.into_iter()
|
||||
.map(build_profile_recharge_product_response)
|
||||
.collect(),
|
||||
benefits: record
|
||||
.benefits
|
||||
.into_iter()
|
||||
.map(build_profile_membership_benefit_response)
|
||||
.collect(),
|
||||
latest_order: record
|
||||
.latest_order
|
||||
.map(build_profile_recharge_order_response),
|
||||
has_points_recharged: record.has_points_recharged,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_recharge_product_response(
|
||||
record: RuntimeProfileRechargeProductRecord,
|
||||
) -> ProfileRechargeProductResponse {
|
||||
ProfileRechargeProductResponse {
|
||||
product_id: record.product_id,
|
||||
title: record.title,
|
||||
price_cents: record.price_cents,
|
||||
kind: record.kind.as_str().to_string(),
|
||||
points_amount: record.points_amount,
|
||||
bonus_points: record.bonus_points,
|
||||
duration_days: record.duration_days,
|
||||
badge_label: record.badge_label,
|
||||
description: record.description,
|
||||
tier: record.tier.as_str().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_membership_benefit_response(
|
||||
record: RuntimeProfileMembershipBenefitRecord,
|
||||
) -> ProfileMembershipBenefitResponse {
|
||||
ProfileMembershipBenefitResponse {
|
||||
benefit_name: record.benefit_name,
|
||||
normal_value: record.normal_value,
|
||||
month_value: record.month_value,
|
||||
season_value: record.season_value,
|
||||
year_value: record.year_value,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_recharge_order_response(
|
||||
record: RuntimeProfileRechargeOrderRecord,
|
||||
) -> ProfileRechargeOrderResponse {
|
||||
ProfileRechargeOrderResponse {
|
||||
order_id: record.order_id,
|
||||
product_id: record.product_id,
|
||||
product_title: record.product_title,
|
||||
kind: record.kind.as_str().to_string(),
|
||||
amount_cents: record.amount_cents,
|
||||
status: record.status.as_str().to_string(),
|
||||
payment_channel: record.payment_channel,
|
||||
paid_at: record.paid_at,
|
||||
created_at: record.created_at,
|
||||
points_delta: record.points_delta,
|
||||
membership_expires_at: record.membership_expires_at,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use axum::{
|
||||
@@ -210,6 +354,43 @@ mod tests {
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_recharge_center_requires_authentication() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/profile/recharge-center")
|
||||
.body(Body::empty())
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_recharge_order_requires_authentication() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/profile/recharge/orders")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(r#"{"productId":"points_10"}"#))
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_dashboard_compat_route_matches_main_route_error_shape() {
|
||||
assert_compat_route_matches_main_route_error_shape(
|
||||
|
||||
Reference in New Issue
Block a user