use std::time::Duration; use chrono::{DateTime, Utc}; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; use crate::AuthService; use crate::errors::{AuthError, AuthResult}; use crate::models::users::users; use crate::session::Session; #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, ToSchema)] pub struct MeResponse { pub user_id: i64, pub username: String, pub displayname: Option, pub email: String, pub is_admin: bool, pub avatar_url: Option, pub email_verified_at: Option>, pub created_at: DateTime, pub version: i32, } impl From for MeResponse { fn from(user: users::Model) -> Self { Self { user_id: user.id, username: user.username, displayname: user.displayname, email: user.email, is_admin: user.is_admin, avatar_url: None, email_verified_at: user.email_verified_at, created_at: user.created_at, version: user.version, } } } pub const ME_CACHE_TTL_SECS: u64 = 300; const ME_COUNT_TTL_SECS: u64 = 600; const ME_REFRESH_EVERY: i64 = 5; pub fn me_cache_keys(user_id: i64) -> (String, String) { ( format!("auth:me:{user_id}"), format!("auth:me:count:{user_id}"), ) } pub async fn invalidate_me_cache(cache: &crate::cache::Cache, user_id: i64) { let (cache_key, count_key) = me_cache_keys(user_id); let dropped = async { cache.delete(&cache_key).await?; cache.delete(&count_key).await?; Ok::<(), crate::cache::CacheError>(()) } .await; if let Err(error) = dropped { tracing::warn!(%error, user_id, "failed to drop the me cache"); } } impl AuthService { #[tracing::instrument(skip_all, fields(request_id = %_request_id, client_ip = ?session.client_ip().ok().flatten()))] pub async fn me(&self, session: Session, _request_id: String) -> AuthResult { let user_id = self .accounts .require_session(&session, Some(_request_id.as_str())) .await?; self.me_for_user(user_id).await } pub(crate) async fn me_for_user(&self, user_id: i64) -> AuthResult { let (cache_key, count_key) = me_cache_keys(user_id); let count = self.cache().incr(&count_key).await?; if count == 1 { let _ = self .cache() .expire(&count_key, Duration::from_secs(ME_COUNT_TTL_SECS)) .await; } if count % ME_REFRESH_EVERY != 0 && let Some(cached) = self.cache().get_json::(&cache_key).await? { return Ok(cached); } let user = users::Entity::find_by_id(user_id) .filter(users::Column::DeletedAt.is_null()) .one(self.db().read()) .await? .ok_or(AuthError::Unauthenticated)?; let me = MeResponse::from(user); let _ = self .cache() .set_json_ex(&cache_key, &me, Duration::from_secs(ME_CACHE_TTL_SECS)) .await; Ok(me) } }