headless_lms_server/controllers/main_frontend/
global_stats.rs1use crate::{domain::authorization::authorize, prelude::*};
4
5use models::library::TimeGranularity;
6use models::library::global_stats::{
7 CourseCompletionStats, DomainCompletionStats, GlobalCourseModuleStatEntry, GlobalStatEntry,
8};
9use std::collections::HashMap;
10use utoipa::OpenApi;
11
12#[derive(OpenApi)]
13#[openapi(
14 paths(
15 get_number_of_people_completed_a_course,
16 get_number_of_people_registered_completion_to_study_registry,
17 get_number_of_people_done_at_least_one_exercise,
18 get_number_of_people_started_course,
19 get_course_module_stats_by_completions_registered_to_study_registry,
20 get_completion_stats_by_email_domain,
21 get_course_completion_stats_for_email_domain
22 ),
23 components(schemas(TimeGranularity))
24)]
25pub(crate) struct MainFrontendGlobalStatsApiDoc;
26
27#[utoipa::path(
34 get,
35 path = "/number-of-people-completed-a-course",
36 operation_id = "getNumberOfPeopleCompletedACourse",
37 tag = "global-stats",
38 params(
39 ("granularity" = Option<TimeGranularity>, Query, description = "Time granularity")
40 ),
41 responses(
42 (status = 200, description = "Global completion stats", body = [GlobalStatEntry])
43 )
44)]
45#[instrument(skip(pool))]
46async fn get_number_of_people_completed_a_course(
47 pool: web::Data<PgPool>,
48 user: AuthUser,
49 query: web::Query<HashMap<String, String>>,
50) -> ControllerResult<web::Json<Vec<GlobalStatEntry>>> {
51 let mut conn = pool.acquire().await?;
52 let token = authorize(
53 &mut conn,
54 Act::ViewStats,
55 Some(user.id),
56 Res::GlobalPermissions,
57 )
58 .await?;
59
60 let granularity = query
61 .get("granularity")
62 .map(|s| s.parse().unwrap_or(TimeGranularity::Year))
63 .unwrap_or(TimeGranularity::Year);
64
65 let res = models::library::global_stats::get_number_of_people_completed_a_course(
66 &mut conn,
67 granularity,
68 )
69 .await?;
70
71 token.authorized_ok(web::Json(res))
72}
73
74#[utoipa::path(
81 get,
82 path = "/number-of-people-registered-completion-to-study-registry",
83 operation_id = "getNumberOfPeopleRegisteredCompletionToStudyRegistry",
84 tag = "global-stats",
85 params(
86 ("granularity" = Option<TimeGranularity>, Query, description = "Time granularity")
87 ),
88 responses(
89 (status = 200, description = "Study registry completion stats", body = [GlobalStatEntry])
90 )
91)]
92#[instrument(skip(pool))]
93async fn get_number_of_people_registered_completion_to_study_registry(
94 pool: web::Data<PgPool>,
95 user: AuthUser,
96 query: web::Query<HashMap<String, String>>,
97) -> ControllerResult<web::Json<Vec<GlobalStatEntry>>> {
98 let mut conn = pool.acquire().await?;
99 let token = authorize(
100 &mut conn,
101 Act::ViewStats,
102 Some(user.id),
103 Res::GlobalPermissions,
104 )
105 .await?;
106
107 let granularity = query
108 .get("granularity")
109 .map(|s| s.parse().unwrap_or(TimeGranularity::Year))
110 .unwrap_or(TimeGranularity::Year);
111
112 let res = models::library::global_stats::get_number_of_people_registered_completion_to_study_registry(&mut conn, granularity).await?;
113
114 token.authorized_ok(web::Json(res))
115}
116
117#[utoipa::path(
124 get,
125 path = "/number-of-people-done-at-least-one-exercise",
126 operation_id = "getNumberOfPeopleDoneAtLeastOneExercise",
127 tag = "global-stats",
128 params(
129 ("granularity" = Option<TimeGranularity>, Query, description = "Time granularity")
130 ),
131 responses(
132 (status = 200, description = "Exercise participation stats", body = [GlobalStatEntry])
133 )
134)]
135#[instrument(skip(pool))]
136async fn get_number_of_people_done_at_least_one_exercise(
137 pool: web::Data<PgPool>,
138 user: AuthUser,
139 query: web::Query<HashMap<String, String>>,
140) -> ControllerResult<web::Json<Vec<GlobalStatEntry>>> {
141 let mut conn = pool.acquire().await?;
142 let token = authorize(
143 &mut conn,
144 Act::ViewStats,
145 Some(user.id),
146 Res::GlobalPermissions,
147 )
148 .await?;
149
150 let granularity = query
151 .get("granularity")
152 .map(|s| s.parse().unwrap_or(TimeGranularity::Year))
153 .unwrap_or(TimeGranularity::Year);
154
155 let res = models::library::global_stats::get_number_of_people_done_at_least_one_exercise(
156 &mut conn,
157 granularity,
158 )
159 .await?;
160
161 token.authorized_ok(web::Json(res))
162}
163
164#[utoipa::path(
171 get,
172 path = "/number-of-people-started-course",
173 operation_id = "getNumberOfPeopleStartedCourse",
174 tag = "global-stats",
175 params(
176 ("granularity" = Option<TimeGranularity>, Query, description = "Time granularity")
177 ),
178 responses(
179 (status = 200, description = "Course start stats", body = [GlobalStatEntry])
180 )
181)]
182#[instrument(skip(pool))]
183async fn get_number_of_people_started_course(
184 pool: web::Data<PgPool>,
185 user: AuthUser,
186 query: web::Query<HashMap<String, String>>,
187) -> ControllerResult<web::Json<Vec<GlobalStatEntry>>> {
188 let mut conn = pool.acquire().await?;
189 let token = authorize(
190 &mut conn,
191 Act::ViewStats,
192 Some(user.id),
193 Res::GlobalPermissions,
194 )
195 .await?;
196
197 let granularity = query
198 .get("granularity")
199 .map(|s| s.parse().unwrap_or(TimeGranularity::Year))
200 .unwrap_or(TimeGranularity::Year);
201
202 let res =
203 models::library::global_stats::get_number_of_people_started_course(&mut conn, granularity)
204 .await?;
205
206 token.authorized_ok(web::Json(res))
207}
208
209#[utoipa::path(
216 get,
217 path = "/course-module-stats-by-completions-registered-to-study-registry",
218 operation_id = "getCourseModuleStatsByCompletionsRegisteredToStudyRegistry",
219 tag = "global-stats",
220 params(
221 ("granularity" = Option<TimeGranularity>, Query, description = "Time granularity")
222 ),
223 responses(
224 (
225 status = 200,
226 description = "Course module completion stats",
227 body = [GlobalCourseModuleStatEntry]
228 )
229 )
230)]
231#[instrument(skip(pool))]
232async fn get_course_module_stats_by_completions_registered_to_study_registry(
233 pool: web::Data<PgPool>,
234 user: AuthUser,
235 query: web::Query<HashMap<String, String>>,
236) -> ControllerResult<web::Json<Vec<GlobalCourseModuleStatEntry>>> {
237 let mut conn = pool.acquire().await?;
238 let token = authorize(
239 &mut conn,
240 Act::ViewStats,
241 Some(user.id),
242 Res::GlobalPermissions,
243 )
244 .await?;
245
246 let granularity = query
247 .get("granularity")
248 .map(|s| s.parse().unwrap_or(TimeGranularity::Year))
249 .unwrap_or(TimeGranularity::Year);
250
251 let res = models::library::global_stats::get_course_module_stats_by_completions_registered_to_study_registry(&mut conn, granularity).await?;
252
253 token.authorized_ok(web::Json(res))
254}
255
256#[utoipa::path(
263 get,
264 path = "/completion-stats-by-email-domain",
265 operation_id = "getCompletionStatsByEmailDomain",
266 tag = "global-stats",
267 params(
268 ("year" = Option<i32>, Query, description = "Optional year")
269 ),
270 responses(
271 (
272 status = 200,
273 description = "Completion stats by email domain",
274 body = [DomainCompletionStats]
275 )
276 )
277)]
278#[instrument(skip(pool))]
279async fn get_completion_stats_by_email_domain(
280 pool: web::Data<PgPool>,
281 user: AuthUser,
282 query: web::Query<HashMap<String, String>>,
283) -> ControllerResult<web::Json<Vec<DomainCompletionStats>>> {
284 let mut conn = pool.acquire().await?;
285 let token = authorize(
286 &mut conn,
287 Act::ViewStats,
288 Some(user.id),
289 Res::GlobalPermissions,
290 )
291 .await?;
292
293 let year = query.get("year").and_then(|y| y.parse::<i32>().ok());
294
295 let res = models::library::global_stats::get_completion_stats_by_email_domain(&mut conn, year)
296 .await?;
297
298 token.authorized_ok(web::Json(res))
299}
300
301#[utoipa::path(
309 get,
310 path = "/course-completion-stats-for-email-domain",
311 operation_id = "getCourseCompletionStatsForEmailDomain",
312 tag = "global-stats",
313 params(
314 ("email_domain" = String, Query, description = "Email domain"),
315 ("year" = Option<i32>, Query, description = "Optional year")
316 ),
317 responses(
318 (
319 status = 200,
320 description = "Course completion stats for email domain",
321 body = [CourseCompletionStats]
322 )
323 )
324)]
325#[instrument(skip(pool))]
326async fn get_course_completion_stats_for_email_domain(
327 pool: web::Data<PgPool>,
328 user: AuthUser,
329 query: web::Query<HashMap<String, String>>,
330) -> ControllerResult<web::Json<Vec<CourseCompletionStats>>> {
331 let mut conn = pool.acquire().await?;
332 let token = authorize(
333 &mut conn,
334 Act::ViewStats,
335 Some(user.id),
336 Res::GlobalPermissions,
337 )
338 .await?;
339
340 let email_domain = query
341 .get("email_domain")
342 .ok_or_else(|| {
343 ControllerError::new(
344 ControllerErrorType::BadRequest,
345 "email_domain is required".to_string(),
346 None,
347 )
348 })?
349 .to_string();
350
351 let year = query.get("year").and_then(|y| y.parse::<i32>().ok());
352
353 let res = models::library::global_stats::get_course_completion_stats_for_email_domain(
354 &mut conn,
355 email_domain,
356 year,
357 )
358 .await?;
359
360 token.authorized_ok(web::Json(res))
361}
362
363pub fn _add_routes(cfg: &mut ServiceConfig) {
371 cfg.route(
372 "/number-of-people-completed-a-course",
373 web::get().to(get_number_of_people_completed_a_course),
374 )
375 .route(
376 "/number-of-people-registered-completion-to-study-registry",
377 web::get().to(get_number_of_people_registered_completion_to_study_registry),
378 )
379 .route(
380 "/number-of-people-done-at-least-one-exercise",
381 web::get().to(get_number_of_people_done_at_least_one_exercise),
382 )
383 .route(
384 "/number-of-people-started-course",
385 web::get().to(get_number_of_people_started_course),
386 )
387 .route(
388 "/course-module-stats-by-completions-registered-to-study-registry",
389 web::get().to(get_course_module_stats_by_completions_registered_to_study_registry),
390 )
391 .route(
392 "/completion-stats-by-email-domain",
393 web::get().to(get_completion_stats_by_email_domain),
394 )
395 .route(
396 "/course-completion-stats-for-email-domain",
397 web::get().to(get_course_completion_stats_for_email_domain),
398 );
399}