🧹 Remove unused legacy modules and functions

- Deleted quota.rs module (quota management not needed via HTTP)
- Removed legacy get_id_by_token GraphQL function
- Removed unused set_user_quota and increase_user_quota methods
- Cleaned up unused imports and legacy structs
- Simplified handlers/mod.rs to only expose universal_handler

Architecture now focused on core functionality:
- GET / (user info)
- GET /<filename> (file serving)
- POST / (file upload)
This commit is contained in:
2025-09-02 11:27:48 +03:00
parent 6c03863a86
commit d3bee5144f
9 changed files with 119 additions and 331 deletions

View File

@@ -179,42 +179,4 @@ impl AppState {
Ok(new_quota)
}
/// Устанавливает квоту пользователя в байтах (позволяет увеличить или уменьшить)
pub async fn set_user_quota(&self, user_id: &str, bytes: u64) -> Result<u64, actix_web::Error> {
let mut redis = self.redis.clone();
let quota_key = format!("quota:{}", user_id);
// Устанавливаем новое значение квоты
redis
.set::<_, u64, ()>(&quota_key, bytes)
.await
.map_err(|_| ErrorInternalServerError("Failed to set user quota in Redis"))?;
Ok(bytes)
}
/// Увеличивает квоту пользователя на указанное количество байт
pub async fn increase_user_quota(
&self,
user_id: &str,
additional_bytes: u64,
) -> Result<u64, actix_web::Error> {
let mut redis = self.redis.clone();
let quota_key = format!("quota:{}", user_id);
// Получаем текущую квоту
let current_quota: u64 = redis.get(&quota_key).await.unwrap_or(0);
// Вычисляем новую квоту
let new_quota = current_quota + additional_bytes;
// Устанавливаем новое значение
redis
.set::<_, u64, ()>(&quota_key, new_quota)
.await
.map_err(|_| ErrorInternalServerError("Failed to increase user quota in Redis"))?;
Ok(new_quota)
}
}

View File

@@ -2,33 +2,8 @@ use actix_web::error::ErrorInternalServerError;
use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode};
use log::{info, warn};
use redis::{AsyncCommands, aio::MultiplexedConnection};
use reqwest::Client as HTTPClient;
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::{collections::HashMap, env, error::Error};
// Старые структуры для совместимости с get_id_by_token
#[derive(Deserialize)]
struct AuthResponse {
data: Option<AuthData>,
}
#[derive(Deserialize)]
struct AuthData {
validate_jwt_token: Option<ValidateJWTToken>,
}
#[derive(Deserialize)]
struct ValidateJWTToken {
is_valid: bool,
claims: Option<Claims>,
}
#[derive(Deserialize)]
struct Claims {
sub: Option<String>,
}
use std::error::Error;
// Структуры для JWT токенов
#[derive(Debug, Deserialize)]
@@ -51,57 +26,6 @@ pub struct Author {
pub device_info: Option<String>,
}
/// Получает айди пользователя из токена в заголовке
#[allow(clippy::collapsible_if)]
pub async fn get_id_by_token(token: &str) -> Result<String, Box<dyn Error>> {
let auth_api_base = env::var("CORE_URL")?;
let query_name = "validate_jwt_token";
let operation = "ValidateToken";
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
let mut variables = HashMap::<String, HashMap<String, String>>::new();
let mut params = HashMap::<String, String>::new();
params.insert("token".to_string(), token.to_string());
params.insert("token_type".to_string(), "access_token".to_string());
variables.insert("params".to_string(), params);
let gql = json!({
"query": format!("query {}($params: ValidateJWTTokenInput!) {{ {}(params: $params) {{ is_valid claims }} }}", operation, query_name),
"operationName": operation,
"variables": variables
});
let client = HTTPClient::new();
let response = client
.post(&auth_api_base)
.headers(headers)
.json(&gql)
.send()
.await?;
if response.status().is_success() {
let auth_response: AuthResponse = response.json().await?;
if let Some(auth_data) = auth_response.data {
if let Some(validate_jwt_token) = auth_data.validate_jwt_token {
if validate_jwt_token.is_valid {
if let Some(claims) = validate_jwt_token.claims {
if let Some(sub) = claims.sub {
return Ok(sub);
}
}
}
}
}
Err(Box::new(std::io::Error::other("Invalid token response")))
} else {
Err(Box::new(std::io::Error::other(format!(
"Request failed with status: {}",
response.status()
))))
}
}
/// Декодирует JWT токен и извлекает claims с проверкой истечения
fn decode_jwt_token(token: &str) -> Result<TokenClaims, Box<dyn Error>> {
// В реальном приложении здесь должен быть настоящий секретный ключ

View File

@@ -1,13 +1,10 @@
mod proxy;
mod quota;
mod serve_file;
mod upload;
mod user;
mod universal;
pub use proxy::proxy_handler;
pub use quota::{get_quota_handler, increase_quota_handler, set_quota_handler};
pub use upload::upload_handler;
pub use user::get_current_user_handler;
pub use universal::universal_handler;
// Общий лимит квоты на пользователя: 5 ГБ
pub const MAX_USER_QUOTA_BYTES: u64 = 5 * 1024 * 1024 * 1024;
// Общий лимит квоты на пользователя: 12 ГБ
pub const MAX_USER_QUOTA_BYTES: u64 = 12 * 1024 * 1024 * 1024;

View File

@@ -1,166 +0,0 @@
use actix_web::{HttpRequest, HttpResponse, Result, web};
use log::warn;
use serde::{Deserialize, Serialize};
use crate::app_state::AppState;
use crate::auth::get_id_by_token;
#[derive(Deserialize)]
pub struct QuotaRequest {
pub user_id: String,
pub additional_bytes: Option<u64>,
pub new_quota_bytes: Option<u64>,
}
#[derive(Serialize)]
pub struct QuotaResponse {
pub user_id: String,
pub current_quota: u64,
pub max_quota: u64,
}
/// Обработчик для получения информации о квоте пользователя
pub async fn get_quota_handler(
req: HttpRequest,
state: web::Data<AppState>,
) -> Result<HttpResponse, actix_web::Error> {
// Проверяем авторизацию
let token = req
.headers()
.get("Authorization")
.and_then(|header_value| header_value.to_str().ok());
if token.is_none() {
return Err(actix_web::error::ErrorUnauthorized("Unauthorized"));
}
let _admin_id = get_id_by_token(token.unwrap()).await.map_err(|e| {
let error_msg = if e.to_string().contains("expired") {
"Admin token has expired"
} else {
"Invalid admin token"
};
actix_web::error::ErrorUnauthorized(error_msg)
})?;
// Получаем user_id из query параметров
let user_id = req
.query_string()
.split("user_id=")
.nth(1)
.and_then(|s| s.split('&').next())
.ok_or_else(|| actix_web::error::ErrorBadRequest("Missing user_id parameter"))?;
// Получаем текущую квоту пользователя
let current_quota = state.get_or_create_quota(user_id).await?;
let response = QuotaResponse {
user_id: user_id.to_string(),
current_quota,
max_quota: crate::handlers::MAX_USER_QUOTA_BYTES,
};
Ok(HttpResponse::Ok().json(response))
}
/// Обработчик для увеличения квоты пользователя
pub async fn increase_quota_handler(
req: HttpRequest,
quota_data: web::Json<QuotaRequest>,
state: web::Data<AppState>,
) -> Result<HttpResponse, actix_web::Error> {
// Проверяем авторизацию
let token = req
.headers()
.get("Authorization")
.and_then(|header_value| header_value.to_str().ok());
if token.is_none() {
return Err(actix_web::error::ErrorUnauthorized("Unauthorized"));
}
let _admin_id = get_id_by_token(token.unwrap()).await.map_err(|e| {
let error_msg = if e.to_string().contains("expired") {
"Admin token has expired"
} else {
"Invalid admin token"
};
actix_web::error::ErrorUnauthorized(error_msg)
})?;
let additional_bytes = quota_data
.additional_bytes
.ok_or_else(|| actix_web::error::ErrorBadRequest("Missing additional_bytes parameter"))?;
if additional_bytes == 0 {
return Err(actix_web::error::ErrorBadRequest(
"additional_bytes must be greater than 0",
));
}
// Увеличиваем квоту пользователя
let new_quota = state
.increase_user_quota(&quota_data.user_id, additional_bytes)
.await?;
warn!(
"Increased quota for user {} by {} bytes, new total: {} bytes",
quota_data.user_id, additional_bytes, new_quota
);
let response = QuotaResponse {
user_id: quota_data.user_id.clone(),
current_quota: new_quota,
max_quota: crate::handlers::MAX_USER_QUOTA_BYTES,
};
Ok(HttpResponse::Ok().json(response))
}
/// Обработчик для установки квоты пользователя
pub async fn set_quota_handler(
req: HttpRequest,
quota_data: web::Json<QuotaRequest>,
state: web::Data<AppState>,
) -> Result<HttpResponse, actix_web::Error> {
// Проверяем авторизацию
let token = req
.headers()
.get("Authorization")
.and_then(|header_value| header_value.to_str().ok());
if token.is_none() {
return Err(actix_web::error::ErrorUnauthorized("Unauthorized"));
}
let _admin_id = get_id_by_token(token.unwrap()).await.map_err(|e| {
let error_msg = if e.to_string().contains("expired") {
"Admin token has expired"
} else {
"Invalid admin token"
};
actix_web::error::ErrorUnauthorized(error_msg)
})?;
let new_quota_bytes = quota_data
.new_quota_bytes
.ok_or_else(|| actix_web::error::ErrorBadRequest("Missing new_quota_bytes parameter"))?;
// Устанавливаем новую квоту пользователя
let new_quota = state
.set_user_quota(&quota_data.user_id, new_quota_bytes)
.await?;
warn!(
"Set quota for user {} to {} bytes",
quota_data.user_id, new_quota
);
let response = QuotaResponse {
user_id: quota_data.user_id.clone(),
current_quota: new_quota,
max_quota: crate::handlers::MAX_USER_QUOTA_BYTES,
};
Ok(HttpResponse::Ok().json(response))
}

68
src/handlers/universal.rs Normal file
View File

@@ -0,0 +1,68 @@
use actix_web::{HttpRequest, HttpResponse, Result, web};
use actix_multipart::Multipart;
use log::{info, warn};
use crate::app_state::AppState;
/// Универсальный обработчик, который определяет HTTP метод и путь
pub async fn universal_handler(
req: HttpRequest,
payload: web::Payload,
state: web::Data<AppState>,
) -> Result<HttpResponse, actix_web::Error> {
let method = req.method().clone();
let path = req.path().to_string();
info!("Universal handler: {} {}", method, path);
// Возвращаем 404 для .well-known путей (для Let's Encrypt ACME)
if path.starts_with("/.well-known/") {
warn!("ACME challenge path requested: {}", path);
return Ok(HttpResponse::NotFound().finish());
}
match method.as_str() {
"GET" => handle_get(req, state, &path).await,
"POST" => handle_post(req, payload, state, &path).await,
_ => {
warn!("Unsupported HTTP method: {}", method);
Ok(HttpResponse::MethodNotAllowed().json(serde_json::json!({
"error": "Method not allowed"
})))
}
}
}
async fn handle_get(
req: HttpRequest,
state: web::Data<AppState>,
path: &str,
) -> Result<HttpResponse, actix_web::Error> {
if path == "/" {
// GET / - получение информации о пользователе
crate::handlers::user::get_current_user_handler(req, state).await
} else {
// GET /{path} - получение файла через proxy
let path_without_slash = path.trim_start_matches('/');
let requested_res = web::Path::from(path_without_slash.to_string());
crate::handlers::proxy::proxy_handler(req, requested_res, state).await
}
}
async fn handle_post(
req: HttpRequest,
payload: web::Payload,
state: web::Data<AppState>,
path: &str,
) -> Result<HttpResponse, actix_web::Error> {
if path == "/" {
// POST / - загрузка файла (multipart)
let multipart = Multipart::new(&req.headers(), payload);
crate::handlers::upload::upload_handler(req, multipart, state).await
} else {
warn!("Unsupported POST path: {}", path);
Ok(HttpResponse::NotFound().json(serde_json::json!({
"error": "Endpoint not found"
})))
}
}

View File

@@ -14,10 +14,7 @@ use actix_web::{
};
use app_state::AppState;
use handlers::{
get_current_user_handler, get_quota_handler, increase_quota_handler, proxy_handler,
set_quota_handler, upload_handler,
};
use handlers::universal_handler;
use log::warn;
use std::env;
use tokio::task::spawn_blocking;
@@ -63,19 +60,7 @@ async fn main() -> std::io::Result<()> {
.app_data(web::Data::new(app_state.clone()))
.wrap(cors)
.wrap(Logger::default())
.route("/", web::get().to(get_current_user_handler))
.route("/", web::post().to(upload_handler))
.route("/quota", web::get().to(get_quota_handler))
.route("/quota/increase", web::post().to(increase_quota_handler))
.route("/quota/set", web::post().to(set_quota_handler))
.service(
web::scope("/.well-known")
.service(
actix_files::Files::new("/", "/tmp/.well-known")
.show_files_listing()
)
)
.route("/{path:.*}", web::get().to(proxy_handler))
.default_service(web::to(universal_handler))
})
.bind(addr)?
.run()