Basic note fetching
This commit is contained in:
parent
f0e56deca9
commit
3cd43d840a
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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 }
|
||||
|
@ -25,3 +26,4 @@ strum = { workspace = true }
|
|||
chrono = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
once_cell = "1.18.0"
|
||||
|
|
|
@ -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<I: IntoIden>(&self, alias: I) -> RelationDef;
|
||||
}
|
||||
|
||||
impl<T: RelationTrait> AliasSourceExt for T {
|
||||
fn with_alias<I: IntoIden>(&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<Self, CalckeyDbError> {
|
||||
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<Option<following::Model>, 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<Option<blocking::Model>, 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<Option<muting::Model>, 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<Option<renote_muting::Model>, 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<Vec<emoji::Model>, CalckeyDbError> {
|
||||
Ok(emoji::Entity::find()
|
||||
.filter(emoji::Column::Host.is_null())
|
||||
|
|
|
@ -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<drive_file::Model>,
|
||||
pub banner: Option<drive_file::Model>,
|
||||
pub reply: Option<Box<NoteData>>,
|
||||
pub renote: Option<Box<NoteData>>,
|
||||
}
|
||||
|
||||
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<Self, DbErr> {
|
||||
let reply = note::Model::from_query_result_optional(res, REPLY)?
|
||||
.map::<Result<_, DbErr>, _>(|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::<Result<_, DbErr>, _>(|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<Alias>, user: Option<Alias>) -> SimpleExpr;
|
||||
}
|
||||
|
||||
pub struct NoteResolveOptions {
|
||||
visibility_filter: Box<dyn NoteVisibilityFilterFactory>,
|
||||
with_user: bool,
|
||||
with_reply_target: bool,
|
||||
with_renote_target: bool,
|
||||
}
|
||||
|
||||
trait SelectColumnsExt {
|
||||
fn add_aliased_columns<T: EntityTrait>(self, alias: Option<&str>, entity: T) -> Self;
|
||||
}
|
||||
|
||||
impl SelectColumnsExt for Select<note::Entity> {
|
||||
fn add_aliased_columns<T: EntityTrait>(
|
||||
mut self: Select<note::Entity>,
|
||||
alias: Option<&str>,
|
||||
entity: T,
|
||||
) -> Select<note::Entity> {
|
||||
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<Alias> = Lazy::new(|| Alias::new(USER));
|
||||
const ALIAS_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(USER_AVATAR));
|
||||
const ALIAS_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(USER_BANNER));
|
||||
const ALIAS_REPLY: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY));
|
||||
const ALIAS_REPLY_USER: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER));
|
||||
const ALIAS_REPLY_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER_AVATAR));
|
||||
const ALIAS_REPLY_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER_BANNER));
|
||||
const ALIAS_RENOTE: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE));
|
||||
const ALIAS_RENOTE_USER: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER));
|
||||
const ALIAS_RENOTE_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER_AVATAR));
|
||||
const ALIAS_RENOTE_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER_BANNER));
|
||||
|
||||
impl NoteResolver {
|
||||
pub async fn get_one(
|
||||
&self,
|
||||
options: &NoteResolveOptions,
|
||||
) -> Result<Option<NoteData>, 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::<NoteData>()
|
||||
.one(self.db.inner())
|
||||
.await?;
|
||||
Ok(notes)
|
||||
}
|
||||
|
||||
pub fn resolve(&self, options: &NoteResolveOptions) -> Select<note::Entity> {
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
100
src/model/mod.rs
100
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<ck::meta::Model>,
|
||||
self_user: Option<Arc<ck::user::Model>>,
|
||||
service: Arc<MagnetarService>,
|
||||
limits: ProcessingLimits,
|
||||
relationships: Arc<Mutex<HashMap<UserRelationshipLink, bool>>>,
|
||||
}
|
||||
|
||||
pub trait PackType<I>: '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<bool, CalckeyDbError> {
|
||||
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<bool, CalckeyDbError> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use thiserror::Error;
|
|||
|
||||
pub mod drive;
|
||||
pub mod emoji;
|
||||
pub mod note;
|
||||
pub mod user;
|
||||
|
||||
#[derive(Debug, Error, strum::IntoStaticStr)]
|
||||
|
|
|
@ -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<String>);
|
||||
|
||||
impl NoteVisibilityFilterFactory for NoteVisibilityFilterSimple {
|
||||
fn with_note_and_user_tables(
|
||||
&self,
|
||||
note_tbl: Option<Alias>,
|
||||
user_tbl: Option<Alias>,
|
||||
) -> 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<bool, CalckeyDbError> {
|
||||
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))
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -27,20 +27,20 @@ impl From<UserCacheError> 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<String, ()>,
|
||||
id_to_user: HashMap<String, Arc<ck::user::Model>>,
|
||||
token_to_user: HashMap<String, Arc<ck::user::Model>>,
|
||||
uri_to_user: HashMap<String, Arc<ck::user::Model>>,
|
||||
}
|
||||
|
||||
impl UserCache {
|
||||
impl LocalUserCache {
|
||||
fn purge(&mut self, user: impl AsRef<ck::user::Model>) {
|
||||
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<Mutex<UserCache>>,
|
||||
cache: Arc<Mutex<LocalUserCache>>,
|
||||
}
|
||||
|
||||
impl UserCacheService {
|
||||
impl LocalUserCacheService {
|
||||
pub(super) async fn new(
|
||||
config: &MagnetarConfig,
|
||||
db: CalckeyModel,
|
||||
redis: CalckeyCache,
|
||||
) -> Result<Self, UserCacheError> {
|
||||
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(),
|
|
@ -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<ck::drive_file::Entity>,
|
||||
|
@ -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<Self, ServiceInitError> {
|
||||
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,
|
||||
|
|
|
@ -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?;
|
||||
|
||||
|
|
Loading…
Reference in New Issue