Refactored note fetching
ci/woodpecker/push/ociImagePush Pipeline failed
Details
ci/woodpecker/push/ociImagePush Pipeline failed
Details
This commit is contained in:
parent
074c6f999e
commit
deb7f6ef5e
|
@ -1507,7 +1507,6 @@ dependencies = [
|
|||
"hyper",
|
||||
"idna 0.5.0",
|
||||
"itertools 0.12.0",
|
||||
"lazy_static",
|
||||
"lru",
|
||||
"magnetar_calckey_model",
|
||||
"magnetar_common",
|
||||
|
@ -1568,7 +1567,6 @@ dependencies = [
|
|||
"ext_calckey_model_migration",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"lazy_static",
|
||||
"magnetar_common",
|
||||
"magnetar_sdk",
|
||||
"once_cell",
|
||||
|
|
|
@ -41,7 +41,6 @@ http = "1.0"
|
|||
hyper = "1.1"
|
||||
idna = "0.5"
|
||||
itertools = "0.12"
|
||||
lazy_static = "1.4"
|
||||
lru = "0.12"
|
||||
miette = "5.9"
|
||||
nom = "7"
|
||||
|
@ -108,7 +107,6 @@ either = { workspace = true }
|
|||
futures = { workspace = true }
|
||||
futures-util = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
miette = { workspace = true, features = ["fancy"] }
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM docker.io/rust:1.74-bookworm as build
|
||||
FROM docker.io/rust:1.75-bookworm as build
|
||||
|
||||
RUN update-ca-certificates
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ magnetar_sdk = { path = "../magnetar_sdk" }
|
|||
dotenvy = { workspace = true}
|
||||
futures-core = { workspace = true }
|
||||
futures-util = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tokio-util = { workspace = true}
|
||||
redis = { workspace = true, features = ["tokio-comp", "json", "serde_json"]}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
pub mod emoji;
|
||||
pub mod model_ext;
|
||||
pub mod note_model;
|
||||
pub mod paginated;
|
||||
pub mod poll;
|
||||
pub mod user_model;
|
||||
|
||||
pub use ck;
|
||||
use ck::*;
|
||||
pub use sea_orm;
|
||||
use user_model::UserResolver;
|
||||
|
||||
use crate::note_model::NoteResolver;
|
||||
use chrono::Utc;
|
||||
|
@ -307,7 +311,11 @@ impl CalckeyModel {
|
|||
}
|
||||
|
||||
pub fn get_note_resolver(&self) -> NoteResolver {
|
||||
NoteResolver::new(self.clone())
|
||||
NoteResolver::new(self.clone(), self.get_user_resolver())
|
||||
}
|
||||
|
||||
pub fn get_user_resolver(&self) -> UserResolver {
|
||||
UserResolver::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
use ext_calckey_model_migration::{Alias, Expr, IntoIden, SelectExpr, SelectStatement, TableRef};
|
||||
use sea_orm::{ColumnTrait, Condition, EntityTrait, Iden, Iterable, JoinType, RelationDef};
|
||||
|
||||
pub(crate) trait SelectColumnsExt {
|
||||
fn add_aliased_columns<T: EntityTrait>(&mut self, alias: &str) -> &mut Self;
|
||||
|
||||
fn join_columns<I: IntoIden>(
|
||||
&mut self,
|
||||
join: JoinType,
|
||||
rel: RelationDef,
|
||||
alias: I,
|
||||
) -> &mut Self;
|
||||
}
|
||||
|
||||
pub(crate) fn join_columns_default(rel: RelationDef) -> Condition {
|
||||
let tbl_id = |tbl: &TableRef| {
|
||||
match tbl {
|
||||
TableRef::Table(id) => id,
|
||||
TableRef::TableAlias(_, id) => id,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.clone()
|
||||
};
|
||||
|
||||
let mut cond = Condition::all();
|
||||
for (owner_key, foreign_key) in rel.from_col.into_iter().zip(rel.to_col.into_iter()) {
|
||||
cond = cond.add(
|
||||
Expr::col((tbl_id(&rel.from_tbl), owner_key))
|
||||
.equals((tbl_id(&rel.to_tbl), foreign_key)),
|
||||
);
|
||||
}
|
||||
cond
|
||||
}
|
||||
|
||||
impl SelectColumnsExt for SelectStatement {
|
||||
fn add_aliased_columns<T: EntityTrait>(&mut self, iden: &str) -> &mut Self {
|
||||
for col in T::Column::iter() {
|
||||
let column: &T::Column = &col;
|
||||
|
||||
let alias = format!("{}{}", iden, col.to_string());
|
||||
|
||||
let column_ref = Expr::col((Alias::new(iden), column.as_column_ref().1));
|
||||
|
||||
self.expr(SelectExpr {
|
||||
expr: col.select_as(column_ref),
|
||||
alias: Some(Alias::new(&alias).into_iden()),
|
||||
window: None,
|
||||
});
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn join_columns<I: IntoIden>(
|
||||
&mut self,
|
||||
join: JoinType,
|
||||
mut rel: RelationDef,
|
||||
alias: I,
|
||||
) -> &mut Self {
|
||||
let alias = alias.into_iden();
|
||||
rel.to_tbl = rel.to_tbl.alias(alias);
|
||||
self.join(join, rel.to_tbl.clone(), join_columns_default(rel));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn joined_prefix_str(prefix: &str, suffix: &str) -> String {
|
||||
format!("{prefix}{suffix}")
|
||||
}
|
||||
|
||||
pub(crate) fn joined_prefix_alias(prefix: &str, suffix: &str) -> Alias {
|
||||
Alias::new(joined_prefix_str(prefix, suffix))
|
||||
}
|
||||
|
||||
pub(crate) fn joined_prefix(prefix: &str, suffix: &str) -> impl IntoIden {
|
||||
joined_prefix_alias(prefix, suffix).into_iden()
|
||||
}
|
||||
|
||||
pub(crate) trait EntityPrefixExt {
|
||||
fn base_prefix_str(&self) -> String;
|
||||
fn base_prefix(&self) -> impl IntoIden;
|
||||
}
|
||||
|
||||
impl<T: EntityTrait> EntityPrefixExt for T {
|
||||
fn base_prefix_str(&self) -> String {
|
||||
format!("{}.", self.table_name())
|
||||
}
|
||||
|
||||
fn base_prefix(&self) -> impl IntoIden {
|
||||
Alias::new(self.base_prefix_str()).into_iden()
|
||||
}
|
||||
}
|
|
@ -1,16 +1,21 @@
|
|||
use lazy_static::lazy_static;
|
||||
use ext_calckey_model_migration::SelectStatement;
|
||||
use sea_orm::sea_query::{
|
||||
Alias, Asterisk, Expr, IntoCondition, IntoIden, Query, SelectExpr, SimpleExpr,
|
||||
};
|
||||
use sea_orm::{
|
||||
ColumnTrait, DbErr, EntityName, EntityTrait, FromQueryResult, Iden, Iterable, JoinType,
|
||||
QueryFilter, QueryOrder, QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
|
||||
DbErr, EntityName, EntityTrait, FromQueryResult, Iden, JoinType, QueryFilter, QueryOrder,
|
||||
QueryResult, QuerySelect, QueryTrait, Select,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use ck::{drive_file, note, note_reaction, user, user_note_pining};
|
||||
use magnetar_sdk::types::{EndFilter, RangeFilter, SpanFilter, StartFilter};
|
||||
use ck::{note, note_reaction, user, user_note_pining};
|
||||
use magnetar_sdk::types::SpanFilter;
|
||||
|
||||
use crate::model_ext::{
|
||||
joined_prefix, joined_prefix_alias, joined_prefix_str, EntityPrefixExt, SelectColumnsExt,
|
||||
};
|
||||
use crate::paginated::PaginatedModel;
|
||||
use crate::user_model::{UserData, UserResolver};
|
||||
use crate::{AliasSourceExt, CalckeyDbError, CalckeyModel};
|
||||
|
||||
pub mod sub_interaction_renote {
|
||||
|
@ -48,9 +53,7 @@ pub struct NoteData {
|
|||
pub note: note::Model,
|
||||
pub interaction_user_renote: Option<sub_interaction_renote::Model>,
|
||||
pub interaction_user_reaction: Option<sub_interaction_reaction::Model>,
|
||||
pub user: user::Model,
|
||||
pub avatar: Option<drive_file::Model>,
|
||||
pub banner: Option<drive_file::Model>,
|
||||
pub user: UserData,
|
||||
pub reply: Option<Box<NoteData>>,
|
||||
pub renote: Option<Box<NoteData>>,
|
||||
}
|
||||
|
@ -59,125 +62,36 @@ const PIN: &str = "pin.";
|
|||
const INTERACTION_REACTION: &str = "interaction.reaction.";
|
||||
const INTERACTION_RENOTE: &str = "interaction.renote.";
|
||||
const USER: &str = "user.";
|
||||
const USER_AVATAR: &str = "user.avatar.";
|
||||
const USER_BANNER: &str = "user.banner.";
|
||||
const REPLY: &str = "reply.";
|
||||
const REPLY_INTERACTION_REACTION: &str = "reply.interaction.reaction.";
|
||||
const REPLY_INTERACTION_RENOTE: &str = "reply.interaction.renote.";
|
||||
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_INTERACTION_REACTION: &str = "renote.interaction.reaction.";
|
||||
const RENOTE_INTERACTION_RENOTE: &str = "renote.interaction.renote.";
|
||||
const RENOTE_USER: &str = "renote.user.";
|
||||
const RENOTE_USER_AVATAR: &str = "renote.user.avatar.";
|
||||
const RENOTE_USER_BANNER: &str = "renote.user.banner.";
|
||||
const RENOTE_REPLY: &str = "renote.reply.";
|
||||
const RENOTE_REPLY_INTERACTION_REACTION: &str = "renote.reply.interaction.reaction.";
|
||||
const RENOTE_REPLY_INTERACTION_RENOTE: &str = "renote.reply.interaction.renote.";
|
||||
const RENOTE_REPLY_USER: &str = "renote.reply.user.";
|
||||
const RENOTE_REPLY_USER_AVATAR: &str = "renote.reply.user.avatar.";
|
||||
const RENOTE_REPLY_USER_BANNER: &str = "renote.reply.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,
|
||||
interaction_user_renote:
|
||||
sub_interaction_renote::Model::from_query_result_optional(
|
||||
res,
|
||||
REPLY_INTERACTION_RENOTE,
|
||||
)?,
|
||||
interaction_user_reaction:
|
||||
sub_interaction_reaction::Model::from_query_result_optional(
|
||||
res,
|
||||
REPLY_INTERACTION_REACTION,
|
||||
)?,
|
||||
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_reply = note::Model::from_query_result_optional(res, RENOTE_REPLY)?
|
||||
.map::<Result<_, DbErr>, _>(|r| {
|
||||
Ok(Box::new(NoteData {
|
||||
note: r,
|
||||
interaction_user_renote:
|
||||
sub_interaction_renote::Model::from_query_result_optional(
|
||||
res,
|
||||
RENOTE_REPLY_INTERACTION_RENOTE,
|
||||
)?,
|
||||
interaction_user_reaction:
|
||||
sub_interaction_reaction::Model::from_query_result_optional(
|
||||
res,
|
||||
RENOTE_REPLY_INTERACTION_REACTION,
|
||||
)?,
|
||||
user: user::Model::from_query_result(res, RENOTE_REPLY_USER)?,
|
||||
avatar: drive_file::Model::from_query_result_optional(
|
||||
res,
|
||||
RENOTE_REPLY_USER_AVATAR,
|
||||
)?,
|
||||
banner: drive_file::Model::from_query_result_optional(
|
||||
res,
|
||||
RENOTE_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,
|
||||
interaction_user_renote:
|
||||
sub_interaction_renote::Model::from_query_result_optional(
|
||||
res,
|
||||
RENOTE_INTERACTION_RENOTE,
|
||||
)?,
|
||||
interaction_user_reaction:
|
||||
sub_interaction_reaction::Model::from_query_result_optional(
|
||||
res,
|
||||
RENOTE_INTERACTION_REACTION,
|
||||
)?,
|
||||
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: renote_reply,
|
||||
renote: None,
|
||||
}))
|
||||
})
|
||||
.transpose()?;
|
||||
fn from_query_result(res: &QueryResult, prefix: &str) -> Result<Self, DbErr> {
|
||||
let fallback = format!("{}.", note::Entity.table_name());
|
||||
let prefix = if prefix.is_empty() { &fallback } else { prefix };
|
||||
|
||||
Ok(NoteData {
|
||||
note: note::Model::from_query_result(res, "")?,
|
||||
note: note::Model::from_query_result(res, prefix)?,
|
||||
interaction_user_renote: sub_interaction_renote::Model::from_query_result_optional(
|
||||
res,
|
||||
INTERACTION_RENOTE,
|
||||
&joined_prefix_str(prefix, INTERACTION_RENOTE),
|
||||
)?,
|
||||
interaction_user_reaction: sub_interaction_reaction::Model::from_query_result_optional(
|
||||
res,
|
||||
INTERACTION_REACTION,
|
||||
&joined_prefix_str(prefix, INTERACTION_REACTION),
|
||||
)?,
|
||||
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,
|
||||
user: UserData::from_query_result(res, &joined_prefix_str(prefix, USER))?,
|
||||
reply: NoteData::from_query_result_optional(res, &joined_prefix_str(prefix, REPLY))?
|
||||
.map(Box::new),
|
||||
renote: NoteData::from_query_result_optional(res, &joined_prefix_str(prefix, RENOTE))?
|
||||
.map(Box::new),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NoteResolver {
|
||||
db: CalckeyModel,
|
||||
user_resolver: UserResolver,
|
||||
}
|
||||
|
||||
pub trait NoteVisibilityFilterFactory: Send + Sync {
|
||||
|
@ -196,55 +110,29 @@ pub struct NoteResolveOptions {
|
|||
pub only_pins_from: Option<String>, // User ID
|
||||
}
|
||||
|
||||
trait SelectColumnsExt {
|
||||
fn add_aliased_columns<T: EntityTrait>(self, alias: Option<&str>, entity: T) -> Self;
|
||||
|
||||
fn add_sub_select_reaction(
|
||||
self,
|
||||
source_note_alias: Option<&str>,
|
||||
trait SelectNoteInteractionsExt {
|
||||
fn add_sub_select_interaction_reaction(
|
||||
&mut self,
|
||||
source_note_alias: &str,
|
||||
alias: &str,
|
||||
user_id: &str,
|
||||
) -> Select<note::Entity>;
|
||||
) -> &mut Self;
|
||||
|
||||
fn add_sub_select_renote(
|
||||
self,
|
||||
source_note_alias: Option<&str>,
|
||||
fn add_sub_select_interaction_renote(
|
||||
&mut self,
|
||||
source_note_alias: &str,
|
||||
alias: &str,
|
||||
user_id: &str,
|
||||
) -> Select<note::Entity>;
|
||||
) -> &mut 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
|
||||
}
|
||||
|
||||
fn add_sub_select_reaction(
|
||||
mut self: Select<note::Entity>,
|
||||
source_note_alias: Option<&str>,
|
||||
impl SelectNoteInteractionsExt for SelectStatement {
|
||||
fn add_sub_select_interaction_reaction(
|
||||
&mut self,
|
||||
iden: &str,
|
||||
prefix_alias: &str,
|
||||
user_id: &str,
|
||||
) -> Select<note::Entity> {
|
||||
let iden = source_note_alias.unwrap_or_else(|| note::Entity.table_name());
|
||||
) -> &mut Self {
|
||||
let note_id_col = Expr::col((Alias::new(iden), note::Column::Id));
|
||||
|
||||
let column = sub_interaction_reaction::Column::ReactionName;
|
||||
|
@ -262,21 +150,19 @@ impl SelectColumnsExt for Select<note::Entity> {
|
|||
.take()
|
||||
.into_sub_query_statement();
|
||||
|
||||
QuerySelect::query(&mut self).expr(SimpleExpr::SubQuery(None, Box::new(sub_query)));
|
||||
|
||||
self.expr(SimpleExpr::SubQuery(None, Box::new(sub_query)));
|
||||
self
|
||||
}
|
||||
|
||||
fn add_sub_select_renote(
|
||||
mut self: Select<note::Entity>,
|
||||
source_note_alias: Option<&str>,
|
||||
fn add_sub_select_interaction_renote(
|
||||
&mut self,
|
||||
iden: &str,
|
||||
prefix_alias: &str,
|
||||
user_id: &str,
|
||||
) -> Select<note::Entity> {
|
||||
let iden = source_note_alias.unwrap_or_else(|| note::Entity.table_name());
|
||||
) -> &mut Self {
|
||||
let note_id_col = Expr::col((Alias::new(iden), note::Column::Id));
|
||||
|
||||
let renote_note_tbl = Alias::new(format!("{}{}", prefix_alias, "note"));
|
||||
let renote_note_tbl = joined_prefix_alias(prefix_alias, "note");
|
||||
|
||||
let column = sub_interaction_renote::Column::Renotes;
|
||||
let alias = format!("{}{}", prefix_alias, column.to_string());
|
||||
|
@ -295,99 +181,24 @@ impl SelectColumnsExt for Select<note::Entity> {
|
|||
.take()
|
||||
.into_sub_query_statement();
|
||||
|
||||
QuerySelect::query(&mut self).expr(SimpleExpr::SubQuery(None, Box::new(sub_query)));
|
||||
|
||||
self.expr(SimpleExpr::SubQuery(None, Box::new(sub_query)));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref ALIAS_PIN: Alias = Alias::new(PIN);
|
||||
static ref ALIAS_INTERACTION_RENOTE: Alias = Alias::new(INTERACTION_RENOTE);
|
||||
static ref ALIAS_INTERACTION_REACTION: Alias = Alias::new(INTERACTION_REACTION);
|
||||
static ref ALIAS_USER: Alias = Alias::new(USER);
|
||||
static ref ALIAS_USER_AVATAR: Alias = Alias::new(USER_AVATAR);
|
||||
static ref ALIAS_USER_BANNER: Alias = Alias::new(USER_BANNER);
|
||||
static ref ALIAS_REPLY: Alias = Alias::new(REPLY);
|
||||
static ref ALIAS_REPLY_INTERACTION_RENOTE: Alias = Alias::new(REPLY_INTERACTION_RENOTE);
|
||||
static ref ALIAS_REPLY_INTERACTION_REACTION: Alias = Alias::new(REPLY_INTERACTION_REACTION);
|
||||
static ref ALIAS_REPLY_USER: Alias = Alias::new(REPLY_USER);
|
||||
static ref ALIAS_REPLY_USER_AVATAR: Alias = Alias::new(REPLY_USER_AVATAR);
|
||||
static ref ALIAS_REPLY_USER_BANNER: Alias = Alias::new(REPLY_USER_BANNER);
|
||||
static ref ALIAS_RENOTE: Alias = Alias::new(RENOTE);
|
||||
static ref ALIAS_RENOTE_INTERACTION_RENOTE: Alias = Alias::new(RENOTE_INTERACTION_RENOTE);
|
||||
static ref ALIAS_RENOTE_INTERACTION_REACTION: Alias = Alias::new(RENOTE_INTERACTION_REACTION);
|
||||
static ref ALIAS_RENOTE_USER: Alias = Alias::new(RENOTE_USER);
|
||||
static ref ALIAS_RENOTE_USER_AVATAR: Alias = Alias::new(RENOTE_USER_AVATAR);
|
||||
static ref ALIAS_RENOTE_USER_BANNER: Alias = Alias::new(RENOTE_USER_BANNER);
|
||||
static ref ALIAS_RENOTE_REPLY: Alias = Alias::new(RENOTE_REPLY);
|
||||
static ref ALIAS_RENOTE_REPLY_INTERACTION_RENOTE: Alias =
|
||||
Alias::new(RENOTE_REPLY_INTERACTION_RENOTE);
|
||||
static ref ALIAS_RENOTE_REPLY_INTERACTION_REACTION: Alias =
|
||||
Alias::new(RENOTE_REPLY_INTERACTION_REACTION);
|
||||
static ref ALIAS_RENOTE_REPLY_USER: Alias = Alias::new(RENOTE_REPLY_USER);
|
||||
static ref ALIAS_RENOTE_REPLY_USER_AVATAR: Alias = Alias::new(RENOTE_REPLY_USER_AVATAR);
|
||||
static ref ALIAS_RENOTE_REPLY_USER_BANNER: Alias = Alias::new(RENOTE_REPLY_USER_BANNER);
|
||||
}
|
||||
|
||||
fn range_into_expr(filter: &SpanFilter) -> Option<SimpleExpr> {
|
||||
match filter {
|
||||
SpanFilter::Range(RangeFilter {
|
||||
time_start,
|
||||
time_end,
|
||||
id_start,
|
||||
id_end,
|
||||
}) => Some(
|
||||
Expr::tuple([
|
||||
Expr::col(note::Column::CreatedAt).into(),
|
||||
Expr::col(note::Column::Id).into(),
|
||||
])
|
||||
.between(
|
||||
Expr::tuple([
|
||||
Expr::value(time_start.clone()),
|
||||
Expr::value(id_start.clone()),
|
||||
]),
|
||||
Expr::tuple([Expr::value(time_end.clone()), Expr::value(id_end.clone())]),
|
||||
),
|
||||
),
|
||||
SpanFilter::Start(StartFilter {
|
||||
id_start,
|
||||
time_start,
|
||||
}) => Some(
|
||||
Expr::tuple([
|
||||
Expr::col(note::Column::CreatedAt).into(),
|
||||
Expr::col(note::Column::Id).into(),
|
||||
])
|
||||
.gt(Expr::tuple([
|
||||
Expr::value(time_start.clone()),
|
||||
Expr::value(id_start.clone()),
|
||||
])),
|
||||
),
|
||||
SpanFilter::End(EndFilter { id_end, time_end }) => Some(
|
||||
Expr::tuple([
|
||||
Expr::col(note::Column::CreatedAt).into(),
|
||||
Expr::col(note::Column::Id).into(),
|
||||
])
|
||||
.lt(Expr::tuple([
|
||||
Expr::value(time_end.clone()),
|
||||
Expr::value(id_end.clone()),
|
||||
])),
|
||||
),
|
||||
SpanFilter::None(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn ids_into_expr(ids: &Vec<String>) -> SimpleExpr {
|
||||
let id_col = Expr::col((note::Entity.base_prefix(), note::Column::Id));
|
||||
|
||||
if ids.len() == 1 {
|
||||
note::Column::Id.eq(&ids[0])
|
||||
id_col.eq(&ids[0])
|
||||
} else {
|
||||
note::Column::Id.is_in(ids)
|
||||
id_col.is_in(ids)
|
||||
}
|
||||
}
|
||||
|
||||
impl NoteResolver {
|
||||
pub fn new(db: CalckeyModel) -> Self {
|
||||
NoteResolver { db }
|
||||
pub fn new(db: CalckeyModel, user_resolver: UserResolver) -> Self {
|
||||
NoteResolver { db, user_resolver }
|
||||
}
|
||||
|
||||
pub async fn get_one(
|
||||
|
@ -396,7 +207,10 @@ impl NoteResolver {
|
|||
) -> 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().and_then(range_into_expr);
|
||||
let time_filter = options
|
||||
.time_range
|
||||
.as_ref()
|
||||
.and_then(note::Model::range_into_expr);
|
||||
let id_filter = options.ids.as_ref().map(ids_into_expr);
|
||||
|
||||
let notes = select
|
||||
|
@ -416,7 +230,10 @@ impl NoteResolver {
|
|||
) -> Result<Vec<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().and_then(range_into_expr);
|
||||
let time_filter = options
|
||||
.time_range
|
||||
.as_ref()
|
||||
.and_then(note::Model::range_into_expr);
|
||||
let id_filter = options.ids.as_ref().map(ids_into_expr);
|
||||
|
||||
let notes = select
|
||||
|
@ -425,7 +242,7 @@ impl NoteResolver {
|
|||
.apply_if(time_filter, Select::<note::Entity>::filter)
|
||||
.apply_if(options.only_pins_from.as_deref(), |s, _| {
|
||||
s.order_by_desc(Expr::col((
|
||||
ALIAS_PIN.clone(),
|
||||
joined_prefix(¬e::Entity.base_prefix_str(), PIN),
|
||||
user_note_pining::Column::CreatedAt,
|
||||
)))
|
||||
})
|
||||
|
@ -437,201 +254,111 @@ impl NoteResolver {
|
|||
Ok(notes)
|
||||
}
|
||||
|
||||
pub fn resolve(&self, options: &NoteResolveOptions) -> Select<note::Entity> {
|
||||
let mut select = note::Entity::find().add_aliased_columns(Some(USER), user::Entity);
|
||||
fn attach_note(
|
||||
&self,
|
||||
q: &mut SelectStatement,
|
||||
prefix: &str,
|
||||
reply_depth: usize,
|
||||
renote_depth: usize,
|
||||
options: &NoteResolveOptions,
|
||||
user_resolver: &UserResolver,
|
||||
) {
|
||||
q.add_aliased_columns::<note::Entity>(prefix);
|
||||
|
||||
if let Some(pins_user) = options.only_pins_from.clone() {
|
||||
// Add the note's author
|
||||
q.add_aliased_columns::<user::Entity>(&joined_prefix_str(prefix, USER));
|
||||
q.join_columns(
|
||||
JoinType::LeftJoin,
|
||||
note::Relation::User.with_alias(Alias::new(prefix).into_iden()),
|
||||
joined_prefix(prefix, USER),
|
||||
);
|
||||
|
||||
// Interactions like renotes or reactions from the specified user
|
||||
if let Some(user_id) = &options.with_interactions_from {
|
||||
q.add_sub_select_interaction_reaction(
|
||||
prefix,
|
||||
&joined_prefix_str(prefix, INTERACTION_REACTION),
|
||||
user_id,
|
||||
);
|
||||
q.add_sub_select_interaction_renote(
|
||||
prefix,
|
||||
&joined_prefix_str(prefix, INTERACTION_RENOTE),
|
||||
user_id,
|
||||
);
|
||||
}
|
||||
|
||||
// Recursively attach reply parents
|
||||
if reply_depth > 0 {
|
||||
q.join_columns(
|
||||
JoinType::LeftJoin,
|
||||
note::Relation::SelfRef2.with_alias(Alias::new(prefix).into_iden()),
|
||||
joined_prefix(prefix, REPLY),
|
||||
);
|
||||
|
||||
self.attach_note(
|
||||
q,
|
||||
&joined_prefix_str(prefix, REPLY),
|
||||
reply_depth - 1,
|
||||
renote_depth,
|
||||
options,
|
||||
user_resolver,
|
||||
);
|
||||
}
|
||||
|
||||
// Recursively attach renote/quote targets
|
||||
if renote_depth > 0 {
|
||||
q.join_columns(
|
||||
JoinType::LeftJoin,
|
||||
note::Relation::SelfRef1.with_alias(Alias::new(prefix).into_iden()),
|
||||
joined_prefix(prefix, RENOTE),
|
||||
);
|
||||
|
||||
self.attach_note(
|
||||
q,
|
||||
&joined_prefix_str(prefix, RENOTE),
|
||||
reply_depth,
|
||||
renote_depth - 1,
|
||||
options,
|
||||
user_resolver,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve(&self, options: &NoteResolveOptions) -> Select<note::Entity> {
|
||||
let mut select = note::Entity::find();
|
||||
|
||||
let prefix = note::Entity.base_prefix_str();
|
||||
|
||||
let query = QuerySelect::query(&mut select);
|
||||
query.clear_selects();
|
||||
query.from_as(note::Entity, Alias::new(&prefix));
|
||||
|
||||
if let Some(pins_user) = &options.only_pins_from {
|
||||
select = select
|
||||
.join_as(
|
||||
JoinType::InnerJoin,
|
||||
note::Relation::UserNotePining.def(),
|
||||
ALIAS_PIN.clone(),
|
||||
note::Relation::UserNotePining.with_alias(Alias::new(&prefix)),
|
||||
joined_prefix(&prefix, PIN),
|
||||
)
|
||||
.filter(
|
||||
Expr::col((ALIAS_PIN.clone(), user_note_pining::Column::UserId))
|
||||
.eq(&pins_user)
|
||||
.into_condition(),
|
||||
Expr::col((
|
||||
joined_prefix(&prefix, PIN),
|
||||
user_note_pining::Column::UserId,
|
||||
))
|
||||
.eq(pins_user)
|
||||
.into_condition(),
|
||||
)
|
||||
}
|
||||
|
||||
if let Some(user_id) = &options.with_interactions_from {
|
||||
select = select
|
||||
.add_sub_select_reaction(None, INTERACTION_REACTION, user_id)
|
||||
.add_sub_select_renote(None, INTERACTION_RENOTE, user_id);
|
||||
}
|
||||
|
||||
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 let Some(user_id) = &options.with_interactions_from {
|
||||
select = select
|
||||
.add_sub_select_reaction(Some(REPLY), REPLY_INTERACTION_REACTION, user_id)
|
||||
.add_sub_select_renote(Some(REPLY), REPLY_INTERACTION_RENOTE, user_id);
|
||||
}
|
||||
|
||||
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 let Some(user_id) = &options.with_interactions_from {
|
||||
select = select
|
||||
.add_sub_select_reaction(Some(RENOTE), RENOTE_INTERACTION_REACTION, user_id)
|
||||
.add_sub_select_renote(Some(RENOTE), RENOTE_INTERACTION_RENOTE, user_id);
|
||||
}
|
||||
|
||||
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
|
||||
.add_aliased_columns(Some(RENOTE_REPLY), note::Entity)
|
||||
.add_aliased_columns(Some(RENOTE_REPLY_USER), user::Entity);
|
||||
|
||||
if let Some(user_id) = &options.with_interactions_from {
|
||||
select = select
|
||||
.add_sub_select_reaction(
|
||||
Some(RENOTE_REPLY),
|
||||
RENOTE_REPLY_INTERACTION_REACTION,
|
||||
user_id,
|
||||
)
|
||||
.add_sub_select_renote(
|
||||
Some(RENOTE_REPLY),
|
||||
RENOTE_REPLY_INTERACTION_RENOTE,
|
||||
user_id,
|
||||
);
|
||||
}
|
||||
|
||||
if options.with_user {
|
||||
select = select
|
||||
.add_aliased_columns(Some(RENOTE_REPLY_USER_AVATAR), drive_file::Entity)
|
||||
.add_aliased_columns(Some(RENOTE_REPLY_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::LeftJoin,
|
||||
note::Relation::User.with_alias(ALIAS_RENOTE.clone()),
|
||||
ALIAS_RENOTE_USER.clone(),
|
||||
);
|
||||
|
||||
if options.with_reply_target {
|
||||
select = select
|
||||
.join_as(
|
||||
JoinType::LeftJoin,
|
||||
note::Relation::SelfRef2.with_alias(ALIAS_RENOTE.clone()),
|
||||
ALIAS_RENOTE_REPLY.clone(),
|
||||
)
|
||||
.join_as(
|
||||
JoinType::LeftJoin,
|
||||
note::Relation::User.with_alias(ALIAS_RENOTE_REPLY.clone()),
|
||||
ALIAS_RENOTE_REPLY_USER.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
select = select.join_as(
|
||||
JoinType::InnerJoin,
|
||||
note::Relation::User.def(),
|
||||
ALIAS_USER.clone(),
|
||||
self.attach_note(
|
||||
QuerySelect::query(&mut select),
|
||||
&prefix,
|
||||
options.with_reply_target.then_some(1).unwrap_or_default(),
|
||||
options.with_renote_target.then_some(1).unwrap_or_default(),
|
||||
options,
|
||||
&self.user_resolver,
|
||||
);
|
||||
|
||||
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(),
|
||||
);
|
||||
|
||||
if options.with_reply_target {
|
||||
select = select
|
||||
.join_as(
|
||||
JoinType::LeftJoin,
|
||||
user::Relation::DriveFile2.with_alias(ALIAS_RENOTE_REPLY_USER.clone()),
|
||||
ALIAS_RENOTE_REPLY_USER_AVATAR.clone(),
|
||||
)
|
||||
.join_as(
|
||||
JoinType::LeftJoin,
|
||||
user::Relation::DriveFile1.with_alias(ALIAS_RENOTE_REPLY_USER.clone()),
|
||||
ALIAS_RENOTE_REPLY_USER_BANNER.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
select
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
use ext_calckey_model_migration::{Expr, SimpleExpr};
|
||||
use magnetar_sdk::types::{EndFilter, RangeFilter, SpanFilter, StartFilter};
|
||||
use sea_orm::Iden;
|
||||
|
||||
pub trait PaginatedModel: 'static {
|
||||
fn time_column() -> impl Iden;
|
||||
fn id_column() -> impl Iden;
|
||||
|
||||
fn range_into_expr(filter: &SpanFilter) -> Option<SimpleExpr> {
|
||||
match filter {
|
||||
SpanFilter::Range(RangeFilter {
|
||||
time_start,
|
||||
time_end,
|
||||
id_start,
|
||||
id_end,
|
||||
}) => Some(
|
||||
Expr::tuple([
|
||||
Expr::col(Self::time_column()).into(),
|
||||
Expr::col(Self::id_column()).into(),
|
||||
])
|
||||
.between(
|
||||
Expr::tuple([
|
||||
Expr::value(time_start.clone()),
|
||||
Expr::value(id_start.clone()),
|
||||
]),
|
||||
Expr::tuple([Expr::value(time_end.clone()), Expr::value(id_end.clone())]),
|
||||
),
|
||||
),
|
||||
SpanFilter::Start(StartFilter {
|
||||
id_start,
|
||||
time_start,
|
||||
}) => Some(
|
||||
Expr::tuple([
|
||||
Expr::col(Self::time_column()).into(),
|
||||
Expr::col(Self::id_column()).into(),
|
||||
])
|
||||
.gt(Expr::tuple([
|
||||
Expr::value(time_start.clone()),
|
||||
Expr::value(id_start.clone()),
|
||||
])),
|
||||
),
|
||||
SpanFilter::End(EndFilter { id_end, time_end }) => Some(
|
||||
Expr::tuple([
|
||||
Expr::col(Self::time_column()).into(),
|
||||
Expr::col(Self::id_column()).into(),
|
||||
])
|
||||
.lt(Expr::tuple([
|
||||
Expr::value(time_end.clone()),
|
||||
Expr::value(id_end.clone()),
|
||||
])),
|
||||
),
|
||||
SpanFilter::None(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PaginatedModel for ck::note::Model {
|
||||
fn time_column() -> impl Iden {
|
||||
ck::note::Column::CreatedAt
|
||||
}
|
||||
|
||||
fn id_column() -> impl Iden {
|
||||
ck::note::Column::Id
|
||||
}
|
||||
}
|
||||
|
||||
impl PaginatedModel for ck::user::Model {
|
||||
fn time_column() -> impl Iden {
|
||||
ck::user::Column::CreatedAt
|
||||
}
|
||||
|
||||
fn id_column() -> impl Iden {
|
||||
ck::user::Column::Id
|
||||
}
|
||||
}
|
||||
|
||||
impl PaginatedModel for ck::following::Model {
|
||||
fn time_column() -> impl Iden {
|
||||
ck::following::Column::CreatedAt
|
||||
}
|
||||
|
||||
fn id_column() -> impl Iden {
|
||||
ck::following::Column::Id
|
||||
}
|
||||
}
|
||||
|
||||
impl PaginatedModel for ck::follow_request::Model {
|
||||
fn time_column() -> impl Iden {
|
||||
ck::follow_request::Column::CreatedAt
|
||||
}
|
||||
|
||||
fn id_column() -> impl Iden {
|
||||
ck::follow_request::Column::Id
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
use crate::model_ext::EntityPrefixExt;
|
||||
use crate::{
|
||||
model_ext::{joined_prefix, joined_prefix_str, SelectColumnsExt},
|
||||
AliasSourceExt, CalckeyModel,
|
||||
};
|
||||
use ck::{drive_file, follow_request, following, user};
|
||||
use ext_calckey_model_migration::{Alias, SelectStatement};
|
||||
use sea_orm::{DbErr, EntityName, FromQueryResult, JoinType, QueryResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserData {
|
||||
pub user: user::Model,
|
||||
pub avatar: Option<drive_file::Model>,
|
||||
pub banner: Option<drive_file::Model>,
|
||||
}
|
||||
|
||||
pub struct UserResolveOptions {
|
||||
pub with_avatar_and_banner: bool,
|
||||
}
|
||||
|
||||
const AVATAR: &str = "avatar.";
|
||||
const BANNER: &str = "banner.";
|
||||
|
||||
impl FromQueryResult for UserData {
|
||||
fn from_query_result(res: &QueryResult, prefix: &str) -> Result<Self, DbErr> {
|
||||
let fallback = user::Entity.base_prefix_str();
|
||||
let prefix = if prefix.is_empty() { &fallback } else { prefix };
|
||||
|
||||
Ok(UserData {
|
||||
user: user::Model::from_query_result(res, prefix)?,
|
||||
avatar: drive_file::Model::from_query_result_optional(
|
||||
res,
|
||||
&joined_prefix_str(prefix, AVATAR),
|
||||
)?,
|
||||
banner: drive_file::Model::from_query_result_optional(
|
||||
res,
|
||||
&joined_prefix_str(prefix, BANNER),
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UserResolver {
|
||||
db: CalckeyModel,
|
||||
}
|
||||
|
||||
impl UserResolver {
|
||||
pub fn new(db: CalckeyModel) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
pub fn resolve(
|
||||
&self,
|
||||
q: &mut SelectStatement,
|
||||
prefix: &str,
|
||||
UserResolveOptions {
|
||||
with_avatar_and_banner,
|
||||
}: &UserResolveOptions,
|
||||
) {
|
||||
q.add_aliased_columns::<user::Entity>(prefix);
|
||||
|
||||
if *with_avatar_and_banner {
|
||||
q.add_aliased_columns::<drive_file::Entity>(&joined_prefix_str(prefix, AVATAR))
|
||||
.add_aliased_columns::<drive_file::Entity>(&joined_prefix_str(prefix, BANNER));
|
||||
|
||||
q.join_columns(
|
||||
JoinType::LeftJoin,
|
||||
user::Relation::DriveFile2.with_alias(Alias::new(prefix)),
|
||||
joined_prefix(prefix, AVATAR),
|
||||
)
|
||||
.join_columns(
|
||||
JoinType::LeftJoin,
|
||||
user::Relation::DriveFile1.with_alias(Alias::new(prefix)),
|
||||
joined_prefix(prefix, BANNER),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_followers(&self, user_id: &str) -> Vec<UserData> {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PackNoteMaybeAttachments } from "./packed/PackNoteMaybeAttachments";
|
||||
import type { PackNoteMaybeFull } from "./packed/PackNoteMaybeFull";
|
||||
|
||||
export interface NoteDetailExt { parent_note: PackNoteMaybeAttachments | null, renoted_note: PackNoteMaybeAttachments | null, }
|
||||
export interface NoteDetailExt { parent_note: PackNoteMaybeFull | null, renoted_note: PackNoteMaybeFull | null, }
|
|
@ -1,3 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type TimelineType = "home" | "timeline" | "recommended" | "hybrid" | "global";
|
||||
export type TimelineType = "home" | "recommended" | "hybrid" | "global" | "local";
|
|
@ -1,4 +1,4 @@
|
|||
use crate::endpoints::Endpoint;
|
||||
use crate::endpoints::{Empty, Endpoint};
|
||||
use crate::util_types::deserialize_array_urlenc;
|
||||
use http::Method;
|
||||
use magnetar_sdk_macros::Endpoint;
|
||||
|
@ -82,3 +82,48 @@ pub struct GetManyUsersById;
|
|||
response = "PackUserMaybeAll"
|
||||
)]
|
||||
pub struct GetUserByAcct;
|
||||
|
||||
#[derive(Endpoint)]
|
||||
#[endpoint(
|
||||
endpoint = "/users/:id/followers",
|
||||
method = Method::GET,
|
||||
request = "Empty",
|
||||
response = "Vec<PackUserBase>"
|
||||
)]
|
||||
pub struct GetFollowersById;
|
||||
|
||||
#[derive(Endpoint)]
|
||||
#[endpoint(
|
||||
endpoint = "/users/:id/following",
|
||||
method = Method::GET,
|
||||
request = "Empty",
|
||||
response = "Vec<PackUserBase>"
|
||||
)]
|
||||
pub struct GetFollowingById;
|
||||
|
||||
#[derive(Endpoint)]
|
||||
#[endpoint(
|
||||
endpoint = "/users/@self/followers",
|
||||
method = Method::GET,
|
||||
request = "Empty",
|
||||
response = "Vec<PackUserBase>"
|
||||
)]
|
||||
pub struct GetFollowersSelf;
|
||||
|
||||
#[derive(Endpoint)]
|
||||
#[endpoint(
|
||||
endpoint = "/users/@self/following",
|
||||
method = Method::GET,
|
||||
request = "Empty",
|
||||
response = "Vec<PackUserBase>"
|
||||
)]
|
||||
pub struct GetFollowingSelf;
|
||||
|
||||
#[derive(Endpoint)]
|
||||
#[endpoint(
|
||||
endpoint = "/users/@self/follow-requests",
|
||||
method = Method::GET,
|
||||
request = "Empty",
|
||||
response = "Vec<PackUserBase>"
|
||||
)]
|
||||
pub struct GetFollowRequestsSelf;
|
||||
|
|
|
@ -7,7 +7,6 @@ pub mod user;
|
|||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::RangeInclusive;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
|
|
|
@ -99,6 +99,15 @@ pub struct UserProfilePinsEx {
|
|||
// pub pinned_page: Option<Page>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum UserRelationship {
|
||||
Follow,
|
||||
FollowRequest,
|
||||
Mute,
|
||||
Block,
|
||||
RenoteMute,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct UserRelationExt {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::model::processing::user::UserModel;
|
||||
use crate::model::processing::user::{UserBorrowedData, UserModel};
|
||||
use crate::model::processing::PackError;
|
||||
use crate::model::PackingContext;
|
||||
use crate::service::MagnetarService;
|
||||
|
@ -11,6 +11,7 @@ use futures_util::TryStreamExt;
|
|||
use itertools::Itertools;
|
||||
use magnetar_common::util::lenient_parse_tag_decode;
|
||||
use magnetar_sdk::endpoints::user::{
|
||||
GetFollowRequestsSelf, GetFollowersById, GetFollowersSelf, GetFollowingById, GetFollowingSelf,
|
||||
GetManyUsersById, GetUserByAcct, GetUserById, GetUserSelf, ManyUsersByIdReq,
|
||||
};
|
||||
use magnetar_sdk::endpoints::{Req, Res};
|
||||
|
@ -24,7 +25,15 @@ pub async fn handle_user_info_self(
|
|||
) -> Result<Json<Res<GetUserSelf>>, ApiError> {
|
||||
let ctx = PackingContext::new(service, Some(user.clone())).await?;
|
||||
let user = UserModel
|
||||
.self_full_from_base(&ctx, user.as_ref(), &req, None, None)
|
||||
.self_full_from_base(
|
||||
&ctx,
|
||||
&UserBorrowedData {
|
||||
user: user.as_ref(),
|
||||
avatar: None,
|
||||
banner: None,
|
||||
},
|
||||
&req,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(user))
|
||||
}
|
||||
|
@ -43,7 +52,15 @@ pub async fn handle_user_info(
|
|||
.ok_or(ObjectNotFound(id))?;
|
||||
|
||||
let user = UserModel
|
||||
.foreign_full_from_base(&ctx, &user_model, &req, None, None)
|
||||
.foreign_full_from_base(
|
||||
&ctx,
|
||||
&UserBorrowedData {
|
||||
user: &user_model,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
},
|
||||
&req,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(user))
|
||||
}
|
||||
|
@ -67,7 +84,15 @@ pub async fn handle_user_info_by_acct(
|
|||
.ok_or(ObjectNotFound(tag_str))?;
|
||||
|
||||
let user = UserModel
|
||||
.foreign_full_from_base(&ctx, &user_model, &req, None, None)
|
||||
.foreign_full_from_base(
|
||||
&ctx,
|
||||
&UserBorrowedData {
|
||||
user: &user_model,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
},
|
||||
&req,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(user))
|
||||
}
|
||||
|
@ -89,9 +114,18 @@ pub async fn handle_user_by_id_many(
|
|||
let ctx = PackingContext::new(service, user.clone()).await?;
|
||||
let user_model = UserModel;
|
||||
|
||||
let futures = users
|
||||
let user_data = users
|
||||
.iter()
|
||||
.map(|u| user_model.base_from_existing(&ctx, u, None))
|
||||
.map(|user| UserBorrowedData {
|
||||
user,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let futures = user_data
|
||||
.iter()
|
||||
.map(|user| user_model.base_from_existing(&ctx, user))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let users_proc = futures::stream::iter(futures)
|
||||
|
|
|
@ -71,6 +71,7 @@ impl PackType<NoteBaseSource<'_>> for NoteBase {
|
|||
has_poll: note.has_poll,
|
||||
file_ids: note.file_ids.clone(),
|
||||
emojis: emoji_context.clone(),
|
||||
is_quote: note.is_quote.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::model::processing::PackResult;
|
|||
use crate::service::MagnetarService;
|
||||
use either::Either;
|
||||
use magnetar_calckey_model::{ck, CalckeyDbError};
|
||||
use magnetar_sdk::types::user::UserRelationship;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
@ -21,15 +22,6 @@ impl Default for ProcessingLimits {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
enum UserRelationship {
|
||||
Follow,
|
||||
FollowRequest,
|
||||
Mute,
|
||||
Block,
|
||||
RenoteMute,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
struct UserRelationshipLink {
|
||||
from: String,
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::model::data::id::BaseId;
|
||||
use crate::model::data::note::{NoteAttachmentSource, NoteBaseSource, NoteDetailSource};
|
||||
use crate::model::processing::emoji::EmojiModel;
|
||||
use crate::model::processing::user::UserModel;
|
||||
use crate::model::processing::{get_mm_token_emoji, PackError, PackResult};
|
||||
use crate::model::{PackType, PackingContext, UserRelationship};
|
||||
use crate::model::{PackType, PackingContext};
|
||||
use compact_str::CompactString;
|
||||
use either::Either;
|
||||
use futures_util::future::{try_join_all, BoxFuture};
|
||||
|
@ -11,7 +13,8 @@ use futures_util::{FutureExt, StreamExt, TryFutureExt, TryStreamExt};
|
|||
use magnetar_calckey_model::ck::sea_orm_active_enums::NoteVisibilityEnum;
|
||||
use magnetar_calckey_model::emoji::EmojiTag;
|
||||
use magnetar_calckey_model::note_model::{
|
||||
NoteData, NoteResolveOptions, NoteVisibilityFilterFactory,
|
||||
sub_interaction_reaction, sub_interaction_renote, NoteData, NoteResolveOptions,
|
||||
NoteVisibilityFilterFactory,
|
||||
};
|
||||
use magnetar_calckey_model::poll::PollResolver;
|
||||
use magnetar_calckey_model::sea_orm::prelude::Expr;
|
||||
|
@ -27,12 +30,14 @@ use magnetar_sdk::types::note::{
|
|||
PackNoteMaybeAttachments, PackNoteMaybeFull, PackPollBase, PollBase, Reaction, ReactionPair,
|
||||
ReactionShortcode, ReactionUnicode, ReactionUnknown,
|
||||
};
|
||||
use magnetar_sdk::types::user::UserRelationship;
|
||||
use magnetar_sdk::types::{Id, MmXml};
|
||||
use magnetar_sdk::{mmm, Optional, Packed, Required};
|
||||
use serde::Deserialize;
|
||||
use tokio::try_join;
|
||||
|
||||
use super::drive::DriveModel;
|
||||
use super::user::UserShapedData;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NoteVisibilityFilterSimple(Option<String>);
|
||||
|
@ -161,6 +166,80 @@ impl NoteVisibilityFilterModel {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait NoteShapedData<'a>: Send + Sync {
|
||||
fn note(&self) -> &'a ck::note::Model;
|
||||
fn interaction_user_renote(&self) -> Option<&'a sub_interaction_renote::Model>;
|
||||
fn interaction_user_reaction(&self) -> Option<&'a sub_interaction_reaction::Model>;
|
||||
fn user(&self) -> Arc<dyn UserShapedData<'a> + 'a>;
|
||||
fn reply(&self) -> Option<Arc<dyn NoteShapedData<'a> + 'a>>;
|
||||
fn renote(&self) -> Option<Arc<dyn NoteShapedData<'a> + 'a>>;
|
||||
}
|
||||
|
||||
pub struct NoteBorrowedData<'a> {
|
||||
pub note: &'a ck::note::Model,
|
||||
pub interaction_user_renote: Option<&'a sub_interaction_renote::Model>,
|
||||
pub interaction_user_reaction: Option<&'a sub_interaction_reaction::Model>,
|
||||
pub user: Arc<dyn UserShapedData<'a> + 'a>,
|
||||
pub reply: Option<Arc<dyn NoteShapedData<'a> + 'a>>,
|
||||
pub renote: Option<Arc<dyn NoteShapedData<'a> + 'a>>,
|
||||
}
|
||||
|
||||
impl<'a> NoteShapedData<'a> for NoteBorrowedData<'a> {
|
||||
fn note(&self) -> &'a ck::note::Model {
|
||||
self.note
|
||||
}
|
||||
|
||||
fn interaction_user_renote(&self) -> Option<&'a sub_interaction_renote::Model> {
|
||||
self.interaction_user_renote
|
||||
}
|
||||
|
||||
fn interaction_user_reaction(&self) -> Option<&'a sub_interaction_reaction::Model> {
|
||||
self.interaction_user_reaction
|
||||
}
|
||||
|
||||
fn user(&self) -> Arc<dyn UserShapedData<'a> + 'a> {
|
||||
self.user.clone()
|
||||
}
|
||||
|
||||
fn reply(&self) -> Option<Arc<dyn NoteShapedData<'a> + 'a>> {
|
||||
self.reply.as_ref().cloned()
|
||||
}
|
||||
|
||||
fn renote(&self) -> Option<Arc<dyn NoteShapedData<'a> + 'a>> {
|
||||
self.renote.as_ref().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> NoteShapedData<'a> for &'a NoteData {
|
||||
fn note(&self) -> &'a ck::note::Model {
|
||||
&self.note
|
||||
}
|
||||
|
||||
fn interaction_user_renote(&self) -> Option<&'a sub_interaction_renote::Model> {
|
||||
self.interaction_user_renote.as_ref()
|
||||
}
|
||||
|
||||
fn interaction_user_reaction(&self) -> Option<&'a sub_interaction_reaction::Model> {
|
||||
self.interaction_user_reaction.as_ref()
|
||||
}
|
||||
|
||||
fn user(&self) -> Arc<dyn UserShapedData<'a> + 'a> {
|
||||
Arc::new(&self.user)
|
||||
}
|
||||
|
||||
fn reply(&self) -> Option<Arc<dyn NoteShapedData<'a> + 'a>> {
|
||||
self.reply
|
||||
.as_deref()
|
||||
.map(|x| Arc::new(x) as Arc<dyn NoteShapedData<'a> + 'a>)
|
||||
}
|
||||
|
||||
fn renote(&self) -> Option<Arc<dyn NoteShapedData<'a> + 'a>> {
|
||||
self.renote
|
||||
.as_deref()
|
||||
.map(|x| Arc::new(x) as Arc<dyn NoteShapedData<'a> + 'a>)
|
||||
}
|
||||
}
|
||||
|
||||
struct SpeechTransformNyan;
|
||||
|
||||
impl SpeechTransformNyan {
|
||||
|
@ -194,15 +273,17 @@ impl NoteModel {
|
|||
pub async fn extract_base(
|
||||
&self,
|
||||
ctx: &PackingContext,
|
||||
note_data: &NoteData,
|
||||
note_data: &dyn NoteShapedData<'_>,
|
||||
) -> PackResult<PackNoteBase> {
|
||||
let note = note_data.note();
|
||||
|
||||
let Required(ref user) = UserModel
|
||||
.base_from_existing(ctx, ¬e_data.user, None)
|
||||
.base_from_existing(ctx, note_data.user().as_ref())
|
||||
.await?
|
||||
.user;
|
||||
|
||||
let cw_tok = self.tokenize_note_cw(¬e_data.note);
|
||||
let mut text_tok = self.tokenize_note_text(¬e_data.note);
|
||||
let cw_tok = self.tokenize_note_cw(note);
|
||||
let mut text_tok = self.tokenize_note_text(note);
|
||||
|
||||
let mut emoji_extracted = Vec::new();
|
||||
|
||||
|
@ -213,7 +294,7 @@ impl NoteModel {
|
|||
if let Some(text_tok) = &mut text_tok {
|
||||
emoji_extracted.extend_from_slice(&get_mm_token_emoji(text_tok));
|
||||
|
||||
if note_data.user.is_cat && note_data.user.speak_as_cat {
|
||||
if note_data.user().user().is_cat && note_data.user().user().speak_as_cat {
|
||||
let transformer = SpeechTransformNyan::new();
|
||||
text_tok.walk_speech_transform(&|text| transformer.transform(text));
|
||||
}
|
||||
|
@ -225,7 +306,7 @@ impl NoteModel {
|
|||
// Parse the JSON into an ordered map and turn it into a Vec of pairs, parsing the reaction codes
|
||||
// Failed reaction parses -> Left, Successful ones -> Right
|
||||
let reactions_raw =
|
||||
serde_json::Map::<String, serde_json::Value>::deserialize(¬e_data.note.reactions)?
|
||||
serde_json::Map::<String, serde_json::Value>::deserialize(¬e.reactions)?
|
||||
.into_iter()
|
||||
.map(|(ref code, count)| {
|
||||
let reaction = parse_reaction(code)
|
||||
|
@ -235,7 +316,7 @@ impl NoteModel {
|
|||
reaction,
|
||||
count,
|
||||
note_data
|
||||
.interaction_user_reaction
|
||||
.interaction_user_reaction()
|
||||
.as_ref()
|
||||
.and_then(|r| r.reaction_name.as_deref())
|
||||
.map(|r| r == code),
|
||||
|
@ -266,8 +347,11 @@ impl NoteModel {
|
|||
.emoji_cache
|
||||
.get_many_tagged(&reactions_to_resolve)
|
||||
.map_err(PackError::from);
|
||||
let emoji_fetch =
|
||||
emoji_model.fetch_many_emojis(ctx, &shortcodes, note_data.user.host.as_deref());
|
||||
let emoji_fetch = emoji_model.fetch_many_emojis(
|
||||
ctx,
|
||||
&shortcodes,
|
||||
note_data.user().user().host.as_deref(),
|
||||
);
|
||||
|
||||
let (reactions_fetched, emojis) = try_join!(reaction_fetch, emoji_fetch)?;
|
||||
|
||||
|
@ -318,7 +402,7 @@ impl NoteModel {
|
|||
let note_base = NoteBase::extract(
|
||||
ctx,
|
||||
NoteBaseSource {
|
||||
note: ¬e_data.note,
|
||||
note,
|
||||
cw_mm: cw_tok
|
||||
.as_ref()
|
||||
.map(mmm::to_xml_string)
|
||||
|
@ -338,7 +422,7 @@ impl NoteModel {
|
|||
);
|
||||
|
||||
Ok(PackNoteBase::pack_from((
|
||||
Required(Id::from(note_data.note.id.clone())),
|
||||
Required(Id::from(note.id.clone())),
|
||||
Required(note_base),
|
||||
)))
|
||||
}
|
||||
|
@ -346,11 +430,10 @@ impl NoteModel {
|
|||
fn extract_interaction(
|
||||
&self,
|
||||
ctx: &PackingContext,
|
||||
note: &NoteData,
|
||||
note: &dyn NoteShapedData<'_>,
|
||||
) -> PackResult<Option<NoteSelfContextExt>> {
|
||||
Ok(note
|
||||
.interaction_user_renote
|
||||
.as_ref()
|
||||
.interaction_user_renote()
|
||||
.map(|renote_info| NoteSelfContextExt::extract(ctx, renote_info)))
|
||||
}
|
||||
|
||||
|
@ -405,12 +488,12 @@ impl NoteModel {
|
|||
&self,
|
||||
ctx: &PackingContext,
|
||||
drive_model: &DriveModel,
|
||||
note_data: &NoteData,
|
||||
note_data: &dyn NoteShapedData<'_>,
|
||||
) -> PackResult<PackNoteMaybeAttachments> {
|
||||
let (PackNoteBase { id, note }, attachments_pack, poll_pack) = try_join!(
|
||||
self.extract_base(ctx, note_data),
|
||||
self.extract_attachments(ctx, drive_model, ¬e_data.note),
|
||||
self.extract_poll(ctx, ¬e_data.note)
|
||||
self.extract_attachments(ctx, drive_model, note_data.note()),
|
||||
self.extract_poll(ctx, note_data.note())
|
||||
)?;
|
||||
|
||||
Ok(PackNoteMaybeAttachments::pack_from((
|
||||
|
|
|
@ -3,10 +3,11 @@ use crate::model::processing::drive::DriveModel;
|
|||
use crate::model::processing::emoji::EmojiModel;
|
||||
use crate::model::processing::note::NoteModel;
|
||||
use crate::model::processing::{get_mm_token_emoji, PackError, PackResult};
|
||||
use crate::model::{PackType, PackingContext, UserRelationship};
|
||||
use crate::model::{PackType, PackingContext};
|
||||
use either::Either;
|
||||
use futures_util::future::OptionFuture;
|
||||
use magnetar_calckey_model::ck;
|
||||
use magnetar_calckey_model::user_model::UserData;
|
||||
use magnetar_sdk::endpoints::user::{UserByIdReq, UserSelfReq};
|
||||
use magnetar_sdk::mmm::Token;
|
||||
use magnetar_sdk::types::drive::PackDriveFileBase;
|
||||
|
@ -15,7 +16,7 @@ use magnetar_sdk::types::instance::InstanceTicker;
|
|||
use magnetar_sdk::types::user::{
|
||||
MovedTo, PackSecurityKeyBase, PackUserBase, PackUserMaybeAll, PackUserSelfMaybeAll,
|
||||
ProfileField, SecurityKeyBase, UserAuthOverviewExt, UserBase, UserDetailExt, UserProfileExt,
|
||||
UserProfilePinsEx, UserRelationExt, UserSecretsExt,
|
||||
UserProfilePinsEx, UserRelationExt, UserRelationship, UserSecretsExt,
|
||||
};
|
||||
use magnetar_sdk::types::{Id, MmXml};
|
||||
use magnetar_sdk::{mmm, Optional, Packed, Required};
|
||||
|
@ -24,6 +25,46 @@ use tokio::{join, try_join};
|
|||
use tracing::warn;
|
||||
use url::Url;
|
||||
|
||||
pub trait UserShapedData<'a>: Send + Sync {
|
||||
fn user(&self) -> &'a ck::user::Model;
|
||||
fn avatar(&self) -> Option<&'a ck::drive_file::Model>;
|
||||
fn banner(&self) -> Option<&'a ck::drive_file::Model>;
|
||||
}
|
||||
|
||||
pub struct UserBorrowedData<'a> {
|
||||
pub user: &'a ck::user::Model,
|
||||
pub avatar: Option<&'a ck::drive_file::Model>,
|
||||
pub banner: Option<&'a ck::drive_file::Model>,
|
||||
}
|
||||
|
||||
impl<'a> UserShapedData<'a> for &'a UserData {
|
||||
fn user(&self) -> &'a ck::user::Model {
|
||||
&self.user
|
||||
}
|
||||
|
||||
fn avatar(&self) -> Option<&'a ck::drive_file::Model> {
|
||||
self.avatar.as_ref()
|
||||
}
|
||||
|
||||
fn banner(&self) -> Option<&'a ck::drive_file::Model> {
|
||||
self.banner.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> UserShapedData<'a> for UserBorrowedData<'a> {
|
||||
fn user(&self) -> &'a ck::user::Model {
|
||||
self.user
|
||||
}
|
||||
|
||||
fn avatar(&self) -> Option<&'a ck::drive_file::Model> {
|
||||
self.avatar
|
||||
}
|
||||
|
||||
fn banner(&self) -> Option<&'a ck::drive_file::Model> {
|
||||
self.banner
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProfileFieldRaw<'a> {
|
||||
name: &'a str,
|
||||
|
@ -74,14 +115,15 @@ impl UserModel {
|
|||
})?)
|
||||
}
|
||||
|
||||
pub async fn base_from_existing(
|
||||
pub async fn base_from_existing<'a>(
|
||||
&self,
|
||||
ctx: &PackingContext,
|
||||
user: &ck::user::Model,
|
||||
hint_avatar_file: Option<&ck::drive_file::Model>,
|
||||
user_data: &dyn UserShapedData<'a>,
|
||||
) -> PackResult<PackUserBase> {
|
||||
let user = user_data.user();
|
||||
|
||||
let drive_file_pack = DriveModel;
|
||||
let avatar = match (hint_avatar_file, &user.avatar_id) {
|
||||
let avatar = match (user_data.avatar(), &user.avatar_id) {
|
||||
(Some(avatar_file), _) => Some(drive_file_pack.pack_existing(ctx, avatar_file)),
|
||||
(None, Some(av_id)) => drive_file_pack.get_cached_base(ctx, av_id).await?,
|
||||
_ => None,
|
||||
|
@ -110,7 +152,7 @@ impl UserModel {
|
|||
let base = UserBase::extract(
|
||||
ctx,
|
||||
UserBaseSource {
|
||||
user,
|
||||
user: &user,
|
||||
username_mm: mmm::to_xml_string(&username_mm).map(MmXml).as_ref().ok(),
|
||||
avatar_url,
|
||||
avatar: avatar.as_ref(),
|
||||
|
@ -137,17 +179,18 @@ impl UserModel {
|
|||
.ok_or_else(|| PackError::DataError("Missing user profile".to_string()))
|
||||
}
|
||||
|
||||
pub async fn profile_from_base(
|
||||
pub async fn profile_from_base<'a>(
|
||||
&self,
|
||||
ctx: &PackingContext,
|
||||
user: &ck::user::Model,
|
||||
user_data: &dyn UserShapedData<'a>,
|
||||
profile: &ck::user_profile::Model,
|
||||
relation: Option<&UserRelationExt>,
|
||||
emoji_out: &mut EmojiContext,
|
||||
hint_banner_file: Option<&ck::drive_file::Model>,
|
||||
) -> PackResult<UserProfileExt> {
|
||||
let user = user_data.user();
|
||||
|
||||
let drive_file_pack = DriveModel;
|
||||
let banner = match (hint_banner_file, &user.banner_id) {
|
||||
let banner = match (user_data.banner(), &user.banner_id) {
|
||||
(Some(banner_file), _) => Some(drive_file_pack.pack_existing(ctx, banner_file)),
|
||||
(None, Some(av_id)) => drive_file_pack.get_cached_base(ctx, av_id).await?,
|
||||
_ => None,
|
||||
|
@ -244,22 +287,20 @@ impl UserModel {
|
|||
Ok(UserSecretsExt::extract(ctx, (profile, &secrets)))
|
||||
}
|
||||
|
||||
pub async fn self_full_from_base(
|
||||
pub async fn self_full_from_base<'a>(
|
||||
&self,
|
||||
ctx: &PackingContext,
|
||||
user: &ck::user::Model,
|
||||
user_data: &impl UserShapedData<'a>,
|
||||
req: &UserSelfReq,
|
||||
hint_avatar_file: Option<&ck::drive_file::Model>,
|
||||
hint_banner_file: Option<&ck::drive_file::Model>,
|
||||
) -> PackResult<PackUserSelfMaybeAll> {
|
||||
let user = user_data.user();
|
||||
|
||||
let should_fetch_profile =
|
||||
req.profile.unwrap_or_default() || req.secrets.unwrap_or_default();
|
||||
let profile_raw_promise =
|
||||
OptionFuture::from(should_fetch_profile.then(|| self.get_profile(ctx, user)));
|
||||
let (base_res, profile_res) = join!(
|
||||
self.base_from_existing(ctx, user, hint_avatar_file),
|
||||
profile_raw_promise
|
||||
);
|
||||
let (base_res, profile_res) =
|
||||
join!(self.base_from_existing(ctx, user_data), profile_raw_promise);
|
||||
let mut base = base_res?;
|
||||
let profile_raw = profile_res.transpose()?;
|
||||
|
||||
|
@ -271,11 +312,10 @@ impl UserModel {
|
|||
let profile = OptionFuture::from(req.profile.unwrap_or_default().then(|| {
|
||||
self.profile_from_base(
|
||||
ctx,
|
||||
user,
|
||||
user_data,
|
||||
profile_raw.as_ref().unwrap(),
|
||||
None,
|
||||
&mut base.user.0.emojis,
|
||||
hint_banner_file,
|
||||
)
|
||||
}));
|
||||
|
||||
|
@ -380,18 +420,15 @@ impl UserModel {
|
|||
pub async fn foreign_full_from_base(
|
||||
&self,
|
||||
ctx: &PackingContext,
|
||||
user: &ck::user::Model,
|
||||
user_data: &dyn UserShapedData<'_>,
|
||||
req: &UserByIdReq,
|
||||
hint_avatar_file: Option<&ck::drive_file::Model>,
|
||||
hint_banner_file: Option<&ck::drive_file::Model>,
|
||||
) -> PackResult<PackUserMaybeAll> {
|
||||
let user = user_data.user();
|
||||
|
||||
let should_fetch_profile = req.profile.unwrap_or_default() || req.auth.unwrap_or_default();
|
||||
let profile_raw_promise =
|
||||
OptionFuture::from(should_fetch_profile.then(|| self.get_profile(ctx, user)));
|
||||
let (base, profile) = join!(
|
||||
self.base_from_existing(ctx, user, hint_avatar_file),
|
||||
profile_raw_promise
|
||||
);
|
||||
let (base, profile) = join!(self.base_from_existing(ctx, user_data), profile_raw_promise);
|
||||
let mut base = base?;
|
||||
let profile_raw = profile.transpose()?;
|
||||
|
||||
|
@ -403,11 +440,10 @@ impl UserModel {
|
|||
let profile = OptionFuture::from(req.profile.unwrap_or_default().then(|| {
|
||||
self.profile_from_base(
|
||||
ctx,
|
||||
user,
|
||||
user_data,
|
||||
profile_raw.as_ref().unwrap(),
|
||||
None,
|
||||
&mut base.user.0.emojis,
|
||||
hint_banner_file,
|
||||
)
|
||||
}));
|
||||
|
||||
|
|
Loading…
Reference in New Issue