headless_lms_server/controllers/main_frontend/
global_stats.rs

1//! Controllers for requests starting with `/api/v0/main-frontend/global-stats`.
2
3use 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;
10
11/**
12GET `/api/v0/main-frontend/global-stats/number-of-people-completed-a-course`
13
14Query parameters:
15- granularity: String - Either "year" or "month" (defaults to "year")
16*/
17#[instrument(skip(pool))]
18async fn get_number_of_people_completed_a_course(
19    pool: web::Data<PgPool>,
20    user: AuthUser,
21    query: web::Query<HashMap<String, String>>,
22) -> ControllerResult<web::Json<Vec<GlobalStatEntry>>> {
23    let mut conn = pool.acquire().await?;
24    let token = authorize(
25        &mut conn,
26        Act::ViewStats,
27        Some(user.id),
28        Res::GlobalPermissions,
29    )
30    .await?;
31
32    let granularity = query
33        .get("granularity")
34        .map(|s| s.parse().unwrap_or(TimeGranularity::Year))
35        .unwrap_or(TimeGranularity::Year);
36
37    let res = models::library::global_stats::get_number_of_people_completed_a_course(
38        &mut conn,
39        granularity,
40    )
41    .await?;
42
43    token.authorized_ok(web::Json(res))
44}
45
46/**
47GET `/api/v0/main-frontend/global-stats/number-of-people-registered-completion-to-study-registry`
48
49Query parameters:
50- granularity: String - Either "year" or "month" (defaults to "year")
51*/
52#[instrument(skip(pool))]
53async fn get_number_of_people_registered_completion_to_study_registry(
54    pool: web::Data<PgPool>,
55    user: AuthUser,
56    query: web::Query<HashMap<String, String>>,
57) -> ControllerResult<web::Json<Vec<GlobalStatEntry>>> {
58    let mut conn = pool.acquire().await?;
59    let token = authorize(
60        &mut conn,
61        Act::ViewStats,
62        Some(user.id),
63        Res::GlobalPermissions,
64    )
65    .await?;
66
67    let granularity = query
68        .get("granularity")
69        .map(|s| s.parse().unwrap_or(TimeGranularity::Year))
70        .unwrap_or(TimeGranularity::Year);
71
72    let res = models::library::global_stats::get_number_of_people_registered_completion_to_study_registry(&mut conn, granularity).await?;
73
74    token.authorized_ok(web::Json(res))
75}
76
77/**
78GET `/api/v0/main-frontend/global-stats/number-of-people-done-at-least-one-exercise`
79
80Query parameters:
81- granularity: String - Either "year" or "month" (defaults to "year")
82*/
83#[instrument(skip(pool))]
84async fn get_number_of_people_done_at_least_one_exercise(
85    pool: web::Data<PgPool>,
86    user: AuthUser,
87    query: web::Query<HashMap<String, String>>,
88) -> ControllerResult<web::Json<Vec<GlobalStatEntry>>> {
89    let mut conn = pool.acquire().await?;
90    let token = authorize(
91        &mut conn,
92        Act::ViewStats,
93        Some(user.id),
94        Res::GlobalPermissions,
95    )
96    .await?;
97
98    let granularity = query
99        .get("granularity")
100        .map(|s| s.parse().unwrap_or(TimeGranularity::Year))
101        .unwrap_or(TimeGranularity::Year);
102
103    let res = models::library::global_stats::get_number_of_people_done_at_least_one_exercise(
104        &mut conn,
105        granularity,
106    )
107    .await?;
108
109    token.authorized_ok(web::Json(res))
110}
111
112/**
113GET `/api/v0/main-frontend/global-stats/number-of-people-started-course`
114
115Query parameters:
116- granularity: String - Either "year" or "month" (defaults to "year")
117*/
118#[instrument(skip(pool))]
119async fn get_number_of_people_started_course(
120    pool: web::Data<PgPool>,
121    user: AuthUser,
122    query: web::Query<HashMap<String, String>>,
123) -> ControllerResult<web::Json<Vec<GlobalStatEntry>>> {
124    let mut conn = pool.acquire().await?;
125    let token = authorize(
126        &mut conn,
127        Act::ViewStats,
128        Some(user.id),
129        Res::GlobalPermissions,
130    )
131    .await?;
132
133    let granularity = query
134        .get("granularity")
135        .map(|s| s.parse().unwrap_or(TimeGranularity::Year))
136        .unwrap_or(TimeGranularity::Year);
137
138    let res =
139        models::library::global_stats::get_number_of_people_started_course(&mut conn, granularity)
140            .await?;
141
142    token.authorized_ok(web::Json(res))
143}
144
145/**
146 * GET `/api/v0/main-frontend/global-stats/course-module-stats-by-completions-registered-to-study-registry`
147 *
148 * Query parameters:
149 * - granularity: String - Either "year" or "month" (defaults to "year")
150 */
151#[instrument(skip(pool))]
152async fn get_course_module_stats_by_completions_registered_to_study_registry(
153    pool: web::Data<PgPool>,
154    user: AuthUser,
155    query: web::Query<HashMap<String, String>>,
156) -> ControllerResult<web::Json<Vec<GlobalCourseModuleStatEntry>>> {
157    let mut conn = pool.acquire().await?;
158    let token = authorize(
159        &mut conn,
160        Act::ViewStats,
161        Some(user.id),
162        Res::GlobalPermissions,
163    )
164    .await?;
165
166    let granularity = query
167        .get("granularity")
168        .map(|s| s.parse().unwrap_or(TimeGranularity::Year))
169        .unwrap_or(TimeGranularity::Year);
170
171    let res = models::library::global_stats::get_course_module_stats_by_completions_registered_to_study_registry(&mut conn, granularity).await?;
172
173    token.authorized_ok(web::Json(res))
174}
175
176/**
177 * GET `/api/v0/main-frontend/global-stats/completion-stats-by-email-domain`
178 *
179 * Query parameters:
180 * - year: Optional<i32> - Filter results to specific year (e.g. ?year=2023)
181 */
182#[instrument(skip(pool))]
183async fn get_completion_stats_by_email_domain(
184    pool: web::Data<PgPool>,
185    user: AuthUser,
186    query: web::Query<HashMap<String, String>>,
187) -> ControllerResult<web::Json<Vec<DomainCompletionStats>>> {
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 year = query.get("year").and_then(|y| y.parse::<i32>().ok());
198
199    let res = models::library::global_stats::get_completion_stats_by_email_domain(&mut conn, year)
200        .await?;
201
202    token.authorized_ok(web::Json(res))
203}
204
205/**
206 * GET `/api/v0/main-frontend/global-stats/course-completion-stats-for-email-domain`
207 *
208 * Query parameters:
209 * - email_domain: String - The email domain to get stats for (required)
210 * - year: Optional<i32> - Filter results to specific year (e.g. ?year=2023)
211 */
212#[instrument(skip(pool))]
213async fn get_course_completion_stats_for_email_domain(
214    pool: web::Data<PgPool>,
215    user: AuthUser,
216    query: web::Query<HashMap<String, String>>,
217) -> ControllerResult<web::Json<Vec<CourseCompletionStats>>> {
218    let mut conn = pool.acquire().await?;
219    let token = authorize(
220        &mut conn,
221        Act::ViewStats,
222        Some(user.id),
223        Res::GlobalPermissions,
224    )
225    .await?;
226
227    let email_domain = query
228        .get("email_domain")
229        .ok_or_else(|| {
230            ControllerError::new(
231                ControllerErrorType::BadRequest,
232                "email_domain is required".to_string(),
233                None,
234            )
235        })?
236        .to_string();
237
238    let year = query.get("year").and_then(|y| y.parse::<i32>().ok());
239
240    let res = models::library::global_stats::get_course_completion_stats_for_email_domain(
241        &mut conn,
242        email_domain,
243        year,
244    )
245    .await?;
246
247    token.authorized_ok(web::Json(res))
248}
249
250/**
251Add a route for each controller in this module.
252
253The name starts with an underline in order to appear before other functions in the module documentation.
254
255We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
256*/
257pub fn _add_routes(cfg: &mut ServiceConfig) {
258    cfg.route(
259        "/number-of-people-completed-a-course",
260        web::get().to(get_number_of_people_completed_a_course),
261    )
262    .route(
263        "/number-of-people-registered-completion-to-study-registry",
264        web::get().to(get_number_of_people_registered_completion_to_study_registry),
265    )
266    .route(
267        "/number-of-people-done-at-least-one-exercise",
268        web::get().to(get_number_of_people_done_at_least_one_exercise),
269    )
270    .route(
271        "/number-of-people-started-course",
272        web::get().to(get_number_of_people_started_course),
273    )
274    .route(
275        "/course-module-stats-by-completions-registered-to-study-registry",
276        web::get().to(get_course_module_stats_by_completions_registered_to_study_registry),
277    )
278    .route(
279        "/completion-stats-by-email-domain",
280        web::get().to(get_completion_stats_by_email_domain),
281    )
282    .route(
283        "/course-completion-stats-for-email-domain",
284        web::get().to(get_course_completion_stats_for_email_domain),
285    );
286}