cloudillo_profile/
update.rs1use axum::{
4 extract::{Path, State},
5 http::StatusCode,
6 Json,
7};
8
9use serde::Serialize;
10
11use crate::prelude::*;
12use cloudillo_core::extract::Auth;
13use cloudillo_types::meta_adapter::UpdateProfileData;
14use cloudillo_types::types::{AdminProfilePatch, ProfileInfo, ProfilePatch};
15
16#[derive(Serialize)]
17pub struct UpdateProfileResponse {
18 profile: ProfileInfo,
19}
20
21fn profile_type_label(db_type: &str) -> &str {
22 match db_type {
23 "P" => "person",
24 "C" => "community",
25 other => other,
26 }
27}
28
29pub async fn patch_own_profile(
31 State(app): State<App>,
32 Auth(auth): Auth,
33 Json(patch): Json<ProfilePatch>,
34) -> ClResult<(StatusCode, Json<UpdateProfileResponse>)> {
35 let tn_id = auth.tn_id;
36
37 let profile_update =
39 UpdateProfileData { name: patch.name.map(|s| s.into()), ..Default::default() };
40
41 app.meta_adapter.update_profile(tn_id, &auth.id_tag, &profile_update).await?;
43
44 let profile_data = app.meta_adapter.get_profile_info(tn_id, &auth.id_tag).await?;
46
47 let profile = ProfileInfo {
48 id_tag: profile_data.id_tag.to_string(),
49 name: profile_data.name.to_string(),
50 r#type: Some(profile_type_label(&profile_data.r#type).to_string()),
51 profile_pic: profile_data.profile_pic.map(|s| s.to_string()),
52 status: None,
53 connected: None,
54 following: None,
55 roles: None,
56 created_at: Some(profile_data.created_at),
57 };
58
59 info!("User {} updated their profile", auth.id_tag);
60 Ok((StatusCode::OK, Json(UpdateProfileResponse { profile })))
61}
62
63pub async fn patch_profile_admin(
65 State(app): State<App>,
66 Auth(auth): Auth,
67 Path(id_tag): Path<String>,
68 Json(patch): Json<AdminProfilePatch>,
69) -> ClResult<(StatusCode, Json<UpdateProfileResponse>)> {
70 let has_admin_role = auth.roles.iter().any(|role| role.as_ref() == "leader");
72 if !has_admin_role {
73 warn!("Non-admin user {} attempted to modify profile {}", auth.id_tag, id_tag);
74 return Err(Error::PermissionDenied);
75 }
76
77 let tn_id = auth.tn_id;
78
79 let response_roles = match &patch.roles {
81 Patch::Value(Some(roles)) => Some(roles.clone()),
82 Patch::Value(None) | Patch::Null => Some(vec![]),
83 Patch::Undefined => None,
84 };
85
86 let profile_update = UpdateProfileData {
87 name: patch.name.map(|s| s.into()),
88 roles: patch
89 .roles
90 .map(|opt_roles| opt_roles.map(|roles| roles.into_iter().map(|s| s.into()).collect())),
91 status: patch.status,
92 ..Default::default()
93 };
94
95 app.meta_adapter.update_profile(tn_id, &id_tag, &profile_update).await?;
97
98 let profile_data = app.meta_adapter.get_profile_info(tn_id, &id_tag).await?;
100
101 let profile = ProfileInfo {
102 id_tag: profile_data.id_tag.to_string(),
103 name: profile_data.name.to_string(),
104 r#type: Some(profile_type_label(&profile_data.r#type).to_string()),
105 profile_pic: profile_data.profile_pic.map(|s| s.to_string()),
106 status: None,
107 connected: None,
108 following: None,
109 roles: response_roles,
110 created_at: Some(profile_data.created_at),
111 };
112
113 info!("Admin {} updated profile {}", auth.id_tag, id_tag);
114 Ok((StatusCode::OK, Json(UpdateProfileResponse { profile })))
115}
116
117pub async fn patch_profile_relationship(
119 State(app): State<App>,
120 Auth(auth): Auth,
121 Path(id_tag): Path<String>,
122 Json(patch): Json<cloudillo_types::meta_adapter::UpdateProfileData>,
123) -> ClResult<StatusCode> {
124 let tn_id = auth.tn_id;
125
126 app.meta_adapter.update_profile(tn_id, &id_tag, &patch).await?;
128
129 info!("User {} updated relationship with {}", auth.id_tag, id_tag);
130 Ok(StatusCode::OK)
131}
132
133