diff --git a/src/routes/about.rs b/src/routes/about.rs index c5b81d2d..d6968af1 100644 --- a/src/routes/about.rs +++ b/src/routes/about.rs @@ -2,10 +2,11 @@ use actix_web::http::StatusCode; use actix_web::{web, HttpResponse, Responder}; use crate::errors::ServiceResult; +use crate::routes::API_VERSION; pub fn init(cfg: &mut web::ServiceConfig) { cfg.service( - web::scope("/about") + web::scope(&format!("/{API_VERSION}/about")) .service(web::resource("").route(web::get().to(get))) .service(web::resource("/license").route(web::get().to(license))), ); diff --git a/src/routes/category.rs b/src/routes/category.rs index 865d233d..c1c09f01 100644 --- a/src/routes/category.rs +++ b/src/routes/category.rs @@ -4,10 +4,11 @@ use serde::{Deserialize, Serialize}; use crate::common::WebAppData; use crate::errors::{ServiceError, ServiceResult}; use crate::models::response::OkResponse; +use crate::routes::API_VERSION; pub fn init(cfg: &mut web::ServiceConfig) { cfg.service( - web::scope("/category").service( + web::scope(&format!("/{API_VERSION}/category")).service( web::resource("") .route(web::get().to(get)) .route(web::post().to(add)) diff --git a/src/routes/mod.rs b/src/routes/mod.rs index ce833698..f9b0f2cd 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -8,6 +8,8 @@ pub mod settings; pub mod torrent; pub mod user; +pub const API_VERSION: &str = "v1"; + pub fn init(cfg: &mut web::ServiceConfig) { user::init(cfg); torrent::init(cfg); diff --git a/src/routes/proxy.rs b/src/routes/proxy.rs index c61b9326..0226c628 100644 --- a/src/routes/proxy.rs +++ b/src/routes/proxy.rs @@ -8,6 +8,7 @@ use text_to_png::TextRenderer; use crate::cache::image::manager::Error; use crate::common::WebAppData; use crate::errors::ServiceResult; +use crate::routes::API_VERSION; static ERROR_IMAGE_LOADER: Once = Once::new(); @@ -27,7 +28,9 @@ const ERROR_IMAGE_USER_QUOTA_MET_TEXT: &str = "Image proxy quota met."; const ERROR_IMAGE_UNAUTHENTICATED_TEXT: &str = "Sign in to see image."; pub fn init(cfg: &mut web::ServiceConfig) { - cfg.service(web::scope("/proxy").service(web::resource("/image/{url}").route(web::get().to(get_proxy_image)))); + cfg.service( + web::scope(&format!("/{API_VERSION}/proxy")).service(web::resource("/image/{url}").route(web::get().to(get_proxy_image))), + ); load_error_images(); } diff --git a/src/routes/root.rs b/src/routes/root.rs index ffeb1ed4..29004dd8 100644 --- a/src/routes/root.rs +++ b/src/routes/root.rs @@ -1,7 +1,8 @@ use actix_web::web; -use crate::routes::about; +use crate::routes::{about, API_VERSION}; pub fn init(cfg: &mut web::ServiceConfig) { cfg.service(web::scope("/").service(web::resource("").route(web::get().to(about::get)))); + cfg.service(web::scope(&format!("/{API_VERSION}")).service(web::resource("").route(web::get().to(about::get)))); } diff --git a/src/routes/settings.rs b/src/routes/settings.rs index a5c34e04..c9dba928 100644 --- a/src/routes/settings.rs +++ b/src/routes/settings.rs @@ -4,10 +4,11 @@ use crate::common::WebAppData; use crate::config; use crate::errors::{ServiceError, ServiceResult}; use crate::models::response::OkResponse; +use crate::routes::API_VERSION; pub fn init(cfg: &mut web::ServiceConfig) { cfg.service( - web::scope("/settings") + web::scope(&format!("/{API_VERSION}/settings")) .service(web::resource("").route(web::get().to(get)).route(web::post().to(update))) .service(web::resource("/name").route(web::get().to(site_name))) .service(web::resource("/public").route(web::get().to(get_public))), diff --git a/src/routes/torrent.rs b/src/routes/torrent.rs index e670c27d..0dc13881 100644 --- a/src/routes/torrent.rs +++ b/src/routes/torrent.rs @@ -14,12 +14,13 @@ use crate::errors::{ServiceError, ServiceResult}; use crate::models::info_hash::InfoHash; use crate::models::response::{NewTorrentResponse, OkResponse, TorrentResponse}; use crate::models::torrent::TorrentRequest; +use crate::routes::API_VERSION; use crate::utils::parse_torrent; use crate::AsCSV; pub fn init(cfg: &mut web::ServiceConfig) { cfg.service( - web::scope("/torrent") + web::scope(&format!("/{API_VERSION}/torrent")) .service(web::resource("/upload").route(web::post().to(upload))) .service(web::resource("/download/{info_hash}").route(web::get().to(download_torrent_handler))) .service( @@ -29,7 +30,9 @@ pub fn init(cfg: &mut web::ServiceConfig) { .route(web::delete().to(delete)), ), ); - cfg.service(web::scope("/torrents").service(web::resource("").route(web::get().to(get_torrents_handler)))); + cfg.service( + web::scope(&format!("/{API_VERSION}/torrents")).service(web::resource("").route(web::get().to(get_torrents_handler))), + ); } #[derive(FromRow)] diff --git a/src/routes/user.rs b/src/routes/user.rs index 02a8780b..4ca0cf60 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -13,12 +13,13 @@ use crate::errors::{ServiceError, ServiceResult}; use crate::mailer::VerifyClaims; use crate::models::response::{OkResponse, TokenResponse}; use crate::models::user::UserAuthentication; +use crate::routes::API_VERSION; use crate::utils::clock; use crate::utils::regex::validate_email_address; pub fn init(cfg: &mut web::ServiceConfig) { cfg.service( - web::scope("/user") + web::scope(&format!("/{API_VERSION}/user")) .service(web::resource("/register").route(web::post().to(register))) .service(web::resource("/login").route(web::post().to(login))) // code-review: should not this be a POST method? We add the user to the blacklist. We do not delete the user. diff --git a/tests/common/client.rs b/tests/common/client.rs index 0135a517..67bae6bc 100644 --- a/tests/common/client.rs +++ b/tests/common/client.rs @@ -19,12 +19,16 @@ impl Client { // todo: forms in POST requests can be passed by reference. It's already // changed for the `update_settings` method. + fn base_path() -> String { + "/v1".to_string() + } + pub fn unauthenticated(bind_address: &str) -> Self { - Self::new(ConnectionInfo::anonymous(bind_address)) + Self::new(ConnectionInfo::anonymous(bind_address, &Self::base_path())) } pub fn authenticated(bind_address: &str, token: &str) -> Self { - Self::new(ConnectionInfo::new(bind_address, token)) + Self::new(ConnectionInfo::new(bind_address, &Self::base_path(), token)) } pub fn new(connection_info: ConnectionInfo) -> Self { @@ -42,25 +46,25 @@ impl Client { // Context: about pub async fn about(&self) -> TextResponse { - self.http_client.get("about", Query::empty()).await + self.http_client.get("/about", Query::empty()).await } pub async fn license(&self) -> TextResponse { - self.http_client.get("about/license", Query::empty()).await + self.http_client.get("/about/license", Query::empty()).await } // Context: category pub async fn get_categories(&self) -> TextResponse { - self.http_client.get("category", Query::empty()).await + self.http_client.get("/category", Query::empty()).await } pub async fn add_category(&self, add_category_form: AddCategoryForm) -> TextResponse { - self.http_client.post("category", &add_category_form).await + self.http_client.post("/category", &add_category_form).await } pub async fn delete_category(&self, delete_category_form: DeleteCategoryForm) -> TextResponse { - self.http_client.delete_with_body("category", &delete_category_form).await + self.http_client.delete_with_body("/category", &delete_category_form).await } // Context: root @@ -72,86 +76,82 @@ impl Client { // Context: settings pub async fn get_public_settings(&self) -> TextResponse { - self.http_client.get("settings/public", Query::empty()).await + self.http_client.get("/settings/public", Query::empty()).await } pub async fn get_site_name(&self) -> TextResponse { - self.http_client.get("settings/name", Query::empty()).await + self.http_client.get("/settings/name", Query::empty()).await } pub async fn get_settings(&self) -> TextResponse { - self.http_client.get("settings", Query::empty()).await + self.http_client.get("/settings", Query::empty()).await } pub async fn update_settings(&self, update_settings_form: &UpdateSettings) -> TextResponse { - self.http_client.post("settings", &update_settings_form).await + self.http_client.post("/settings", &update_settings_form).await } // Context: torrent pub async fn get_torrents(&self, params: Query) -> TextResponse { - self.http_client.get("torrents", params).await + self.http_client.get("/torrents", params).await } pub async fn get_torrent(&self, info_hash: &InfoHash) -> TextResponse { - self.http_client.get(&format!("torrent/{info_hash}"), Query::empty()).await + self.http_client.get(&format!("/torrent/{info_hash}"), Query::empty()).await } pub async fn delete_torrent(&self, info_hash: &InfoHash) -> TextResponse { - self.http_client.delete(&format!("torrent/{info_hash}")).await + self.http_client.delete(&format!("/torrent/{info_hash}")).await } pub async fn update_torrent(&self, info_hash: &InfoHash, update_torrent_form: UpdateTorrentFrom) -> TextResponse { self.http_client - .put(&format!("torrent/{info_hash}"), &update_torrent_form) + .put(&format!("/torrent/{info_hash}"), &update_torrent_form) .await } pub async fn upload_torrent(&self, form: multipart::Form) -> TextResponse { - self.http_client.post_multipart("torrent/upload", form).await + self.http_client.post_multipart("/torrent/upload", form).await } pub async fn download_torrent(&self, info_hash: &InfoHash) -> responses::BinaryResponse { self.http_client - .get_binary(&format!("torrent/download/{info_hash}"), Query::empty()) + .get_binary(&format!("/torrent/download/{info_hash}"), Query::empty()) .await } // Context: user pub async fn register_user(&self, registration_form: RegistrationForm) -> TextResponse { - self.http_client.post("user/register", ®istration_form).await + self.http_client.post("/user/register", ®istration_form).await } pub async fn login_user(&self, registration_form: LoginForm) -> TextResponse { - self.http_client.post("user/login", ®istration_form).await + self.http_client.post("/user/login", ®istration_form).await } pub async fn verify_token(&self, token_verification_form: TokenVerificationForm) -> TextResponse { - self.http_client.post("user/token/verify", &token_verification_form).await + self.http_client.post("/user/token/verify", &token_verification_form).await } pub async fn renew_token(&self, token_verification_form: TokenRenewalForm) -> TextResponse { - self.http_client.post("user/token/renew", &token_verification_form).await + self.http_client.post("/user/token/renew", &token_verification_form).await } pub async fn ban_user(&self, username: Username) -> TextResponse { - self.http_client.delete(&format!("user/ban/{}", &username.value)).await + self.http_client.delete(&format!("/user/ban/{}", &username.value)).await } } /// Generic HTTP Client struct Http { connection_info: ConnectionInfo, - base_path: String, } impl Http { pub fn new(connection_info: ConnectionInfo) -> Self { - Self { - connection_info, - base_path: "/".to_string(), - } + Self { connection_info } } pub async fn get(&self, path: &str, params: Query) -> TextResponse { @@ -307,6 +307,9 @@ impl Http { } fn base_url(&self, path: &str) -> String { - format!("http://{}{}{path}", &self.connection_info.bind_address, &self.base_path) + format!( + "http://{}{}{path}", + &self.connection_info.bind_address, &self.connection_info.base_path + ) } } diff --git a/tests/common/connection_info.rs b/tests/common/connection_info.rs index 0b08f026..3f6c919e 100644 --- a/tests/common/connection_info.rs +++ b/tests/common/connection_info.rs @@ -1,20 +1,23 @@ #[derive(Clone)] pub struct ConnectionInfo { pub bind_address: String, + pub base_path: String, pub token: Option, } impl ConnectionInfo { - pub fn new(bind_address: &str, token: &str) -> Self { + pub fn new(bind_address: &str, base_path: &str, token: &str) -> Self { Self { bind_address: bind_address.to_string(), + base_path: base_path.to_string(), token: Some(token.to_string()), } } - pub fn anonymous(bind_address: &str) -> Self { + pub fn anonymous(bind_address: &str, base_path: &str) -> Self { Self { bind_address: bind_address.to_string(), + base_path: base_path.to_string(), token: None, } }