Experimental data model
This commit is contained in:
parent
7bffc5f16a
commit
e045d2aae4
|
@ -1,6 +1,6 @@
|
|||
mod user;
|
||||
|
||||
use crate::api_v1::user::handle_user_info;
|
||||
use crate::api_v1::user::{handle_user_info, handle_user_info_self};
|
||||
use crate::service::MagnetarService;
|
||||
use crate::web::auth;
|
||||
use crate::web::auth::AuthState;
|
||||
|
@ -11,7 +11,8 @@ use std::sync::Arc;
|
|||
|
||||
pub fn create_api_router(service: Arc<MagnetarService>) -> Router {
|
||||
Router::new()
|
||||
.route("/user/@self", get(handle_user_info))
|
||||
.route("/users/@self", get(handle_user_info_self))
|
||||
.route("/users/:id", get(handle_user_info))
|
||||
.layer(from_fn_with_state(
|
||||
AuthState::new(service.clone()),
|
||||
auth::auth,
|
||||
|
|
|
@ -1,18 +1,36 @@
|
|||
use crate::service::MagnetarService;
|
||||
use crate::web::auth::AuthenticatedUser;
|
||||
use crate::web::auth::{AuthenticatedUser, MaybeUser};
|
||||
use crate::web::ApiError;
|
||||
use axum::extract::State;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::Json;
|
||||
use magnetar_sdk::endpoints::user::{GetUserById, GetUserSelf, UserByIdReq, UserSelfReq};
|
||||
use magnetar_sdk::endpoints::{Req, Res};
|
||||
use std::sync::Arc;
|
||||
|
||||
// TODO: Not a real endpoint
|
||||
pub async fn handle_user_info(
|
||||
State(service): State<Arc<MagnetarService>>,
|
||||
AuthenticatedUser(user): AuthenticatedUser,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
let db = service.db.clone();
|
||||
let user = db.get_user_by_id(&user.id).await?;
|
||||
|
||||
Ok(Json(user))
|
||||
pub async fn handle_user_info_self(
|
||||
Query(UserSelfReq {
|
||||
detail: _,
|
||||
pins: _,
|
||||
profile: _,
|
||||
secrets: _,
|
||||
}): Query<Req<GetUserSelf>>,
|
||||
State(_service): State<Arc<MagnetarService>>,
|
||||
AuthenticatedUser(_user): AuthenticatedUser,
|
||||
) -> Result<Json<Res<GetUserSelf>>, ApiError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn handle_user_info(
|
||||
Path(_id): Path<String>,
|
||||
Query(UserByIdReq {
|
||||
detail: _,
|
||||
pins: _,
|
||||
profile: _,
|
||||
relation: _,
|
||||
auth: _,
|
||||
}): Query<Req<GetUserById>>,
|
||||
State(_service): State<Arc<MagnetarService>>,
|
||||
MaybeUser(_user): MaybeUser,
|
||||
) -> Result<Json<Res<GetUserById>>, ApiError> {
|
||||
todo!()
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
mod api_v1;
|
||||
pub mod model;
|
||||
pub mod nodeinfo;
|
||||
pub mod service;
|
||||
pub mod util;
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
use magnetar_calckey_model::ck;
|
||||
use magnetar_sdk::types::drive::DriveFileBase;
|
||||
|
||||
use crate::model::{PackBase, PackExtract, PackType, PackingContext};
|
||||
|
||||
impl PackType for PackExtract<DriveFileBase> {
|
||||
type Input = PackBase<ck::drive_file::Model>;
|
||||
|
||||
fn extract(context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
let PackBase(file) = data;
|
||||
|
||||
PackExtract(DriveFileBase {
|
||||
name: file.name,
|
||||
created_at: file.created_at.into(),
|
||||
size: file.size as u64,
|
||||
sha256: todo!(),
|
||||
mime_type: file.r#type,
|
||||
image_meta: todo!(),
|
||||
url: Some(file.url),
|
||||
thumbnail_url: file.thumbnail_url,
|
||||
sensitive: file.is_sensitive,
|
||||
comment: file.comment,
|
||||
folder_id: file.folder_id,
|
||||
user_id: file.user_id,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
use crate::model::{PackBase, PackExtract, PackType, PackingContext};
|
||||
use magnetar_calckey_model::ck;
|
||||
use magnetar_sdk::types::emoji::EmojiBase;
|
||||
|
||||
impl PackType for PackExtract<EmojiBase> {
|
||||
type Input = PackBase<ck::emoji::Model>;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
let PackBase(model) = data;
|
||||
|
||||
PackExtract(EmojiBase {
|
||||
shortcode: model.name.clone(),
|
||||
category: model.category.clone(),
|
||||
url: model.public_url.clone(),
|
||||
width: model.width,
|
||||
height: model.height,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
use magnetar_calckey_model::ck;
|
||||
|
||||
pub trait BaseId: 'static {
|
||||
fn get_id(&self) -> &str;
|
||||
}
|
||||
|
||||
macro_rules! impl_id {
|
||||
($id:ty) => {
|
||||
impl BaseId for $id {
|
||||
fn get_id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_id!(ck::emoji::Model);
|
||||
impl_id!(ck::user::Model);
|
||||
impl_id!(ck::note::Model);
|
|
@ -0,0 +1,5 @@
|
|||
pub mod drive;
|
||||
pub mod emoji;
|
||||
pub mod id;
|
||||
pub mod note;
|
||||
pub mod user;
|
|
@ -0,0 +1,87 @@
|
|||
use magnetar_calckey_model::ck;
|
||||
use magnetar_sdk::types::{
|
||||
drive::PackDriveFileBase,
|
||||
emoji::EmojiContext,
|
||||
note::{NoteAttachmentExt, NoteBase, NoteVisibility, PackReactionBase, ReactionBase},
|
||||
user::UserBase,
|
||||
};
|
||||
|
||||
use crate::model::{
|
||||
LinkAuto, PackAggregate, PackBase, PackChild, PackExtract, PackMulti, PackPacked, PackType,
|
||||
PackingContext,
|
||||
};
|
||||
|
||||
impl PackType for PackExtract<ReactionBase> {
|
||||
type Input = PackBase<ck::note_reaction::Model>;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
let PackBase(reaction) = data;
|
||||
|
||||
PackExtract(ReactionBase {
|
||||
created_at: reaction.created_at.into(),
|
||||
user_id: reaction.user_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PackType for PackExtract<NoteBase> {
|
||||
type Input = PackMulti<(
|
||||
PackBase<ck::note::Model>,
|
||||
PackAggregate<PackChild<PackPacked<PackReactionBase>, LinkAuto>>,
|
||||
PackChild<PackExtract<EmojiContext>, LinkAuto>,
|
||||
PackChild<PackExtract<UserBase>, LinkAuto>,
|
||||
)>;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
let PackMulti((
|
||||
PackBase(note),
|
||||
PackAggregate(reactions, ..),
|
||||
PackChild(PackExtract(emojis), ..),
|
||||
PackChild(PackExtract(user), ..),
|
||||
)) = data;
|
||||
|
||||
let reactions = reactions
|
||||
.into_iter()
|
||||
.map(|PackChild(PackPacked(t, ..), ..)| t)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
use ck::sea_orm_active_enums::NoteVisibilityEnum as NVE;
|
||||
PackExtract(NoteBase {
|
||||
created_at: note.created_at.into(),
|
||||
cw: note.cw,
|
||||
uri: note.uri,
|
||||
url: note.url,
|
||||
text: note.text.unwrap_or_default(),
|
||||
visibility: match note.visibility {
|
||||
NVE::Followers => NoteVisibility::Followers,
|
||||
NVE::Hidden => NoteVisibility::Direct,
|
||||
NVE::Public => NoteVisibility::Public,
|
||||
NVE::Home => NoteVisibility::Home,
|
||||
NVE::Specified => NoteVisibility::Direct,
|
||||
},
|
||||
user: Box::new(user),
|
||||
parent_note_id: note.reply_id,
|
||||
renoted_note_id: note.renote_id,
|
||||
reply_count: note.replies_count as u64,
|
||||
renote_count: note.renote_count as u64,
|
||||
hashtags: note.tags,
|
||||
reactions,
|
||||
emojis,
|
||||
local_only: note.local_only,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PackType for PackExtract<NoteAttachmentExt> {
|
||||
type Input = PackMulti<(
|
||||
PackBase<ck::note::Model>,
|
||||
PackAggregate<PackChild<PackPacked<PackDriveFileBase>, LinkAuto>>,
|
||||
)>;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
PackExtract(NoteAttachmentExt {
|
||||
attachments: todo!(),
|
||||
poll: todo!(),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
use magnetar_calckey_model::ck;
|
||||
use magnetar_calckey_model::ck::sea_orm_active_enums::UserProfileFfvisibilityEnum;
|
||||
use magnetar_sdk::types::emoji::{EmojiContext, PackEmojiBase};
|
||||
use magnetar_sdk::types::note::PackNoteWithAttachments;
|
||||
use magnetar_sdk::types::user::{
|
||||
AvatarDecoration, PackSecurityKeyBase, SecurityKeyBase, SpeechTransform, UserBase,
|
||||
UserDetailExt, UserProfileExt, UserProfilePinsEx, UserRelationExt, UserSecretsExt,
|
||||
};
|
||||
|
||||
use crate::model::{
|
||||
LinkAuto, LinkIdent, PackAggregate, PackBase, PackChild, PackExtract, PackMaybe, PackMulti,
|
||||
PackPacked, PackType, PackingContext,
|
||||
};
|
||||
|
||||
impl PackType for PackExtract<EmojiContext> {
|
||||
type Input = PackMulti<(
|
||||
PackBase<ck::user::Model>,
|
||||
PackChild<PackAggregate<PackPacked<PackEmojiBase>>, LinkAuto>,
|
||||
)>;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
let PackMulti((PackBase(_), PackChild(PackAggregate(emojis), ..))) = data;
|
||||
|
||||
PackExtract(EmojiContext(
|
||||
emojis
|
||||
.into_iter()
|
||||
.map(|PackPacked(t, ..)| t)
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct UserAvatarLink;
|
||||
impl From<UserAvatarLink> for LinkIdent {
|
||||
fn from(_: UserAvatarLink) -> Self {
|
||||
LinkIdent::Named("avatar".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl PackType for PackExtract<UserBase> {
|
||||
type Input = PackMulti<(
|
||||
PackBase<ck::user::Model>,
|
||||
PackChild<PackMaybe<PackBase<ck::drive_file::Model>>, UserAvatarLink>,
|
||||
PackChild<PackExtract<EmojiContext>, LinkAuto>,
|
||||
)>;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
let PackMulti((
|
||||
PackBase(ref user),
|
||||
PackChild(PackMaybe(ref avatar), _),
|
||||
PackChild(PackExtract(ref emoji_context), _),
|
||||
)) = data;
|
||||
|
||||
let avatar = avatar.as_ref().map(|PackBase(v)| v);
|
||||
|
||||
PackExtract(UserBase {
|
||||
acct: user
|
||||
.host
|
||||
.as_ref()
|
||||
.map(|host| format!("@{}@{}", user.username, host))
|
||||
.unwrap_or_else(|| format!("@{}", user.username)),
|
||||
username: user.username.clone(),
|
||||
display_name: user.name.clone().unwrap_or_else(|| user.username.clone()),
|
||||
host: user.host.clone(),
|
||||
speech_transform: if user.is_cat && user.speak_as_cat {
|
||||
SpeechTransform::Cat
|
||||
} else {
|
||||
SpeechTransform::None
|
||||
},
|
||||
created_at: user.created_at.into(),
|
||||
avatar_url: avatar.map(|v| v.url.clone()),
|
||||
avatar_blurhash: avatar.and_then(|v| v.blurhash.clone()),
|
||||
avatar_color: None,
|
||||
avatar_decoration: if user.is_cat {
|
||||
AvatarDecoration::CatEars
|
||||
} else {
|
||||
AvatarDecoration::None
|
||||
},
|
||||
is_admin: user.is_admin,
|
||||
is_moderator: user.is_moderator,
|
||||
is_bot: user.is_bot,
|
||||
emojis: emoji_context.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PackType for PackExtract<UserProfileExt> {
|
||||
type Input = PackMulti<(
|
||||
PackBase<ck::user::Model>,
|
||||
PackChild<PackBase<ck::user_profile::Model>, LinkAuto>,
|
||||
PackMaybe<PackExtract<UserRelationExt>>,
|
||||
)>;
|
||||
|
||||
fn extract(context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
let PackMulti((
|
||||
PackBase(ref user),
|
||||
PackChild(PackBase(ref profile), ..),
|
||||
PackMaybe(ref relation),
|
||||
)) = data;
|
||||
|
||||
let relation = relation.as_ref().map(|PackExtract(v)| v);
|
||||
|
||||
let follow_visibility = match profile.ff_visibility {
|
||||
UserProfileFfvisibilityEnum::Public => true,
|
||||
UserProfileFfvisibilityEnum::Followers => {
|
||||
relation.as_ref().is_some_and(|r| r.follows_you)
|
||||
}
|
||||
UserProfileFfvisibilityEnum::Private => false,
|
||||
} || context.is_self(user);
|
||||
|
||||
PackExtract(UserProfileExt {
|
||||
is_locked: user.is_locked,
|
||||
is_silenced: user.is_silenced,
|
||||
is_suspended: user.is_suspended,
|
||||
description: profile.description.clone(),
|
||||
location: profile.location.clone(),
|
||||
birthday: profile
|
||||
.birthday
|
||||
.clone()
|
||||
.and_then(|b| b.parse().map_or_else(|_| None, Some)),
|
||||
fields: serde_json::from_value(profile.fields.clone()).unwrap_or_else(|_| Vec::new()),
|
||||
follower_count: follow_visibility.then_some(user.followers_count as u64),
|
||||
following_count: follow_visibility.then_some(user.following_count as u64),
|
||||
note_count: Some(user.notes_count as u64),
|
||||
url: profile.url.clone(),
|
||||
moved_to_uri: user.moved_to_uri.clone(),
|
||||
also_known_as: user.also_known_as.clone(),
|
||||
banner_url: None,
|
||||
banner_color: None,
|
||||
banner_blurhash: None,
|
||||
has_public_reactions: profile.public_reactions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PackType for PackExtract<UserDetailExt> {
|
||||
type Input = PackBase<ck::user::Model>;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
let PackBase(model) = data;
|
||||
|
||||
PackExtract(UserDetailExt {
|
||||
last_fetched_at: model.last_fetched_at.map(Into::into),
|
||||
uri: model.uri.clone(),
|
||||
updated_at: model.updated_at.map(Into::into),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PackType for PackExtract<UserRelationExt> {
|
||||
type Input = PackMulti<(
|
||||
PackChild<PackBase<ck::following::Model>, LinkAuto>,
|
||||
PackChild<PackBase<ck::blocking::Model>, LinkAuto>,
|
||||
PackChild<PackBase<ck::muting::Model>, LinkAuto>,
|
||||
PackChild<PackBase<ck::renote_muting::Model>, LinkAuto>,
|
||||
)>;
|
||||
|
||||
fn extract(context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
let PackMulti((
|
||||
PackChild(PackBase(ref follow), ..),
|
||||
PackChild(PackBase(ref block), ..),
|
||||
PackChild(PackBase(ref mute), ..),
|
||||
PackChild(PackBase(ref renote_mute), ..),
|
||||
)) = data;
|
||||
|
||||
let self_user = context.self_user();
|
||||
|
||||
PackExtract(UserRelationExt {
|
||||
follows_you: self_user.is_some_and(|self_user| {
|
||||
self_user.id == follow.followee_id && self_user.id != follow.follower_id
|
||||
}),
|
||||
you_follow: self_user.is_some_and(|self_user| {
|
||||
self_user.id == follow.follower_id && self_user.id != follow.followee_id
|
||||
}),
|
||||
blocks_you: self_user.is_some_and(|self_user| {
|
||||
self_user.id == block.blockee_id && self_user.id != block.blocker_id
|
||||
}),
|
||||
you_block: self_user.is_some_and(|self_user| {
|
||||
self_user.id == block.blocker_id && self_user.id != block.blockee_id
|
||||
}),
|
||||
mute: self_user.is_some_and(|self_user| {
|
||||
self_user.id == mute.muter_id && self_user.id != mute.mutee_id
|
||||
}),
|
||||
mute_renotes: self_user.is_some_and(|self_user| {
|
||||
self_user.id == renote_mute.muter_id && self_user.id != renote_mute.mutee_id
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PackType for PackExtract<UserProfilePinsEx> {
|
||||
type Input = PackMulti<(
|
||||
PackBase<ck::user::Model>,
|
||||
PackChild<
|
||||
PackAggregate<
|
||||
PackMulti<(
|
||||
PackBase<ck::user_note_pining::Model>,
|
||||
PackChild<PackPacked<PackNoteWithAttachments>, LinkAuto>,
|
||||
)>,
|
||||
>,
|
||||
LinkAuto,
|
||||
>,
|
||||
)>;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
let PackMulti((PackBase(_), PackChild(PackAggregate(pins), ..))) = data;
|
||||
|
||||
PackExtract(UserProfilePinsEx {
|
||||
pinned_notes: pins
|
||||
.into_iter()
|
||||
.map(|PackMulti((_, PackChild(PackPacked(note, ..), ..)))| note)
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PackType for PackExtract<SecurityKeyBase> {
|
||||
type Input = PackBase<ck::user_security_key::Model>;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
let PackBase(model) = data;
|
||||
|
||||
PackExtract(SecurityKeyBase {
|
||||
name: model.name.clone(),
|
||||
last_used_at: Some(model.last_used.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PackType for PackExtract<UserSecretsExt> {
|
||||
type Input = PackMulti<(
|
||||
PackBase<ck::user::Model>,
|
||||
PackChild<
|
||||
PackMulti<(
|
||||
PackBase<ck::user_profile::Model>,
|
||||
PackChild<PackAggregate<PackPacked<PackSecurityKeyBase>>, LinkAuto>,
|
||||
)>,
|
||||
LinkAuto,
|
||||
>,
|
||||
)>;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
let PackMulti((
|
||||
_,
|
||||
PackChild(PackMulti((PackBase(ref profile), PackChild(PackAggregate(keys), ..))), ..),
|
||||
)) = data;
|
||||
|
||||
let keys = keys
|
||||
.into_iter()
|
||||
.map(|PackPacked(t, ..)| t)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
PackExtract(UserSecretsExt {
|
||||
email: profile.email.clone(),
|
||||
email_verified: profile.email_verified,
|
||||
security_keys: keys.clone(),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
use crate::model::LinkIdent;
|
||||
use std::any::TypeId;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
type PMT = PackMapType;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Multi(Vec<Arc<PMT>>);
|
||||
|
||||
impl Multi {
|
||||
pub fn get_base(&self) -> Option<&PMT> {
|
||||
for pmt in &self.0 {
|
||||
match pmt.as_ref() {
|
||||
PackMapType::FromType(CommandPackFromType { base: true, .. }) => return Some(pmt),
|
||||
PackMapType::Maybe(CommandPackMaybe { inner }) => {
|
||||
if let PackMapType::FromType(..) = inner.as_ref() {
|
||||
return Some(pmt);
|
||||
}
|
||||
}
|
||||
PackMapType::Multi(CommandPackMulti { inner }) => {
|
||||
if let Some(pmt) = inner.get_base() {
|
||||
return Some(pmt);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<PMT>> for Multi {
|
||||
fn from(v: Vec<PMT>) -> Self {
|
||||
Multi(v.into_iter().map(Arc::new).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PackMapType {
|
||||
FromType(CommandPackFromType),
|
||||
Maybe(CommandPackMaybe),
|
||||
Child(CommandPackChild),
|
||||
Aggregate(CommandPackAggregate),
|
||||
Multi(CommandPackMulti),
|
||||
}
|
||||
|
||||
trait PackCommand {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommandPackFromType {
|
||||
pub name: String,
|
||||
pub id: TypeId,
|
||||
pub base: bool,
|
||||
}
|
||||
|
||||
impl PackCommand for CommandPackFromType {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommandPackMaybe {
|
||||
pub inner: Arc<PackMapType>,
|
||||
}
|
||||
|
||||
impl PackCommand for CommandPackMaybe {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommandPackChild {
|
||||
pub inner: Arc<PackMapType>,
|
||||
pub link: LinkIdent,
|
||||
}
|
||||
|
||||
impl PackCommand for CommandPackChild {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommandPackAggregate {
|
||||
pub inner: Arc<PackMapType>,
|
||||
}
|
||||
|
||||
impl PackCommand for CommandPackAggregate {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommandPackMulti {
|
||||
pub inner: Multi,
|
||||
}
|
||||
|
||||
impl PackCommand for CommandPackMulti {}
|
||||
|
||||
impl PackMapType {
|
||||
pub fn from_type<T: Sized + 'static>(base: bool) -> Self {
|
||||
PackMapType::FromType(CommandPackFromType {
|
||||
name: std::any::type_name::<T>().to_string(),
|
||||
id: TypeId::of::<T>(),
|
||||
base,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_base_type(&self) -> TypeId {
|
||||
let id = match self {
|
||||
PackMapType::FromType(CommandPackFromType { base: true, .. }) => Some(self),
|
||||
PackMapType::Multi(CommandPackMulti { inner }) => inner.get_base(),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let PackMapType::FromType(CommandPackFromType { id, .. }) = id.expect("no base type") else {
|
||||
panic!("base type is not FromType")
|
||||
};
|
||||
|
||||
*id
|
||||
}
|
||||
|
||||
pub fn as_edges(
|
||||
&self,
|
||||
base_parent: &Option<(usize, String)>,
|
||||
parent: &Option<(usize, String)>,
|
||||
level: usize,
|
||||
) -> Vec<(Option<String>, String)> {
|
||||
match self {
|
||||
PackMapType::FromType(CommandPackFromType {
|
||||
name, base: true, ..
|
||||
}) => {
|
||||
vec![(base_parent.clone().map(|(_, s)| s), name.clone())]
|
||||
}
|
||||
PackMapType::FromType(CommandPackFromType {
|
||||
name, base: false, ..
|
||||
}) => {
|
||||
vec![(parent.clone().map(|(_, s)| s), name.clone())]
|
||||
}
|
||||
PackMapType::Maybe(CommandPackMaybe { inner }) => {
|
||||
inner.as_edges(base_parent, parent, level)
|
||||
}
|
||||
PackMapType::Aggregate(CommandPackAggregate { inner }) => {
|
||||
inner.as_edges(base_parent, parent, level)
|
||||
}
|
||||
PackMapType::Child(CommandPackChild { inner, .. }) => {
|
||||
inner.as_edges(parent, &None, level + 1)
|
||||
}
|
||||
PackMapType::Multi(CommandPackMulti { inner }) => {
|
||||
let new_parent = if let Some(PackMapType::FromType(CommandPackFromType {
|
||||
name,
|
||||
base: true,
|
||||
..
|
||||
})) = inner.get_base()
|
||||
{
|
||||
if parent.clone().is_some_and(|(l, _)| l == level) {
|
||||
parent.clone()
|
||||
} else {
|
||||
Some((level, name.clone()))
|
||||
}
|
||||
} else {
|
||||
parent.clone()
|
||||
};
|
||||
|
||||
let mut edges = Vec::new();
|
||||
for t in &inner.0 {
|
||||
edges.extend(t.as_edges(base_parent, &new_parent, level));
|
||||
}
|
||||
|
||||
edges
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,394 @@
|
|||
use crate::model::map_type::{
|
||||
CommandPackAggregate, CommandPackChild, CommandPackMaybe, CommandPackMulti, PackMapType,
|
||||
};
|
||||
use magnetar_calckey_model::ck;
|
||||
use magnetar_sdk::types::Id;
|
||||
use magnetar_sdk::{Packed, Required};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use self::data::id::BaseId;
|
||||
|
||||
pub mod data;
|
||||
pub mod db_pack;
|
||||
pub mod map_type;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct InternalContext {
|
||||
self_user: Option<Arc<ck::user::Model>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PackingContext<T: ?Sized> {
|
||||
context: Arc<InternalContext>,
|
||||
data: Arc<PackMapType>,
|
||||
_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: ?Sized> PackingContext<T> {
|
||||
fn cloned<S>(&self) -> PackingContext<S> {
|
||||
PackingContext {
|
||||
context: self.context.clone(),
|
||||
data: self.data.clone(),
|
||||
_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ReportType: 'static {
|
||||
type Input: 'static;
|
||||
|
||||
fn report_type() -> PackMapType;
|
||||
}
|
||||
|
||||
pub trait PackType: 'static {
|
||||
type Input: 'static;
|
||||
|
||||
fn extract(context: &PackingContext<Self>, data: Self::Input) -> Self;
|
||||
|
||||
fn is_base_type() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PackExtract<T>(T);
|
||||
|
||||
impl<Out> ReportType for PackExtract<Out>
|
||||
where
|
||||
Self: PackType,
|
||||
<Self as PackType>::Input: ReportType + PackType,
|
||||
{
|
||||
type Input = <Self as PackType>::Input;
|
||||
|
||||
fn report_type() -> PackMapType {
|
||||
<Self as PackType>::Input::report_type()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum LinkIdent {
|
||||
Auto,
|
||||
Named(String),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct LinkAuto;
|
||||
|
||||
impl From<LinkAuto> for LinkIdent {
|
||||
fn from(_: LinkAuto) -> Self {
|
||||
LinkIdent::Auto
|
||||
}
|
||||
}
|
||||
|
||||
// Raw input type children
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PackBase<T: 'static>(T);
|
||||
|
||||
impl<In: 'static> ReportType for PackBase<In> {
|
||||
type Input = In;
|
||||
|
||||
fn report_type() -> PackMapType {
|
||||
PackMapType::from_type::<In>(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl<In: 'static> PackType for PackBase<In> {
|
||||
type Input = In;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: <Self as PackType>::Input) -> Self {
|
||||
PackBase(data)
|
||||
}
|
||||
|
||||
fn is_base_type() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// Raw input type children, non-base
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PackExt<T: 'static>(T);
|
||||
|
||||
impl<In: 'static> ReportType for PackExt<In> {
|
||||
type Input = In;
|
||||
|
||||
fn report_type() -> PackMapType {
|
||||
PackMapType::from_type::<In>(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl<In: 'static> PackType for PackExt<In> {
|
||||
type Input = In;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: <Self as PackType>::Input) -> Self {
|
||||
PackExt(data)
|
||||
}
|
||||
}
|
||||
|
||||
// Tree children
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PackChild<T: ReportType + PackType, L: Into<LinkIdent> + Default + 'static>(
|
||||
T,
|
||||
PhantomData<fn() -> L>,
|
||||
);
|
||||
|
||||
impl<In: ReportType + PackType, L: Into<LinkIdent> + Default + 'static> ReportType
|
||||
for PackChild<In, L>
|
||||
{
|
||||
type Input = In;
|
||||
|
||||
fn report_type() -> PackMapType {
|
||||
PackMapType::Child(CommandPackChild {
|
||||
inner: Arc::new(In::report_type()),
|
||||
link: L::into(L::default()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<In: ReportType + PackType, L: Into<LinkIdent> + Default + 'static> PackType
|
||||
for PackChild<In, L>
|
||||
{
|
||||
type Input = In;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: <Self as PackType>::Input) -> Self {
|
||||
PackChild(data, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
// Aggregate multiple entries 1:N
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PackAggregate<T: ReportType + PackType>(Vec<T>);
|
||||
|
||||
impl<In: ReportType + PackType> ReportType for PackAggregate<In> {
|
||||
type Input = In;
|
||||
|
||||
fn report_type() -> PackMapType {
|
||||
PackMapType::Aggregate(CommandPackAggregate {
|
||||
inner: Arc::new(In::report_type()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<In: ReportType + PackType> PackType for PackAggregate<In> {
|
||||
type Input = Vec<In>;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: <Self as PackType>::Input) -> Self {
|
||||
PackAggregate(data)
|
||||
}
|
||||
}
|
||||
|
||||
// An item that may or may not be present
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PackMaybe<T: ReportType + PackType>(Option<T>);
|
||||
|
||||
impl<In: ReportType + PackType> ReportType for PackMaybe<In> {
|
||||
type Input = In;
|
||||
|
||||
fn report_type() -> PackMapType {
|
||||
In::report_type()
|
||||
}
|
||||
}
|
||||
|
||||
impl<In: ReportType + PackType> PackType for PackMaybe<In> {
|
||||
type Input = Option<In>;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: <Self as PackType>::Input) -> Self {
|
||||
PackMaybe(data)
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple adjacent values
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PackMulti<T>(T);
|
||||
|
||||
macro_rules! impl_pack_multi {
|
||||
($($a:ident as $b:ident),*) => {
|
||||
impl<$($a,)*> ReportType for PackMulti<($($a,)*)>
|
||||
where $($a: ReportType + PackType,)* {
|
||||
type Input = ($(<$a as ReportType>::Input,)*);
|
||||
|
||||
fn report_type() -> PackMapType {
|
||||
PackMapType::Multi(CommandPackMulti {
|
||||
inner: vec![$(
|
||||
$a::report_type(),
|
||||
)*].into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($a,)*> PackType for PackMulti<($($a,)*)>
|
||||
where $($a: ReportType + PackType,)* {
|
||||
type Input = ($(<$a as PackType>::Input,)*);
|
||||
|
||||
fn extract(context: &PackingContext<Self>, ($($b,)*): <Self as PackType>::Input) -> Self {
|
||||
PackMulti(($(
|
||||
<$a as PackType>::extract(&context.cloned(), $b),
|
||||
)*))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_pack_multi!();
|
||||
impl_pack_multi!(A as a);
|
||||
impl_pack_multi!(A as a, B as b);
|
||||
impl_pack_multi!(A as a, B as b, C as c);
|
||||
impl_pack_multi!(A as a, B as b, C as c, D as d);
|
||||
impl_pack_multi!(A as a, B as b, C as c, D as d, E as e);
|
||||
impl_pack_multi!(A as a, B as b, C as c, D as d, E as e, F as f);
|
||||
impl_pack_multi!(A as a, B as b, C as c, D as d, E as e, F as f, G as g);
|
||||
impl_pack_multi!(A as a, B as b, C as c, D as d, E as e, F as f, G as g, H as h);
|
||||
|
||||
// API packed types
|
||||
|
||||
pub struct PackPacked<T: Packed, In = <T as Packed>::Input>(T, PhantomData<In>);
|
||||
|
||||
macro_rules! impl_pack_raw_tuple {
|
||||
($($a:ident),*) => {
|
||||
impl<$($a,)* P: Packed> ReportType for PackPacked<P, ($($a,)*)>
|
||||
where $(PackWrapper<$a>: ReportType + PackType),* {
|
||||
type Input = ($($a,)*);
|
||||
|
||||
fn report_type() -> PackMapType {
|
||||
PackMapType::Multi(CommandPackMulti {
|
||||
inner: vec![
|
||||
$(
|
||||
PackWrapper::<$a>::report_type()
|
||||
),*
|
||||
].into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($a,)* P: Packed<Input = ($($a,)*)>> PackType for PackPacked<P, ($($a,)*)>
|
||||
where $(PackWrapper<$a>: ReportType + PackType),* {
|
||||
type Input = ($($a,)*);
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, data: <Self as PackType>::Input) -> Self {
|
||||
PackPacked(P::pack_from(data), PhantomData)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_pack_raw_tuple!(A);
|
||||
impl_pack_raw_tuple!(A, B);
|
||||
impl_pack_raw_tuple!(A, B, C);
|
||||
impl_pack_raw_tuple!(A, B, C, D);
|
||||
impl_pack_raw_tuple!(A, B, C, D, E);
|
||||
impl_pack_raw_tuple!(A, B, C, D, E, F);
|
||||
impl_pack_raw_tuple!(A, B, C, D, E, F, G);
|
||||
impl_pack_raw_tuple!(A, B, C, D, E, F, G, H);
|
||||
|
||||
// Helper wrapper for Multi components
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PackWrapper<T>(T);
|
||||
|
||||
impl<In: 'static> ReportType for PackWrapper<Required<In>>
|
||||
where
|
||||
PackExtract<In>: ReportType + PackType,
|
||||
{
|
||||
type Input = <PackExtract<In> as ReportType>::Input;
|
||||
|
||||
fn report_type() -> PackMapType {
|
||||
PackExtract::<In>::report_type()
|
||||
}
|
||||
}
|
||||
|
||||
impl<In: 'static> PackType for PackWrapper<Required<In>>
|
||||
where
|
||||
PackExtract<In>: ReportType + PackType,
|
||||
{
|
||||
type Input = <PackExtract<In> as PackType>::Input;
|
||||
|
||||
fn extract(context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
PackWrapper(Required(
|
||||
PackExtract::<In>::extract(&context.cloned(), data).0,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<In: 'static> ReportType for PackWrapper<Option<In>>
|
||||
where
|
||||
PackExtract<In>: ReportType + PackType,
|
||||
{
|
||||
type Input = In;
|
||||
|
||||
fn report_type() -> PackMapType {
|
||||
PackMapType::Maybe(CommandPackMaybe {
|
||||
inner: Arc::new(PackExtract::<In>::report_type()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<In: 'static> PackType for PackWrapper<Option<In>>
|
||||
where
|
||||
PackExtract<In>: ReportType + PackType,
|
||||
{
|
||||
type Input = Option<In>;
|
||||
|
||||
fn extract(context: &PackingContext<Self>, data: Self::Input) -> Self {
|
||||
PackWrapper(
|
||||
PackMaybe::<PackExtract<In>>::extract(&context.cloned(), data.map(PackExtract))
|
||||
.0
|
||||
.map(|PackExtract(x)| x),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Concrete extractable types
|
||||
impl PackType for PackExtract<Id> {
|
||||
type Input = PackExt<Box<dyn BaseId>>;
|
||||
|
||||
fn extract(_context: &PackingContext<Self>, PackExt(data): <Self as PackType>::Input) -> Self {
|
||||
PackExtract(Id {
|
||||
id: data.get_id().to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Packing context that gets passed in
|
||||
impl<T> PackingContext<T> {
|
||||
fn self_user(&self) -> Option<&ck::user::Model> {
|
||||
self.context.self_user.as_deref()
|
||||
}
|
||||
|
||||
fn is_self(&self, user: &ck::user::Model) -> bool {
|
||||
self.self_user()
|
||||
.is_some_and(|self_user| self_user.id == user.id)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Packer<T> {
|
||||
context: PackingContext<T>,
|
||||
}
|
||||
|
||||
pub fn pack_from_db<T: PackType + ReportType>(
|
||||
self_user: Option<Arc<ck::user::Model>>,
|
||||
) -> Packer<T> {
|
||||
let types = T::report_type();
|
||||
let context = PackingContext {
|
||||
context: Arc::new(InternalContext { self_user }),
|
||||
data: Arc::new(types),
|
||||
_type: PhantomData,
|
||||
};
|
||||
|
||||
Packer { context }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use magnetar_sdk::types::user::PackUserSelfMaybeAll;
|
||||
|
||||
#[test]
|
||||
fn test_pack_type() {
|
||||
let types = <PackPacked<PackUserSelfMaybeAll> as ReportType>::report_type();
|
||||
println!("{:?}", types);
|
||||
|
||||
let edges = types.as_edges(&None, &None, 0);
|
||||
for (from, to) in edges {
|
||||
println!("{:?} --> {:?}", from, to);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue