fix: 修复多处代码质量问题-移除unwrap/println/dead-code/重复代码/错误吞咽
This commit is contained in:
@@ -276,7 +276,7 @@ mod signature_tests {
|
|||||||
assert!(result.is_ok(), "Signing failed: {:?}", result.err());
|
assert!(result.is_ok(), "Signing failed: {:?}", result.err());
|
||||||
let signature = result.unwrap();
|
let signature = result.unwrap();
|
||||||
|
|
||||||
println!("Our signature: {}", &signature[..signature.len().min(50)]);
|
tracing::debug!("Generated signature for test verification ({} chars)", signature.len());
|
||||||
|
|
||||||
// To verify this matches OpenSSL, you would need to run:
|
// To verify this matches OpenSSL, you would need to run:
|
||||||
// echo -n "test content..." | openssl dgst -sha256 -sign key.pem | base64
|
// echo -n "test content..." | openssl dgst -sha256 -sign key.pem | base64
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ pub fn generate_refresh_token(user_id: i32, secret: &str) -> Result<String, Stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 解析 refresh_token,返回 (user_id, expires_at)
|
// 解析 refresh_token,返回 (user_id, expires_at)
|
||||||
pub fn verify_refresh_token(token: &str, secret: &str) -> Result<(i32, i64), String> {
|
pub fn verify_refresh_token(token: &str, _secret: &str) -> Result<(i32, i64), String> {
|
||||||
let decoded = BASE64.decode(token)
|
let decoded = BASE64.decode(token)
|
||||||
.map_err(|e| format!("Refresh token 格式错误: {}", e))?;
|
.map_err(|e| format!("Refresh token 格式错误: {}", e))?;
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ impl AppConfig {
|
|||||||
let config_dir = manifest_dir.join("config");
|
let config_dir = manifest_dir.join("config");
|
||||||
|
|
||||||
let default_path = config_dir.join("default.toml");
|
let default_path = config_dir.join("default.toml");
|
||||||
let env_path = config_dir.join(&format!("{}.toml", env));
|
let env_path = config_dir.join(format!("{}.toml", env));
|
||||||
|
|
||||||
let mut settings = toml::Table::new();
|
let mut settings = toml::Table::new();
|
||||||
|
|
||||||
@@ -72,11 +72,10 @@ impl AppConfig {
|
|||||||
if let Ok(val) = std::env::var("APP_RUST_LOG") {
|
if let Ok(val) = std::env::var("APP_RUST_LOG") {
|
||||||
settings.insert("rust_log".into(), toml::Value::String(val));
|
settings.insert("rust_log".into(), toml::Value::String(val));
|
||||||
}
|
}
|
||||||
if let Ok(val) = std::env::var("APP_FREE_USER_DATA_LIMIT") {
|
if let Ok(val) = std::env::var("APP_FREE_USER_DATA_LIMIT")
|
||||||
if let Ok(num) = val.parse::<i64>() {
|
&& let Ok(num) = val.parse::<i64>() {
|
||||||
settings.insert("free_user_data_limit".into(), toml::Value::Integer(num));
|
settings.insert("free_user_data_limit".into(), toml::Value::Integer(num));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let app_config: AppConfig = toml::from_str(&settings.to_string())
|
let app_config: AppConfig = toml::from_str(&settings.to_string())
|
||||||
.map_err(|e| format!("配置反序列化失败: {}", e))?;
|
.map_err(|e| format!("配置反序列化失败: {}", e))?;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::error::AppError;
|
|||||||
pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user_id: i32) -> Result<i32, AppError> {
|
pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user_id: i32) -> Result<i32, AppError> {
|
||||||
// 配额检查:非付费用户数据条数限制
|
// 配额检查:非付费用户数据条数限制
|
||||||
let user = get_user_by_id(pool, user_id).await?;
|
let user = get_user_by_id(pool, user_id).await?;
|
||||||
let is_paid_active = user.is_paid && (user.paid_expires_at.is_none() || user.paid_expires_at.unwrap() > Utc::now());
|
let is_paid_active = user.is_paid && user.paid_expires_at.map_or(true, |expires| expires > Utc::now());
|
||||||
|
|
||||||
if !is_paid_active {
|
if !is_paid_active {
|
||||||
let current_count = count_user_weather_data(pool, user_id).await?;
|
let current_count = count_user_weather_data(pool, user_id).await?;
|
||||||
@@ -490,8 +490,7 @@ pub async fn get_user_quota(
|
|||||||
) -> Result<(i64, bool, Option<chrono::DateTime<chrono::Utc>>), AppError> {
|
) -> Result<(i64, bool, Option<chrono::DateTime<chrono::Utc>>), AppError> {
|
||||||
let user = get_user_by_id(pool, user_id).await?;
|
let user = get_user_by_id(pool, user_id).await?;
|
||||||
let is_paid_active = user.is_paid
|
let is_paid_active = user.is_paid
|
||||||
&& (user.paid_expires_at.is_none()
|
&& user.paid_expires_at.map_or(true, |expires| expires > chrono::Utc::now());
|
||||||
|| user.paid_expires_at.unwrap() > chrono::Utc::now());
|
|
||||||
let used = count_user_weather_data(pool, user_id).await?;
|
let used = count_user_weather_data(pool, user_id).await?;
|
||||||
Ok((used, is_paid_active, user.paid_expires_at))
|
Ok((used, is_paid_active, user.paid_expires_at))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,14 @@ use reqwest::Client;
|
|||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
use crate::auth::{generate_token, generate_refresh_token, verify_refresh_token};
|
use crate::auth::{generate_token, generate_refresh_token};
|
||||||
use crate::db;
|
use crate::db;
|
||||||
use crate::error::{AppError, ErrorResponse};
|
use crate::error::ErrorResponse;
|
||||||
use crate::models::Claims;
|
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
AppState, LoginResponse, RefreshTokenRequest, TokenRefreshResponse,
|
AppState, LoginResponse, RefreshTokenRequest, TokenRefreshResponse,
|
||||||
WeChatApiResponse, WeChatLoginRequest,
|
WeChatApiResponse, WeChatLoginRequest,
|
||||||
};
|
};
|
||||||
use crate::rate_limiter::LOGIN_RATE_LIMITER;
|
use crate::rate_limiter::LOGIN_RATE_LIMITER;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[post("/api/login")]
|
#[post("/api/login")]
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
@@ -248,7 +246,7 @@ pub async fn mock_login(
|
|||||||
let user_id = match query.user_id {
|
let user_id = match query.user_id {
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
// 验证用户存在
|
// 验证用户存在
|
||||||
match sqlx::query_as::<_, (i32,)>(("SELECT id FROM users WHERE id = $1"))
|
match sqlx::query_as::<_, (i32,)>("SELECT id FROM users WHERE id = $1" )
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.fetch_optional(pool.get_ref())
|
.fetch_optional(pool.get_ref())
|
||||||
.await
|
.await
|
||||||
@@ -447,8 +445,8 @@ pub async fn web_generate_login_code(
|
|||||||
.map(|_| {
|
.map(|_| {
|
||||||
let chars = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
let chars = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
let idx = (Utc::now().timestamp_millis() % 36) as u8;
|
let idx = (Utc::now().timestamp_millis() % 36) as u8;
|
||||||
let ch = chars[(idx % 36) as usize] as char;
|
|
||||||
ch
|
chars[(idx % 36) as usize] as char
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let ts = Utc::now().timestamp();
|
let ts = Utc::now().timestamp();
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// handlers/payment.rs — 支付相关处理器(接入支付宝)
|
// handlers/payment.rs — 支付相关处理器(接入支付宝)
|
||||||
use actix_web::{get, post, web, HttpRequest, HttpResponse};
|
use actix_web::{get, post, web, HttpRequest, HttpResponse};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use rsa::pkcs1v15::{Pkcs1v15Sign, SigningKey, VerifyingKey};
|
use rsa::pkcs1v15::SigningKey;
|
||||||
use rsa::pkcs8::DecodePrivateKey;
|
use rsa::pkcs8::DecodePrivateKey;
|
||||||
use rsa::signature::{SignatureEncoding, Signer, Verifier};
|
use rsa::signature::{SignatureEncoding, Signer};
|
||||||
use rsa::RsaPrivateKey;
|
use rsa::RsaPrivateKey;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::Sha256;
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
@@ -110,6 +110,54 @@ fn rsa2_sign(content: &str, private_key_pem: &str) -> Result<String, String> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 合并 mock 支付 HTML(在 payment_page 和 alipay_pay_page 中复用)
|
||||||
|
fn build_mock_pay_html(order_no: &str, display_name: &str, jwt: &str) -> String {
|
||||||
|
format!(r#"<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head><meta charset="utf-8"><title>模拟支付</title></head>
|
||||||
|
<body style="font-family:-apple-system;padding:40px;text-align:center">
|
||||||
|
<h2 style="color:#52c41a">模拟支付环境</h2>
|
||||||
|
<p style="color:#666">当前为沙箱模拟环境,无需真实支付</p>
|
||||||
|
<p style="color:#999;font-size:14px">订单号: {}</p>
|
||||||
|
<p style="color:#999;font-size:14px">套餐: {}</p>
|
||||||
|
<button id="confirmBtn" onclick="confirmMockPay()" style="padding:12px 32px;font-size:16px;background:#1677ff;color:#fff;border:none;border-radius:4px;cursor:pointer">确认模拟支付</button>
|
||||||
|
<p id="result"></p>
|
||||||
|
<p><a href="/payment" style="color:#1677ff">返回重试</a></p>
|
||||||
|
<script>
|
||||||
|
function confirmMockPay() {{
|
||||||
|
var btn = document.getElementById('confirmBtn');
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = '处理中...';
|
||||||
|
fetch('/api/payment/mock-confirm', {{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {{
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer {}'
|
||||||
|
}},
|
||||||
|
body: JSON.stringify({{order_id: '{}'}})
|
||||||
|
}})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {{
|
||||||
|
if (data.success) {{
|
||||||
|
document.getElementById('result').innerHTML = '<span style="color:#52c41a">支付成功!</span>';
|
||||||
|
setTimeout(() => window.location.href = '/payment?mock=1', 1500);
|
||||||
|
}} else {{
|
||||||
|
document.getElementById('result').innerHTML = '<span style="color:#f5222d">失败: ' + (data.error || '未知错误') + '</span>';
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = '重试';
|
||||||
|
}}
|
||||||
|
}})
|
||||||
|
.catch(e => {{
|
||||||
|
document.getElementById('result').innerHTML = '<span style="color:#f5222d">网络错误</span>';
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = '重试';
|
||||||
|
}});
|
||||||
|
}}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>"#, order_no, display_name, jwt, order_no)
|
||||||
|
}
|
||||||
|
|
||||||
/// 验证 RSA2 签名
|
/// 验证 RSA2 签名
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn rsa2_verify(content: &str, sign: &str, public_key_pem: &str) -> Result<bool, String> {
|
fn rsa2_verify(content: &str, sign: &str, public_key_pem: &str) -> Result<bool, String> {
|
||||||
@@ -577,51 +625,7 @@ pub async fn payment_page(
|
|||||||
|
|
||||||
let Some(config) = AlipayConfig::from_env() else {
|
let Some(config) = AlipayConfig::from_env() else {
|
||||||
let jwt_for_mock = token.clone();
|
let jwt_for_mock = token.clone();
|
||||||
let mock_html = format!(r#"<!DOCTYPE html>
|
let mock_html = build_mock_pay_html(&order_no, pkg.display_name, &jwt_for_mock);
|
||||||
<html lang="zh-CN">
|
|
||||||
<head><meta charset="utf-8"><title>模拟支付</title></head>
|
|
||||||
<body style="font-family:-apple-system;padding:40px;text-align:center">
|
|
||||||
<h2 style="color:#52c41a">模拟支付环境</h2>
|
|
||||||
<p style="color:#666">当前为沙箱模拟环境,无需真实支付</p>
|
|
||||||
<p style="color:#999;font-size:14px">订单号: {}</p>
|
|
||||||
<p style="color:#999;font-size:14px">套餐: {}</p>
|
|
||||||
<button id="confirmBtn" onclick="confirmMockPay()" style="padding:12px 32px;font-size:16px;background:#1677ff;color:#fff;border:none;border-radius:4px;cursor:pointer">确认模拟支付</button>
|
|
||||||
<p id="result"></p>
|
|
||||||
<p><a href="/payment" style="color:#1677ff">返回重试</a></p>
|
|
||||||
<script>
|
|
||||||
function confirmMockPay() {{
|
|
||||||
var btn = document.getElementById('confirmBtn');
|
|
||||||
btn.disabled = true;
|
|
||||||
btn.textContent = '处理中...';
|
|
||||||
fetch('/api/payment/mock-confirm', {{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {{
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer {}'
|
|
||||||
}},
|
|
||||||
body: JSON.stringify({{order_id: '{}'}})
|
|
||||||
}})
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(data => {{
|
|
||||||
if (data.success) {{
|
|
||||||
document.getElementById('result').innerHTML = '<span style="color:#52c41a">支付成功!</span>';
|
|
||||||
setTimeout(() => window.location.href = '/payment?mock=1', 1500);
|
|
||||||
}} else {{
|
|
||||||
document.getElementById('result').innerHTML = '<span style="color:#f5222d">失败: ' + (data.error || '未知错误') + '</span>';
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.textContent = '重试';
|
|
||||||
}}
|
|
||||||
}})
|
|
||||||
.catch(e => {{
|
|
||||||
document.getElementById('result').innerHTML = '<span style="color:#f5222d">网络错误</span>';
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.textContent = '重试';
|
|
||||||
}});
|
|
||||||
}}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"#, order_no, pkg.display_name, jwt_for_mock, order_no);
|
|
||||||
return Ok(HttpResponse::Ok()
|
return Ok(HttpResponse::Ok()
|
||||||
.content_type("text/html; charset=utf-8")
|
.content_type("text/html; charset=utf-8")
|
||||||
.body(mock_html));
|
.body(mock_html));
|
||||||
@@ -676,51 +680,7 @@ pub async fn alipay_pay_page(
|
|||||||
|
|
||||||
let Some(config) = AlipayConfig::from_env() else {
|
let Some(config) = AlipayConfig::from_env() else {
|
||||||
let jwt_for_mock = token.clone();
|
let jwt_for_mock = token.clone();
|
||||||
let mock_html = format!(r#"<!DOCTYPE html>
|
let mock_html = build_mock_pay_html(&query.order_no, pkg.display_name, &jwt_for_mock);
|
||||||
<html lang="zh-CN">
|
|
||||||
<head><meta charset="utf-8"><title>模拟支付</title></head>
|
|
||||||
<body style="font-family:-apple-system;padding:40px;text-align:center">
|
|
||||||
<h2 style="color:#52c41a">模拟支付环境</h2>
|
|
||||||
<p style="color:#666">当前为沙箱模拟环境,无需真实支付</p>
|
|
||||||
<p style="color:#999;font-size:14px">订单号: {}</p>
|
|
||||||
<p style="color:#999;font-size:14px">套餐: {}</p>
|
|
||||||
<button id="confirmBtn" onclick="confirmMockPay()" style="padding:12px 32px;font-size:16px;background:#1677ff;color:#fff;border:none;border-radius:4px;cursor:pointer">确认模拟支付</button>
|
|
||||||
<p id="result"></p>
|
|
||||||
<p><a href="/payment" style="color:#1677ff">返回重试</a></p>
|
|
||||||
<script>
|
|
||||||
function confirmMockPay() {{
|
|
||||||
var btn = document.getElementById('confirmBtn');
|
|
||||||
btn.disabled = true;
|
|
||||||
btn.textContent = '处理中...';
|
|
||||||
fetch('/api/payment/mock-confirm', {{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {{
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer {}'
|
|
||||||
}},
|
|
||||||
body: JSON.stringify({{order_id: '{}'}})
|
|
||||||
}})
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(data => {{
|
|
||||||
if (data.success) {{
|
|
||||||
document.getElementById('result').innerHTML = '<span style="color:#52c41a">支付成功!</span>';
|
|
||||||
setTimeout(() => window.location.href = '/payment?mock=1', 1500);
|
|
||||||
}} else {{
|
|
||||||
document.getElementById('result').innerHTML = '<span style="color:#f5222d">失败: ' + (data.error || '未知错误') + '</span>';
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.textContent = '重试';
|
|
||||||
}}
|
|
||||||
}})
|
|
||||||
.catch(e => {{
|
|
||||||
document.getElementById('result').innerHTML = '<span style="color:#f5222d">网络错误</span>';
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.textContent = '重试';
|
|
||||||
}});
|
|
||||||
}}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"#, query.order_no, pkg.display_name, jwt_for_mock, query.order_no);
|
|
||||||
return Ok(HttpResponse::Ok()
|
return Ok(HttpResponse::Ok()
|
||||||
.content_type("text/html; charset=utf-8")
|
.content_type("text/html; charset=utf-8")
|
||||||
.body(mock_html));
|
.body(mock_html));
|
||||||
@@ -860,11 +820,10 @@ pub async fn payment_success(
|
|||||||
let order_no = query.order_no.as_deref().unwrap_or("");
|
let order_no = query.order_no.as_deref().unwrap_or("");
|
||||||
|
|
||||||
// 同步确认订单(幂等:已确认的订单会跳过)
|
// 同步确认订单(幂等:已确认的订单会跳过)
|
||||||
if !order_no.is_empty() {
|
if !order_no.is_empty()
|
||||||
if let Err(e) = db::confirm_payment_order_by_orderno(pool.get_ref(), order_no).await {
|
&& let Err(e) = db::confirm_payment_order_by_orderno(pool.get_ref(), order_no).await {
|
||||||
tracing::warn!("支付成功页同步确认失败(可能是异步回调已处理): {}", e);
|
tracing::warn!("支付成功页同步确认失败(可能是异步回调已处理): {}", e);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let html = build_success_html(order_no);
|
let html = build_success_html(order_no);
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
@@ -948,8 +907,10 @@ pub async fn sync_order(
|
|||||||
) -> Result<HttpResponse, AppError> {
|
) -> Result<HttpResponse, AppError> {
|
||||||
let user_id = claims.user_id;
|
let user_id = claims.user_id;
|
||||||
|
|
||||||
// 尝试确认订单(幂等)
|
// 尝试确认订单(幂等),失败时记录日志
|
||||||
let _ = db::confirm_payment_order(pool.get_ref(), &body.order_id, user_id).await;
|
if let Err(e) = db::confirm_payment_order(pool.get_ref(), &body.order_id, user_id).await {
|
||||||
|
tracing::warn!("确认订单失败(可能已被异步回调处理): {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
// 查询最新状态
|
// 查询最新状态
|
||||||
let user = sqlx::query_as::<_, (bool, Option<chrono::DateTime<chrono::Utc>>)>(
|
let user = sqlx::query_as::<_, (bool, Option<chrono::DateTime<chrono::Utc>>)>(
|
||||||
@@ -962,7 +923,7 @@ pub async fn sync_order(
|
|||||||
.ok_or_else(|| AppError::NotFound("用户不存在".to_string()))?;
|
.ok_or_else(|| AppError::NotFound("用户不存在".to_string()))?;
|
||||||
|
|
||||||
let (is_paid, paid_expires_at) = user;
|
let (is_paid, paid_expires_at) = user;
|
||||||
let is_paid_active = is_paid && (paid_expires_at.is_none() || paid_expires_at.unwrap() > Utc::now());
|
let is_paid_active = is_paid && paid_expires_at.map_or(true, |expires| expires > Utc::now());
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(serde_json::json!({
|
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||||
"success": true,
|
"success": true,
|
||||||
@@ -1082,7 +1043,7 @@ pub async fn generate_code(
|
|||||||
pub async fn payment_login_status(
|
pub async fn payment_login_status(
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
query: web::Query<LoginStatusQuery>,
|
query: web::Query<LoginStatusQuery>,
|
||||||
app_state: web::Data<crate::models::AppState>,
|
_app_state: web::Data<crate::models::AppState>,
|
||||||
) -> Result<HttpResponse, AppError> {
|
) -> Result<HttpResponse, AppError> {
|
||||||
let code = query.code.trim();
|
let code = query.code.trim();
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub async fn get_current_user_profile(
|
|||||||
|
|
||||||
let user = db::get_user_by_id(pool.get_ref(), user_id).await?;
|
let user = db::get_user_by_id(pool.get_ref(), user_id).await?;
|
||||||
let is_paid_active = user.is_paid &&
|
let is_paid_active = user.is_paid &&
|
||||||
(user.paid_expires_at.is_none() || user.paid_expires_at.unwrap() > chrono::Utc::now());
|
user.paid_expires_at.map_or(true, |expires| expires > chrono::Utc::now());
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(serde_json::json!({
|
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||||
"success": true,
|
"success": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user