use crate::model_ext::{ AliasSourceExt, AliasSuffixExt, CursorPaginationExt, EntityPrefixExt, MagIden, ModelPagination, }; use crate::{model_ext::SelectColumnsExt, CalckeyDbError, CalckeyModel}; use chrono::{DateTime, Utc}; use ck::{drive_file, follow_request, following, user, user_profile}; use ext_model_migration::{IntoIden, SelectStatement}; use magnetar_sdk::types::SpanFilter; use sea_orm::{ ColumnTrait, DbErr, EntityTrait, FromQueryResult, Iden, JoinType, QueryFilter, QueryResult, QuerySelect, }; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserData { pub user: user::Model, pub profile: Option, pub avatar: Option, pub banner: Option, } impl FromQueryResult for UserData { fn from_query_result(res: &QueryResult, prefix: &str) -> Result { let prefix = if prefix.is_empty() { user::Entity.base_prefix() } else { MagIden::alias(prefix) }; Ok(UserData { user: user::Model::from_query_result(res, &prefix.to_string())?, profile: user_profile::Model::from_query_result_optional( res, &prefix.join_str_as_str(PROFILE), )?, avatar: drive_file::Model::from_query_result_optional( res, &prefix.join_str_as_str(AVATAR), )?, banner: drive_file::Model::from_query_result_optional( res, &prefix.join_str_as_str(BANNER), )?, }) } } impl ModelPagination for UserData { fn id(&self) -> &str { &self.user.id } fn time(&self) -> DateTime { self.user.created_at.into() } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserFollowData { pub follow: following::Model, pub user: UserData, } impl FromQueryResult for UserFollowData { fn from_query_result(res: &QueryResult, prefix: &str) -> Result { Ok(UserFollowData { user: UserData::from_query_result(res, prefix)?, follow: following::Model::from_query_result(res, prefix)?, }) } } impl ModelPagination for UserFollowData { fn id(&self) -> &str { &self.follow.id } fn time(&self) -> DateTime { self.follow.created_at.into() } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserFollowRequestData { pub follow_request: follow_request::Model, pub user: UserData, } impl FromQueryResult for UserFollowRequestData { fn from_query_result(res: &QueryResult, prefix: &str) -> Result { Ok(UserFollowRequestData { user: UserData::from_query_result(res, prefix)?, follow_request: follow_request::Model::from_query_result(res, prefix)?, }) } } impl ModelPagination for UserFollowRequestData { fn id(&self) -> &str { &self.follow_request.id } fn time(&self) -> DateTime { self.follow_request.created_at.into() } } #[derive(Clone)] pub struct UserResolveOptions { pub with_avatar: bool, pub with_banner: bool, pub with_profile: bool, } const PROFILE: &str = "profile."; const AVATAR: &str = "avatar."; const BANNER: &str = "banner."; #[derive(Clone)] pub struct UserResolver { db: CalckeyModel, } impl UserResolver { pub fn new(db: CalckeyModel) -> Self { Self { db } } pub fn resolve( &self, q: &mut SelectStatement, user_tbl: &MagIden, UserResolveOptions { with_avatar, with_banner, with_profile, }: &UserResolveOptions, ) { q.add_aliased_columns::(user_tbl); if *with_profile { let profile_tbl = user_tbl.join_str(PROFILE); q.add_aliased_columns::(&profile_tbl); q.join_columns( JoinType::LeftJoin, user::Relation::UserProfile.with_from_alias(user_tbl), &profile_tbl, ); } if *with_avatar { let avatar_tbl = user_tbl.join_str(AVATAR); q.add_aliased_columns::(&avatar_tbl); q.join_columns( JoinType::LeftJoin, user::Relation::DriveFile2.with_from_alias(user_tbl), &avatar_tbl, ); } if *with_banner { let banner_tbl = user_tbl.join_str(BANNER); q.add_aliased_columns::(&banner_tbl); q.join_columns( JoinType::LeftJoin, user::Relation::DriveFile1.with_from_alias(user_tbl), &banner_tbl, ); } } pub async fn get_follow_requests( &self, options: &UserResolveOptions, followee: &str, pagination: &SpanFilter, prev: &mut Option, next: &mut Option, limit: u64, ) -> Result, CalckeyDbError> { let user_tbl = user::Entity.base_prefix(); let mut select = follow_request::Entity::find().join_as( JoinType::InnerJoin, follow_request::Relation::User1.with_to_alias(&user_tbl), user_tbl.clone().into_iden(), ); let query = QuerySelect::query(&mut select); self.resolve(query, &user_tbl, options); let followers = select .filter(follow_request::Column::FolloweeId.eq(followee)) .get_paginated_model::( &self.db.0, None, ( follow_request::Column::CreatedAt, follow_request::Column::Id, ), pagination, prev, next, limit, ) .await? .into_iter() .map(|u| u.user) .collect(); Ok(followers) } pub async fn get_followees( &self, options: &UserResolveOptions, follower: &str, pagination: &SpanFilter, prev: &mut Option, next: &mut Option, limit: u64, ) -> Result, CalckeyDbError> { let user_tbl = user::Entity.base_prefix(); let mut select = following::Entity::find().join_as( JoinType::InnerJoin, following::Relation::User2.with_to_alias(&user_tbl), user_tbl.clone().into_iden(), ); let query = QuerySelect::query(&mut select); self.resolve(query, &user_tbl, options); let followers = select .filter(following::Column::FollowerId.eq(follower)) .get_paginated_model::( &self.db.0, None, (following::Column::CreatedAt, following::Column::Id), pagination, prev, next, limit, ) .await? .into_iter() .map(|u| u.user) .collect(); Ok(followers) } pub async fn get_followers( &self, options: &UserResolveOptions, followee: &str, pagination: &SpanFilter, prev: &mut Option, next: &mut Option, limit: u64, ) -> Result, CalckeyDbError> { let user_tbl = user::Entity.base_prefix(); let mut select = following::Entity::find().join_as( JoinType::InnerJoin, following::Relation::User1.with_to_alias(&user_tbl), user_tbl.clone().into_iden(), ); let query = QuerySelect::query(&mut select); self.resolve(query, &user_tbl, options); let followers = select .filter(following::Column::FolloweeId.eq(followee)) .get_paginated_model::( &self.db.0, None, (following::Column::CreatedAt, following::Column::Id), pagination, prev, next, limit, ) .await? .into_iter() .map(|u| u.user) .collect(); Ok(followers) } }