Basic note fetching
This commit is contained in:
parent
f0e56deca9
commit
3cd43d840a
|
@ -1453,6 +1453,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
"either",
|
||||||
"headers",
|
"headers",
|
||||||
"hyper",
|
"hyper",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
|
@ -1513,6 +1514,8 @@ 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",
|
||||||
|
@ -2754,9 +2757,9 @@ checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smawk"
|
name = "smawk"
|
||||||
version = "0.3.1"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
|
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
|
@ -3073,9 +3076,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "supports-color"
|
name = "supports-color"
|
||||||
version = "2.0.0"
|
version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354"
|
checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"is-terminal",
|
"is-terminal",
|
||||||
"is_ci",
|
"is_ci",
|
||||||
|
@ -3621,13 +3624,9 @@ checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-linebreak"
|
name = "unicode-linebreak"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
|
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||||
dependencies = [
|
|
||||||
"hashbrown 0.12.3",
|
|
||||||
"regex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
|
|
|
@ -99,6 +99,7 @@ cfg-if = { workspace = true }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
itertools = { 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"] }
|
||||||
|
|
|
@ -11,6 +11,7 @@ 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 }
|
||||||
|
@ -24,4 +25,5 @@ serde_json = { workspace = true }
|
||||||
strum = { workspace = true }
|
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,3 +1,5 @@
|
||||||
|
pub mod note_model;
|
||||||
|
|
||||||
pub use ck;
|
pub use ck;
|
||||||
use ck::*;
|
use ck::*;
|
||||||
pub use sea_orm;
|
pub use sea_orm;
|
||||||
|
@ -6,10 +8,11 @@ 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, QueryFilter, QueryOrder,
|
ColumnTrait, ConnectOptions, DatabaseConnection, DbErr, EntityTrait, JoinType, QueryFilter,
|
||||||
TransactionTrait,
|
QueryOrder, RelationDef, RelationTrait, TransactionTrait,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
@ -34,6 +37,18 @@ 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)
|
||||||
|
@ -116,6 +131,66 @@ 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())
|
||||||
|
|
|
@ -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 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;
|
||||||
|
|
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::model::processing::PackResult;
|
||||||
use crate::service::MagnetarService;
|
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 std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod processing;
|
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)]
|
#[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 {
|
||||||
|
@ -39,6 +59,7 @@ impl PackingContext {
|
||||||
self_user,
|
self_user,
|
||||||
service,
|
service,
|
||||||
limits: Default::default(),
|
limits: Default::default(),
|
||||||
|
relationships: Arc::new(Mutex::new(HashMap::new())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,8 +67,81 @@ impl PackingContext {
|
||||||
self.self_user.as_deref()
|
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()
|
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 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)]
|
||||||
|
|
|
@ -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::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;
|
||||||
|
|
||||||
|
|
|
@ -27,20 +27,20 @@ impl From<UserCacheError> for ApiError {
|
||||||
UserCacheError::RedisError(err) => err.into(),
|
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
|
api_error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UserCache {
|
struct LocalUserCache {
|
||||||
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 UserCache {
|
impl LocalUserCache {
|
||||||
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 UserCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UserCacheService {
|
pub struct LocalUserCacheService {
|
||||||
db: CalckeyModel,
|
db: CalckeyModel,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
token_watch: CalckeySub,
|
token_watch: CalckeySub,
|
||||||
cache: Arc<Mutex<UserCache>>,
|
cache: Arc<Mutex<LocalUserCache>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserCacheService {
|
impl LocalUserCacheService {
|
||||||
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(UserCache {
|
let cache = Arc::new(Mutex::new(LocalUserCache {
|
||||||
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(),
|
|
@ -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 user_cache;
|
pub mod local_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 auth_cache: user_cache::UserCacheService,
|
pub local_user_cache: local_user_cache::LocalUserCacheService,
|
||||||
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] user_cache::UserCacheError),
|
AuthCacheError(#[from] local_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 auth_cache =
|
let local_user_cache =
|
||||||
user_cache::UserCacheService::new(config, db.clone(), cache.clone()).await?;
|
local_user_cache::LocalUserCacheService::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,
|
||||||
auth_cache,
|
local_user_cache,
|
||||||
instance_meta_cache,
|
instance_meta_cache,
|
||||||
emoji_cache,
|
emoji_cache,
|
||||||
drive_file_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::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.auth_cache;
|
let user_cache = &self.service.local_user_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
|
||||||
.auth_cache
|
.local_user_cache
|
||||||
.get_by_id(&access_token.user_id)
|
.get_by_id(&access_token.user_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue