update
This commit is contained in:
5
.env
5
.env
@@ -6,6 +6,11 @@ OSU_CLIENT_ID =
|
||||
OSU_CLIENT_SECRET =
|
||||
OSU_REDIRECT_URI = http://localhost:3000/api/auth/callback/osu
|
||||
|
||||
# OAuth Configuration for g0v0.top server
|
||||
GU_CLIENT_ID = 13
|
||||
GU_CLIENT_SECRET = fdcab562f6cfd5c62c0345e40791e7273ab8d4ed5459a4bac690d91f5c3f3b3f
|
||||
GU_REDIRECT_URI = http://localhost:3000/api/auth/callback/gu
|
||||
|
||||
# JWT Configuration
|
||||
JWT_SECRET=457ceac798afbc2e305c4833844a2736
|
||||
JWT_EXPIRY_HOURS=24
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -100,6 +100,7 @@ version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"bcrypt",
|
||||
|
||||
@@ -28,6 +28,7 @@ jsonwebtoken = "10.3"
|
||||
bcrypt = "0.18"
|
||||
argon2 = "0.5"
|
||||
rand = "0.10"
|
||||
async-trait = "0.1"
|
||||
|
||||
# HTTP client for OAuth
|
||||
reqwest = { version = "0.13", features = ["json"] }
|
||||
|
||||
161
src/api/auth.rs
161
src/api/auth.rs
@@ -23,6 +23,8 @@ pub fn routes() -> Router<ServiceState> {
|
||||
Router::new()
|
||||
.route("/osu", get(osu_auth))
|
||||
.route("/callback/osu", get(osu_callback))
|
||||
.route("/gu", get(gu_auth))
|
||||
.route("/callback/gu", get(gu_callback))
|
||||
.route("/refresh", get(refresh_token))
|
||||
.route("/logout", get(logout))
|
||||
}
|
||||
@@ -72,6 +74,29 @@ async fn osu_auth() -> Redirect {
|
||||
Redirect::to(&auth_url)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/auth/gu",
|
||||
responses(
|
||||
(status = 302, description = "重定向到GU授权页面"),
|
||||
),
|
||||
tag = "auth"
|
||||
)]
|
||||
async fn gu_auth() -> Redirect {
|
||||
let client_id = env::var("GU_CLIENT_ID").expect("GU_CLIENT_ID not set");
|
||||
let redirect_uri = env::var("GU_REDIRECT_URI").expect("GU_REDIRECT_URI not set");
|
||||
let state = Uuid::new_v4().to_string();
|
||||
|
||||
let auth_url = format!(
|
||||
"https://lazer-api.g0v0.top/oauth/authorize?client_id={}&redirect_uri={}&response_type=code&scope=identify%20public&state={}",
|
||||
client_id,
|
||||
urlencoding::encode(&redirect_uri),
|
||||
state
|
||||
);
|
||||
|
||||
Redirect::to(&auth_url)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/auth/callback/osu",
|
||||
@@ -222,3 +247,139 @@ async fn logout() -> Result<Json<ApiResponse<()>>> {
|
||||
// 这里需要清除客户端的令牌
|
||||
Ok(Json(ApiResponse::success_with_message((), "登出成功")))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/auth/callback/gu",
|
||||
params(
|
||||
("code" = String, Query, description = "GU OAuth回调代码"),
|
||||
("state" = String, Query, description = "状态参数"),
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "OAuth回调成功"),
|
||||
(status = 400, description = "请求参数错误"),
|
||||
(status = 500, description = "内部服务器错误"),
|
||||
),
|
||||
tag = "auth"
|
||||
)]
|
||||
async fn gu_callback(
|
||||
Query(params): Query<OsuCallbackQuery>,
|
||||
State(state): State<ServiceState>,
|
||||
) -> Result<Json<ApiResponse<AuthResponse>>> {
|
||||
let client_id = env::var("GU_CLIENT_ID").expect("GU_CLIENT_ID not set");
|
||||
let client_secret = env::var("GU_CLIENT_SECRET").expect("GU_CLIENT_SECRET not set");
|
||||
let redirect_uri = env::var("GU_REDIRECT_URI").expect("GU_REDIRECT_URI not set");
|
||||
|
||||
// 获取访问令牌
|
||||
let client = Client::new();
|
||||
let token_response = client
|
||||
.post("https://lazer-api.g0v0.top/oauth/token")
|
||||
.json(&serde_json::json!({
|
||||
"client_id": client_id,
|
||||
"client_secret": client_secret,
|
||||
"code": params.code,
|
||||
"grant_type": "authorization_code",
|
||||
"redirect_uri": redirect_uri
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
crate::error::AppError::ExternalApiError(format!("Failed to get token: {}", e))
|
||||
})?
|
||||
.json::<OsuTokenResponse>()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
crate::error::AppError::ExternalApiError(format!(
|
||||
"Failed to parse token response: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
// 获取用户信息
|
||||
let user_data = client
|
||||
.get("https://lazer-api.g0v0.top/api/v2/me")
|
||||
.header(
|
||||
"Authorization",
|
||||
format!("Bearer {}", token_response.access_token),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
crate::error::AppError::ExternalApiError(format!("Failed to get user info: {}", e))
|
||||
})?
|
||||
.json::<OsuUserData>()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
crate::error::AppError::ExternalApiError(format!("Failed to parse user info: {}", e))
|
||||
})?;
|
||||
|
||||
// 处理用户数据
|
||||
let user_service = UserService::new(state.db);
|
||||
let existing_user = user_service.find_by_osu_id(&user_data.id).await?;
|
||||
|
||||
// 提前复制需要的值,避免移动后借用
|
||||
let user_id = user_data.id.clone();
|
||||
let username = user_data.username.clone();
|
||||
|
||||
let user = match existing_user {
|
||||
Some(existing) => {
|
||||
// 更新现有用户,包括GU服务器信息
|
||||
let update_request = crate::dto::user::UpdateUserRequest {
|
||||
username: Some(username.clone()),
|
||||
avatar_url: user_data.avatar_url,
|
||||
pp: user_data.statistics.as_ref().and_then(|s| s.pp),
|
||||
global_rank: user_data.statistics.as_ref().and_then(|s| s.global_rank),
|
||||
country: user_data.country.as_ref().map(|c| c.code.clone()),
|
||||
country_rank: user_data.statistics.as_ref().and_then(|s| s.country_rank),
|
||||
approved: None,
|
||||
season: None,
|
||||
};
|
||||
let updated_user = user_service.update(existing.id, update_request).await?;
|
||||
|
||||
// 更新用户的gu_server_user_id和gu_server_username
|
||||
let gu_user_id = user_id.parse::<i32>().unwrap_or(0);
|
||||
let updated_user_with_gu_info = user_service
|
||||
.update_gu_server_info(updated_user.id, gu_user_id, username.clone())
|
||||
.await?;
|
||||
updated_user_with_gu_info
|
||||
}
|
||||
None => {
|
||||
// 创建新用户
|
||||
let create_request = CreateUserRequest {
|
||||
username: username.clone(),
|
||||
avatar_url: user_data.avatar_url,
|
||||
pp: user_data.statistics.as_ref().and_then(|s| s.pp),
|
||||
global_rank: user_data.statistics.as_ref().and_then(|s| s.global_rank),
|
||||
country: user_data.country.as_ref().map(|c| c.code.clone()),
|
||||
country_rank: user_data.statistics.as_ref().and_then(|s| s.country_rank),
|
||||
};
|
||||
let new_user = user_service.create(user_id.clone(), create_request).await?;
|
||||
|
||||
// 设置用户的gu_server_user_id和gu_server_username
|
||||
let gu_user_id = user_id.parse::<i32>().unwrap_or(0);
|
||||
let new_user_with_gu_info = user_service
|
||||
.update_gu_server_info(new_user.id, gu_user_id, username.clone())
|
||||
.await?;
|
||||
new_user_with_gu_info
|
||||
}
|
||||
};
|
||||
|
||||
// 生成JWT令牌
|
||||
let osu_id = user.osu_id.clone();
|
||||
let user_group_str = match user.user_group {
|
||||
crate::entity::sea_orm_active_enums::UserGroup::Player => "player",
|
||||
crate::entity::sea_orm_active_enums::UserGroup::Admin => "admin",
|
||||
};
|
||||
let jwt_token =
|
||||
crate::utils::jwt::generate_token(osu_id, user_group_str.to_string()).map_err(|e| {
|
||||
crate::error::AppError::InternalError(format!("Failed to generate token: {}", e))
|
||||
})?;
|
||||
|
||||
let auth_response = AuthResponse {
|
||||
access_token: jwt_token,
|
||||
refresh_token: token_response.refresh_token,
|
||||
user: crate::dto::user::UserResponse::from(user),
|
||||
};
|
||||
|
||||
Ok(Json(ApiResponse::success(auth_response)))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod auth;
|
||||
pub mod tournament_settings;
|
||||
pub mod user;
|
||||
|
||||
use axum::{Router, routing::get};
|
||||
@@ -14,6 +15,7 @@ pub fn configure_routes() -> Router<ServiceState> {
|
||||
.route("/health", get(|| async { "OK" }))
|
||||
.nest("/api/auth", auth::routes())
|
||||
.nest("/api/users", user::routes())
|
||||
.nest("/api/tournament-settings", tournament_settings::routes())
|
||||
}
|
||||
|
||||
#[derive(OpenApi)]
|
||||
@@ -31,6 +33,8 @@ pub fn configure_routes() -> Router<ServiceState> {
|
||||
user::update_registration_status,
|
||||
user::update_season,
|
||||
user::update_approved,
|
||||
tournament_settings::get_tournament_settings,
|
||||
tournament_settings::update_tournament_settings,
|
||||
),
|
||||
components(
|
||||
schemas(
|
||||
@@ -45,11 +49,16 @@ pub fn configure_routes() -> Router<ServiceState> {
|
||||
crate::dto::ApiResponseSchema<crate::api::auth::AuthResponse>,
|
||||
crate::dto::PaginatedResponseSchema<crate::dto::user::UserResponse>,
|
||||
user::PaginatedUserResponse,
|
||||
crate::dto::tournament_settings::TournamentSettingResponse,
|
||||
crate::dto::tournament_settings::UpdateTournamentSettingRequest,
|
||||
crate::dto::tournament_settings::TournamentSettingQueryParams,
|
||||
crate::api::tournament_settings::PaginatedTournamentSettingsResponse,
|
||||
)
|
||||
),
|
||||
tags(
|
||||
(name = "auth", description = "认证相关API"),
|
||||
(name = "users", description = "用户管理API"),
|
||||
(name = "tournament-settings", description = "比赛设置API"),
|
||||
)
|
||||
)]
|
||||
pub struct ApiDoc;
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::{Query, State},
|
||||
routing::{get, put},
|
||||
};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::{
|
||||
dto::{
|
||||
ApiResponse,
|
||||
tournament_settings::{
|
||||
TournamentSettingQueryParams, TournamentSettingResponse, UpdateTournamentSettingRequest,
|
||||
},
|
||||
},
|
||||
error::Result,
|
||||
middleware::auth::{AuthUser, require_admin},
|
||||
service::{ServiceState, tournament_settings::TournamentSettingService},
|
||||
};
|
||||
|
||||
pub fn routes() -> Router<ServiceState> {
|
||||
Router::new()
|
||||
.route("/", get(get_tournament_settings))
|
||||
.route("/", put(update_tournament_settings))
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, utoipa::ToSchema)]
|
||||
pub struct PaginatedTournamentSettingsResponse {
|
||||
pub items: Vec<TournamentSettingResponse>,
|
||||
pub total: u64,
|
||||
pub page: u64,
|
||||
pub page_size: u64,
|
||||
pub total_pages: u64,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/tournament-settings",
|
||||
params(TournamentSettingQueryParams),
|
||||
responses(
|
||||
(status = 200, description = "获取比赛设置列表成功", body = PaginatedTournamentSettingsResponse),
|
||||
(status = 401, description = "未授权"),
|
||||
(status = 500, description = "内部服务器错误"),
|
||||
),
|
||||
tag = "tournament-settings"
|
||||
)]
|
||||
async fn get_tournament_settings(
|
||||
State(state): State<ServiceState>,
|
||||
Query(params): Query<TournamentSettingQueryParams>,
|
||||
) -> Result<Json<ApiResponse<PaginatedTournamentSettingsResponse>>> {
|
||||
let service = TournamentSettingService::new(state.db);
|
||||
let page = params.page.unwrap_or(1);
|
||||
let page_size = params.page_size.unwrap_or(20);
|
||||
let (settings, total) = service.find_all(params).await?;
|
||||
|
||||
let response = PaginatedTournamentSettingsResponse {
|
||||
items: settings
|
||||
.into_iter()
|
||||
.map(|s| TournamentSettingResponse {
|
||||
id: s.id,
|
||||
tournament_name: s.tournament_name,
|
||||
max_pp_for_registration: s.max_pp_for_registration,
|
||||
min_pp_for_registration: s.min_pp_for_registration,
|
||||
current_season: s.current_season,
|
||||
current_season_stage: s.current_season_stage,
|
||||
mappool_visible: s.mappool_visible,
|
||||
created_at: s.created_at.to_rfc3339(),
|
||||
updated_at: s.updated_at.to_rfc3339(),
|
||||
})
|
||||
.collect(),
|
||||
total,
|
||||
page,
|
||||
page_size,
|
||||
total_pages: (total as f64 / page_size as f64).ceil() as u64,
|
||||
};
|
||||
|
||||
Ok(Json(ApiResponse::success(response)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/api/tournament-settings",
|
||||
request_body = UpdateTournamentSettingRequest,
|
||||
responses(
|
||||
(status = 200, description = "更新比赛设置成功", body = TournamentSettingResponse),
|
||||
(status = 400, description = "请求参数错误"),
|
||||
(status = 401, description = "未授权"),
|
||||
(status = 403, description = "权限不足,需要管理员权限"),
|
||||
(status = 500, description = "内部服务器错误"),
|
||||
),
|
||||
tag = "tournament-settings"
|
||||
)]
|
||||
async fn update_tournament_settings(
|
||||
State(state): State<ServiceState>,
|
||||
auth_user: AuthUser,
|
||||
Json(data): Json<UpdateTournamentSettingRequest>,
|
||||
) -> Result<Json<ApiResponse<TournamentSettingResponse>>> {
|
||||
// 验证用户是否为admin
|
||||
require_admin(&auth_user)?;
|
||||
|
||||
let service = TournamentSettingService::new(state.db);
|
||||
|
||||
// 检查是否存在设置,如果不存在则初始化
|
||||
let settings = match service.get_current_settings().await? {
|
||||
Some(existing) => {
|
||||
// 更新现有设置
|
||||
service.update(existing.id, data).await?
|
||||
}
|
||||
None => {
|
||||
// 初始化新设置
|
||||
let create_data = crate::dto::tournament_settings::CreateTournamentSettingRequest {
|
||||
tournament_name: data
|
||||
.tournament_name
|
||||
.unwrap_or_else(|| "Default Tournament".to_string()),
|
||||
max_pp_for_registration: data.max_pp_for_registration,
|
||||
min_pp_for_registration: data.min_pp_for_registration,
|
||||
current_season: data.current_season,
|
||||
current_season_stage: data.current_season_stage,
|
||||
mappool_visible: data.mappool_visible,
|
||||
};
|
||||
service.create(create_data).await?
|
||||
}
|
||||
};
|
||||
|
||||
let response = TournamentSettingResponse {
|
||||
id: settings.id,
|
||||
tournament_name: settings.tournament_name,
|
||||
max_pp_for_registration: settings.max_pp_for_registration,
|
||||
min_pp_for_registration: settings.min_pp_for_registration,
|
||||
current_season: settings.current_season,
|
||||
current_season_stage: settings.current_season_stage,
|
||||
mappool_visible: settings.mappool_visible,
|
||||
created_at: settings.created_at.to_rfc3339(),
|
||||
updated_at: settings.updated_at.to_rfc3339(),
|
||||
};
|
||||
|
||||
Ok(Json(ApiResponse::success(response)))
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ use crate::{
|
||||
dto::{
|
||||
ApiResponse, PaginatedResponse,
|
||||
user::{
|
||||
CreateUserRequest, UpdateApprovedRequest, UpdateRegistrationStatusRequest, UpdateSeasonRequest, UpdateUserGroupRequest, UpdateUserRequest, UserQueryParams,
|
||||
CreateUserRequest, UpdateApprovedRequest, UpdateRegistrationStatusRequest,
|
||||
UpdateSeasonRequest, UpdateUserGroupRequest, UpdateUserRequest, UserQueryParams,
|
||||
UserResponse,
|
||||
},
|
||||
},
|
||||
error::Result,
|
||||
middleware::auth::{AuthUser, require_admin},
|
||||
service::{ServiceState, user::UserService},
|
||||
};
|
||||
|
||||
@@ -114,11 +116,25 @@ async fn create_user() -> Result<Json<ApiResponse<UserResponse>>> {
|
||||
),
|
||||
tag = "users"
|
||||
)]
|
||||
#[axum::debug_handler]
|
||||
async fn update_user(
|
||||
State(state): State<ServiceState>,
|
||||
Path(id): Path<i32>,
|
||||
auth_user: AuthUser,
|
||||
Json(data): Json<UpdateUserRequest>,
|
||||
) -> Result<Json<ApiResponse<UserResponse>>> {
|
||||
// 检查是否为管理员,或者是否更新自己的信息
|
||||
// 注意:这里需要从数据库获取用户的osu_id来匹配
|
||||
// 暂时简化处理,实际项目中需要查询数据库
|
||||
let is_admin = auth_user.user_group == "admin";
|
||||
let is_self = auth_user.osu_id == id.to_string(); // 这里需要根据实际情况调整
|
||||
|
||||
if !is_admin && !is_self {
|
||||
return Err(crate::error::AppError::Forbidden(
|
||||
"只有管理员或用户本人可以更新用户信息".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let service = UserService::new(state.db);
|
||||
let user = service.update(id, data).await?;
|
||||
|
||||
@@ -141,11 +157,16 @@ async fn update_user(
|
||||
),
|
||||
tag = "users"
|
||||
)]
|
||||
#[axum::debug_handler]
|
||||
async fn update_user_group(
|
||||
State(state): State<ServiceState>,
|
||||
Path(id): Path<i32>,
|
||||
user: AuthUser,
|
||||
Json(data): Json<UpdateUserGroupRequest>,
|
||||
) -> Result<Json<ApiResponse<UserResponse>>> {
|
||||
// 检查是否为管理员
|
||||
require_admin(&user)?;
|
||||
|
||||
let service = UserService::new(state.db);
|
||||
let user = service.update_user_group(id, data).await?;
|
||||
|
||||
@@ -169,7 +190,11 @@ async fn update_user_group(
|
||||
async fn delete_user(
|
||||
State(state): State<ServiceState>,
|
||||
Path(id): Path<i32>,
|
||||
user: AuthUser,
|
||||
) -> Result<Json<ApiResponse<()>>> {
|
||||
// 检查是否为管理员
|
||||
require_admin(&user)?;
|
||||
|
||||
let service = UserService::new(state.db);
|
||||
service.delete(id).await?;
|
||||
|
||||
@@ -244,16 +269,18 @@ async fn update_season(
|
||||
),
|
||||
tag = "users"
|
||||
)]
|
||||
#[axum::debug_handler]
|
||||
async fn update_approved(
|
||||
State(state): State<ServiceState>,
|
||||
Path(id): Path<i32>,
|
||||
user: AuthUser,
|
||||
Json(data): Json<UpdateApprovedRequest>,
|
||||
) -> Result<Json<ApiResponse<UserResponse>>> {
|
||||
// 检查是否为管理员
|
||||
require_admin(&user)?;
|
||||
|
||||
let service = UserService::new(state.db);
|
||||
// 这里需要从认证中间件获取用户信息,检查是否为管理员
|
||||
// 暂时硬编码为true,实际项目中需要从JWT令牌中解析
|
||||
let is_admin = true;
|
||||
let user = service.update_approved(id, data, is_admin).await?;
|
||||
let user = service.update_approved(id, data, true).await?;
|
||||
|
||||
Ok(Json(ApiResponse::success(UserResponse::from(user))))
|
||||
}
|
||||
|
||||
@@ -21,9 +21,22 @@ pub struct UpdateTournamentSettingRequest {
|
||||
pub mappool_visible: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
#[derive(Debug, Deserialize, ToSchema, utoipa::IntoParams)]
|
||||
pub struct TournamentSettingQueryParams {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
pub current_season: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct TournamentSettingResponse {
|
||||
pub id: i32,
|
||||
pub tournament_name: String,
|
||||
pub max_pp_for_registration: Option<f32>,
|
||||
pub min_pp_for_registration: Option<f32>,
|
||||
pub current_season: Option<String>,
|
||||
pub current_season_stage: Option<String>,
|
||||
pub mappool_visible: Option<i32>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
10
src/main.rs
10
src/main.rs
@@ -30,9 +30,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
print_banner();
|
||||
println!("启动 AstraCup 后端服务...");
|
||||
println!("配置加载成功");
|
||||
println!("监听地址: {}:{}", config.host, config.port);
|
||||
println!("数据库: {}", mask_database_url(&config.database_url));
|
||||
|
||||
println!("连接数据库...");
|
||||
let db = Database::connect(&config.database_url).await?;
|
||||
@@ -42,8 +39,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let service_state = ServiceState::new(db);
|
||||
|
||||
// 配置应用
|
||||
let app = api::configure_routes()
|
||||
.with_state(service_state);
|
||||
let app = api::configure_routes().with_state(service_state);
|
||||
|
||||
println!("配置加载成功");
|
||||
|
||||
// 启动服务器
|
||||
let listener = tokio::net::TcpListener::bind(format!("{}:{}", config.host, config.port))
|
||||
@@ -51,6 +49,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.unwrap();
|
||||
|
||||
println!("服务器启动成功");
|
||||
println!("监听地址: {}:{}", config.host, config.port);
|
||||
println!("数据库: {}", mask_database_url(&config.database_url));
|
||||
println!("API文档: http://{}:{}/api/docs", config.host, config.port);
|
||||
println!("健康检查: http://{}:{}/health", config.host, config.port);
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
use axum::extract::FromRequestParts;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::AppError;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AuthUser {
|
||||
pub id: i32,
|
||||
pub osu_id: String,
|
||||
pub username: String,
|
||||
pub user_group: String,
|
||||
}
|
||||
|
||||
impl<S> FromRequestParts<S> for AuthUser
|
||||
where
|
||||
S: Send + Sync + Clone + 'static,
|
||||
{
|
||||
type Rejection = AppError;
|
||||
|
||||
fn from_request_parts(
|
||||
parts: &mut axum::http::request::Parts,
|
||||
_state: &S,
|
||||
) -> impl std::future::Future<Output = std::result::Result<Self, Self::Rejection>> + Send {
|
||||
async move {
|
||||
let auth_header = parts
|
||||
.headers
|
||||
.get("Authorization")
|
||||
.and_then(|header| header.to_str().ok())
|
||||
.ok_or_else(|| AppError::Unauthorized("Authorization header missing".into()))?;
|
||||
|
||||
if !auth_header.starts_with("Bearer ") {
|
||||
return Err(AppError::Unauthorized(
|
||||
"Invalid authorization format".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let token = &auth_header[7..];
|
||||
|
||||
let claims = crate::utils::jwt::verify_token(token)
|
||||
.map_err(|_| AppError::Unauthorized("Invalid or expired token".into()))?;
|
||||
|
||||
Ok(AuthUser {
|
||||
id: 0,
|
||||
osu_id: claims.sub,
|
||||
username: String::new(),
|
||||
user_group: claims.user_group,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn require_admin(user: &AuthUser) -> std::result::Result<(), AppError> {
|
||||
if user.user_group != "admin" {
|
||||
return Err(AppError::Forbidden("只有管理员才能执行此操作".into()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ pub struct ServiceState {
|
||||
pub db: DatabaseConnection,
|
||||
}
|
||||
|
||||
/// 确保 ServiceState 实现了 Send + Sync traits
|
||||
unsafe impl Send for ServiceState {}
|
||||
unsafe impl Sync for ServiceState {}
|
||||
|
||||
impl ServiceState {
|
||||
/// 创建新的服务状态
|
||||
pub fn new(db: DatabaseConnection) -> Self {
|
||||
|
||||
@@ -50,7 +50,7 @@ impl TournamentSettingService {
|
||||
tournament_settings::Entity::find_by_id(id)
|
||||
.one(&self.db)
|
||||
.await?
|
||||
.ok_or_else(|| AppError::NotFound(format!("锦标赛设置 {} 不存在", id)))
|
||||
.ok_or_else(|| AppError::NotFound(format!("比赛设置 {} 不存在", id)))
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
@@ -116,7 +116,7 @@ impl TournamentSettingService {
|
||||
.exec(&self.db)
|
||||
.await?;
|
||||
if result.rows_affected == 0 {
|
||||
return Err(AppError::NotFound(format!("锦标赛设置 {} 不存在", id)));
|
||||
return Err(AppError::NotFound(format!("比赛设置 {} 不存在", id)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -207,4 +207,13 @@ impl UserService {
|
||||
|
||||
Ok(user.update(&self.db).await?)
|
||||
}
|
||||
|
||||
pub async fn update_gu_server_info(&self, id: i32, gu_user_id: i32, gu_username: String) -> Result<user::Model> {
|
||||
let mut user: user::ActiveModel = self.find_by_id(id).await?.into();
|
||||
user.gu_server_user_id = Set(Some(gu_user_id));
|
||||
user.gu_server_username = Set(Some(gu_username));
|
||||
user.updated_at = Set(chrono::Utc::now());
|
||||
|
||||
Ok(user.update(&self.db).await?)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user