feat: add centralized API service layer for frontend
All checks were successful
Build MIPS Binary / build (push) Successful in 5m18s
All checks were successful
Build MIPS Binary / build (push) Successful in 5m18s
- Create frontend/src/api/mod.rs with centralized HTTP client and error handling - Implement api::auth module (login, logout, check_auth, get_user) - Implement api::torrent module (add, action, delete, start, stop, set_label, set_priority) - Implement api::setup module (get_status, setup) - Implement api::settings module (set_global_limits) - Implement api::push module (get_public_key, subscribe) - Update all components to use api service layer instead of direct gloo_net calls - Add thiserror dependency for error handling
This commit is contained in:
243
frontend/src/api/mod.rs
Normal file
243
frontend/src/api/mod.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
use gloo_net::http::Request;
|
||||
use shared::{AddTorrentRequest, TorrentActionRequest};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ApiError {
|
||||
#[error("Network error")]
|
||||
Network,
|
||||
#[error("Server error: {status}")]
|
||||
Server { status: u16 },
|
||||
#[error("Login failed")]
|
||||
LoginFailed,
|
||||
#[error("Unauthorized")]
|
||||
Unauthorized,
|
||||
#[error("Too many requests")]
|
||||
RateLimited,
|
||||
}
|
||||
|
||||
fn base_url() -> String {
|
||||
"/api".to_string()
|
||||
}
|
||||
|
||||
pub mod auth {
|
||||
use super::*;
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct LoginRequest {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub remember_me: bool,
|
||||
}
|
||||
|
||||
pub async fn login(
|
||||
username: &str,
|
||||
password: &str,
|
||||
remember_me: bool,
|
||||
) -> Result<(), ApiError> {
|
||||
let req = LoginRequest {
|
||||
username: username.to_string(),
|
||||
password: password.to_string(),
|
||||
remember_me,
|
||||
};
|
||||
let resp = Request::post(&format!("{}/auth/login", base_url()))
|
||||
.json(&req)
|
||||
.map_err(|_| ApiError::Network)?
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
|
||||
if resp.ok() {
|
||||
Ok(())
|
||||
} else if resp.status() == 429 {
|
||||
Err(ApiError::RateLimited)
|
||||
} else {
|
||||
Err(ApiError::LoginFailed)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn logout() -> Result<(), ApiError> {
|
||||
Request::post(&format!("{}/auth/logout", base_url()))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn check_auth() -> Result<bool, ApiError> {
|
||||
let resp = Request::get(&format!("{}/auth/check", base_url()))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
Ok(resp.ok())
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct UserResponse {
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
pub async fn get_user() -> Result<UserResponse, ApiError> {
|
||||
let resp = Request::get(&format!("{}/auth/check", base_url()))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
let user = resp.json().await.map_err(|_| ApiError::Network)?;
|
||||
Ok(user)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod setup {
|
||||
use super::*;
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct SetupRequest {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct SetupStatusResponse {
|
||||
pub completed: bool,
|
||||
}
|
||||
|
||||
pub async fn get_status() -> Result<SetupStatusResponse, ApiError> {
|
||||
let resp = Request::get(&format!("{}/setup/status", base_url()))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
let status = resp.json().await.map_err(|_| ApiError::Network)?;
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
pub async fn setup(username: &str, password: &str) -> Result<(), ApiError> {
|
||||
let req = SetupRequest {
|
||||
username: username.to_string(),
|
||||
password: password.to_string(),
|
||||
};
|
||||
Request::post(&format!("{}/setup", base_url()))
|
||||
.json(&req)
|
||||
.map_err(|_| ApiError::Network)?
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod settings {
|
||||
use super::*;
|
||||
use shared::GlobalLimitRequest;
|
||||
|
||||
pub async fn set_global_limits(req: &GlobalLimitRequest) -> Result<(), ApiError> {
|
||||
Request::post(&format!("{}/settings/global-limits", base_url()))
|
||||
.json(req)
|
||||
.map_err(|_| ApiError::Network)?
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod push {
|
||||
use super::*;
|
||||
use crate::store::PushSubscriptionData;
|
||||
|
||||
pub async fn get_public_key() -> Result<String, ApiError> {
|
||||
let resp = Request::get(&format!("{}/push/public-key", base_url()))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
let key = resp.text().await.map_err(|_| ApiError::Network)?;
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
pub async fn subscribe(req: &PushSubscriptionData) -> Result<(), ApiError> {
|
||||
Request::post(&format!("{}/push/subscribe", base_url()))
|
||||
.json(req)
|
||||
.map_err(|_| ApiError::Network)?
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod torrent {
|
||||
use super::*;
|
||||
|
||||
pub async fn add(uri: &str) -> Result<(), ApiError> {
|
||||
let req = AddTorrentRequest {
|
||||
uri: uri.to_string(),
|
||||
};
|
||||
Request::post(&format!("{}/torrents/add", base_url()))
|
||||
.json(&req)
|
||||
.map_err(|_| ApiError::Network)?
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn action(hash: &str, action: &str) -> Result<(), ApiError> {
|
||||
let req = TorrentActionRequest {
|
||||
hash: hash.to_string(),
|
||||
action: action.to_string(),
|
||||
};
|
||||
Request::post(&format!("{}/torrents/action", base_url()))
|
||||
.json(&req)
|
||||
.map_err(|_| ApiError::Network)?
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete(hash: &str) -> Result<(), ApiError> {
|
||||
action(hash, "delete").await
|
||||
}
|
||||
|
||||
pub async fn delete_with_data(hash: &str) -> Result<(), ApiError> {
|
||||
action(hash, "delete_with_data").await
|
||||
}
|
||||
|
||||
pub async fn start(hash: &str) -> Result<(), ApiError> {
|
||||
action(hash, "start").await
|
||||
}
|
||||
|
||||
pub async fn stop(hash: &str) -> Result<(), ApiError> {
|
||||
action(hash, "stop").await
|
||||
}
|
||||
|
||||
pub async fn set_label(hash: &str, label: &str) -> Result<(), ApiError> {
|
||||
use shared::SetLabelRequest;
|
||||
let req = SetLabelRequest {
|
||||
hash: hash.to_string(),
|
||||
label: label.to_string(),
|
||||
};
|
||||
Request::post(&format!("{}/torrents/set_label", base_url()))
|
||||
.json(&req)
|
||||
.map_err(|_| ApiError::Network)?
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_priority(hash: &str, file_index: u32, priority: u8) -> Result<(), ApiError> {
|
||||
use shared::SetFilePriorityRequest;
|
||||
let req = SetFilePriorityRequest {
|
||||
hash: hash.to_string(),
|
||||
file_index,
|
||||
priority,
|
||||
};
|
||||
Request::post(&format!("{}/torrents/set_priority", base_url()))
|
||||
.json(&req)
|
||||
.map_err(|_| ApiError::Network)?
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user