From e045d2aae4ab6fb55be05ed5ccbefddb7b71d297 Mon Sep 17 00:00:00 2001 From: Natty Date: Sat, 23 Sep 2023 13:53:55 +0200 Subject: [PATCH] Experimental data model --- src/api_v1/mod.rs | 5 +- src/api_v1/user.rs | 42 +++-- src/main.rs | 1 + src/model/data/drive.rs | 27 +++ src/model/data/emoji.rs | 19 ++ src/model/data/id.rs | 19 ++ src/model/data/mod.rs | 5 + src/model/data/note.rs | 87 +++++++++ src/model/data/user.rs | 260 ++++++++++++++++++++++++++ src/model/db_pack/mod.rs | 0 src/model/map_type.rs | 163 ++++++++++++++++ src/model/mod.rs | 394 +++++++++++++++++++++++++++++++++++++++ 12 files changed, 1008 insertions(+), 14 deletions(-) create mode 100644 src/model/data/drive.rs create mode 100644 src/model/data/emoji.rs create mode 100644 src/model/data/id.rs create mode 100644 src/model/data/mod.rs create mode 100644 src/model/data/note.rs create mode 100644 src/model/data/user.rs create mode 100644 src/model/db_pack/mod.rs create mode 100644 src/model/map_type.rs create mode 100644 src/model/mod.rs diff --git a/src/api_v1/mod.rs b/src/api_v1/mod.rs index 9eefbb7..3d798b3 100644 --- a/src/api_v1/mod.rs +++ b/src/api_v1/mod.rs @@ -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) -> 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, diff --git a/src/api_v1/user.rs b/src/api_v1/user.rs index d8d8f95..f2b7c9b 100644 --- a/src/api_v1/user.rs +++ b/src/api_v1/user.rs @@ -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>, - AuthenticatedUser(user): AuthenticatedUser, -) -> Result { - 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>, + State(_service): State>, + AuthenticatedUser(_user): AuthenticatedUser, +) -> Result>, ApiError> { + todo!() +} + +pub async fn handle_user_info( + Path(_id): Path, + Query(UserByIdReq { + detail: _, + pins: _, + profile: _, + relation: _, + auth: _, + }): Query>, + State(_service): State>, + MaybeUser(_user): MaybeUser, +) -> Result>, ApiError> { + todo!() } diff --git a/src/main.rs b/src/main.rs index 40dcc28..e802e89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod api_v1; +pub mod model; pub mod nodeinfo; pub mod service; pub mod util; diff --git a/src/model/data/drive.rs b/src/model/data/drive.rs new file mode 100644 index 0000000..e267f8e --- /dev/null +++ b/src/model/data/drive.rs @@ -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 { + type Input = PackBase; + + fn extract(context: &PackingContext, 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, + }) + } +} diff --git a/src/model/data/emoji.rs b/src/model/data/emoji.rs new file mode 100644 index 0000000..54cfe29 --- /dev/null +++ b/src/model/data/emoji.rs @@ -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 { + type Input = PackBase; + + fn extract(_context: &PackingContext, 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, + }) + } +} diff --git a/src/model/data/id.rs b/src/model/data/id.rs new file mode 100644 index 0000000..fd1f0fa --- /dev/null +++ b/src/model/data/id.rs @@ -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); diff --git a/src/model/data/mod.rs b/src/model/data/mod.rs new file mode 100644 index 0000000..aeb50c5 --- /dev/null +++ b/src/model/data/mod.rs @@ -0,0 +1,5 @@ +pub mod drive; +pub mod emoji; +pub mod id; +pub mod note; +pub mod user; diff --git a/src/model/data/note.rs b/src/model/data/note.rs new file mode 100644 index 0000000..e39b728 --- /dev/null +++ b/src/model/data/note.rs @@ -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 { + type Input = PackBase; + + fn extract(_context: &PackingContext, 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 { + type Input = PackMulti<( + PackBase, + PackAggregate, LinkAuto>>, + PackChild, LinkAuto>, + PackChild, LinkAuto>, + )>; + + fn extract(_context: &PackingContext, 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::>(); + + 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 { + type Input = PackMulti<( + PackBase, + PackAggregate, LinkAuto>>, + )>; + + fn extract(_context: &PackingContext, data: Self::Input) -> Self { + PackExtract(NoteAttachmentExt { + attachments: todo!(), + poll: todo!(), + }) + } +} diff --git a/src/model/data/user.rs b/src/model/data/user.rs new file mode 100644 index 0000000..7b498e0 --- /dev/null +++ b/src/model/data/user.rs @@ -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 { + type Input = PackMulti<( + PackBase, + PackChild>, LinkAuto>, + )>; + + fn extract(_context: &PackingContext, data: Self::Input) -> Self { + let PackMulti((PackBase(_), PackChild(PackAggregate(emojis), ..))) = data; + + PackExtract(EmojiContext( + emojis + .into_iter() + .map(|PackPacked(t, ..)| t) + .collect::>(), + )) + } +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct UserAvatarLink; +impl From for LinkIdent { + fn from(_: UserAvatarLink) -> Self { + LinkIdent::Named("avatar".to_string()) + } +} + +impl PackType for PackExtract { + type Input = PackMulti<( + PackBase, + PackChild>, UserAvatarLink>, + PackChild, LinkAuto>, + )>; + + fn extract(_context: &PackingContext, 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 { + type Input = PackMulti<( + PackBase, + PackChild, LinkAuto>, + PackMaybe>, + )>; + + fn extract(context: &PackingContext, 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 { + type Input = PackBase; + + fn extract(_context: &PackingContext, 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 { + type Input = PackMulti<( + PackChild, LinkAuto>, + PackChild, LinkAuto>, + PackChild, LinkAuto>, + PackChild, LinkAuto>, + )>; + + fn extract(context: &PackingContext, 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 { + type Input = PackMulti<( + PackBase, + PackChild< + PackAggregate< + PackMulti<( + PackBase, + PackChild, LinkAuto>, + )>, + >, + LinkAuto, + >, + )>; + + fn extract(_context: &PackingContext, 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 { + type Input = PackBase; + + fn extract(_context: &PackingContext, 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 { + type Input = PackMulti<( + PackBase, + PackChild< + PackMulti<( + PackBase, + PackChild>, LinkAuto>, + )>, + LinkAuto, + >, + )>; + + fn extract(_context: &PackingContext, 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::>(); + + PackExtract(UserSecretsExt { + email: profile.email.clone(), + email_verified: profile.email_verified, + security_keys: keys.clone(), + }) + } +} diff --git a/src/model/db_pack/mod.rs b/src/model/db_pack/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/model/map_type.rs b/src/model/map_type.rs new file mode 100644 index 0000000..449f86f --- /dev/null +++ b/src/model/map_type.rs @@ -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>); + +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> for Multi { + fn from(v: Vec) -> 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, +} + +impl PackCommand for CommandPackMaybe {} + +#[derive(Clone, Debug)] +pub struct CommandPackChild { + pub inner: Arc, + pub link: LinkIdent, +} + +impl PackCommand for CommandPackChild {} + +#[derive(Clone, Debug)] +pub struct CommandPackAggregate { + pub inner: Arc, +} + +impl PackCommand for CommandPackAggregate {} + +#[derive(Clone, Debug)] +pub struct CommandPackMulti { + pub inner: Multi, +} + +impl PackCommand for CommandPackMulti {} + +impl PackMapType { + pub fn from_type(base: bool) -> Self { + PackMapType::FromType(CommandPackFromType { + name: std::any::type_name::().to_string(), + id: TypeId::of::(), + 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)> { + 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 + } + } + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs new file mode 100644 index 0000000..aa65374 --- /dev/null +++ b/src/model/mod.rs @@ -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>, +} + +#[derive(Clone, Debug)] +pub struct PackingContext { + context: Arc, + data: Arc, + _type: PhantomData, +} + +impl PackingContext { + fn cloned(&self) -> PackingContext { + 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, data: Self::Input) -> Self; + + fn is_base_type() -> bool { + false + } +} + +#[derive(Clone, Debug)] +pub struct PackExtract(T); + +impl ReportType for PackExtract +where + Self: PackType, + ::Input: ReportType + PackType, +{ + type Input = ::Input; + + fn report_type() -> PackMapType { + ::Input::report_type() + } +} + +#[derive(Clone, Debug)] +pub enum LinkIdent { + Auto, + Named(String), +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct LinkAuto; + +impl From for LinkIdent { + fn from(_: LinkAuto) -> Self { + LinkIdent::Auto + } +} + +// Raw input type children +#[derive(Clone, Debug)] +pub struct PackBase(T); + +impl ReportType for PackBase { + type Input = In; + + fn report_type() -> PackMapType { + PackMapType::from_type::(true) + } +} + +impl PackType for PackBase { + type Input = In; + + fn extract(_context: &PackingContext, data: ::Input) -> Self { + PackBase(data) + } + + fn is_base_type() -> bool { + true + } +} + +// Raw input type children, non-base +#[derive(Clone, Debug)] +pub struct PackExt(T); + +impl ReportType for PackExt { + type Input = In; + + fn report_type() -> PackMapType { + PackMapType::from_type::(false) + } +} + +impl PackType for PackExt { + type Input = In; + + fn extract(_context: &PackingContext, data: ::Input) -> Self { + PackExt(data) + } +} + +// Tree children +#[derive(Clone, Debug)] +pub struct PackChild + Default + 'static>( + T, + PhantomData L>, +); + +impl + Default + 'static> ReportType + for PackChild +{ + type Input = In; + + fn report_type() -> PackMapType { + PackMapType::Child(CommandPackChild { + inner: Arc::new(In::report_type()), + link: L::into(L::default()), + }) + } +} + +impl + Default + 'static> PackType + for PackChild +{ + type Input = In; + + fn extract(_context: &PackingContext, data: ::Input) -> Self { + PackChild(data, PhantomData) + } +} + +// Aggregate multiple entries 1:N +#[derive(Clone, Debug)] +pub struct PackAggregate(Vec); + +impl ReportType for PackAggregate { + type Input = In; + + fn report_type() -> PackMapType { + PackMapType::Aggregate(CommandPackAggregate { + inner: Arc::new(In::report_type()), + }) + } +} + +impl PackType for PackAggregate { + type Input = Vec; + + fn extract(_context: &PackingContext, data: ::Input) -> Self { + PackAggregate(data) + } +} + +// An item that may or may not be present +#[derive(Clone, Debug)] +pub struct PackMaybe(Option); + +impl ReportType for PackMaybe { + type Input = In; + + fn report_type() -> PackMapType { + In::report_type() + } +} + +impl PackType for PackMaybe { + type Input = Option; + + fn extract(_context: &PackingContext, data: ::Input) -> Self { + PackMaybe(data) + } +} + +// Multiple adjacent values +#[derive(Clone, Debug)] +pub struct PackMulti(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, ($($b,)*): ::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::Input>(T, PhantomData); + +macro_rules! impl_pack_raw_tuple { + ($($a:ident),*) => { + impl<$($a,)* P: Packed> ReportType for PackPacked + 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> PackType for PackPacked + where $(PackWrapper<$a>: ReportType + PackType),* { + type Input = ($($a,)*); + + fn extract(_context: &PackingContext, data: ::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); + +impl ReportType for PackWrapper> +where + PackExtract: ReportType + PackType, +{ + type Input = as ReportType>::Input; + + fn report_type() -> PackMapType { + PackExtract::::report_type() + } +} + +impl PackType for PackWrapper> +where + PackExtract: ReportType + PackType, +{ + type Input = as PackType>::Input; + + fn extract(context: &PackingContext, data: Self::Input) -> Self { + PackWrapper(Required( + PackExtract::::extract(&context.cloned(), data).0, + )) + } +} + +impl ReportType for PackWrapper> +where + PackExtract: ReportType + PackType, +{ + type Input = In; + + fn report_type() -> PackMapType { + PackMapType::Maybe(CommandPackMaybe { + inner: Arc::new(PackExtract::::report_type()), + }) + } +} + +impl PackType for PackWrapper> +where + PackExtract: ReportType + PackType, +{ + type Input = Option; + + fn extract(context: &PackingContext, data: Self::Input) -> Self { + PackWrapper( + PackMaybe::>::extract(&context.cloned(), data.map(PackExtract)) + .0 + .map(|PackExtract(x)| x), + ) + } +} + +// Concrete extractable types +impl PackType for PackExtract { + type Input = PackExt>; + + fn extract(_context: &PackingContext, PackExt(data): ::Input) -> Self { + PackExtract(Id { + id: data.get_id().to_owned(), + }) + } +} + +// Packing context that gets passed in +impl PackingContext { + 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 { + context: PackingContext, +} + +pub fn pack_from_db( + self_user: Option>, +) -> Packer { + 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 = as ReportType>::report_type(); + println!("{:?}", types); + + let edges = types.as_edges(&None, &None, 0); + for (from, to) in edges { + println!("{:?} --> {:?}", from, to); + } + } +}