This commit is contained in:
627
server-rs/crates/api-server/src/runtime_profile.rs
Normal file
627
server-rs/crates/api-server/src/runtime_profile.rs
Normal file
@@ -0,0 +1,627 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Extension, State},
|
||||
http::StatusCode,
|
||||
response::Response,
|
||||
};
|
||||
use module_runtime::{
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_MOCK, RuntimeProfileMembershipBenefitRecord,
|
||||
RuntimeProfileRechargeCenterRecord, RuntimeProfileRechargeOrderRecord,
|
||||
RuntimeProfileRechargeProductRecord, RuntimeReferralInviteCenterRecord,
|
||||
RuntimeReferralRedeemRecord,
|
||||
};
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::runtime::{
|
||||
CreateProfileRechargeOrderRequest, CreateProfileRechargeOrderResponse,
|
||||
ProfileDashboardSummaryResponse, ProfileMembershipBenefitResponse, ProfileMembershipResponse,
|
||||
ProfilePlayStatsResponse, ProfilePlayedWorkSummaryResponse, ProfileRechargeCenterResponse,
|
||||
ProfileRechargeOrderResponse, ProfileRechargeProductResponse,
|
||||
ProfileReferralInviteCenterResponse, ProfileWalletLedgerEntryResponse,
|
||||
ProfileWalletLedgerResponse, RedeemProfileReferralInviteCodeRequest,
|
||||
RedeemProfileReferralInviteCodeResponse,
|
||||
};
|
||||
use spacetime_client::SpacetimeClientError;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{
|
||||
api_response::json_success_body, auth::AuthenticatedAccessToken, http_error::AppError,
|
||||
request_context::RequestContext, state::AppState,
|
||||
};
|
||||
|
||||
pub async fn get_profile_dashboard(
|
||||
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_dashboard(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),
|
||||
ProfileDashboardSummaryResponse {
|
||||
wallet_balance: record.wallet_balance,
|
||||
total_play_time_ms: record.total_play_time_ms,
|
||||
played_world_count: record.played_world_count,
|
||||
updated_at: record.updated_at,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_profile_wallet_ledger(
|
||||
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 entries = state
|
||||
.spacetime_client()
|
||||
.list_profile_wallet_ledger(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),
|
||||
ProfileWalletLedgerResponse {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(|entry| ProfileWalletLedgerEntryResponse {
|
||||
id: entry.wallet_ledger_id,
|
||||
amount_delta: entry.amount_delta,
|
||||
balance_after: entry.balance_after,
|
||||
source_type: entry.source_type.as_str().to_string(),
|
||||
created_at: entry.created_at,
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
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_referral_invite_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_referral_invite_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_referral_invite_center_response(record),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn redeem_profile_referral_invite_code(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
Json(payload): Json<RedeemProfileReferralInviteCodeRequest>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
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),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_profile_play_stats(
|
||||
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_play_stats(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),
|
||||
ProfilePlayStatsResponse {
|
||||
total_play_time_ms: record.total_play_time_ms,
|
||||
played_works: record
|
||||
.played_works
|
||||
.into_iter()
|
||||
.map(|entry| ProfilePlayedWorkSummaryResponse {
|
||||
world_key: entry.world_key,
|
||||
owner_user_id: entry.owner_user_id,
|
||||
profile_id: entry.profile_id,
|
||||
world_type: entry.world_type,
|
||||
world_title: entry.world_title,
|
||||
world_subtitle: entry.world_subtitle,
|
||||
first_played_at: entry.first_played_at,
|
||||
last_played_at: entry.last_played_at,
|
||||
last_observed_play_time_ms: entry.last_observed_play_time_ms,
|
||||
})
|
||||
.collect(),
|
||||
updated_at: record.updated_at,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn map_runtime_profile_client_error(error: SpacetimeClientError) -> AppError {
|
||||
let (status, provider) = match error {
|
||||
SpacetimeClientError::Runtime(_) => (StatusCode::BAD_REQUEST, "runtime-profile"),
|
||||
_ => (StatusCode::BAD_GATEWAY, "spacetimedb"),
|
||||
};
|
||||
|
||||
AppError::from_status(status).with_details(json!({
|
||||
"provider": provider,
|
||||
"message": error.to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn runtime_profile_error_response(request_context: &RequestContext, error: AppError) -> Response {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_referral_invite_center_response(
|
||||
record: RuntimeReferralInviteCenterRecord,
|
||||
) -> ProfileReferralInviteCenterResponse {
|
||||
ProfileReferralInviteCenterResponse {
|
||||
invite_code: record.invite_code,
|
||||
invite_link_path: record.invite_link_path,
|
||||
invited_count: record.invited_count,
|
||||
rewarded_invite_count: record.rewarded_invite_count,
|
||||
today_inviter_reward_count: record.today_inviter_reward_count,
|
||||
today_inviter_reward_remaining: record.today_inviter_reward_remaining,
|
||||
reward_points: record.reward_points,
|
||||
has_redeemed_code: record.has_redeemed_code,
|
||||
bound_inviter_user_id: record.bound_inviter_user_id,
|
||||
bound_at: record.bound_at,
|
||||
updated_at: record.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_redeem_profile_referral_invite_code_response(
|
||||
record: 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,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use axum::{
|
||||
body::Body,
|
||||
http::{Request, StatusCode},
|
||||
};
|
||||
use http_body_util::BodyExt;
|
||||
use platform_auth::{
|
||||
AccessTokenClaims, AccessTokenClaimsInput, AuthProvider, BindingStatus, sign_access_token,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use time::OffsetDateTime;
|
||||
use tower::ServiceExt;
|
||||
|
||||
use crate::{app::build_router, config::AppConfig, state::AppState};
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_dashboard_requires_authentication() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/runtime/profile/dashboard")
|
||||
.body(Body::empty())
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_wallet_ledger_requires_authentication() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/runtime/profile/wallet-ledger")
|
||||
.body(Body::empty())
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_play_stats_requires_authentication() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/runtime/profile/play-stats")
|
||||
.body(Body::empty())
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
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_60"}"#))
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_referral_invite_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/referrals/invite-center")
|
||||
.body(Body::empty())
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_referral_redeem_code_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/referrals/redeem-code")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(r#"{"inviteCode":"SY12345678"}"#))
|
||||
.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(
|
||||
"/api/runtime/profile/dashboard",
|
||||
"/api/profile/dashboard",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_wallet_ledger_compat_route_matches_main_route_error_shape() {
|
||||
assert_compat_route_matches_main_route_error_shape(
|
||||
"/api/runtime/profile/wallet-ledger",
|
||||
"/api/profile/wallet-ledger",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_play_stats_compat_route_matches_main_route_error_shape() {
|
||||
assert_compat_route_matches_main_route_error_shape(
|
||||
"/api/runtime/profile/play-stats",
|
||||
"/api/profile/play-stats",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn assert_compat_route_matches_main_route_error_shape(
|
||||
main_route: &str,
|
||||
compat_route: &str,
|
||||
) {
|
||||
let state = seed_authenticated_state().await;
|
||||
let token = issue_access_token(&state);
|
||||
let app = build_router(state);
|
||||
|
||||
let main_response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("GET")
|
||||
.uri(main_route)
|
||||
.header("authorization", format!("Bearer {token}"))
|
||||
.header("x-genarrative-response-envelope", "v1")
|
||||
.body(Body::empty())
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
let compat_response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("GET")
|
||||
.uri(compat_route)
|
||||
.header("authorization", format!("Bearer {token}"))
|
||||
.header("x-genarrative-response-envelope", "v1")
|
||||
.body(Body::empty())
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(main_response.status(), compat_response.status());
|
||||
|
||||
let main_body = main_response
|
||||
.into_body()
|
||||
.collect()
|
||||
.await
|
||||
.expect("body should collect")
|
||||
.to_bytes();
|
||||
let compat_body = compat_response
|
||||
.into_body()
|
||||
.collect()
|
||||
.await
|
||||
.expect("body should collect")
|
||||
.to_bytes();
|
||||
let main_payload: Value =
|
||||
serde_json::from_slice(&main_body).expect("response body should be valid json");
|
||||
let compat_payload: Value =
|
||||
serde_json::from_slice(&compat_body).expect("response body should be valid json");
|
||||
|
||||
assert_eq!(
|
||||
main_payload["error"]["details"]["provider"],
|
||||
compat_payload["error"]["details"]["provider"]
|
||||
);
|
||||
}
|
||||
|
||||
async fn seed_authenticated_state() -> AppState {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
state
|
||||
.seed_test_phone_user_with_password("13800138104", "secret123")
|
||||
.await
|
||||
.id;
|
||||
state
|
||||
}
|
||||
|
||||
fn issue_access_token(state: &AppState) -> String {
|
||||
let claims = AccessTokenClaims::from_input(
|
||||
AccessTokenClaimsInput {
|
||||
user_id: "user_00000001".to_string(),
|
||||
session_id: "sess_runtime_profile".to_string(),
|
||||
provider: AuthProvider::Password,
|
||||
roles: vec!["user".to_string()],
|
||||
token_version: 2,
|
||||
phone_verified: true,
|
||||
binding_status: BindingStatus::Active,
|
||||
display_name: Some("资料页用户".to_string()),
|
||||
},
|
||||
state.auth_jwt_config(),
|
||||
OffsetDateTime::now_utc(),
|
||||
)
|
||||
.expect("claims should build");
|
||||
|
||||
sign_access_token(&claims, state.auth_jwt_config()).expect("token should sign")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user