magnetar/src/model/processing/note.rs

138 lines
4.8 KiB
Rust

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(&note.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))
}
}