use models::users::user::Audience; use models::users::user_preferences; use sea_orm::{ActiveModelTrait, EntityTrait, IntoActiveModel, Set}; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use crate::AuthService; use crate::errors::AuthBusinessError; #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct UserPreferencesDto { pub theme_mode: String, pub diff_view_style: Option, /// Default view for the 问题集 page: `board` or `list`. pub board_view_mode: String, pub in_app_flags: i32, pub email_flags: i32, pub push_flags: i32, pub email_digest: bool, pub mute_until: Option, pub allow_dms_from: Audience, pub allow_mentions_from: Audience, pub show_online_status: bool, } #[derive(Debug, Clone, Serialize, Deserialize, Default, utoipa::ToSchema)] pub struct UpdatePreferencesCmd { pub theme_mode: Option, pub diff_view_style: Option, pub board_view_mode: Option, pub in_app_flags: Option, pub email_flags: Option, pub push_flags: Option, pub email_digest: Option, /// 三态:缺省不动、`null` 清除、给值设置(与 `lib/webapi/src/openapi.rs` /// 声明的 PATCH 语义一致)。 #[serde( default, deserialize_with = "api::serde_datetime::deserialize_opt_opt_datetime" )] pub mute_until: Option>, pub allow_dms_from: Option, pub allow_mentions_from: Option, pub show_online_status: Option, } impl UserPreferencesDto { fn defaults() -> Self { Self { theme_mode: "system".to_string(), diff_view_style: None, board_view_mode: "board".to_string(), in_app_flags: 0, email_flags: 0, push_flags: 0, email_digest: false, mute_until: None, allow_dms_from: Audience::Everyone, allow_mentions_from: Audience::Everyone, // 在线状态默认对同空间/同私聊可见(与 Discord/Slack 一致), // 用户可在隐私设置里显式关闭;关闭后读路径一律表现为离线。 show_online_status: true, } } } impl AuthService { pub async fn preferences(&self, uid: i64) -> Result { let Some(row) = user_preferences::Entity::find_by_id(uid) .one(self.db().read()) .await? else { return Ok(UserPreferencesDto::defaults()); }; Ok(UserPreferencesDto { theme_mode: row.theme_mode, diff_view_style: row.diff_view_style, board_view_mode: row.board_view_mode, in_app_flags: row.in_app_flags, email_flags: row.email_flags, push_flags: row.push_flags, email_digest: row.email_digest, mute_until: row.mute_until, allow_dms_from: row.allow_dms_from, allow_mentions_from: row.allow_mentions_from, show_online_status: row.show_online_status, }) } pub async fn update_preferences( &self, uid: i64, cmd: UpdatePreferencesCmd, ) -> Result { let db = self.db(); let now = OffsetDateTime::now_utc(); let existing = user_preferences::Entity::find_by_id(uid) .one(db.write()) .await?; if let Some(row) = existing { let mut active = row.into_active_model(); if let Some(theme_mode) = cmd.theme_mode { active.theme_mode = Set(theme_mode); } if cmd.diff_view_style.is_some() { active.diff_view_style = Set(cmd.diff_view_style); } if let Some(view) = cmd.board_view_mode { active.board_view_mode = Set(view); } if let Some(flags) = cmd.in_app_flags { active.in_app_flags = Set(flags); } if let Some(flags) = cmd.email_flags { active.email_flags = Set(flags); } if let Some(flags) = cmd.push_flags { active.push_flags = Set(flags); } if let Some(digest) = cmd.email_digest { active.email_digest = Set(digest); } if let Some(mute_until) = cmd.mute_until { active.mute_until = Set(mute_until); } if let Some(audience) = cmd.allow_dms_from { active.allow_dms_from = Set(audience); } if let Some(audience) = cmd.allow_mentions_from { active.allow_mentions_from = Set(audience); } if let Some(online) = cmd.show_online_status { active.show_online_status = Set(online); } active.updated_at = Set(now); active.update(db.write()).await?; } else { let defaults = UserPreferencesDto::defaults(); let row = user_preferences::ActiveModel { user_id: Set(uid), theme_mode: Set(cmd.theme_mode.unwrap_or(defaults.theme_mode)), theme_token: Set(None), theme_version: Set(None), diff_view_style: Set(cmd.diff_view_style.or(defaults.diff_view_style)), board_view_mode: Set(cmd.board_view_mode.unwrap_or(defaults.board_view_mode)), in_app_flags: Set(cmd.in_app_flags.unwrap_or(defaults.in_app_flags)), email_flags: Set(cmd.email_flags.unwrap_or(defaults.email_flags)), push_flags: Set(cmd.push_flags.unwrap_or(defaults.push_flags)), email_digest: Set(cmd.email_digest.unwrap_or(defaults.email_digest)), mute_until: Set(cmd.mute_until.flatten().or(defaults.mute_until)), allow_dms_from: Set(cmd.allow_dms_from.unwrap_or(defaults.allow_dms_from)), allow_mentions_from: Set(cmd .allow_mentions_from .unwrap_or(defaults.allow_mentions_from)), show_online_status: Set(cmd .show_online_status .unwrap_or(defaults.show_online_status)), created_at: Set(now), updated_at: Set(now), }; row.insert(db.write()).await?; } self.preferences(uid).await } }