Compare commits
No commits in common. "acdc3e8bc107bc28f6eeff8b07bfc3a871078d86" and "771795d81f675543e98a6dab42ba49e32f99f22f" have entirely different histories.
acdc3e8bc1
...
771795d81f
|
@ -1452,9 +1452,7 @@ dependencies = [
|
||||||
"cached",
|
"cached",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"chrono",
|
"chrono",
|
||||||
"compact_str",
|
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"either",
|
|
||||||
"headers",
|
"headers",
|
||||||
"hyper",
|
"hyper",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
|
@ -1467,7 +1465,6 @@ dependencies = [
|
||||||
"magnetar_webfinger",
|
"magnetar_webfinger",
|
||||||
"miette",
|
"miette",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"regex",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum",
|
"strum",
|
||||||
|
@ -1516,8 +1513,6 @@ dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"magnetar_common",
|
"magnetar_common",
|
||||||
"magnetar_sdk",
|
|
||||||
"once_cell",
|
|
||||||
"redis",
|
"redis",
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -2759,9 +2754,9 @@ checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smawk"
|
name = "smawk"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
|
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
|
@ -3078,9 +3073,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "supports-color"
|
name = "supports-color"
|
||||||
version = "2.1.0"
|
version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89"
|
checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"is-terminal",
|
"is-terminal",
|
||||||
"is_ci",
|
"is_ci",
|
||||||
|
@ -3626,9 +3621,13 @@ checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-linebreak"
|
name = "unicode-linebreak"
|
||||||
version = "0.1.5"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.12.3",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
|
|
|
@ -99,12 +99,9 @@ cfg-if = { workspace = true }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
|
||||||
compact_str = { workspace = true }
|
|
||||||
either = { workspace = true }
|
|
||||||
strum = { workspace = true, features = ["derive"] }
|
strum = { workspace = true, features = ["derive"] }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
miette = { workspace = true, features = ["fancy"] }
|
miette = { workspace = true, features = ["fancy"] }
|
||||||
regex = { workspace = true }
|
|
||||||
|
|
||||||
percent-encoding = { workspace = true }
|
percent-encoding = { workspace = true }
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ ck = { path = "./entity_ck" }
|
||||||
ext_calckey_model_migration = { path = "./migration" }
|
ext_calckey_model_migration = { path = "./migration" }
|
||||||
|
|
||||||
magnetar_common = { path = "../magnetar_common" }
|
magnetar_common = { path = "../magnetar_common" }
|
||||||
magnetar_sdk = { path = "../magnetar_sdk" }
|
|
||||||
|
|
||||||
dotenvy = { workspace = true}
|
dotenvy = { workspace = true}
|
||||||
futures-core = { workspace = true }
|
futures-core = { workspace = true }
|
||||||
|
@ -26,4 +25,3 @@ strum = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
once_cell = "1.18.0"
|
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
pub mod note_model;
|
|
||||||
|
|
||||||
pub use ck;
|
pub use ck;
|
||||||
use ck::*;
|
use ck::*;
|
||||||
pub use sea_orm;
|
pub use sea_orm;
|
||||||
|
|
||||||
use crate::note_model::NoteResolver;
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use ext_calckey_model_migration::{Migrator, MigratorTrait};
|
use ext_calckey_model_migration::{Migrator, MigratorTrait};
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use redis::IntoConnectionInfo;
|
use redis::IntoConnectionInfo;
|
||||||
use sea_orm::sea_query::IntoIden;
|
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
ColumnTrait, ConnectOptions, DatabaseConnection, DbErr, EntityTrait, JoinType, QueryFilter,
|
ColumnTrait, ConnectOptions, DatabaseConnection, DbErr, EntityTrait, QueryFilter, QueryOrder,
|
||||||
QueryOrder, RelationDef, RelationTrait, TransactionTrait,
|
TransactionTrait,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
@ -38,18 +34,6 @@ pub enum CalckeyDbError {
|
||||||
DbError(#[from] DbErr),
|
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 {
|
impl CalckeyModel {
|
||||||
pub async fn new(config: ConnectorConfig) -> Result<Self, CalckeyDbError> {
|
pub async fn new(config: ConnectorConfig) -> Result<Self, CalckeyDbError> {
|
||||||
let opt = ConnectOptions::new(config.url)
|
let opt = ConnectOptions::new(config.url)
|
||||||
|
@ -132,66 +116,6 @@ impl CalckeyModel {
|
||||||
.await?)
|
.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> {
|
pub async fn get_local_emoji(&self) -> Result<Vec<emoji::Model>, CalckeyDbError> {
|
||||||
Ok(emoji::Entity::find()
|
Ok(emoji::Entity::find()
|
||||||
.filter(emoji::Column::Host.is_null())
|
.filter(emoji::Column::Host.is_null())
|
||||||
|
@ -287,10 +211,6 @@ impl CalckeyModel {
|
||||||
|
|
||||||
Ok(meta)
|
Ok(meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_note_resolver(&self) -> NoteResolver {
|
|
||||||
NoteResolver::new(self.clone())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -1,281 +0,0 @@
|
||||||
use sea_orm::sea_query::{Alias, Expr, IntoIden, SelectExpr, SimpleExpr};
|
|
||||||
use sea_orm::{
|
|
||||||
ColumnTrait, DbErr, EntityTrait, FromQueryResult, Iden, Iterable, JoinType, QueryFilter,
|
|
||||||
QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use ck::{drive_file, note, user};
|
|
||||||
use magnetar_sdk::types::RangeFilter;
|
|
||||||
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: Send + Sync {
|
|
||||||
fn with_note_and_user_tables(&self, note: Option<Alias>) -> SimpleExpr;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NoteResolveOptions {
|
|
||||||
pub ids: Option<Vec<String>>,
|
|
||||||
pub visibility_filter: Box<dyn NoteVisibilityFilterFactory>,
|
|
||||||
pub time_range: Option<RangeFilter>,
|
|
||||||
pub with_user: bool,
|
|
||||||
pub with_reply_target: bool,
|
|
||||||
pub 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 fn new(db: CalckeyModel) -> Self {
|
|
||||||
NoteResolver { db }
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
let time_filter = options.time_range.as_ref().map(|f| match f {
|
|
||||||
RangeFilter::TimeStart(start) => note::Column::CreatedAt.gte(start.clone()),
|
|
||||||
RangeFilter::TimeRange(range) => {
|
|
||||||
note::Column::CreatedAt.between(range.start().clone(), range.end().clone())
|
|
||||||
}
|
|
||||||
RangeFilter::TimeEnd(end) => note::Column::CreatedAt.lt(end.clone()),
|
|
||||||
});
|
|
||||||
|
|
||||||
let id_filter = options.ids.as_ref().map(|ids| {
|
|
||||||
if ids.len() == 1 {
|
|
||||||
note::Column::Id.eq(&ids[0])
|
|
||||||
} else {
|
|
||||||
note::Column::Id.is_in(ids)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let notes = select
|
|
||||||
.filter(visibility_filter)
|
|
||||||
.apply_if(id_filter, Select::<note::Entity>::filter)
|
|
||||||
.apply_if(time_filter, Select::<note::Entity>::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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -255,25 +255,6 @@ impl Token {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn walk_speech_transform(&mut self, func: &impl Fn(&mut CompactString)) {
|
|
||||||
match self {
|
|
||||||
Token::Sequence(items) => {
|
|
||||||
items
|
|
||||||
.iter_mut()
|
|
||||||
.for_each(|tok| tok.walk_speech_transform(func));
|
|
||||||
}
|
|
||||||
Token::Small(inner)
|
|
||||||
| Token::BoldItalic(inner)
|
|
||||||
| Token::Bold(inner)
|
|
||||||
| Token::Italic(inner)
|
|
||||||
| Token::Center(inner)
|
|
||||||
| Token::Function { inner, .. }
|
|
||||||
| Token::Strikethrough(inner) => inner.walk_speech_transform(func),
|
|
||||||
Token::PlainText(text) => func(text),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write<T: Write>(&self, writer: &mut quick_xml::Writer<T>) -> quick_xml::Result<()> {
|
fn write<T: Write>(&self, writer: &mut quick_xml::Writer<T>) -> quick_xml::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Token::PlainText(plain) => {
|
Token::PlainText(plain) => {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
pub mod note;
|
|
||||||
pub mod timeline;
|
pub mod timeline;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
use crate::endpoints::Endpoint;
|
|
||||||
use crate::types::note::PackNoteMaybeFull;
|
|
||||||
use http::Method;
|
|
||||||
use magnetar_sdk_macros::Endpoint;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use ts_rs::TS;
|
|
||||||
|
|
||||||
// Get note by id
|
|
||||||
#[derive(Serialize, Deserialize, TS)]
|
|
||||||
#[ts(export)]
|
|
||||||
pub struct NoteByIdReq {
|
|
||||||
#[serde(default)]
|
|
||||||
pub context: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub attachments: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Endpoint)]
|
|
||||||
#[endpoint(
|
|
||||||
endpoint = "/notes/:id",
|
|
||||||
method = Method::GET,
|
|
||||||
request = NoteByIdReq,
|
|
||||||
response = PackNoteMaybeFull
|
|
||||||
)]
|
|
||||||
pub struct GetNoteById;
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::endpoints::Endpoint;
|
use crate::endpoints::Endpoint;
|
||||||
use crate::types::note::{NoteListFilter, PackNoteMaybeFull};
|
use crate::types::note::{NoteListFilter, PackNoteFull};
|
||||||
use crate::util_types::U64Range;
|
use crate::util_types::U64Range;
|
||||||
use http::Method;
|
use http::Method;
|
||||||
use magnetar_sdk_macros::Endpoint;
|
use magnetar_sdk_macros::Endpoint;
|
||||||
|
@ -23,13 +23,13 @@ fn default_timeline_limit<const MIN: u64, const MAX: u64>() -> U64Range<MIN, MAX
|
||||||
#[derive(Serialize, Deserialize, TS)]
|
#[derive(Serialize, Deserialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct GetTimelineRes(pub Vec<PackNoteMaybeFull>);
|
pub struct GetTimelineRes(pub Vec<PackNoteFull>);
|
||||||
|
|
||||||
#[derive(Endpoint)]
|
#[derive(Endpoint)]
|
||||||
#[endpoint(
|
#[endpoint(
|
||||||
endpoint = "/timeline",
|
endpoint = "/timeline",
|
||||||
method = Method::GET,
|
method = Method::GET,
|
||||||
request = GetTimelineReq,
|
request = GetTimelineReq,
|
||||||
response = Vec::<PackNoteMaybeFull>,
|
response = Vec::<PackNoteFull>,
|
||||||
)]
|
)]
|
||||||
pub struct GetTimeline;
|
pub struct GetTimeline;
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub struct UserSelfReq {
|
||||||
|
|
||||||
#[derive(Endpoint)]
|
#[derive(Endpoint)]
|
||||||
#[endpoint(
|
#[endpoint(
|
||||||
endpoint = "/users/@self",
|
endpoint = "/users/@self/overview/info",
|
||||||
method = Method::GET,
|
method = Method::GET,
|
||||||
request = UserSelfReq,
|
request = UserSelfReq,
|
||||||
response = PackUserSelfMaybeAll
|
response = PackUserSelfMaybeAll
|
||||||
|
@ -47,7 +47,7 @@ pub struct UserByIdReq {
|
||||||
|
|
||||||
#[derive(Endpoint)]
|
#[derive(Endpoint)]
|
||||||
#[endpoint(
|
#[endpoint(
|
||||||
endpoint = "/users/:user_id",
|
endpoint = "/users/:user_id/info",
|
||||||
method = Method::GET,
|
method = Method::GET,
|
||||||
request = UserByIdReq,
|
request = UserByIdReq,
|
||||||
response = PackUserMaybeAll
|
response = PackUserMaybeAll
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub struct NoteBase {
|
||||||
pub uri: Option<String>,
|
pub uri: Option<String>,
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub text_mm: Option<MmXml>,
|
pub text_mm: MmXml,
|
||||||
pub visibility: NoteVisibility,
|
pub visibility: NoteVisibility,
|
||||||
pub user: Box<PackUserBase>,
|
pub user: Box<PackUserBase>,
|
||||||
pub parent_note_id: Option<String>,
|
pub parent_note_id: Option<String>,
|
||||||
|
@ -92,8 +92,8 @@ pub struct NoteDetailExt {
|
||||||
}
|
}
|
||||||
|
|
||||||
pack!(
|
pack!(
|
||||||
PackNoteMaybeFull,
|
PackNoteFull,
|
||||||
Required<Id> as id & Required<NoteBase> as note & Option<NoteAttachmentExt> as attachment & Option<NoteDetailExt> as detail
|
Required<Id> as id & Required<NoteBase> as note & Required<NoteAttachmentExt> as attachment & Required<NoteDetailExt> as detail
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
|
|
@ -5,7 +5,7 @@ use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::types::note::PackNoteMaybeFull;
|
use crate::types::note::PackNoteFull;
|
||||||
use magnetar_sdk_macros::pack;
|
use magnetar_sdk_macros::pack;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
||||||
|
@ -88,7 +88,7 @@ pub struct UserProfileExt {
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct UserProfilePinsEx {
|
pub struct UserProfilePinsEx {
|
||||||
pub pinned_notes: Vec<PackNoteMaybeFull>,
|
pub pinned_notes: Vec<PackNoteFull>,
|
||||||
// pub pinned_page: Option<Page>,
|
// pub pinned_page: Option<Page>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
mod note;
|
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
use crate::api_v1::note::handle_note;
|
|
||||||
use crate::api_v1::user::{handle_user_info, handle_user_info_self};
|
use crate::api_v1::user::{handle_user_info, handle_user_info_self};
|
||||||
use crate::service::MagnetarService;
|
use crate::service::MagnetarService;
|
||||||
use crate::web::auth;
|
use crate::web::auth;
|
||||||
|
@ -15,7 +13,6 @@ pub fn create_api_router(service: Arc<MagnetarService>) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/users/@self", get(handle_user_info_self))
|
.route("/users/@self", get(handle_user_info_self))
|
||||||
.route("/users/:id", get(handle_user_info))
|
.route("/users/:id", get(handle_user_info))
|
||||||
.route("/notes/:id", get(handle_note))
|
|
||||||
.layer(from_fn_with_state(
|
.layer(from_fn_with_state(
|
||||||
AuthState::new(service.clone()),
|
AuthState::new(service.clone()),
|
||||||
auth::auth,
|
auth::auth,
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
use axum::extract::{Path, Query, State};
|
|
||||||
use axum::{debug_handler, Json};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::model::processing::note::NoteModel;
|
|
||||||
use crate::model::PackingContext;
|
|
||||||
use magnetar_sdk::endpoints::note::{GetNoteById, NoteByIdReq};
|
|
||||||
use magnetar_sdk::endpoints::{Req, Res};
|
|
||||||
|
|
||||||
use crate::service::MagnetarService;
|
|
||||||
use crate::web::auth::MaybeUser;
|
|
||||||
use crate::web::{ApiError, ObjectNotFound};
|
|
||||||
|
|
||||||
#[debug_handler]
|
|
||||||
pub async fn handle_note(
|
|
||||||
Path(id): Path<String>,
|
|
||||||
Query(NoteByIdReq {
|
|
||||||
context,
|
|
||||||
attachments,
|
|
||||||
}): Query<Req<GetNoteById>>,
|
|
||||||
State(service): State<Arc<MagnetarService>>,
|
|
||||||
MaybeUser(self_user): MaybeUser,
|
|
||||||
) -> Result<Json<Res<GetNoteById>>, ApiError> {
|
|
||||||
let ctx = PackingContext::new(service, self_user.clone()).await?;
|
|
||||||
let note = NoteModel
|
|
||||||
.fetch_single(&ctx, self_user.as_deref(), &id, context, attachments)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| ObjectNotFound(id))?;
|
|
||||||
|
|
||||||
Ok(Json(note.into()))
|
|
||||||
}
|
|
|
@ -5,6 +5,7 @@ use crate::web::auth::{AuthenticatedUser, MaybeUser};
|
||||||
use crate::web::{ApiError, ObjectNotFound};
|
use crate::web::{ApiError, ObjectNotFound};
|
||||||
use axum::extract::{Path, Query, State};
|
use axum::extract::{Path, Query, State};
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
|
use magnetar_calckey_model::ck;
|
||||||
use magnetar_sdk::endpoints::user::{GetUserById, GetUserSelf, UserByIdReq, UserSelfReq};
|
use magnetar_sdk::endpoints::user::{GetUserById, GetUserSelf, UserByIdReq, UserSelfReq};
|
||||||
use magnetar_sdk::endpoints::{Req, Res};
|
use magnetar_sdk::endpoints::{Req, Res};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
|
@ -29,7 +29,7 @@ impl PackType<&ck::note_reaction::Model> for ReactionBase {
|
||||||
pub struct NoteBaseSource<'a> {
|
pub struct NoteBaseSource<'a> {
|
||||||
pub note: &'a ck::note::Model,
|
pub note: &'a ck::note::Model,
|
||||||
pub cw_mm: Option<&'a MmXml>,
|
pub cw_mm: Option<&'a MmXml>,
|
||||||
pub text_mm: Option<&'a MmXml>,
|
pub text_mm: &'a MmXml,
|
||||||
pub reactions: &'a Vec<PackReactionBase>,
|
pub reactions: &'a Vec<PackReactionBase>,
|
||||||
pub user: &'a UserBase,
|
pub user: &'a UserBase,
|
||||||
pub emoji_context: &'a EmojiContext,
|
pub emoji_context: &'a EmojiContext,
|
||||||
|
@ -55,7 +55,7 @@ impl PackType<NoteBaseSource<'_>> for NoteBase {
|
||||||
uri: note.uri.clone(),
|
uri: note.uri.clone(),
|
||||||
url: note.url.clone(),
|
url: note.url.clone(),
|
||||||
text: note.text.clone().unwrap_or_default(),
|
text: note.text.clone().unwrap_or_default(),
|
||||||
text_mm: text_mm.map(ToOwned::to_owned).clone(),
|
text_mm: text_mm.clone(),
|
||||||
visibility: match note.visibility {
|
visibility: match note.visibility {
|
||||||
NVE::Followers => NoteVisibility::Followers,
|
NVE::Followers => NoteVisibility::Followers,
|
||||||
NVE::Hidden => NoteVisibility::Direct,
|
NVE::Hidden => NoteVisibility::Direct,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use magnetar_calckey_model::ck;
|
use magnetar_calckey_model::ck;
|
||||||
use magnetar_calckey_model::ck::sea_orm_active_enums::UserProfileFfvisibilityEnum;
|
use magnetar_calckey_model::ck::sea_orm_active_enums::UserProfileFfvisibilityEnum;
|
||||||
use magnetar_sdk::types::emoji::{EmojiContext, PackEmojiBase};
|
use magnetar_sdk::types::emoji::{EmojiContext, PackEmojiBase};
|
||||||
use magnetar_sdk::types::note::PackNoteMaybeFull;
|
use magnetar_sdk::types::note::PackNoteFull;
|
||||||
use magnetar_sdk::types::user::{
|
use magnetar_sdk::types::user::{
|
||||||
AvatarDecoration, PackSecurityKeyBase, ProfileField, SecurityKeyBase, SpeechTransform,
|
AvatarDecoration, PackSecurityKeyBase, ProfileField, SecurityKeyBase, SpeechTransform,
|
||||||
UserBase, UserDetailExt, UserProfileExt, UserProfilePinsEx, UserRelationExt, UserSecretsExt,
|
UserBase, UserDetailExt, UserProfileExt, UserProfilePinsEx, UserRelationExt, UserSecretsExt,
|
||||||
|
@ -187,8 +187,8 @@ impl PackType<UserRelationExtSource<'_>> for UserRelationExt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PackType<&[PackNoteMaybeFull]> for UserProfilePinsEx {
|
impl PackType<&[PackNoteFull]> for UserProfilePinsEx {
|
||||||
fn extract(_context: &PackingContext, pinned_notes: &[PackNoteMaybeFull]) -> Self {
|
fn extract(_context: &PackingContext, pinned_notes: &[PackNoteFull]) -> Self {
|
||||||
UserProfilePinsEx {
|
UserProfilePinsEx {
|
||||||
pinned_notes: pinned_notes.to_owned(),
|
pinned_notes: pinned_notes.to_owned(),
|
||||||
}
|
}
|
||||||
|
|
100
src/model/mod.rs
100
src/model/mod.rs
|
@ -1,11 +1,7 @@
|
||||||
use crate::model::data::id::BaseId;
|
|
||||||
use crate::model::processing::PackResult;
|
use crate::model::processing::PackResult;
|
||||||
use crate::service::MagnetarService;
|
use crate::service::MagnetarService;
|
||||||
use either::Either;
|
use magnetar_calckey_model::ck;
|
||||||
use magnetar_calckey_model::{ck, CalckeyDbError};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod processing;
|
pub mod processing;
|
||||||
|
@ -21,28 +17,12 @@ 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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PackingContext {
|
pub struct PackingContext {
|
||||||
instance_meta: Arc<ck::meta::Model>,
|
instance_meta: Arc<ck::meta::Model>,
|
||||||
self_user: Option<Arc<ck::user::Model>>,
|
self_user: Option<Arc<ck::user::Model>>,
|
||||||
service: Arc<MagnetarService>,
|
service: Arc<MagnetarService>,
|
||||||
limits: ProcessingLimits,
|
limits: ProcessingLimits,
|
||||||
relationships: Arc<Mutex<HashMap<UserRelationshipLink, bool>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PackType<I>: 'static {
|
pub trait PackType<I>: 'static {
|
||||||
|
@ -59,7 +39,6 @@ impl PackingContext {
|
||||||
self_user,
|
self_user,
|
||||||
service,
|
service,
|
||||||
limits: Default::default(),
|
limits: Default::default(),
|
||||||
relationships: Arc::new(Mutex::new(HashMap::new())),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,81 +46,8 @@ impl PackingContext {
|
||||||
self.self_user.as_deref()
|
self.self_user.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_id_self(&self, user_id: &str) -> bool {
|
|
||||||
self.self_user()
|
|
||||||
.is_some_and(|self_user| self_user.id == user_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_self(&self, user: &ck::user::Model) -> bool {
|
fn is_self(&self, user: &ck::user::Model) -> bool {
|
||||||
self.is_id_self(&user.id)
|
self.self_user()
|
||||||
}
|
.is_some_and(|self_user| self_user.id == 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,7 +8,6 @@ use thiserror::Error;
|
||||||
|
|
||||||
pub mod drive;
|
pub mod drive;
|
||||||
pub mod emoji;
|
pub mod emoji;
|
||||||
pub mod note;
|
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
#[derive(Debug, Error, strum::IntoStaticStr)]
|
#[derive(Debug, Error, strum::IntoStaticStr)]
|
||||||
|
|
|
@ -1,259 +0,0 @@
|
||||||
use crate::model::data::id::BaseId;
|
|
||||||
use crate::model::data::note::NoteBaseSource;
|
|
||||||
use crate::model::processing::emoji::EmojiModel;
|
|
||||||
use crate::model::processing::user::UserModel;
|
|
||||||
use crate::model::processing::{get_mm_token_emoji, PackResult};
|
|
||||||
use crate::model::{PackType, PackingContext, UserRelationship};
|
|
||||||
use compact_str::CompactString;
|
|
||||||
use either::Either;
|
|
||||||
use magnetar_calckey_model::ck::sea_orm_active_enums::NoteVisibilityEnum;
|
|
||||||
use magnetar_calckey_model::note_model::{NoteResolveOptions, 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::{ActiveEnum, ColumnTrait, IntoSimpleExpr};
|
|
||||||
use magnetar_calckey_model::{ck, CalckeyDbError};
|
|
||||||
use magnetar_sdk::mmm::Token;
|
|
||||||
use magnetar_sdk::types::emoji::EmojiContext;
|
|
||||||
use magnetar_sdk::types::note::{NoteBase, PackNoteMaybeFull};
|
|
||||||
use magnetar_sdk::types::{Id, MmXml};
|
|
||||||
use magnetar_sdk::{mmm, Packed, Required};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct NoteVisibilityFilterSimple(Option<String>);
|
|
||||||
|
|
||||||
impl NoteVisibilityFilterFactory for NoteVisibilityFilterSimple {
|
|
||||||
fn with_note_and_user_tables(&self, note_tbl: Option<Alias>) -> SimpleExpr {
|
|
||||||
let note_tbl_name =
|
|
||||||
note_tbl.map_or_else(|| ck::note::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((note_tbl_name, ck::note::Column::UserId));
|
|
||||||
|
|
||||||
let is_public = note_visibility
|
|
||||||
.clone()
|
|
||||||
.eq(NoteVisibilityEnum::Public.as_enum())
|
|
||||||
.or(note_visibility
|
|
||||||
.clone()
|
|
||||||
.eq(NoteVisibilityEnum::Home.as_enum()));
|
|
||||||
|
|
||||||
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.as_enum())
|
|
||||||
.or(note_visibility
|
|
||||||
.clone()
|
|
||||||
.eq(NoteVisibilityEnum::Followers.as_enum()))
|
|
||||||
.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.as_enum())
|
|
||||||
.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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SpeechTransformNyan;
|
|
||||||
|
|
||||||
impl SpeechTransformNyan {
|
|
||||||
fn new() -> Self {
|
|
||||||
SpeechTransformNyan
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transform(&self, text: &mut CompactString) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NoteModel;
|
|
||||||
|
|
||||||
impl NoteModel {
|
|
||||||
pub fn tokenize_note_text(&self, note: &ck::note::Model) -> Option<Token> {
|
|
||||||
note.text
|
|
||||||
.as_deref()
|
|
||||||
.map(|text| mmm::Context::default().parse_full(text))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tokenize_note_cw(&self, note: &ck::note::Model) -> Option<Token> {
|
|
||||||
note.cw
|
|
||||||
.as_deref()
|
|
||||||
.map(|text| mmm::Context::default().parse_ui(text))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn fetch_single(
|
|
||||||
&self,
|
|
||||||
ctx: &PackingContext,
|
|
||||||
as_user: Option<&ck::user::Model>,
|
|
||||||
id: &str,
|
|
||||||
show_context: bool,
|
|
||||||
attachments: bool,
|
|
||||||
) -> PackResult<Option<PackNoteMaybeFull>> {
|
|
||||||
let note_resolver = ctx.service.db.get_note_resolver();
|
|
||||||
let Some(note) = note_resolver
|
|
||||||
.get_one(&NoteResolveOptions {
|
|
||||||
ids: Some(vec![id.to_owned()]),
|
|
||||||
visibility_filter: Box::new(
|
|
||||||
NoteVisibilityFilterModel
|
|
||||||
.new_note_visibility_filter(as_user.map(ck::user::Model::get_id)),
|
|
||||||
),
|
|
||||||
time_range: None,
|
|
||||||
with_user: show_context,
|
|
||||||
with_reply_target: show_context,
|
|
||||||
with_renote_target: show_context,
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
|
|
||||||
let Required(ref user) = UserModel.base_from_existing(ctx, ¬e.user).await?.user;
|
|
||||||
|
|
||||||
let cw_tok = self.tokenize_note_cw(¬e.note);
|
|
||||||
let mut text_tok = self.tokenize_note_text(¬e.note);
|
|
||||||
|
|
||||||
let mut emoji_extracted = Vec::new();
|
|
||||||
|
|
||||||
if let Some(cw_tok) = &cw_tok {
|
|
||||||
emoji_extracted.extend_from_slice(&get_mm_token_emoji(cw_tok));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(text_tok) = &mut text_tok {
|
|
||||||
emoji_extracted.extend_from_slice(&get_mm_token_emoji(text_tok));
|
|
||||||
|
|
||||||
if note.user.is_cat && note.user.speak_as_cat {
|
|
||||||
let transformer = SpeechTransformNyan::new();
|
|
||||||
text_tok.walk_speech_transform(&|text| transformer.transform(text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let emoji_model = EmojiModel;
|
|
||||||
let shortcodes = emoji_model.deduplicate_emoji(ctx, emoji_extracted);
|
|
||||||
let emojis = emoji_model
|
|
||||||
.fetch_many_emojis(ctx, &shortcodes, note.user.host.as_deref())
|
|
||||||
.await?;
|
|
||||||
let emoji_context = &EmojiContext(emojis);
|
|
||||||
|
|
||||||
// TODO: Polls, reactions, attachments, ...
|
|
||||||
|
|
||||||
let note_base = NoteBase::extract(
|
|
||||||
ctx,
|
|
||||||
NoteBaseSource {
|
|
||||||
note: ¬e.note,
|
|
||||||
cw_mm: cw_tok
|
|
||||||
.as_ref()
|
|
||||||
.map(mmm::to_xml_string)
|
|
||||||
.and_then(Result::ok)
|
|
||||||
.map(MmXml)
|
|
||||||
.as_ref(),
|
|
||||||
text_mm: text_tok
|
|
||||||
.as_ref()
|
|
||||||
.map(mmm::to_xml_string)
|
|
||||||
.and_then(Result::ok)
|
|
||||||
.map(MmXml)
|
|
||||||
.as_ref(),
|
|
||||||
reactions: &vec![],
|
|
||||||
user,
|
|
||||||
emoji_context,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Some(PackNoteMaybeFull::pack_from((
|
|
||||||
Required(Id::from(note.note.id.clone())),
|
|
||||||
Required(note_base),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
))))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,11 +3,13 @@ use crate::model::processing::emoji::EmojiModel;
|
||||||
use crate::model::processing::{get_mm_token_emoji, PackResult};
|
use crate::model::processing::{get_mm_token_emoji, PackResult};
|
||||||
use crate::model::{PackType, PackingContext};
|
use crate::model::{PackType, PackingContext};
|
||||||
use magnetar_calckey_model::ck;
|
use magnetar_calckey_model::ck;
|
||||||
|
use magnetar_calckey_model::sea_orm::EntityTrait;
|
||||||
use magnetar_sdk::mmm::Token;
|
use magnetar_sdk::mmm::Token;
|
||||||
use magnetar_sdk::types::emoji::EmojiContext;
|
use magnetar_sdk::types::emoji::EmojiContext;
|
||||||
use magnetar_sdk::types::user::{PackUserBase, UserBase};
|
use magnetar_sdk::types::user::{PackUserBase, UserBase};
|
||||||
use magnetar_sdk::types::{Id, MmXml};
|
use magnetar_sdk::types::{Id, MmXml};
|
||||||
use magnetar_sdk::{mmm, Packed, Required};
|
use magnetar_sdk::{mmm, Packed, Required};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct UserModel;
|
pub struct UserModel;
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,13 @@ use thiserror::Error;
|
||||||
pub mod emoji_cache;
|
pub mod emoji_cache;
|
||||||
pub mod generic_id_cache;
|
pub mod generic_id_cache;
|
||||||
pub mod instance_meta_cache;
|
pub mod instance_meta_cache;
|
||||||
pub mod local_user_cache;
|
pub mod user_cache;
|
||||||
|
|
||||||
pub struct MagnetarService {
|
pub struct MagnetarService {
|
||||||
pub db: CalckeyModel,
|
pub db: CalckeyModel,
|
||||||
pub cache: CalckeyCache,
|
pub cache: CalckeyCache,
|
||||||
pub config: &'static MagnetarConfig,
|
pub config: &'static MagnetarConfig,
|
||||||
pub local_user_cache: local_user_cache::LocalUserCacheService,
|
pub auth_cache: user_cache::UserCacheService,
|
||||||
pub instance_meta_cache: instance_meta_cache::InstanceMetaCacheService,
|
pub instance_meta_cache: instance_meta_cache::InstanceMetaCacheService,
|
||||||
pub emoji_cache: emoji_cache::EmojiCacheService,
|
pub emoji_cache: emoji_cache::EmojiCacheService,
|
||||||
pub drive_file_cache: generic_id_cache::GenericIdCacheService<ck::drive_file::Entity>,
|
pub drive_file_cache: generic_id_cache::GenericIdCacheService<ck::drive_file::Entity>,
|
||||||
|
@ -32,7 +32,7 @@ impl Debug for MagnetarService {
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ServiceInitError {
|
pub enum ServiceInitError {
|
||||||
#[error("Authentication cache initialization error: {0}")]
|
#[error("Authentication cache initialization error: {0}")]
|
||||||
AuthCacheError(#[from] local_user_cache::UserCacheError),
|
AuthCacheError(#[from] user_cache::UserCacheError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MagnetarService {
|
impl MagnetarService {
|
||||||
|
@ -41,8 +41,8 @@ impl MagnetarService {
|
||||||
db: CalckeyModel,
|
db: CalckeyModel,
|
||||||
cache: CalckeyCache,
|
cache: CalckeyCache,
|
||||||
) -> Result<Self, ServiceInitError> {
|
) -> Result<Self, ServiceInitError> {
|
||||||
let local_user_cache =
|
let auth_cache =
|
||||||
local_user_cache::LocalUserCacheService::new(config, db.clone(), cache.clone()).await?;
|
user_cache::UserCacheService::new(config, db.clone(), cache.clone()).await?;
|
||||||
let instance_meta_cache = instance_meta_cache::InstanceMetaCacheService::new(db.clone());
|
let instance_meta_cache = instance_meta_cache::InstanceMetaCacheService::new(db.clone());
|
||||||
let emoji_cache = emoji_cache::EmojiCacheService::new(db.clone());
|
let emoji_cache = emoji_cache::EmojiCacheService::new(db.clone());
|
||||||
let drive_file_cache =
|
let drive_file_cache =
|
||||||
|
@ -52,7 +52,7 @@ impl MagnetarService {
|
||||||
db,
|
db,
|
||||||
cache,
|
cache,
|
||||||
config,
|
config,
|
||||||
local_user_cache,
|
auth_cache,
|
||||||
instance_meta_cache,
|
instance_meta_cache,
|
||||||
emoji_cache,
|
emoji_cache,
|
||||||
drive_file_cache,
|
drive_file_cache,
|
||||||
|
|
|
@ -27,20 +27,20 @@ impl From<UserCacheError> for ApiError {
|
||||||
UserCacheError::RedisError(err) => err.into(),
|
UserCacheError::RedisError(err) => err.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
api_error.message = format!("Local user cache error: {}", api_error.message);
|
api_error.message = format!("User cache error: {}", api_error.message);
|
||||||
|
|
||||||
api_error
|
api_error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LocalUserCache {
|
struct UserCache {
|
||||||
lifetime: TimedCache<String, ()>,
|
lifetime: TimedCache<String, ()>,
|
||||||
id_to_user: HashMap<String, Arc<ck::user::Model>>,
|
id_to_user: HashMap<String, Arc<ck::user::Model>>,
|
||||||
token_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>>,
|
uri_to_user: HashMap<String, Arc<ck::user::Model>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LocalUserCache {
|
impl UserCache {
|
||||||
fn purge(&mut self, user: impl AsRef<ck::user::Model>) {
|
fn purge(&mut self, user: impl AsRef<ck::user::Model>) {
|
||||||
let user = user.as_ref();
|
let user = user.as_ref();
|
||||||
|
|
||||||
|
@ -120,20 +120,20 @@ impl LocalUserCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LocalUserCacheService {
|
pub struct UserCacheService {
|
||||||
db: CalckeyModel,
|
db: CalckeyModel,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
token_watch: CalckeySub,
|
token_watch: CalckeySub,
|
||||||
cache: Arc<Mutex<LocalUserCache>>,
|
cache: Arc<Mutex<UserCache>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LocalUserCacheService {
|
impl UserCacheService {
|
||||||
pub(super) async fn new(
|
pub(super) async fn new(
|
||||||
config: &MagnetarConfig,
|
config: &MagnetarConfig,
|
||||||
db: CalckeyModel,
|
db: CalckeyModel,
|
||||||
redis: CalckeyCache,
|
redis: CalckeyCache,
|
||||||
) -> Result<Self, UserCacheError> {
|
) -> Result<Self, UserCacheError> {
|
||||||
let cache = Arc::new(Mutex::new(LocalUserCache {
|
let cache = Arc::new(Mutex::new(UserCache {
|
||||||
lifetime: TimedCache::with_lifespan(60 * 5),
|
lifetime: TimedCache::with_lifespan(60 * 5),
|
||||||
id_to_user: HashMap::new(),
|
id_to_user: HashMap::new(),
|
||||||
token_to_user: HashMap::new(),
|
token_to_user: HashMap::new(),
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::service::local_user_cache::UserCacheError;
|
use crate::service::user_cache::UserCacheError;
|
||||||
use crate::service::MagnetarService;
|
use crate::service::MagnetarService;
|
||||||
use crate::web::{ApiError, IntoErrorCode};
|
use crate::web::{ApiError, IntoErrorCode};
|
||||||
use axum::async_trait;
|
use axum::async_trait;
|
||||||
|
@ -175,7 +175,7 @@ impl AuthState {
|
||||||
let token = token.token();
|
let token = token.token();
|
||||||
|
|
||||||
if is_user_token(token) {
|
if is_user_token(token) {
|
||||||
let user_cache = &self.service.local_user_cache;
|
let user_cache = &self.service.auth_cache;
|
||||||
let user = user_cache.get_by_token(token).await?;
|
let user = user_cache.get_by_token(token).await?;
|
||||||
|
|
||||||
if let Some(user) = user {
|
if let Some(user) = user {
|
||||||
|
@ -194,7 +194,7 @@ impl AuthState {
|
||||||
|
|
||||||
let user = self
|
let user = self
|
||||||
.service
|
.service
|
||||||
.local_user_cache
|
.auth_cache
|
||||||
.get_by_id(&access_token.user_id)
|
.get_by_id(&access_token.user_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue