headless_lms_server/controllers/course_material/
user_details.rs1use std::net::IpAddr;
2
3use headless_lms_utils::ip_to_country::IpToCountryMapper;
4use models::user_details::UserDetail;
5use utoipa::{OpenApi, ToSchema};
6
7use crate::prelude::*;
8
9#[derive(OpenApi)]
10#[openapi(paths(get_user_details, update_user_info, get_user_country_by_ip))]
11pub(crate) struct CourseMaterialUserDetailsApiDoc;
12
13#[utoipa::path(
17 get,
18 path = "/user",
19 operation_id = "getCourseMaterialAuthenticatedUserDetails",
20 tag = "course-material-user-details",
21 responses(
22 (status = 200, description = "Authenticated user details", body = UserDetail)
23 )
24)]
25#[instrument(skip(pool))]
26pub async fn get_user_details(
27 user: AuthUser,
28 pool: web::Data<PgPool>,
29) -> ControllerResult<web::Json<UserDetail>> {
30 let mut conn = pool.acquire().await?;
31
32 let token = skip_authorize();
33
34 let res = models::user_details::get_user_details_by_user_id(&mut conn, user.id).await?;
35 token.authorized_ok(web::Json(res))
36}
37
38#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
39
40pub struct UserInfoPayload {
41 pub email: String,
42 pub first_name: String,
43 pub last_name: String,
44 pub country: String,
45 pub email_communication_consent: bool,
46}
47
48#[utoipa::path(
52 post,
53 path = "/update-user-info",
54 operation_id = "updateCourseMaterialUserInfo",
55 tag = "course-material-user-details",
56 request_body = UserInfoPayload,
57 responses(
58 (status = 200, description = "Updated user details", body = UserDetail)
59 )
60)]
61#[instrument(skip(pool, app_conf))]
62pub async fn update_user_info(
63 user: AuthUser,
64 pool: web::Data<PgPool>,
65 payload: web::Json<UserInfoPayload>,
66 app_conf: web::Data<ApplicationConfiguration>,
67) -> ControllerResult<web::Json<UserDetail>> {
68 let mut conn = pool.acquire().await?;
69 let existing = models::user_details::get_user_details_by_user_id(&mut conn, user.id).await?;
70 let res = models::user_details::update_user_info(
71 &mut conn,
72 user.id,
73 &payload.email,
74 &payload.first_name,
75 &payload.last_name,
76 &payload.country,
77 payload.email_communication_consent,
78 )
79 .await?;
80
81 if existing.email != res.email {
82 domain::email_ownership_verification::queue_verification_email_best_effort(
84 &mut conn,
85 app_conf.enable_email_ownership_verification,
86 user.id,
87 )
88 .await;
89 }
90
91 let token = skip_authorize();
92 token.authorized_ok(web::Json(res))
93}
94
95#[utoipa::path(
99 get,
100 path = "/users-ip-country",
101 operation_id = "getCourseMaterialCountryFromIp",
102 tag = "course-material-user-details",
103 responses(
104 (status = 200, description = "Detected country code", body = String)
105 )
106)]
107pub async fn get_user_country_by_ip(
108 req: HttpRequest,
109 ip_to_country_mapper: web::Data<IpToCountryMapper>,
110) -> ControllerResult<String> {
111 let connection_info = req.connection_info();
112
113 let ip: Option<IpAddr> = connection_info
114 .realip_remote_addr()
115 .and_then(|ip| ip.parse::<IpAddr>().ok());
116
117 let country = ip
118 .and_then(|ip| ip_to_country_mapper.map_ip_to_country(&ip))
119 .map(|c| c.to_string())
120 .unwrap_or_default();
121
122 let token = skip_authorize();
123 token.authorized_ok(country.to_string())
124}
125
126pub fn _add_routes(cfg: &mut ServiceConfig) {
134 cfg.route("/user", web::get().to(get_user_details))
135 .route("/update-user-info", web::post().to(update_user_info))
136 .route("/users-ip-country", web::get().to(get_user_country_by_ip));
137}