feat(avatar): update avatar visibility handling and API documentation
ZhenYi committed
This change is very large, so some diffs are not shown. Clone the repository to see the full change.
Modified lib/user/src/handlers/get_user_avatar.rs lib/user/src/handlers/get_user_avatar.rs +6 −4 View lib/user/src/handlers/get_user_avatar.rs
@@ -12,11 +12,11 @@
1212 get,
1313 path = "/users/{user_id}/avatar",
1414 tag = "users",
15 description = "按目标用户可见性规则重定向到 S3 预签名头像链接(7 天有效, Redis 缓存)。",
15 description = "重定向到 S3 预签名头像链接(7 天有效, Redis 缓存)。头像不受资料可见性限制:聊天与成员列表依赖它辨认发言人。",
1616 params(("user_id" = Uuid, Path, description = "user id")),
1717 responses(
1818 (status = 302, description = "redirect to presigned s3 avatar url"),
19 (status = 404, description = "no avatar or user not visible", body = export::rest::AppResponseData<export::rest::NoData>),
19 (status = 404, description = "user not found or has no avatar", body = export::rest::AppResponseData<export::rest::NoData>),
2020 (status = 401, description = "authentication required", body = export::rest::AppResponseData<export::rest::NoData>)
2121 )
2222)]
@@ -25,10 +25,12 @@
2525 session: Session,
2626 Path(user_id): Path<Uuid>,
2727) -> Result<Response, ApiError> {
28 let caller = require_uid(&session).map_err(|_| {
28 // A session is still required to reach the endpoint at all; what is
29 // deliberately *not* checked is who the caller is relative to the target.
30 require_uid(&session).map_err(|_| {
2931 ApiError::Unauthorized(export::rest::AppResponseData::err(401, "unauthorized"))
3032 })?;
31 let url = service.avatar_redirect_url(caller, user_id).await?;
33 let url = service.avatar_redirect_url(user_id).await?;
3234 Ok(Response::builder()
3335 .status(StatusCode::FOUND)
3436 .header(header::LOCATION, url)
Modified lib/user/src/services/avatar.rs lib/user/src/services/avatar.rs +17 −12 View lib/user/src/services/avatar.rs
@@ -1,5 +1,4 @@
11use models::users::user;
2use models::users::user::ProfileVisibility;
32use sea_orm::{ActiveModelTrait, EntityTrait, Set};
43use serde::{Deserialize, Serialize};
54use time::OffsetDateTime;
@@ -236,21 +235,27 @@
236235 Ok(())
237236 }
238237
239 pub async fn avatar_redirect_url(
240 &self,
241 caller: Uuid,
242 target_uid: Uuid,
243 ) -> Result<String, UserError> {
238 /// Avatars are exempt from profile visibility, platform-wide.
239 ///
240 /// An avatar is not extra information about a person, it is how a room
241 /// tells its speakers apart: the transcript, the member list and every
242 /// reaction row already carry this user's display name, so gating the
243 /// picture only renders those views with a broken image. `visibility`
244 /// therefore governs the profile page and its fields — bio, email,
245 /// location, activity — and never this URL.
246 ///
247 /// `is_active` is exempt for the same reason, and it matters more than it
248 /// looks: registration writes `is_active = false` until the email is
249 /// verified, so gating on it made every new member's avatar 404 for the
250 /// rest of the room during their first session.
251 ///
252 /// Still 404: no such user, or a user who has never uploaded an avatar.
253 /// Authentication is required (the handler), just not any per-user rule.
254 pub async fn avatar_redirect_url(&self, target_uid: Uuid) -> Result<String, UserError> {
244255 let target = user::Entity::find_by_id(target_uid)
245256 .one(self.db().read())
246257 .await?
247258 .ok_or(UserError::NotFound)?;
248 if !target.is_active && caller != target_uid {
249 return Err(UserError::NotFound);
250 }
251 if target.visibility == ProfileVisibility::Private && caller != target_uid {
252 return Err(UserError::NotFound);
253 }
254259 if target.avatar_url.is_empty() {
255260 return Err(UserError::NotFound);
256261 }
Modified openapi.json openapi.json View openapi.json
Diff too large to display.