Skip to content

Commit

Permalink
feat!: add API version prefix in API URLs
Browse files Browse the repository at this point in the history
BREAKING CHANGE: API urls have the prefix `/v1/...`
  • Loading branch information
josecelano committed May 17, 2023
1 parent dd3b525 commit 02c4a7e
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 38 deletions.
3 changes: 2 additions & 1 deletion src/routes/about.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))),
);
Expand Down
3 changes: 2 additions & 1 deletion src/routes/category.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 2 additions & 0 deletions src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 4 additions & 1 deletion src/routes/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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();
}
Expand Down
3 changes: 2 additions & 1 deletion src/routes/root.rs
Original file line number Diff line number Diff line change
@@ -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))));
}
3 changes: 2 additions & 1 deletion src/routes/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))),
Expand Down
7 changes: 5 additions & 2 deletions src/routes/torrent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)]
Expand Down
3 changes: 2 additions & 1 deletion src/routes/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
59 changes: 31 additions & 28 deletions tests/common/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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", &registration_form).await
self.http_client.post("/user/register", &registration_form).await
}

pub async fn login_user(&self, registration_form: LoginForm) -> TextResponse {
self.http_client.post("user/login", &registration_form).await
self.http_client.post("/user/login", &registration_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 {
Expand Down Expand Up @@ -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
)
}
}
7 changes: 5 additions & 2 deletions tests/common/connection_info.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
#[derive(Clone)]
pub struct ConnectionInfo {
pub bind_address: String,
pub base_path: String,
pub token: Option<String>,
}

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,
}
}
Expand Down

0 comments on commit 02c4a7e

Please sign in to comment.