Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor API: extract tag service #191

Merged
merged 2 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::config::Configuration;
use crate::databases::database;
use crate::services::authentication::{DbUserAuthenticationRepository, JsonWebToken, Service};
use crate::services::category::{self, DbCategoryRepository};
use crate::services::tag::{self, DbTagRepository};
use crate::services::torrent::{
DbTorrentAnnounceUrlRepository, DbTorrentFileRepository, DbTorrentInfoRepository, DbTorrentListingGenerator,
DbTorrentRepository, DbTorrentTagRepository,
Expand Down Expand Up @@ -58,6 +59,7 @@ pub async fn run(configuration: Configuration, api_implementation: &Implementati

// Repositories
let category_repository = Arc::new(DbCategoryRepository::new(database.clone()));
let tag_repository = Arc::new(DbTagRepository::new(database.clone()));
let user_repository = Arc::new(DbUserRepository::new(database.clone()));
let user_authentication_repository = Arc::new(DbUserAuthenticationRepository::new(database.clone()));
let user_profile_repository = Arc::new(DbUserProfileRepository::new(database.clone()));
Expand All @@ -76,6 +78,7 @@ pub async fn run(configuration: Configuration, api_implementation: &Implementati
let mailer_service = Arc::new(mailer::Service::new(configuration.clone()).await);
let image_cache_service: Arc<ImageCacheService> = Arc::new(ImageCacheService::new(configuration.clone()).await);
let category_service = Arc::new(category::Service::new(category_repository.clone(), user_repository.clone()));
let tag_service = Arc::new(tag::Service::new(tag_repository.clone(), user_repository.clone()));
let proxy_service = Arc::new(proxy::Service::new(image_cache_service.clone(), user_repository.clone()));
let settings_service = Arc::new(settings::Service::new(configuration.clone(), user_repository.clone()));
let torrent_index = Arc::new(torrent::Index::new(
Expand Down Expand Up @@ -123,6 +126,7 @@ pub async fn run(configuration: Configuration, api_implementation: &Implementati
mailer_service,
image_cache_service,
category_repository,
tag_repository,
user_repository,
user_authentication_repository,
user_profile_repository,
Expand All @@ -134,6 +138,7 @@ pub async fn run(configuration: Configuration, api_implementation: &Implementati
torrent_listing_generator,
banned_user_list,
category_service,
tag_service,
proxy_service,
settings_service,
torrent_index,
Expand Down
7 changes: 7 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::config::Configuration;
use crate::databases::database::Database;
use crate::services::authentication::{DbUserAuthenticationRepository, JsonWebToken, Service};
use crate::services::category::{self, DbCategoryRepository};
use crate::services::tag::{self, DbTagRepository};
use crate::services::torrent::{
DbTorrentAnnounceUrlRepository, DbTorrentFileRepository, DbTorrentInfoRepository, DbTorrentListingGenerator,
DbTorrentRepository, DbTorrentTagRepository,
Expand All @@ -30,6 +31,7 @@ pub struct AppData {
pub image_cache_manager: Arc<ImageCacheService>,
// Repositories
pub category_repository: Arc<DbCategoryRepository>,
pub tag_repository: Arc<DbTagRepository>,
pub user_repository: Arc<DbUserRepository>,
pub user_authentication_repository: Arc<DbUserAuthenticationRepository>,
pub user_profile_repository: Arc<DbUserProfileRepository>,
Expand All @@ -42,6 +44,7 @@ pub struct AppData {
pub banned_user_list: Arc<DbBannedUserList>,
// Services
pub category_service: Arc<category::Service>,
pub tag_service: Arc<tag::Service>,
pub proxy_service: Arc<proxy::Service>,
pub settings_service: Arc<settings::Service>,
pub torrent_service: Arc<torrent::Index>,
Expand All @@ -63,6 +66,7 @@ impl AppData {
image_cache_manager: Arc<ImageCacheService>,
// Repositories
category_repository: Arc<DbCategoryRepository>,
tag_repository: Arc<DbTagRepository>,
user_repository: Arc<DbUserRepository>,
user_authentication_repository: Arc<DbUserAuthenticationRepository>,
user_profile_repository: Arc<DbUserProfileRepository>,
Expand All @@ -75,6 +79,7 @@ impl AppData {
banned_user_list: Arc<DbBannedUserList>,
// Services
category_service: Arc<category::Service>,
tag_service: Arc<tag::Service>,
proxy_service: Arc<proxy::Service>,
settings_service: Arc<settings::Service>,
torrent_service: Arc<torrent::Index>,
Expand All @@ -93,6 +98,7 @@ impl AppData {
image_cache_manager,
// Repositories
category_repository,
tag_repository,
user_repository,
user_authentication_repository,
user_profile_repository,
Expand All @@ -105,6 +111,7 @@ impl AppData {
banned_user_list,
// Services
category_service,
tag_service,
proxy_service,
settings_service,
torrent_service,
Expand Down
2 changes: 1 addition & 1 deletion src/databases/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ pub trait Database: Sync + Send {
async fn add_torrent_tag_link(&self, torrent_id: i64, tag_id: TagId) -> Result<(), Error>;

/// Add multiple tags to a torrent at once.
async fn add_torrent_tag_links(&self, torrent_id: i64, tag_ids: &Vec<TagId>) -> Result<(), Error>;
async fn add_torrent_tag_links(&self, torrent_id: i64, tag_ids: &[TagId]) -> Result<(), Error>;

/// Remove a tag from torrent.
async fn delete_torrent_tag_link(&self, torrent_id: i64, tag_id: TagId) -> Result<(), Error>;
Expand Down
4 changes: 2 additions & 2 deletions src/databases/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ impl Database for Mysql {
.map_err(|_| database::Error::Error)
}

async fn add_torrent_tag_links(&self, torrent_id: i64, tag_ids: &Vec<TagId>) -> Result<(), database::Error> {
async fn add_torrent_tag_links(&self, torrent_id: i64, tag_ids: &[TagId]) -> Result<(), database::Error> {
let mut transaction = self
.pool
.begin()
Expand Down Expand Up @@ -778,7 +778,7 @@ impl Database for Mysql {
.bind(name)
.fetch_one(&self.pool)
.await
.map_err(|err| database::Error::TagNotFound)
.map_err(|_| database::Error::TagNotFound)
}

async fn get_tags(&self) -> Result<Vec<TorrentTag>, database::Error> {
Expand Down
4 changes: 2 additions & 2 deletions src/databases/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ impl Database for Sqlite {
.map_err(|_| database::Error::Error)
}

async fn add_torrent_tag_links(&self, torrent_id: i64, tag_ids: &Vec<TagId>) -> Result<(), database::Error> {
async fn add_torrent_tag_links(&self, torrent_id: i64, tag_ids: &[TagId]) -> Result<(), database::Error> {
let mut transaction = self
.pool
.begin()
Expand Down Expand Up @@ -768,7 +768,7 @@ impl Database for Sqlite {
.bind(name)
.fetch_one(&self.pool)
.await
.map_err(|err| database::Error::TagNotFound)
.map_err(|_| database::Error::TagNotFound)
}

async fn get_tags(&self) -> Result<Vec<TorrentTag>, database::Error> {
Expand Down
16 changes: 10 additions & 6 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,17 @@ pub enum ServiceError {
FailedToSendVerificationEmail,

#[display(fmt = "Category already exists.")]
CategoryExists,
CategoryAlreadyExists,

#[display(fmt = "Tag already exists.")]
TagExists,
TagAlreadyExists,

#[display(fmt = "Category not found.")]
CategoryNotFound,

#[display(fmt = "Tag not found.")]
TagNotFound,

#[display(fmt = "Database error.")]
DatabaseError,
}
Expand Down Expand Up @@ -176,14 +179,15 @@ impl ResponseError for ServiceError {
ServiceError::InfoHashAlreadyExists => StatusCode::BAD_REQUEST,
ServiceError::TorrentTitleAlreadyExists => StatusCode::BAD_REQUEST,
ServiceError::TrackerOffline => StatusCode::INTERNAL_SERVER_ERROR,
ServiceError::CategoryExists => StatusCode::BAD_REQUEST,
ServiceError::TagExists => StatusCode::BAD_REQUEST,
ServiceError::CategoryAlreadyExists => StatusCode::BAD_REQUEST,
ServiceError::TagAlreadyExists => StatusCode::BAD_REQUEST,
ServiceError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
ServiceError::EmailMissing => StatusCode::NOT_FOUND,
ServiceError::FailedToSendVerificationEmail => StatusCode::INTERNAL_SERVER_ERROR,
ServiceError::WhitelistingError => StatusCode::INTERNAL_SERVER_ERROR,
ServiceError::DatabaseError => StatusCode::INTERNAL_SERVER_ERROR,
ServiceError::CategoryNotFound => StatusCode::NOT_FOUND,
ServiceError::TagNotFound => StatusCode::NOT_FOUND,
}
}

Expand Down Expand Up @@ -223,9 +227,9 @@ impl From<database::Error> for ServiceError {
database::Error::UsernameTaken => ServiceError::UsernameTaken,
database::Error::EmailTaken => ServiceError::EmailTaken,
database::Error::UserNotFound => ServiceError::UserNotFound,
database::Error::CategoryAlreadyExists => ServiceError::CategoryExists,
database::Error::CategoryAlreadyExists => ServiceError::CategoryAlreadyExists,
database::Error::CategoryNotFound => ServiceError::InvalidCategory,
database::Error::TagAlreadyExists => ServiceError::TagExists,
database::Error::TagAlreadyExists => ServiceError::TagAlreadyExists,
database::Error::TagNotFound => ServiceError::InvalidTag,
database::Error::TorrentNotFound => ServiceError::TorrentNotFound,
database::Error::TorrentAlreadyExists => ServiceError::InfoHashAlreadyExists,
Expand Down
22 changes: 4 additions & 18 deletions src/routes/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use actix_web::{web, HttpRequest, HttpResponse, Responder};
use serde::{Deserialize, Serialize};

use crate::common::WebAppData;
use crate::errors::{ServiceError, ServiceResult};
use crate::errors::ServiceResult;
use crate::models::response::OkResponse;
use crate::models::torrent_tag::TagId;
use crate::routes::API_VERSION;
Expand All @@ -24,7 +24,7 @@ pub fn init(cfg: &mut web::ServiceConfig) {
///
/// This function will return an error if unable to get tags from database.
pub async fn get_all(app_data: WebAppData) -> ServiceResult<impl Responder> {
let tags = app_data.torrent_tag_repository.get_tags().await?;
let tags = app_data.tag_repository.get_all().await?;

Ok(HttpResponse::Ok().json(OkResponse { data: tags }))
}
Expand All @@ -46,14 +46,7 @@ pub struct Create {
pub async fn create(req: HttpRequest, payload: web::Json<Create>, app_data: WebAppData) -> ServiceResult<impl Responder> {
let user_id = app_data.auth.get_user_id_from_request(&req).await?;

let user = app_data.user_repository.get_compact(&user_id).await?;

// check if user is administrator
if !user.administrator {
return Err(ServiceError::Unauthorized);
}

app_data.torrent_tag_repository.add_tag(&payload.name).await?;
app_data.tag_service.add_tag(&payload.name, &user_id).await?;

Ok(HttpResponse::Ok().json(OkResponse {
data: payload.name.to_string(),
Expand All @@ -77,14 +70,7 @@ pub struct Delete {
pub async fn delete(req: HttpRequest, payload: web::Json<Delete>, app_data: WebAppData) -> ServiceResult<impl Responder> {
let user_id = app_data.auth.get_user_id_from_request(&req).await?;

let user = app_data.user_repository.get_compact(&user_id).await?;

// check if user is administrator
if !user.administrator {
return Err(ServiceError::Unauthorized);
}

app_data.torrent_tag_repository.delete_tag(&payload.tag_id).await?;
app_data.tag_service.delete_tag(&payload.tag_id, &user_id).await?;

Ok(HttpResponse::Ok().json(OkResponse { data: payload.tag_id }))
}
4 changes: 2 additions & 2 deletions src/services/category.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ impl Service {
match self.category_repository.add(category_name).await {
Ok(id) => Ok(id),
Err(e) => match e {
DatabaseError::CategoryAlreadyExists => Err(ServiceError::CategoryExists),
DatabaseError::CategoryAlreadyExists => Err(ServiceError::CategoryAlreadyExists),
_ => Err(ServiceError::DatabaseError),
},
}
}

/// Deletes a new category.
/// Deletes a category.
///
/// # Errors
///
Expand Down
1 change: 1 addition & 0 deletions src/services/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ pub mod authentication;
pub mod category;
pub mod proxy;
pub mod settings;
pub mod tag;
pub mod torrent;
pub mod user;
113 changes: 113 additions & 0 deletions src/services/tag.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//! Tag service.
use std::sync::Arc;

use super::user::DbUserRepository;
use crate::databases::database::{Database, Error as DatabaseError, Error};
use crate::errors::ServiceError;
use crate::models::torrent_tag::{TagId, TorrentTag};
use crate::models::user::UserId;

pub struct Service {
tag_repository: Arc<DbTagRepository>,
user_repository: Arc<DbUserRepository>,
}

impl Service {
#[must_use]
pub fn new(tag_repository: Arc<DbTagRepository>, user_repository: Arc<DbUserRepository>) -> Service {
Service {
tag_repository,
user_repository,
}
}

/// Adds a new tag.
///
/// # Errors
///
/// It returns an error if:
///
/// * The user does not have the required permissions.
/// * There is a database error.
pub async fn add_tag(&self, tag_name: &str, user_id: &UserId) -> Result<(), ServiceError> {
let user = self.user_repository.get_compact(user_id).await?;

// Check if user is administrator
// todo: extract authorization service
if !user.administrator {
return Err(ServiceError::Unauthorized);
}

match self.tag_repository.add(tag_name).await {
Ok(_) => Ok(()),
Err(e) => match e {
DatabaseError::TagAlreadyExists => Err(ServiceError::TagAlreadyExists),
_ => Err(ServiceError::DatabaseError),
},
}
}

/// Deletes a tag.
///
/// # Errors
///
/// It returns an error if:
///
/// * The user does not have the required permissions.
/// * There is a database error.
pub async fn delete_tag(&self, tag_id: &TagId, user_id: &UserId) -> Result<(), ServiceError> {
let user = self.user_repository.get_compact(user_id).await?;

// Check if user is administrator
// todo: extract authorization service
if !user.administrator {
return Err(ServiceError::Unauthorized);
}

match self.tag_repository.delete(tag_id).await {
Ok(_) => Ok(()),
Err(e) => match e {
DatabaseError::TagNotFound => Err(ServiceError::TagNotFound),
_ => Err(ServiceError::DatabaseError),
},
}
}
}

pub struct DbTagRepository {
database: Arc<Box<dyn Database>>,
}

impl DbTagRepository {
#[must_use]
pub fn new(database: Arc<Box<dyn Database>>) -> Self {
Self { database }
}

/// It adds a new tag and returns the newly created tag.
///
/// # Errors
///
/// It returns an error if there is a database error.
pub async fn add(&self, tag_name: &str) -> Result<(), Error> {
self.database.add_tag(tag_name).await
}

/// It returns all the tags.
///
/// # Errors
///
/// It returns an error if there is a database error.
pub async fn get_all(&self) -> Result<Vec<TorrentTag>, Error> {
self.database.get_tags().await
}

/// It removes a tag and returns it.
///
/// # Errors
///
/// It returns an error if there is a database error.
pub async fn delete(&self, tag_id: &TagId) -> Result<(), Error> {
self.database.delete_tag(*tag_id).await
}
}
Loading