diff --git a/Cargo.lock b/Cargo.lock index c8158f2..aade267 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1453,6 +1453,7 @@ dependencies = [ "cfg-if", "chrono", "dotenvy", + "either", "headers", "hyper", "itertools 0.11.0", @@ -1513,6 +1514,8 @@ dependencies = [ "futures-core", "futures-util", "magnetar_common", + "magnetar_sdk", + "once_cell", "redis", "sea-orm", "serde", @@ -2754,9 +2757,9 @@ checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "smawk" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" @@ -3073,9 +3076,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "supports-color" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354" +checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" dependencies = [ "is-terminal", "is_ci", @@ -3621,13 +3624,9 @@ checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-linebreak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137" -dependencies = [ - "hashbrown 0.12.3", - "regex", -] +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" diff --git a/Cargo.toml b/Cargo.toml index 172b55b..51c5ae3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,7 @@ cfg-if = { workspace = true } itertools = { workspace = true } +either = { workspace = true } strum = { workspace = true, features = ["derive"] } thiserror = { workspace = true } miette = { workspace = true, features = ["fancy"] } diff --git a/ext_calckey_model/Cargo.toml b/ext_calckey_model/Cargo.toml index 12e6a72..62acb6a 100644 --- a/ext_calckey_model/Cargo.toml +++ b/ext_calckey_model/Cargo.toml @@ -11,6 +11,7 @@ ck = { path = "./entity_ck" } ext_calckey_model_migration = { path = "./migration" } magnetar_common = { path = "../magnetar_common" } +magnetar_sdk = { path = "../magnetar_sdk" } dotenvy = { workspace = true} futures-core = { workspace = true } @@ -24,4 +25,5 @@ serde_json = { workspace = true } strum = { workspace = true } chrono = { workspace = true } tracing = { workspace = true } -thiserror = { workspace = true } \ No newline at end of file +thiserror = { workspace = true } +once_cell = "1.18.0" diff --git a/ext_calckey_model/src/lib.rs b/ext_calckey_model/src/lib.rs index 6f8fdc3..2969783 100644 --- a/ext_calckey_model/src/lib.rs +++ b/ext_calckey_model/src/lib.rs @@ -1,3 +1,5 @@ +pub mod note_model; + pub use ck; use ck::*; pub use sea_orm; @@ -6,10 +8,11 @@ use chrono::Utc; use ext_calckey_model_migration::{Migrator, MigratorTrait}; use futures_util::StreamExt; use redis::IntoConnectionInfo; +use sea_orm::sea_query::IntoIden; use sea_orm::ActiveValue::Set; use sea_orm::{ - ColumnTrait, ConnectOptions, DatabaseConnection, DbErr, EntityTrait, QueryFilter, QueryOrder, - TransactionTrait, + ColumnTrait, ConnectOptions, DatabaseConnection, DbErr, EntityTrait, JoinType, QueryFilter, + QueryOrder, RelationDef, RelationTrait, TransactionTrait, }; use serde::{Deserialize, Serialize}; use std::future::Future; @@ -34,6 +37,18 @@ pub enum CalckeyDbError { DbError(#[from] DbErr), } +trait AliasSourceExt { + fn with_alias(&self, alias: I) -> RelationDef; +} + +impl AliasSourceExt for T { + fn with_alias(&self, alias: I) -> RelationDef { + let mut def = self.def(); + def.from_tbl = def.from_tbl.alias(alias); + def + } +} + impl CalckeyModel { pub async fn new(config: ConnectorConfig) -> Result { let opt = ConnectOptions::new(config.url) @@ -116,6 +131,66 @@ impl CalckeyModel { .await?) } + pub async fn get_follower_status( + &self, + from: &str, + to: &str, + ) -> Result, CalckeyDbError> { + Ok(following::Entity::find() + .filter( + following::Column::FollowerId + .eq(from) + .and(following::Column::FolloweeId.eq(to)), + ) + .one(&self.0) + .await?) + } + + pub async fn get_block_status( + &self, + from: &str, + to: &str, + ) -> Result, CalckeyDbError> { + Ok(blocking::Entity::find() + .filter( + blocking::Column::BlockerId + .eq(from) + .and(blocking::Column::BlockeeId.eq(to)), + ) + .one(&self.0) + .await?) + } + + pub async fn get_mute_status( + &self, + from: &str, + to: &str, + ) -> Result, CalckeyDbError> { + Ok(muting::Entity::find() + .filter( + muting::Column::MuterId + .eq(from) + .and(muting::Column::MuteeId.eq(to)), + ) + .one(&self.0) + .await?) + } + + pub async fn get_renote_mute_status( + &self, + from: &str, + to: &str, + ) -> Result, CalckeyDbError> { + Ok(renote_muting::Entity::find() + .filter( + renote_muting::Column::MuterId + .eq(from) + .and(renote_muting::Column::MuteeId.eq(to)), + ) + .one(&self.0) + .await?) + } + pub async fn get_local_emoji(&self) -> Result, CalckeyDbError> { Ok(emoji::Entity::find() .filter(emoji::Column::Host.is_null()) diff --git a/ext_calckey_model/src/note_model.rs b/ext_calckey_model/src/note_model.rs new file mode 100644 index 0000000..5d2283e --- /dev/null +++ b/ext_calckey_model/src/note_model.rs @@ -0,0 +1,258 @@ +use sea_orm::sea_query::{Alias, Expr, IntoIden, SelectExpr, SimpleExpr}; +use sea_orm::{ + ColumnTrait, DbErr, EntityTrait, FromQueryResult, Iden, Iterable, JoinType, QueryFilter, + QueryResult, QuerySelect, RelationTrait, Select, +}; +use serde::{Deserialize, Serialize}; + +use ck::{drive_file, note, user}; +use once_cell::unsync::Lazy; + +use crate::{AliasSourceExt, CalckeyDbError, CalckeyModel}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NoteData { + pub note: note::Model, + pub user: user::Model, + pub avatar: Option, + pub banner: Option, + pub reply: Option>, + pub renote: Option>, +} + +const USER: &str = "user"; +const USER_AVATAR: &str = "user.avatar"; +const USER_BANNER: &str = "user.banner"; +const REPLY: &str = "reply"; +const REPLY_USER: &str = "reply.user"; +const REPLY_USER_AVATAR: &str = "reply.user.avatar"; +const REPLY_USER_BANNER: &str = "reply.user.banner"; +const RENOTE: &str = "renote"; +const RENOTE_USER: &str = "renote.user"; +const RENOTE_USER_AVATAR: &str = "renote.user.avatar"; +const RENOTE_USER_BANNER: &str = "renote.user.banner"; + +impl FromQueryResult for NoteData { + fn from_query_result(res: &QueryResult, _pre: &str) -> Result { + let reply = note::Model::from_query_result_optional(res, REPLY)? + .map::, _>(|r| { + Ok(Box::new(NoteData { + note: r, + user: user::Model::from_query_result(res, REPLY_USER)?, + avatar: drive_file::Model::from_query_result_optional(res, REPLY_USER_AVATAR)?, + banner: drive_file::Model::from_query_result_optional(res, REPLY_USER_BANNER)?, + reply: None, + renote: None, + })) + }) + .transpose()?; + + let renote = note::Model::from_query_result_optional(res, RENOTE)? + .map::, _>(|r| { + Ok(Box::new(NoteData { + note: r, + user: user::Model::from_query_result(res, RENOTE_USER)?, + avatar: drive_file::Model::from_query_result_optional(res, RENOTE_USER_AVATAR)?, + banner: drive_file::Model::from_query_result_optional(res, RENOTE_USER_BANNER)?, + reply: None, + renote: None, + })) + }) + .transpose()?; + + Ok(NoteData { + note: note::Model::from_query_result(res, "")?, + user: user::Model::from_query_result(res, USER)?, + avatar: drive_file::Model::from_query_result_optional(res, USER_AVATAR)?, + banner: drive_file::Model::from_query_result_optional(res, USER_BANNER)?, + reply, + renote, + }) + } +} + +pub struct NoteResolver { + db: CalckeyModel, +} + +pub trait NoteVisibilityFilterFactory { + fn with_note_and_user_tables(&self, note: Option, user: Option) -> SimpleExpr; +} + +pub struct NoteResolveOptions { + visibility_filter: Box, + with_user: bool, + with_reply_target: bool, + with_renote_target: bool, +} + +trait SelectColumnsExt { + fn add_aliased_columns(self, alias: Option<&str>, entity: T) -> Self; +} + +impl SelectColumnsExt for Select { + fn add_aliased_columns( + mut self: Select, + alias: Option<&str>, + entity: T, + ) -> Select { + for col in T::Column::iter() { + let column: &T::Column = &col; + + let iden = alias.unwrap_or_else(|| entity.table_name()); + let alias = format!("{}{}", iden, col.to_string()); + + let column_ref = Expr::col((Alias::new(iden), column.as_column_ref().1)); + + QuerySelect::query(&mut self).expr(SelectExpr { + expr: col.select_as(column_ref), + alias: Some(Alias::new(&alias).into_iden()), + window: None, + }); + } + + self + } +} + +const ALIAS_USER: Lazy = Lazy::new(|| Alias::new(USER)); +const ALIAS_USER_AVATAR: Lazy = Lazy::new(|| Alias::new(USER_AVATAR)); +const ALIAS_USER_BANNER: Lazy = Lazy::new(|| Alias::new(USER_BANNER)); +const ALIAS_REPLY: Lazy = Lazy::new(|| Alias::new(REPLY)); +const ALIAS_REPLY_USER: Lazy = Lazy::new(|| Alias::new(REPLY_USER)); +const ALIAS_REPLY_USER_AVATAR: Lazy = Lazy::new(|| Alias::new(REPLY_USER_AVATAR)); +const ALIAS_REPLY_USER_BANNER: Lazy = Lazy::new(|| Alias::new(REPLY_USER_BANNER)); +const ALIAS_RENOTE: Lazy = Lazy::new(|| Alias::new(RENOTE)); +const ALIAS_RENOTE_USER: Lazy = Lazy::new(|| Alias::new(RENOTE_USER)); +const ALIAS_RENOTE_USER_AVATAR: Lazy = Lazy::new(|| Alias::new(RENOTE_USER_AVATAR)); +const ALIAS_RENOTE_USER_BANNER: Lazy = Lazy::new(|| Alias::new(RENOTE_USER_BANNER)); + +impl NoteResolver { + pub async fn get_one( + &self, + options: &NoteResolveOptions, + ) -> Result, CalckeyDbError> { + let select = self.resolve(options); + let visibility_filter = options + .visibility_filter + .with_note_and_user_tables(None, Some(ALIAS_USER.clone())); + let notes = select + .filter(visibility_filter) + .into_model::() + .one(self.db.inner()) + .await?; + Ok(notes) + } + + pub fn resolve(&self, options: &NoteResolveOptions) -> Select { + let mut select = note::Entity::find().add_aliased_columns(Some(USER), user::Entity); + + if options.with_user { + select = select + .add_aliased_columns(Some(USER_AVATAR), drive_file::Entity) + .add_aliased_columns(Some(USER_BANNER), drive_file::Entity); + } + + if options.with_reply_target { + select = select + .add_aliased_columns(Some(REPLY), note::Entity) + .add_aliased_columns(Some(REPLY_USER), user::Entity); + + if options.with_user { + select = select + .add_aliased_columns(Some(REPLY_USER_AVATAR), drive_file::Entity) + .add_aliased_columns(Some(REPLY_USER_BANNER), drive_file::Entity); + } + } + + if options.with_renote_target { + select = select + .add_aliased_columns(Some(RENOTE), note::Entity) + .add_aliased_columns(Some(RENOTE_USER), user::Entity); + + if options.with_user { + select = select + .add_aliased_columns(Some(RENOTE_USER_AVATAR), drive_file::Entity) + .add_aliased_columns(Some(RENOTE_USER_BANNER), drive_file::Entity) + } + } + + if options.with_reply_target { + select = select + .join_as( + JoinType::LeftJoin, + note::Relation::SelfRef2.def(), + ALIAS_REPLY.clone(), + ) + .join_as( + JoinType::LeftJoin, + note::Relation::User.with_alias(ALIAS_REPLY.clone()), + ALIAS_REPLY_USER.clone(), + ); + } + + if options.with_renote_target { + select = select + .join_as( + JoinType::LeftJoin, + note::Relation::SelfRef1.def(), + ALIAS_RENOTE.clone(), + ) + .join_as( + JoinType::InnerJoin, + note::Relation::User.with_alias(ALIAS_RENOTE.clone()), + ALIAS_RENOTE_USER.clone(), + ); + } + + select = select.join_as( + JoinType::InnerJoin, + note::Relation::User.def(), + ALIAS_USER.clone(), + ); + + if options.with_user { + select = select + .join_as( + JoinType::LeftJoin, + user::Relation::DriveFile2.with_alias(ALIAS_USER.clone()), + ALIAS_USER_AVATAR.clone(), + ) + .join_as( + JoinType::LeftJoin, + user::Relation::DriveFile1.with_alias(ALIAS_USER.clone()), + ALIAS_USER_BANNER.clone(), + ); + + if options.with_reply_target { + select = select + .join_as( + JoinType::LeftJoin, + user::Relation::DriveFile2.with_alias(ALIAS_REPLY_USER.clone()), + ALIAS_REPLY_USER_AVATAR.clone(), + ) + .join_as( + JoinType::LeftJoin, + user::Relation::DriveFile1.with_alias(ALIAS_REPLY_USER.clone()), + ALIAS_REPLY_USER_BANNER.clone(), + ); + } + + if options.with_renote_target { + select = select + .join_as( + JoinType::LeftJoin, + user::Relation::DriveFile2.with_alias(ALIAS_RENOTE_USER.clone()), + ALIAS_RENOTE_USER_AVATAR.clone(), + ) + .join_as( + JoinType::LeftJoin, + user::Relation::DriveFile1.with_alias(ALIAS_RENOTE_USER.clone()), + ALIAS_RENOTE_USER_BANNER.clone(), + ); + } + } + + select + } +} diff --git a/src/api_v1/user.rs b/src/api_v1/user.rs index 74cdb18..10db333 100644 --- a/src/api_v1/user.rs +++ b/src/api_v1/user.rs @@ -5,7 +5,6 @@ use crate::web::auth::{AuthenticatedUser, MaybeUser}; use crate::web::{ApiError, ObjectNotFound}; use axum::extract::{Path, Query, State}; use axum::Json; -use magnetar_calckey_model::ck; use magnetar_sdk::endpoints::user::{GetUserById, GetUserSelf, UserByIdReq, UserSelfReq}; use magnetar_sdk::endpoints::{Req, Res}; use std::sync::Arc; diff --git a/src/model/mod.rs b/src/model/mod.rs index a1c5e2d..c83ae28 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,7 +1,11 @@ +use crate::model::data::id::BaseId; use crate::model::processing::PackResult; use crate::service::MagnetarService; -use magnetar_calckey_model::ck; +use either::Either; +use magnetar_calckey_model::{ck, CalckeyDbError}; +use std::collections::HashMap; use std::sync::Arc; +use tokio::sync::Mutex; pub mod data; pub mod processing; @@ -17,12 +21,28 @@ impl Default for ProcessingLimits { } } +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +enum UserRelationship { + Follow, + Mute, + Block, + RenoteMute, +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +struct UserRelationshipLink { + from: String, + to: String, + rel_type: UserRelationship, +} + #[derive(Clone, Debug)] pub struct PackingContext { instance_meta: Arc, self_user: Option>, service: Arc, limits: ProcessingLimits, + relationships: Arc>>, } pub trait PackType: 'static { @@ -39,6 +59,7 @@ impl PackingContext { self_user, service, limits: Default::default(), + relationships: Arc::new(Mutex::new(HashMap::new())), }) } @@ -46,8 +67,81 @@ impl PackingContext { self.self_user.as_deref() } - fn is_self(&self, user: &ck::user::Model) -> bool { + fn is_id_self(&self, user_id: &str) -> bool { self.self_user() - .is_some_and(|self_user| self_user.id == user.id) + .is_some_and(|self_user| self_user.id == user_id) + } + + fn is_self(&self, user: &ck::user::Model) -> bool { + self.is_id_self(&user.id) + } + + async fn has_relationship_with( + &self, + user: &ck::user::Model, + rel_type: UserRelationship, + ) -> Result { + let Some(self_user) = self.self_user.as_deref() else { + return Ok(false); + }; + + self.is_relationship_between( + Either::Right(self_user.into()), + Either::Right(user.into()), + rel_type, + ) + .await + } + + async fn is_relationship_between( + &self, + from: Either<&str, &ck::user::Model>, + to: Either<&str, &ck::user::Model>, + rel_type: UserRelationship, + ) -> Result { + let link = UserRelationshipLink { + from: from.left_or_else(ck::user::Model::get_id).to_string(), + to: to.left_or_else(ck::user::Model::get_id).to_string(), + rel_type, + }; + + let read = self.relationships.lock().await; + if let Some(relationship) = read.get(&link) { + return Ok(*relationship); + } + drop(read); + + let relationship = match rel_type { + UserRelationship::Block => self + .service + .db + .get_block_status(&link.from, &link.to) + .await? + .is_some(), + UserRelationship::Follow => self + .service + .db + .get_follower_status(&link.from, &link.to) + .await? + .is_some(), + UserRelationship::Mute => self + .service + .db + .get_mute_status(&link.from, &link.to) + .await? + .is_some(), + UserRelationship::RenoteMute => self + .service + .db + .get_renote_mute_status(&link.from, &link.to) + .await? + .is_some(), + }; + + let mut write = self.relationships.lock().await; + write.insert(link, relationship); + drop(write); + + return Ok(relationship); } } diff --git a/src/model/processing/mod.rs b/src/model/processing/mod.rs index 8d7af81..59153ed 100644 --- a/src/model/processing/mod.rs +++ b/src/model/processing/mod.rs @@ -8,6 +8,7 @@ use thiserror::Error; pub mod drive; pub mod emoji; +pub mod note; pub mod user; #[derive(Debug, Error, strum::IntoStaticStr)] diff --git a/src/model/processing/note.rs b/src/model/processing/note.rs new file mode 100644 index 0000000..06e2095 --- /dev/null +++ b/src/model/processing/note.rs @@ -0,0 +1,137 @@ +use crate::model::{PackingContext, UserRelationship}; +use either::Either; +use magnetar_calckey_model::ck::sea_orm_active_enums::NoteVisibilityEnum; +use magnetar_calckey_model::note_model::NoteVisibilityFilterFactory; +use magnetar_calckey_model::sea_orm::prelude::Expr; +use magnetar_calckey_model::sea_orm::sea_query::{Alias, IntoIden, PgFunc, Query, SimpleExpr}; +use magnetar_calckey_model::sea_orm::{ColumnTrait, IntoSimpleExpr}; +use magnetar_calckey_model::{ck, CalckeyDbError}; + +#[derive(Debug, Clone)] +pub struct NoteVisibilityFilterSimple(Option); + +impl NoteVisibilityFilterFactory for NoteVisibilityFilterSimple { + fn with_note_and_user_tables( + &self, + note_tbl: Option, + user_tbl: Option, + ) -> SimpleExpr { + let note_tbl_name = + note_tbl.map_or_else(|| ck::note::Entity.into_iden(), |a| a.into_iden()); + let user_tbl_name = + user_tbl.map_or_else(|| ck::user::Entity.into_iden(), |a| a.into_iden()); + + let note_visibility = Expr::col((note_tbl_name.clone(), ck::note::Column::Visibility)); + let note_mentions = Expr::col((note_tbl_name.clone(), ck::note::Column::Mentions)); + let note_reply_user_id = Expr::col((note_tbl_name.clone(), ck::note::Column::ReplyUserId)); + let note_visible_user_ids = + Expr::col((note_tbl_name.clone(), ck::note::Column::VisibleUserIds)); + let note_user_id = Expr::col((user_tbl_name, ck::note::Column::UserId)); + + let is_public = note_visibility + .clone() + .eq(NoteVisibilityEnum::Public) + .or(note_visibility.clone().eq(NoteVisibilityEnum::Home)); + + let Some(user_id_str) = &self.0 else { + return is_public; + }; + + let self_user_id = SimpleExpr::Constant(user_id_str.into()); + + let is_self = note_user_id.clone().eq(self_user_id.clone()); + + let is_visible_specified = { + let either_specified_or_followers = note_visibility + .clone() + .eq(NoteVisibilityEnum::Specified) + .or(note_visibility.clone().eq(NoteVisibilityEnum::Followers)) + .into_simple_expr(); + + let mentioned_or_specified = self_user_id + .clone() + .eq(PgFunc::any(note_mentions.into_simple_expr())) + .or(self_user_id.eq(PgFunc::any(note_visible_user_ids))); + + either_specified_or_followers.and(mentioned_or_specified) + }; + + let is_visible_followers = { + note_visibility + .eq(NoteVisibilityEnum::Followers) + .and( + note_user_id.in_subquery( + Query::select() + .column(ck::following::Column::FolloweeId) + .from(ck::following::Entity) + .cond_where(ck::following::Column::FollowerId.eq(user_id_str)) + .to_owned(), + ), + ) + .or(note_reply_user_id.eq(user_id_str)) + }; + + is_self + .or(is_public) + .or(is_visible_followers) + .or(is_visible_specified) + } +} + +pub struct NoteVisibilityFilterModel; + +impl NoteVisibilityFilterModel { + pub async fn is_note_visible( + &self, + ctx: &PackingContext, + user: Option<&ck::user::Model>, + note: &ck::note::Model, + ) -> Result { + if user.is_some_and(|user| user.id == note.user_id) { + return Ok(true); + } + + if matches!( + note.visibility, + NoteVisibilityEnum::Public | NoteVisibilityEnum::Home + ) { + return Ok(true); + } + + if matches!( + note.visibility, + NoteVisibilityEnum::Followers | NoteVisibilityEnum::Specified + ) { + let Some(user) = user else { + return Ok(false); + }; + + if note.mentions.contains(&user.id) || note.visible_user_ids.contains(&user.id) { + return Ok(true); + } + + if matches!(note.visibility, NoteVisibilityEnum::Specified) { + return Ok(false); + } + + let following = ctx + .is_relationship_between( + Either::Right(user), + Either::Left(¬e.user_id), + UserRelationship::Follow, + ) + .await?; + + // The second condition generally will not happen in the API, + // however it allows some AP processing, with activities + // between two foreign objects + return Ok(following || user.host.is_some() && note.user_host.is_some()); + } + + return Ok(false); + } + + pub fn new_note_visibility_filter(&self, user: Option<&str>) -> NoteVisibilityFilterSimple { + NoteVisibilityFilterSimple(user.map(str::to_string)) + } +} diff --git a/src/model/processing/user.rs b/src/model/processing/user.rs index 88ff5b5..5243857 100644 --- a/src/model/processing/user.rs +++ b/src/model/processing/user.rs @@ -3,13 +3,11 @@ use crate::model::processing::emoji::EmojiModel; use crate::model::processing::{get_mm_token_emoji, PackResult}; use crate::model::{PackType, PackingContext}; use magnetar_calckey_model::ck; -use magnetar_calckey_model::sea_orm::EntityTrait; use magnetar_sdk::mmm::Token; use magnetar_sdk::types::emoji::EmojiContext; use magnetar_sdk::types::user::{PackUserBase, UserBase}; use magnetar_sdk::types::{Id, MmXml}; use magnetar_sdk::{mmm, Packed, Required}; -use std::sync::Arc; pub struct UserModel; diff --git a/src/service/user_cache.rs b/src/service/local_user_cache.rs similarity index 96% rename from src/service/user_cache.rs rename to src/service/local_user_cache.rs index 10bc576..571323e 100644 --- a/src/service/user_cache.rs +++ b/src/service/local_user_cache.rs @@ -27,20 +27,20 @@ impl From for ApiError { UserCacheError::RedisError(err) => err.into(), }; - api_error.message = format!("User cache error: {}", api_error.message); + api_error.message = format!("Local user cache error: {}", api_error.message); api_error } } -struct UserCache { +struct LocalUserCache { lifetime: TimedCache, id_to_user: HashMap>, token_to_user: HashMap>, uri_to_user: HashMap>, } -impl UserCache { +impl LocalUserCache { fn purge(&mut self, user: impl AsRef) { let user = user.as_ref(); @@ -120,20 +120,20 @@ impl UserCache { } } -pub struct UserCacheService { +pub struct LocalUserCacheService { db: CalckeyModel, #[allow(dead_code)] token_watch: CalckeySub, - cache: Arc>, + cache: Arc>, } -impl UserCacheService { +impl LocalUserCacheService { pub(super) async fn new( config: &MagnetarConfig, db: CalckeyModel, redis: CalckeyCache, ) -> Result { - let cache = Arc::new(Mutex::new(UserCache { + let cache = Arc::new(Mutex::new(LocalUserCache { lifetime: TimedCache::with_lifespan(60 * 5), id_to_user: HashMap::new(), token_to_user: HashMap::new(), diff --git a/src/service/mod.rs b/src/service/mod.rs index e1f6670..74a091e 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -7,13 +7,13 @@ use thiserror::Error; pub mod emoji_cache; pub mod generic_id_cache; pub mod instance_meta_cache; -pub mod user_cache; +pub mod local_user_cache; pub struct MagnetarService { pub db: CalckeyModel, pub cache: CalckeyCache, pub config: &'static MagnetarConfig, - pub auth_cache: user_cache::UserCacheService, + pub local_user_cache: local_user_cache::LocalUserCacheService, pub instance_meta_cache: instance_meta_cache::InstanceMetaCacheService, pub emoji_cache: emoji_cache::EmojiCacheService, pub drive_file_cache: generic_id_cache::GenericIdCacheService, @@ -32,7 +32,7 @@ impl Debug for MagnetarService { #[derive(Debug, Error)] pub enum ServiceInitError { #[error("Authentication cache initialization error: {0}")] - AuthCacheError(#[from] user_cache::UserCacheError), + AuthCacheError(#[from] local_user_cache::UserCacheError), } impl MagnetarService { @@ -41,8 +41,8 @@ impl MagnetarService { db: CalckeyModel, cache: CalckeyCache, ) -> Result { - let auth_cache = - user_cache::UserCacheService::new(config, db.clone(), cache.clone()).await?; + let local_user_cache = + local_user_cache::LocalUserCacheService::new(config, db.clone(), cache.clone()).await?; let instance_meta_cache = instance_meta_cache::InstanceMetaCacheService::new(db.clone()); let emoji_cache = emoji_cache::EmojiCacheService::new(db.clone()); let drive_file_cache = @@ -52,7 +52,7 @@ impl MagnetarService { db, cache, config, - auth_cache, + local_user_cache, instance_meta_cache, emoji_cache, drive_file_cache, diff --git a/src/web/auth.rs b/src/web/auth.rs index 7483cb5..f740b3a 100644 --- a/src/web/auth.rs +++ b/src/web/auth.rs @@ -1,4 +1,4 @@ -use crate::service::user_cache::UserCacheError; +use crate::service::local_user_cache::UserCacheError; use crate::service::MagnetarService; use crate::web::{ApiError, IntoErrorCode}; use axum::async_trait; @@ -175,7 +175,7 @@ impl AuthState { let token = token.token(); if is_user_token(token) { - let user_cache = &self.service.auth_cache; + let user_cache = &self.service.local_user_cache; let user = user_cache.get_by_token(token).await?; if let Some(user) = user { @@ -194,7 +194,7 @@ impl AuthState { let user = self .service - .auth_cache + .local_user_cache .get_by_id(&access_token.user_id) .await?;