use crate::api::api::{
api_parse_body,
api_service_token,
api_validation
};
use crate::api::structs::api_service_data::ApiServiceData;
use crate::api::structs::query_token::QueryToken;
use crate::common::common::{
hash_id,
hex2bin
};
use crate::tracker::enums::updates_action::UpdatesAction;
use crate::tracker::structs::user_entry_item::UserEntryItem;
use crate::tracker::structs::user_id::UserId;
use actix_web::http::header::ContentType;
use actix_web::http::StatusCode;
use actix_web::web::Data;
use actix_web::{
web,
HttpRequest,
HttpResponse
};
use regex::Regex;
use serde_json::{
json,
Value
};
use std::collections::{
BTreeMap,
HashMap
};
use std::sync::Arc;
lazy_static::lazy_static! {
static ref UUID_REGEX: Regex = Regex::new(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$").unwrap();
}
pub async fn api_service_user_get(request: HttpRequest, path: web::Path<String>, data: Data<Arc<ApiServiceData>>) -> HttpResponse
{
if let Some(error_return) = api_validation(&request, &data).await { return error_return; }
let params = web::Query::<QueryToken>::from_query(request.query_string()).unwrap();
if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; }
let id = path.into_inner();
let (status_code, data) = api_service_users_return_json(id, data);
match status_code {
StatusCode::OK => HttpResponse::Ok().content_type(ContentType::json()).json(data),
_ => HttpResponse::NotFound().content_type(ContentType::json()).json(data),
}
}
pub async fn api_service_users_get(request: HttpRequest, payload: web::Payload, data: Data<Arc<ApiServiceData>>) -> HttpResponse
{
if let Some(error_return) = api_validation(&request, &data).await { return error_return; }
let params = web::Query::<QueryToken>::from_query(request.query_string()).unwrap();
if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; }
let body = match api_parse_body(payload).await {
Ok(data) => data,
Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})),
};
let ids = match serde_json::from_slice::<Vec<String>>(&body) {
Ok(id) => id,
Err(_) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad json body"})),
};
let mut users_output = HashMap::with_capacity(ids.len());
for id in ids {
if id.len() == 40 {
let (_, user_data) = api_service_users_return_json(id.clone(), Data::clone(&data));
users_output.insert(id, user_data);
}
}
HttpResponse::Ok().content_type(ContentType::json()).json(json!({
"status": "ok",
"users": users_output
}))
}
pub async fn api_service_user_post(request: HttpRequest, path: web::Path<(String, String, u64, u64, u64, u64, u8)>, data: Data<Arc<ApiServiceData>>) -> HttpResponse
{
if let Some(error_return) = api_validation(&request, &data).await { return error_return; }
let params = web::Query::<QueryToken>::from_query(request.query_string()).unwrap();
if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; }
let (id, key, uploaded, downloaded, completed, updated, active) = path.into_inner();
if key.len() != 40 {
return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad key_hash"}));
}
let key_hash = match hex2bin(key) {
Ok(hash) => UserId(hash),
Err(_) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "invalid key_hash"})),
};
let mut user_entry = UserEntryItem {
key: key_hash,
user_id: None,
user_uuid: None,
uploaded,
downloaded,
completed,
updated,
active,
torrents_active: BTreeMap::new(),
};
let id_hash = if data.torrent_tracker.config.database_structure.users.id_uuid {
if !UUID_REGEX.is_match(&id) {
return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "invalid uuid"}));
}
user_entry.user_uuid = Some(id.to_lowercase());
hash_id(&id)
} else {
match id.parse::<u64>() {
Ok(user_id) => {
user_entry.user_id = Some(user_id);
hash_id(&id)
}
Err(_) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "invalid id"})),
}
};
if data.torrent_tracker.config.database.persistent {
let _ = data.torrent_tracker.add_user_update(UserId(id_hash), user_entry.clone(), UpdatesAction::Add);
}
if data.torrent_tracker.add_user(UserId(id_hash), user_entry) {
HttpResponse::Ok().content_type(ContentType::json()).json(json!({"status": "user_hash added"}))
} else {
HttpResponse::NotModified().content_type(ContentType::json()).json(json!({"status": "user_hash updated"}))
}
}
pub async fn api_service_users_post(request: HttpRequest, payload: web::Payload, data: Data<Arc<ApiServiceData>>) -> HttpResponse
{
if let Some(error_return) = api_validation(&request, &data).await { return error_return; }
let params = web::Query::<QueryToken>::from_query(request.query_string()).unwrap();
if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; }
let body = match api_parse_body(payload).await {
Ok(data) => data,
Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})),
};
let hashes = match serde_json::from_slice::<Vec<(String, String, u64, u64, u64, u64, u8)>>(&body) {
Ok(data) => data,
Err(_) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad json body"})),
};
let mut users_output = HashMap::with_capacity(hashes.len());
for (id, key, uploaded, downloaded, completed, updated, active) in hashes {
if key.len() == 40 {
let key_hash = match hex2bin(key) {
Ok(hash) => UserId(hash),
Err(_) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "invalid key_hash"})),
};
let mut user_entry = UserEntryItem {
key: key_hash,
user_id: None,
user_uuid: None,
uploaded,
downloaded,
completed,
updated,
active,
torrents_active: BTreeMap::new(),
};
let id_hash = if data.torrent_tracker.config.database_structure.users.id_uuid {
if !UUID_REGEX.is_match(&id) {
return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "invalid uuid"}));
}
user_entry.user_uuid = Some(id.to_lowercase());
hash_id(&id)
} else {
match id.parse::<u64>() {
Ok(user_id) => {
user_entry.user_id = Some(user_id);
hash_id(&id)
}
Err(_) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "invalid id"})),
}
};
if data.torrent_tracker.config.database.persistent {
let _ = data.torrent_tracker.add_user_update(UserId(id_hash), user_entry.clone(), UpdatesAction::Add);
}
let status = if data.torrent_tracker.add_user(UserId(id_hash), user_entry) {
json!({"status": "user_hash added"})
} else {
json!({"status": "user_hash updated"})
};
users_output.insert(id, status);
}
}
HttpResponse::Ok().content_type(ContentType::json()).json(json!({
"status": "ok",
"users": users_output
}))
}
pub async fn api_service_user_delete(request: HttpRequest, path: web::Path<String>, data: Data<Arc<ApiServiceData>>) -> HttpResponse
{
if let Some(error_return) = api_validation(&request, &data).await { return error_return; }
let params = web::Query::<QueryToken>::from_query(request.query_string()).unwrap();
if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; }
let id = path.into_inner();
if id.len() != 40 {
return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad user_hash"}));
}
let id_hash = match hex2bin(id) {
Ok(hash) => UserId(hash),
Err(_) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "invalid user_hash"})),
};
if data.torrent_tracker.config.database.persistent {
let empty_user = UserEntryItem {
key: UserId([0u8; 20]),
user_id: None,
user_uuid: None,
uploaded: 0,
downloaded: 0,
completed: 0,
updated: 0,
active: 0,
torrents_active: BTreeMap::new(),
};
let _ = data.torrent_tracker.add_user_update(id_hash, empty_user, UpdatesAction::Remove);
}
match data.torrent_tracker.remove_user(id_hash) {
None => HttpResponse::NotModified().content_type(ContentType::json()).json(json!({"status": "unknown user_hash"})),
Some(_) => HttpResponse::Ok().content_type(ContentType::json()).json(json!({"status": "ok"})),
}
}
pub async fn api_service_users_delete(request: HttpRequest, payload: web::Payload, data: Data<Arc<ApiServiceData>>) -> HttpResponse
{
if let Some(error_return) = api_validation(&request, &data).await { return error_return; }
let params = web::Query::<QueryToken>::from_query(request.query_string()).unwrap();
if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; }
let body = match api_parse_body(payload).await {
Ok(data) => data,
Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})),
};
let ids = match serde_json::from_slice::<Vec<String>>(&body) {
Ok(data) => data,
Err(_) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad json body"})),
};
let mut users_output = HashMap::with_capacity(ids.len());
for id in ids {
if id.len() == 40 {
let id_hash = match hex2bin(id.clone()) {
Ok(hash) => UserId(hash),
Err(_) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "invalid user_hash"})),
};
if data.torrent_tracker.config.database.persistent {
let empty_user = UserEntryItem {
key: UserId([0u8; 20]),
user_id: None,
user_uuid: None,
uploaded: 0,
downloaded: 0,
completed: 0,
updated: 0,
active: 0,
torrents_active: BTreeMap::new(),
};
let _ = data.torrent_tracker.add_user_update(id_hash, empty_user, UpdatesAction::Remove);
}
let status = match data.torrent_tracker.remove_user(id_hash) {
None => json!({"status": "unknown user_hash"}),
Some(_) => json!({"status": "ok"}),
};
users_output.insert(id, status);
}
}
HttpResponse::Ok().content_type(ContentType::json()).json(json!({
"status": "ok",
"users": users_output
}))
}
pub fn api_service_users_return_json(id: String, data: Data<Arc<ApiServiceData>>) -> (StatusCode, Value)
{
let id_hash = hash_id(&id);
let uses_uuid = data.torrent_tracker.config.database_structure.users.id_uuid;
match data.torrent_tracker.get_user(UserId(id_hash)) {
None => (StatusCode::NOT_FOUND, json!({"status": "unknown user_hash"})),
Some(user_data) => {
let response = if uses_uuid {
json!({
"status": "ok",
"uuid": user_data.user_uuid,
"key": user_data.key,
"uploaded": user_data.uploaded,
"downloaded": user_data.downloaded,
"completed": user_data.completed,
"updated": user_data.updated,
"active": user_data.active,
"torrents_active": user_data.torrents_active
})
} else {
json!({
"status": "ok",
"id": user_data.user_id,
"key": user_data.key,
"uploaded": user_data.uploaded,
"downloaded": user_data.downloaded,
"completed": user_data.completed,
"updated": user_data.updated,
"active": user_data.active,
"torrents_active": user_data.torrents_active
})
};
(StatusCode::OK, response)
}
}
}