Skip to main content

cloudillo_profile/
update.rs

1//! Profile update handlers
2
3use 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
29/// PATCH /me - Update own profile
30pub 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	// Build profile update from patch
38	let profile_update =
39		UpdateProfileData { name: patch.name.map(|s| s.into()), ..Default::default() };
40
41	// Apply the patch
42	app.meta_adapter.update_profile(tn_id, &auth.id_tag, &profile_update).await?;
43
44	// Fetch updated profile
45	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
63/// PATCH /admin/profile/:idTag - Update another user's profile data (admin only)
64pub 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	// Check admin permission - ensure user has "leader" role
71	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	// Extract roles for response before consuming patch
80	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	// Single update call for all fields
96	app.meta_adapter.update_profile(tn_id, &id_tag, &profile_update).await?;
97
98	// Fetch updated profile
99	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
117/// PATCH /profile/:idTag - Update relationship data with another user
118pub 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	// Call meta adapter to update relationship data
127	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// vim: ts=4