• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

doitsu2014 / my-cms / 14187034714

01 Apr 2025 04:12AM UTC coverage: 59.597% (+20.5%) from 39.058%
14187034714

push

github

web-flow
Feature/support multi language (#19)

* Enhance entity structure: add category and post translations, update migration dependencies, and improve README guidelines

* Add support for category translations: update create and modify handlers, requests, and tests

add TODO

* Add category translations support: update create, modify, and read handlers, and adjust request structures

* Refactor category translation requests: remove slug field and update handlers to set category ID for translations

* Add support for post translations: update create and modify requests, handlers, and models

* Update CI configuration and scripts for improved coverage reporting and toolchain management

* Update coverage configuration and scripts for improved reporting and toolchain management

* Update CI and coverage configurations for improved reporting and ignore patterns

* Update CI configuration and coverage scripts for improved reporting and cleanup

* Remove unused coverage step from CI configuration

* Update CI configuration to use fixed lcov report paths for coverage uploads

197 of 396 new or added lines in 19 files covered. (49.75%)

71 existing lines in 11 files now uncovered.

975 of 1636 relevant lines covered (59.6%)

26.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.78
/application_core/src/commands/category/read/category_read_handler.rs
1
use std::{collections::HashMap, sync::Arc};
2

3
use sea_orm::{
4
    prelude::DateTimeWithTimeZone, sea_query::Expr, ActiveEnum, DatabaseConnection, EntityTrait,
5
    QueryFilter, QueryOrder,
6
};
7
use serde::{Deserialize, Serialize};
8
use tracing::instrument;
9
use uuid::Uuid;
10

11
use crate::{
12
    common::app_error::AppError,
13
    entities::{
14
        categories::{Column, Model},
15
        category_translations,
16
        sea_orm_active_enums::CategoryType,
17
        tags,
18
    },
19
    Categories, CategoryTranslations, Tags,
20
};
21

22
#[derive(Debug, Clone, Serialize, Deserialize)]
23
#[serde(rename_all = "camelCase")]
24
pub struct CategoryReadResponse {
25
    pub id: Uuid,
26
    pub display_name: String,
27
    pub slug: String,
28
    pub category_type: CategoryType,
29
    pub created_by: String,
30
    pub created_at: DateTimeWithTimeZone,
31
    pub last_modified_by: Option<String>,
32
    pub last_modified_at: Option<DateTimeWithTimeZone>,
33
    pub parent_id: Option<Uuid>,
34
    pub row_version: i32,
35
    pub tags: Vec<tags::Model>,
36
    pub tag_names: Vec<String>,
37
    pub translations: Vec<category_translations::Model>,
38
}
39

40
impl CategoryReadResponse {
41
    fn new(category: Model, tags: Vec<tags::Model>) -> Self {
26✔
42
        let tag_names = tags
26✔
43
            .iter()
26✔
44
            .map(|tag| tag.name.to_owned())
62✔
45
            .collect::<Vec<String>>();
26✔
46

26✔
47
        CategoryReadResponse {
26✔
48
            id: category.id,
26✔
49
            display_name: category.display_name,
26✔
50
            slug: category.slug,
26✔
51
            category_type: category.category_type,
26✔
52
            created_by: category.created_by,
26✔
53
            created_at: category.created_at,
26✔
54
            last_modified_by: category.last_modified_by,
26✔
55
            last_modified_at: category.last_modified_at,
26✔
56
            parent_id: category.parent_id,
26✔
57
            row_version: category.row_version,
26✔
58
            tags,
26✔
59
            tag_names,
26✔
60
            translations: vec![],
26✔
61
        }
26✔
62
    }
26✔
63
}
64

65
pub trait CategoryReadHandlerTrait {
66
    fn handle_get_all_categories(
67
        &self,
68
    ) -> impl std::future::Future<Output = Result<Vec<CategoryReadResponse>, AppError>> + Send;
69

70
    fn handle_get_with_filtering(
71
        &self,
72
        category_type: Option<CategoryType>,
73
    ) -> impl std::future::Future<Output = Result<Vec<CategoryReadResponse>, AppError>> + Send;
74

75
    fn handle_get_category(
76
        &self,
77
        id: Uuid,
78
    ) -> impl std::future::Future<Output = Result<CategoryReadResponse, AppError>> + Send;
79
}
80

81
#[derive(Debug)]
82
pub struct CategoryReadHandler {
83
    pub db: Arc<DatabaseConnection>,
84
}
85

86
impl CategoryReadHandlerTrait for CategoryReadHandler {
87
    #[instrument]
88
    async fn handle_get_all_categories(&self) -> Result<Vec<CategoryReadResponse>, AppError> {
89
        let db_result = Categories::find()
90
            .find_with_related(Tags)
91
            .all(self.db.as_ref())
92
            .await
93
            .map_err(|e| e.into())?;
×
94

95
        let mut response = db_result
96
            .iter()
97
            .map(|c_and_tags| {
14✔
98
                CategoryReadResponse::new(c_and_tags.0.to_owned(), c_and_tags.1.to_owned())
14✔
99
            })
14✔
100
            .collect::<Vec<CategoryReadResponse>>();
101

102
        let db_result_with_translations: HashMap<Uuid, (Model, Vec<category_translations::Model>)> =
103
            Categories::find()
104
                .find_with_related(CategoryTranslations)
105
                .all(self.db.as_ref())
106
                .await
NEW
107
                .map_err(|e| e.into())?
×
108
                .iter()
109
                .map(|e| (e.0.id, e.to_owned()))
14✔
110
                .collect();
111

112
        // Combine translation data with the existing response
113
        response = response
114
            .iter()
115
            .map(|r| {
14✔
116
                let translations = db_result_with_translations
14✔
117
                    .get(&r.id)
14✔
118
                    .map(|e| e.1.to_owned())
14✔
119
                    .unwrap_or(vec![]);
14✔
120

14✔
121
                CategoryReadResponse {
14✔
122
                    translations: translations.clone(),
14✔
123
                    ..r.to_owned()
14✔
124
                }
14✔
125
            })
14✔
126
            .collect();
127

128
        // let category and tags
129
        Result::Ok(response)
130
    }
131

132
    #[instrument]
133
    async fn handle_get_category(&self, id: Uuid) -> Result<CategoryReadResponse, AppError> {
134
        let db_result = Categories::find_by_id(id)
135
            .find_with_related(Tags)
136
            .all(self.db.as_ref())
137
            .await
138
            .map_err(|e| e.into())?;
×
139

140
        if db_result.is_empty() {
141
            return Result::Err(AppError::NotFound);
142
        }
143

144
        let mut response = db_result
145
            .iter()
146
            .map(|c_and_tags| {
2✔
147
                CategoryReadResponse::new(c_and_tags.0.to_owned(), c_and_tags.1.to_owned())
2✔
148
            })
2✔
149
            .collect::<Vec<CategoryReadResponse>>();
150

151
        let db_result_with_translations: HashMap<Uuid, (Model, Vec<category_translations::Model>)> =
152
            Categories::find_by_id(id)
153
                .find_with_related(CategoryTranslations)
154
                .all(self.db.as_ref())
155
                .await
NEW
156
                .map_err(|e| e.into())?
×
157
                .iter()
158
                .map(|e| (e.0.id, e.to_owned()))
2✔
159
                .collect();
160

161
        response = response
162
            .iter()
163
            .map(|r| {
2✔
164
                let translations = db_result_with_translations
2✔
165
                    .get(&r.id)
2✔
166
                    .map(|e| e.1.to_owned())
2✔
167
                    .unwrap_or(vec![]);
2✔
168

2✔
169
                CategoryReadResponse {
2✔
170
                    translations: translations.clone(),
2✔
171
                    ..r.to_owned()
2✔
172
                }
2✔
173
            })
2✔
174
            .collect();
175

176
        // let category and tags
177
        Result::Ok(response.first().unwrap().to_owned())
178
    }
179

180
    #[instrument]
181
    async fn handle_get_with_filtering(
182
        &self,
183
        category_type: Option<CategoryType>,
184
    ) -> Result<Vec<CategoryReadResponse>, AppError> {
185
        let mut query = Categories::find();
186

187
        if category_type.is_some() {
188
            query =
189
                query.filter(Expr::col(Column::CategoryType).eq(category_type.unwrap().as_enum()));
190
        }
191

192
        let db_result = query
193
            .to_owned()
194
            .find_with_related(Tags)
195
            .order_by_asc(Column::DisplayName)
196
            .all(self.db.as_ref())
197
            .await
198
            .map_err(|e| e.into())?;
×
199

200
        let mut response = db_result
201
            .iter()
202
            .map(|c_and_tags| {
10✔
203
                CategoryReadResponse::new(c_and_tags.0.to_owned(), c_and_tags.1.to_owned())
10✔
204
            })
10✔
205
            .collect::<Vec<CategoryReadResponse>>();
206

207
        let db_result_with_translations: HashMap<Uuid, (Model, Vec<category_translations::Model>)> =
208
            query
209
                .find_with_related(CategoryTranslations)
210
                .order_by_asc(Column::DisplayName)
211
                .all(self.db.as_ref())
212
                .await
NEW
213
                .map_err(|e| e.into())?
×
214
                .iter()
215
                .map(|e| (e.0.id, e.to_owned()))
10✔
216
                .collect();
217

218
        response = response
219
            .iter()
220
            .map(|r| {
10✔
221
                let translations = db_result_with_translations
10✔
222
                    .get(&r.id)
10✔
223
                    .map(|e| e.1.to_owned())
10✔
224
                    .unwrap_or(vec![]);
10✔
225

10✔
226
                CategoryReadResponse {
10✔
227
                    translations: translations.clone(),
10✔
228
                    ..r.to_owned()
10✔
229
                }
10✔
230
            })
10✔
231
            .collect();
232

233
        // let category and tags
234
        Result::Ok(response)
235
    }
236
}
237

238
#[cfg(test)]
239
mod tests {
240
    use std::sync::Arc;
241
    use test_helpers::{setup_test_space, ContainerAsyncPostgresEx};
242

243
    use crate::{
244
        commands::category::{
245
            create::create_handler::{CategoryCreateHandler, CategoryCreateHandlerTrait},
246
            read::category_read_handler::{CategoryReadHandler, CategoryReadHandlerTrait},
247
            test::fake_create_category_request_with_category_type,
248
        },
249
        entities::sea_orm_active_enums::CategoryType,
250
    };
251

252
    #[async_std::test]
253
    async fn handle_get_all_cartegory_testcase_01() {
254
        let test_space = setup_test_space().await;
255
        let conn = test_space.postgres.get_database_connection().await;
256
        let number_of_blogs = 5;
257
        let number_of_others = 5;
258

259
        let create_handler = CategoryCreateHandler {
260
            db: Arc::new(conn.clone()),
261
        };
262
        let read_handler = CategoryReadHandler { db: Arc::new(conn) };
263

264
        for i in 0..number_of_blogs {
265
            let _ = create_handler
266
                .handle_create_category_with_tags(
267
                    fake_create_category_request_with_category_type(i, CategoryType::Blog),
268
                    None,
269
                )
270
                .await;
271
        }
272

273
        for i in 0..number_of_others {
274
            let _ = create_handler
275
                .handle_create_category_with_tags(
276
                    fake_create_category_request_with_category_type(i, CategoryType::Other),
277
                    None,
278
                )
279
                .await;
280
        }
281

282
        let result = read_handler.handle_get_all_categories().await;
283
        match result {
284
            Ok(categories) => assert_eq!(categories.len(), number_of_blogs + number_of_others),
285
            _ => panic!("Failed to test"),
286
        }
287

288
        let blogs = read_handler
289
            .handle_get_with_filtering(Some(CategoryType::Blog))
290
            .await
291
            .unwrap();
292
        assert!(blogs.len() == number_of_blogs);
293

294
        let others = read_handler
295
            .handle_get_with_filtering(Some(CategoryType::Other))
296
            .await
297
            .unwrap();
298
        assert!(others.len() == number_of_others);
299
        // Clean up
300
    }
301
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc