Skip to main content

cloudillo_profile/
update.rs

1// SPDX-FileCopyrightText: Szilárd Hajba
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4//! Profile update handlers
5
6use axum::{
7	extract::{Path, State},
8	http::StatusCode,
9	Json,
10};
11
12use serde::Serialize;
13
14use crate::prelude::*;
15use cloudillo_core::extract::Auth;
16use cloudillo_types::meta_adapter::UpdateProfileData;
17use cloudillo_types::types::{AdminProfilePatch, ProfileInfo, ProfilePatch};
18
19#[derive(Serialize)]
20pub struct UpdateProfileResponse {
21	profile: ProfileInfo,
22}
23
24fn profile_type_label(db_type: &str) -> &str {
25	match db_type {
26		"P" => "person",
27		"C" => "community",
28		other => other,
29	}
30}
31
32/// PATCH /me - Update own profile
33pub async fn patch_own_profile(
34	State(app): State<App>,
35	Auth(auth): Auth,
36	Json(patch): Json<ProfilePatch>,
37) -> ClResult<(StatusCode, Json<UpdateProfileResponse>)> {
38	let tn_id = auth.tn_id;
39
40	// Build profile update from patch
41	let profile_update =
42		UpdateProfileData { name: patch.name.map(Into::into), ..Default::default() };
43
44	// Apply the patch
45	app.meta_adapter.update_profile(tn_id, &auth.id_tag, &profile_update).await?;
46
47	// Fetch updated profile
48	let profile_data = app.meta_adapter.get_profile_info(tn_id, &auth.id_tag).await?;
49
50	let profile = ProfileInfo {
51		id_tag: profile_data.id_tag.to_string(),
52		name: profile_data.name.to_string(),
53		r#type: Some(profile_type_label(&profile_data.r#type).to_string()),
54		profile_pic: profile_data.profile_pic.map(|s| s.to_string()),
55		status: None,
56		connected: None,
57		following: None,
58		roles: None,
59		created_at: Some(profile_data.created_at),
60	};
61
62	info!("User {} updated their profile", auth.id_tag);
63	Ok((StatusCode::OK, Json(UpdateProfileResponse { profile })))
64}
65
66/// PATCH /admin/profile/:idTag - Update another user's profile data (admin only)
67pub async fn patch_profile_admin(
68	State(app): State<App>,
69	Auth(auth): Auth,
70	Path(id_tag): Path<String>,
71	Json(patch): Json<AdminProfilePatch>,
72) -> ClResult<(StatusCode, Json<UpdateProfileResponse>)> {
73	// Check admin permission - ensure user has "leader" role
74	let has_admin_role = auth.roles.iter().any(|role| role.as_ref() == "leader");
75	if !has_admin_role {
76		warn!("Non-admin user {} attempted to modify profile {}", auth.id_tag, id_tag);
77		return Err(Error::PermissionDenied);
78	}
79
80	let tn_id = auth.tn_id;
81
82	// Extract roles for response before consuming patch
83	let response_roles = match &patch.roles {
84		Patch::Value(Some(roles)) => Some(roles.clone()),
85		Patch::Value(None) | Patch::Null => Some(vec![]),
86		Patch::Undefined => None,
87	};
88
89	let profile_update = UpdateProfileData {
90		name: patch.name.map(Into::into),
91		roles: patch
92			.roles
93			.map(|opt_roles| opt_roles.map(|roles| roles.into_iter().map(Into::into).collect())),
94		status: patch.status,
95		..Default::default()
96	};
97
98	// Single update call for all fields
99	app.meta_adapter.update_profile(tn_id, &id_tag, &profile_update).await?;
100
101	// Fetch updated profile
102	let profile_data = app.meta_adapter.get_profile_info(tn_id, &id_tag).await?;
103
104	let profile = ProfileInfo {
105		id_tag: profile_data.id_tag.to_string(),
106		name: profile_data.name.to_string(),
107		r#type: Some(profile_type_label(&profile_data.r#type).to_string()),
108		profile_pic: profile_data.profile_pic.map(|s| s.to_string()),
109		status: None,
110		connected: None,
111		following: None,
112		roles: response_roles,
113		created_at: Some(profile_data.created_at),
114	};
115
116	info!("Admin {} updated profile {}", auth.id_tag, id_tag);
117	Ok((StatusCode::OK, Json(UpdateProfileResponse { profile })))
118}
119
120/// PATCH /profile/:idTag - Update relationship data with another user
121pub async fn patch_profile_relationship(
122	State(app): State<App>,
123	Auth(auth): Auth,
124	Path(id_tag): Path<String>,
125	Json(patch): Json<cloudillo_types::meta_adapter::UpdateProfileData>,
126) -> ClResult<StatusCode> {
127	let tn_id = auth.tn_id;
128
129	// Call meta adapter to update relationship data
130	app.meta_adapter.update_profile(tn_id, &id_tag, &patch).await?;
131
132	info!("User {} updated relationship with {}", auth.id_tag, id_tag);
133	Ok(StatusCode::OK)
134}
135
136// vim: ts=4