727 lines
23 KiB
Rust
727 lines
23 KiB
Rust
use crate::model::data::user::{UserBaseSource, UserProfileExtSource, UserRelationExtSource};
|
|
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};
|
|
use crate::web::pagination::Pagination;
|
|
use crate::web::{AccessForbidden, ApiError};
|
|
use either::Either;
|
|
use futures_util::future::OptionFuture;
|
|
use futures_util::{StreamExt, TryStreamExt};
|
|
use magnetar_calckey_model::ck;
|
|
use magnetar_calckey_model::ck::sea_orm_active_enums::UserProfileFfvisibilityEnum;
|
|
use magnetar_calckey_model::user_model::{UserData, UserResolveOptions};
|
|
use magnetar_sdk::endpoints::user::{UserByIdReq, UserSelfReq};
|
|
use magnetar_sdk::mmm::Token;
|
|
use magnetar_sdk::types::drive::PackDriveFileBase;
|
|
use magnetar_sdk::types::emoji::EmojiContext;
|
|
use magnetar_sdk::types::instance::InstanceTicker;
|
|
use magnetar_sdk::types::user::{
|
|
MovedTo, PackSecurityKeyBase, PackUserBase, PackUserMaybeAll, PackUserSelfMaybeAll,
|
|
ProfileField, SecurityKeyBase, UserAuthOverviewExt, UserBase, UserDetailExt, UserProfileExt,
|
|
UserProfilePinsEx, UserRelationExt, UserRelationship, UserSecretsExt, UserSelfExt,
|
|
};
|
|
use magnetar_sdk::types::{Id, MmXml};
|
|
use magnetar_sdk::{mmm, Optional, Packed, Required};
|
|
use serde::{Deserialize, Serialize};
|
|
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 profile(&self) -> Option<&'a ck::user_profile::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 profile: Option<&'a ck::user_profile::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 profile(&self) -> Option<&'a ck::user_profile::Model> {
|
|
self.profile.as_ref()
|
|
}
|
|
|
|
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 profile(&self) -> Option<&'a ck::user_profile::Model> {
|
|
self.profile
|
|
}
|
|
|
|
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,
|
|
value: &'a str,
|
|
}
|
|
|
|
pub struct UserModel;
|
|
|
|
impl UserModel {
|
|
pub fn tokenize_username(&self, user: &ck::user::Model) -> Token {
|
|
mmm::Context::default().parse_ui(user.name.as_deref().unwrap_or(&user.username))
|
|
}
|
|
|
|
pub fn tokenize_description(&self, user: &ck::user_profile::Model) -> Option<Token> {
|
|
user.description
|
|
.as_deref()
|
|
.map(|d| mmm::Context::default().parse_inline(d))
|
|
}
|
|
|
|
pub fn get_effective_avatar_url(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
user: &ck::user::Model,
|
|
avatar: Option<&PackDriveFileBase>,
|
|
) -> PackResult<Url> {
|
|
Ok(avatar
|
|
.and_then(
|
|
|PackDriveFileBase {
|
|
file: Required(base),
|
|
..
|
|
}| base.thumbnail_url.as_deref(),
|
|
)
|
|
.map(Url::parse)
|
|
.and_then(|r| {
|
|
if let Err(e) = r {
|
|
warn!("Failed to parse avatar URL: {e}");
|
|
}
|
|
|
|
r.ok().map(Ok)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
Url::parse(&format!(
|
|
"{}://{}/identicon/{}",
|
|
ctx.service.config.networking.protocol,
|
|
ctx.service.config.networking.host,
|
|
user.id
|
|
))
|
|
})?)
|
|
}
|
|
|
|
pub async fn base_from_existing<'a>(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
user_data: &dyn UserShapedData<'a>,
|
|
) -> PackResult<PackUserBase> {
|
|
let user = user_data.user();
|
|
|
|
let drive_file_pack = DriveModel;
|
|
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,
|
|
};
|
|
let avatar_url = &self.get_effective_avatar_url(ctx, user, avatar.as_ref())?;
|
|
|
|
let username_mm = self.tokenize_username(user);
|
|
|
|
let emoji_model = EmojiModel;
|
|
let shortcodes = emoji_model.deduplicate_emoji(ctx, get_mm_token_emoji(&username_mm));
|
|
let emojis = emoji_model
|
|
.fetch_many_emojis(ctx, &shortcodes, user.host.as_deref())
|
|
.await?;
|
|
let instance = ctx
|
|
.service
|
|
.remote_instance_cache
|
|
.get(
|
|
user.host
|
|
.as_deref()
|
|
.unwrap_or(&ctx.service.config.networking.host),
|
|
)
|
|
.await?
|
|
.map(|i| InstanceTicker::extract(ctx, i.as_ref()));
|
|
let emoji_context = EmojiContext(emojis);
|
|
|
|
let base = UserBase::extract(
|
|
ctx,
|
|
UserBaseSource {
|
|
user,
|
|
username_mm: mmm::to_xml_string(&username_mm).map(MmXml).as_ref().ok(),
|
|
avatar_url,
|
|
avatar: avatar.as_ref(),
|
|
emoji_context: &emoji_context,
|
|
instance: instance.as_ref(),
|
|
},
|
|
);
|
|
|
|
Ok(PackUserBase::pack_from((
|
|
Required(Id::from(&user.id)),
|
|
Required(base),
|
|
)))
|
|
}
|
|
|
|
async fn get_profile(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
user: &ck::user::Model,
|
|
) -> PackResult<ck::user_profile::Model> {
|
|
ctx.service
|
|
.db
|
|
.get_user_profile_by_id(&user.id)
|
|
.await?
|
|
.ok_or_else(|| PackError::DataError("Missing user profile".to_string()))
|
|
}
|
|
|
|
pub async fn get_profile_by_id(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
id: &str,
|
|
) -> PackResult<ck::user_profile::Model> {
|
|
ctx.service
|
|
.db
|
|
.get_user_profile_by_id(id)
|
|
.await?
|
|
.ok_or_else(|| PackError::DataError("Missing user profile".to_string()))
|
|
}
|
|
|
|
pub async fn profile_from_base<'a>(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
user_data: &dyn UserShapedData<'a>,
|
|
profile: &ck::user_profile::Model,
|
|
relation: Option<&UserRelationExt>,
|
|
emoji_out: &mut EmojiContext,
|
|
) -> PackResult<UserProfileExt> {
|
|
let user = user_data.user();
|
|
|
|
let drive_file_pack = DriveModel;
|
|
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,
|
|
};
|
|
|
|
let moved = match &user.moved_to_uri {
|
|
Some(uri) => {
|
|
let moved = ctx.service.db.get_user_by_uri(uri).await?;
|
|
|
|
moved.and_then(|m| {
|
|
Some(MovedTo {
|
|
moved_to_uri: m.uri?,
|
|
username: m.username,
|
|
host: m
|
|
.host
|
|
.unwrap_or_else(|| ctx.service.config.networking.host.to_string()),
|
|
})
|
|
})
|
|
}
|
|
None => None,
|
|
};
|
|
|
|
let description_mm = self.tokenize_description(profile);
|
|
|
|
let fields = Vec::<ProfileFieldRaw>::deserialize(&profile.fields)?;
|
|
let parser = mmm::Context::new(2);
|
|
let profile_fields = fields
|
|
.into_iter()
|
|
.map(|f| {
|
|
let tok = parser.parse_profile_fields(f.value);
|
|
|
|
ProfileField {
|
|
name: f.name.to_string(),
|
|
value: f.value.to_string(),
|
|
value_mm: mmm::to_xml_string(&tok).map(MmXml).ok(),
|
|
verified_at: None,
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
if let Some(desc_mm) = &description_mm {
|
|
let emoji_model = EmojiModel;
|
|
let shortcodes = emoji_model.deduplicate_emoji(ctx, get_mm_token_emoji(desc_mm));
|
|
let emojis = emoji_model
|
|
.fetch_many_emojis(ctx, &shortcodes, user.host.as_deref())
|
|
.await?;
|
|
|
|
emoji_out.extend_from(&emojis);
|
|
}
|
|
|
|
Ok(UserProfileExt::extract(
|
|
ctx,
|
|
UserProfileExtSource {
|
|
user,
|
|
profile,
|
|
profile_fields: &profile_fields,
|
|
banner: banner.as_ref(),
|
|
description_mm: description_mm
|
|
.and_then(|desc_mm| mmm::to_xml_string(&desc_mm).map(MmXml).ok())
|
|
.as_ref(),
|
|
relation,
|
|
moved_to: moved.as_ref(),
|
|
},
|
|
))
|
|
}
|
|
|
|
pub async fn secrets_from_base(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
user: &ck::user::Model,
|
|
profile: &ck::user_profile::Model,
|
|
) -> PackResult<UserSecretsExt> {
|
|
let secrets = ctx
|
|
.service
|
|
.db
|
|
.get_user_security_keys_by_id(&user.id)
|
|
.await?
|
|
.into_iter()
|
|
.map(|k| {
|
|
PackSecurityKeyBase::pack_from((
|
|
Required(Id::from(&k.id)),
|
|
Required(SecurityKeyBase {
|
|
name: k.name,
|
|
last_used_at: Some(k.last_used.into()),
|
|
}),
|
|
))
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
Ok(UserSecretsExt::extract(ctx, (profile, &secrets)))
|
|
}
|
|
|
|
pub async fn self_full_from_base<'a>(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
user_data: &impl UserShapedData<'a>,
|
|
req: &UserSelfReq,
|
|
) -> PackResult<PackUserSelfMaybeAll> {
|
|
let user = user_data.user();
|
|
|
|
let should_fetch_profile = user_data.profile().is_none()
|
|
&& (req.profile.unwrap_or_default()
|
|
|| req.secrets.unwrap_or_default()
|
|
|| req.self_detail.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_data), profile_raw_promise);
|
|
let mut base = base_res?;
|
|
let profile_raw = profile_res.transpose()?;
|
|
let profile_ref = user_data.profile().or(profile_raw.as_ref());
|
|
|
|
let detail = req
|
|
.detail
|
|
.unwrap_or_default()
|
|
.then(|| UserDetailExt::extract(ctx, user));
|
|
|
|
let profile = OptionFuture::from(req.profile.unwrap_or_default().then(|| {
|
|
self.profile_from_base(
|
|
ctx,
|
|
user_data,
|
|
profile_ref.unwrap(),
|
|
None,
|
|
&mut base.user.0.emojis,
|
|
)
|
|
}));
|
|
|
|
let note_model = NoteModel {
|
|
with_context: true,
|
|
attachments: true,
|
|
};
|
|
let pins = OptionFuture::from(
|
|
req.pins
|
|
.unwrap_or_default()
|
|
.then(|| note_model.fetch_pins(ctx, user)),
|
|
);
|
|
|
|
let secrets = OptionFuture::from(
|
|
req.secrets
|
|
.unwrap_or_default()
|
|
.then(|| self.secrets_from_base(ctx, user, profile_ref.unwrap())),
|
|
);
|
|
|
|
let (profile_res, pins_res, secrets_res) = join!(profile, pins, secrets);
|
|
|
|
let profile_resolved = profile_res.transpose()?;
|
|
let pins_resolved = pins_res
|
|
.transpose()?
|
|
.map(|notes| UserProfilePinsEx::extract(ctx, ¬es));
|
|
let secrets_resolved = secrets_res.transpose()?;
|
|
|
|
let self_info = req
|
|
.self_detail
|
|
.unwrap_or_default()
|
|
.then(|| UserSelfExt::extract(ctx, (&user, profile_ref.unwrap())));
|
|
|
|
Ok(PackUserSelfMaybeAll {
|
|
id: base.id,
|
|
user: base.user,
|
|
profile: Optional(profile_resolved),
|
|
pins: Optional(pins_resolved),
|
|
detail: Optional(detail),
|
|
secrets: Optional(secrets_resolved),
|
|
self_detail: Optional(self_info),
|
|
})
|
|
}
|
|
|
|
pub async fn relations_from_base(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
user: &ck::user::Model,
|
|
) -> PackResult<UserRelationExt> {
|
|
let Some(me_user) = ctx.self_user.as_deref() else {
|
|
return Ok(UserRelationExt {
|
|
follows_you: false,
|
|
you_follow: false,
|
|
they_request_follow: false,
|
|
you_request_follow: false,
|
|
blocks_you: false,
|
|
you_block: false,
|
|
mute: false,
|
|
mute_renotes: false,
|
|
});
|
|
};
|
|
|
|
let me = Either::Right(me_user);
|
|
let them = Either::Right(user);
|
|
|
|
let (
|
|
follow_in,
|
|
follow_out,
|
|
follow_request_in,
|
|
follow_request_out,
|
|
block_in,
|
|
block_out,
|
|
mute,
|
|
renote_mute,
|
|
) = try_join!(
|
|
ctx.is_relationship_between(them, me, UserRelationship::Follow),
|
|
ctx.is_relationship_between(me, them, UserRelationship::Follow),
|
|
ctx.is_relationship_between(them, me, UserRelationship::FollowRequest),
|
|
ctx.is_relationship_between(me, them, UserRelationship::FollowRequest),
|
|
ctx.is_relationship_between(them, me, UserRelationship::Block),
|
|
ctx.is_relationship_between(me, them, UserRelationship::Block),
|
|
ctx.is_relationship_between(me, them, UserRelationship::Mute),
|
|
ctx.is_relationship_between(me, them, UserRelationship::RenoteMute)
|
|
)?;
|
|
|
|
Ok(UserRelationExt::extract(
|
|
ctx,
|
|
UserRelationExtSource {
|
|
follow_in,
|
|
follow_out,
|
|
follow_request_in,
|
|
follow_request_out,
|
|
block_in,
|
|
block_out,
|
|
mute,
|
|
renote_mute,
|
|
},
|
|
))
|
|
}
|
|
|
|
pub fn auth_overview_from_profile(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
profile: &ck::user_profile::Model,
|
|
) -> PackResult<UserAuthOverviewExt> {
|
|
Ok(UserAuthOverviewExt::extract(ctx, profile))
|
|
}
|
|
|
|
pub async fn foreign_full_from_base(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
user_data: &dyn UserShapedData<'_>,
|
|
req: &UserByIdReq,
|
|
) -> 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_data), profile_raw_promise);
|
|
let mut base = base?;
|
|
let profile_raw = profile.transpose()?;
|
|
|
|
let detail = req
|
|
.detail
|
|
.unwrap_or_default()
|
|
.then(|| UserDetailExt::extract(ctx, user));
|
|
|
|
let profile = OptionFuture::from(req.profile.unwrap_or_default().then(|| {
|
|
self.profile_from_base(
|
|
ctx,
|
|
user_data,
|
|
profile_raw.as_ref().unwrap(),
|
|
None,
|
|
&mut base.user.0.emojis,
|
|
)
|
|
}));
|
|
|
|
let note_model = NoteModel {
|
|
with_context: true,
|
|
attachments: true,
|
|
};
|
|
let pins = OptionFuture::from(
|
|
req.pins
|
|
.unwrap_or_default()
|
|
.then(|| note_model.fetch_pins(ctx, user)),
|
|
);
|
|
let relations = OptionFuture::from(
|
|
req.relation
|
|
.unwrap_or_default()
|
|
.then(|| self.relations_from_base(ctx, user)),
|
|
);
|
|
|
|
let auth = req
|
|
.auth
|
|
.unwrap_or_default()
|
|
.then(|| self.auth_overview_from_profile(ctx, profile_raw.as_ref().unwrap()))
|
|
.transpose()?;
|
|
|
|
let (profile_res, pins_res, relations_res) = join!(profile, pins, relations);
|
|
|
|
let profile_resolved = profile_res.transpose()?;
|
|
let pins_resolved = pins_res
|
|
.transpose()?
|
|
.map(|notes| UserProfilePinsEx::extract(ctx, ¬es));
|
|
let relations_resolved = relations_res.transpose()?;
|
|
|
|
Ok(PackUserMaybeAll {
|
|
id: base.id,
|
|
user: base.user,
|
|
profile: Optional(profile_resolved),
|
|
pins: Optional(pins_resolved),
|
|
detail: Optional(detail),
|
|
relation: Optional(relations_resolved),
|
|
auth: Optional(auth),
|
|
})
|
|
}
|
|
|
|
pub async fn pack_many_base(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
users: &[&dyn UserShapedData<'_>],
|
|
) -> PackResult<Vec<PackUserBase>> {
|
|
let futures = users
|
|
.iter()
|
|
.map(|&user| self.base_from_existing(ctx, user))
|
|
.collect::<Vec<_>>();
|
|
|
|
let users_proc = futures::stream::iter(futures)
|
|
.buffered(20)
|
|
.err_into::<PackError>()
|
|
.try_collect::<Vec<_>>()
|
|
.await?;
|
|
|
|
Ok(users_proc)
|
|
}
|
|
|
|
pub async fn pack_many_maybe_full(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
users: &[&dyn UserShapedData<'_>],
|
|
req: &UserByIdReq,
|
|
) -> PackResult<Vec<PackUserMaybeAll>> {
|
|
let futures = users
|
|
.iter()
|
|
.map(|&user| self.foreign_full_from_base(ctx, user, req))
|
|
.collect::<Vec<_>>();
|
|
|
|
let users_proc = futures::stream::iter(futures)
|
|
.buffered(20)
|
|
.err_into::<PackError>()
|
|
.try_collect::<Vec<_>>()
|
|
.await?;
|
|
|
|
Ok(users_proc)
|
|
}
|
|
|
|
pub async fn follower_visibility_check(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
id: &str,
|
|
) -> Result<(), ApiError> {
|
|
if ctx.is_id_self(id) {
|
|
return Ok(());
|
|
}
|
|
|
|
let profile = self.get_profile_by_id(ctx, id).await?;
|
|
|
|
match (&ctx.self_user, &profile.ff_visibility) {
|
|
(_, UserProfileFfvisibilityEnum::Public) => {}
|
|
(Some(self_user), UserProfileFfvisibilityEnum::Followers)
|
|
if ctx
|
|
.is_relationship_between(
|
|
Either::Right(self_user),
|
|
Either::Left(id),
|
|
UserRelationship::Follow,
|
|
)
|
|
.await? => {}
|
|
_ => {
|
|
Err(AccessForbidden(
|
|
"Follower information not visible".to_string(),
|
|
))?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get_followers(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
id: &str,
|
|
pagination: &mut Pagination,
|
|
) -> Result<Vec<PackUserMaybeAll>, ApiError> {
|
|
self.follower_visibility_check(ctx, id).await?;
|
|
|
|
let users = ctx
|
|
.service
|
|
.db
|
|
.get_user_resolver()
|
|
.get_followers(
|
|
&UserResolveOptions {
|
|
with_avatar: true,
|
|
with_banner: true,
|
|
with_profile: true,
|
|
},
|
|
id,
|
|
&pagination.current,
|
|
&mut pagination.prev,
|
|
&mut pagination.next,
|
|
pagination.limit.into(),
|
|
)
|
|
.await?;
|
|
|
|
let users_ref = users
|
|
.iter()
|
|
.map(|u| Box::new(u) as Box<dyn UserShapedData<'_>>)
|
|
.collect::<Vec<_>>();
|
|
|
|
Ok(self
|
|
.pack_many_maybe_full(
|
|
ctx,
|
|
&users_ref.iter().map(Box::as_ref).collect::<Vec<_>>(),
|
|
&UserByIdReq {
|
|
profile: Some(true),
|
|
auth: None,
|
|
detail: None,
|
|
pins: None,
|
|
relation: None,
|
|
},
|
|
)
|
|
.await?)
|
|
}
|
|
|
|
pub async fn get_followees(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
id: &str,
|
|
pagination: &mut Pagination,
|
|
) -> Result<Vec<PackUserMaybeAll>, ApiError> {
|
|
self.follower_visibility_check(ctx, id).await?;
|
|
|
|
let users = ctx
|
|
.service
|
|
.db
|
|
.get_user_resolver()
|
|
.get_followees(
|
|
&UserResolveOptions {
|
|
with_avatar: true,
|
|
with_banner: true,
|
|
with_profile: true,
|
|
},
|
|
id,
|
|
&pagination.current,
|
|
&mut pagination.prev,
|
|
&mut pagination.next,
|
|
pagination.limit.into(),
|
|
)
|
|
.await?;
|
|
|
|
let users_ref = users
|
|
.iter()
|
|
.map(|u| Box::new(u) as Box<dyn UserShapedData<'_>>)
|
|
.collect::<Vec<_>>();
|
|
|
|
Ok(self
|
|
.pack_many_maybe_full(
|
|
ctx,
|
|
&users_ref.iter().map(Box::as_ref).collect::<Vec<_>>(),
|
|
&UserByIdReq {
|
|
profile: Some(true),
|
|
auth: None,
|
|
detail: None,
|
|
pins: None,
|
|
relation: None,
|
|
},
|
|
)
|
|
.await?)
|
|
}
|
|
|
|
pub async fn get_follow_requests(
|
|
&self,
|
|
ctx: &PackingContext,
|
|
id: &str,
|
|
pagination: &mut Pagination,
|
|
) -> Result<Vec<PackUserMaybeAll>, ApiError> {
|
|
let users = ctx
|
|
.service
|
|
.db
|
|
.get_user_resolver()
|
|
.get_follow_requests(
|
|
&UserResolveOptions {
|
|
with_avatar: true,
|
|
with_banner: true,
|
|
with_profile: true,
|
|
},
|
|
id,
|
|
&pagination.current,
|
|
&mut pagination.prev,
|
|
&mut pagination.next,
|
|
pagination.limit.into(),
|
|
)
|
|
.await?;
|
|
|
|
let users_ref = users
|
|
.iter()
|
|
.map(|u| Box::new(u) as Box<dyn UserShapedData<'_>>)
|
|
.collect::<Vec<_>>();
|
|
|
|
Ok(self
|
|
.pack_many_maybe_full(
|
|
ctx,
|
|
&users_ref.iter().map(Box::as_ref).collect::<Vec<_>>(),
|
|
&UserByIdReq {
|
|
profile: Some(true),
|
|
auth: None,
|
|
detail: None,
|
|
pins: None,
|
|
relation: None,
|
|
},
|
|
)
|
|
.await?)
|
|
}
|
|
}
|