torrust_index/services/
category.rs

1//! Category service.
2use std::sync::Arc;
3
4use super::authorization::{self, ACTION};
5use crate::databases::database::{Category, Database, Error as DatabaseError};
6use crate::errors::ServiceError;
7use crate::models::category::CategoryId;
8use crate::models::user::UserId;
9
10pub struct Service {
11    category_repository: Arc<DbCategoryRepository>,
12    authorization_service: Arc<authorization::Service>,
13}
14
15impl Service {
16    #[must_use]
17    pub fn new(category_repository: Arc<DbCategoryRepository>, authorization_service: Arc<authorization::Service>) -> Service {
18        Service {
19            category_repository,
20            authorization_service,
21        }
22    }
23
24    /// Adds a new category.
25    ///
26    /// # Errors
27    ///
28    /// It returns an error if:
29    ///
30    /// * The user does not have the required permissions.
31    /// * The category name is empty.
32    /// * The category already exists.
33    /// * There is a database error.
34    pub async fn add_category(&self, category_name: &str, maybe_user_id: Option<UserId>) -> Result<i64, ServiceError> {
35        self.authorization_service
36            .authorize(ACTION::AddCategory, maybe_user_id)
37            .await?;
38
39        let trimmed_name = category_name.trim();
40
41        if trimmed_name.is_empty() {
42            return Err(ServiceError::CategoryNameEmpty);
43        }
44
45        // Try to get the category by name to check if it already exists
46        match self.category_repository.get_by_name(trimmed_name).await {
47            // Return ServiceError::CategoryAlreadyExists if the category exists
48            Ok(_) => Err(ServiceError::CategoryAlreadyExists),
49            Err(e) => match e {
50                // Otherwise try to create it
51                DatabaseError::CategoryNotFound => match self.category_repository.add(trimmed_name).await {
52                    Ok(id) => Ok(id),
53                    Err(_) => Err(ServiceError::DatabaseError),
54                },
55                _ => Err(ServiceError::DatabaseError),
56            },
57        }
58    }
59
60    /// Deletes a category.
61    ///
62    /// # Errors
63    ///
64    /// It returns an error if:
65    ///
66    /// * The user does not have the required permissions.
67    /// * There is a database error.
68    pub async fn delete_category(&self, category_name: &str, maybe_user_id: Option<UserId>) -> Result<(), ServiceError> {
69        self.authorization_service
70            .authorize(ACTION::DeleteCategory, maybe_user_id)
71            .await?;
72
73        match self.category_repository.delete(category_name).await {
74            Ok(()) => Ok(()),
75            Err(e) => match e {
76                DatabaseError::CategoryNotFound => Err(ServiceError::CategoryNotFound),
77                _ => Err(ServiceError::DatabaseError),
78            },
79        }
80    }
81
82    /// Returns all the categories from the database
83    ///
84    /// # Errors
85    ///
86    /// It returns an error if:
87    ///
88    /// * The user does not have the required permissions.
89    /// * There is a database error retrieving the categories.
90    pub async fn get_categories(&self, maybe_user_id: Option<UserId>) -> Result<Vec<Category>, ServiceError> {
91        self.authorization_service
92            .authorize(ACTION::GetCategories, maybe_user_id)
93            .await?;
94
95        self.category_repository
96            .get_all()
97            .await
98            .map_err(|_| ServiceError::DatabaseError)
99    }
100}
101
102pub struct DbCategoryRepository {
103    database: Arc<Box<dyn Database>>,
104}
105
106impl DbCategoryRepository {
107    #[must_use]
108    pub fn new(database: Arc<Box<dyn Database>>) -> Self {
109        Self { database }
110    }
111
112    /// It returns the categories.
113    ///
114    /// # Errors
115    ///
116    /// It returns an error if there is a database error.
117    pub async fn get_all(&self) -> Result<Vec<Category>, DatabaseError> {
118        self.database.get_categories().await
119    }
120
121    /// Adds a new category.
122    ///
123    /// # Errors
124    ///
125    /// It returns an error if there is a database error.
126    pub async fn add(&self, category_name: &str) -> Result<CategoryId, DatabaseError> {
127        self.database.insert_category_and_get_id(category_name).await
128    }
129
130    /// Deletes a new category.
131    ///
132    /// # Errors
133    ///
134    /// It returns an error if there is a database error.
135    pub async fn delete(&self, category_name: &str) -> Result<(), DatabaseError> {
136        self.database.delete_category(category_name).await
137    }
138
139    /// It finds a category by name
140    ///
141    /// # Errors
142    ///
143    /// It returns an error if there is a database error.
144    pub async fn get_by_name(&self, category_name: &str) -> Result<Category, DatabaseError> {
145        self.database.get_category_from_name(category_name).await
146    }
147
148    /// It finds a category by id
149    ///
150    /// # Errors
151    ///
152    /// It returns an error if there is a database error.
153    pub async fn get_by_id(&self, category_id: &CategoryId) -> Result<Category, DatabaseError> {
154        self.database.get_category_from_id(*category_id).await
155    }
156}