Skip to content

Commit

Permalink
refactor: [#187] extract tag::Service
Browse files Browse the repository at this point in the history
See #157.
  • Loading branch information
josecelano committed Jun 9, 2023
1 parent b97698a commit f693a02
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 57 deletions.
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
2 changes: 1 addition & 1 deletion 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
2 changes: 1 addition & 1 deletion 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
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

0 comments on commit f693a02

Please sign in to comment.