mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Implement most of clans functionality; close #89
This commit is contained in:
@@ -29,6 +29,7 @@ async fn main() -> std::io::Result<()> {
|
||||
.service(robocraft::user_avatar::get)
|
||||
.service(robocraft::user_avatar::post)
|
||||
.service(robocraft::clan_avatar::get)
|
||||
.service(robocraft::clan_avatar::post)
|
||||
.service(robocraft::brawl_data::get)
|
||||
.service(robocraft::campaign_data::get)
|
||||
.service(robocraft::factory::arc::get)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use actix_web::{get, web::{Data, Path}, Responder};
|
||||
use actix_web::{get, post, web::{Data, Path, Bytes}, Responder};
|
||||
|
||||
#[get("/clanavatar/Live/{name}")]
|
||||
pub async fn get(cli: Data<crate::cli::CliArgs>, name: Path<String>) -> impl Responder {
|
||||
@@ -11,3 +11,12 @@ pub async fn get(cli: Data<crate::cli::CliArgs>, name: Path<String>) -> impl Res
|
||||
actix_files::NamedFile::open_async(std::path::PathBuf::from(&cli.assets_robocraft).join(super::DEFAULT_IMAGE)).await
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/clanavatar/Live/{name}")]
|
||||
pub async fn post(cli: Data<crate::cli::CliArgs>, auth: Data<crate::robocraft::IntercomAuth>, name: Path<String>, body: Bytes, req: actix_web::HttpRequest) -> Result<actix_web::HttpResponse, super::IntercomOpError> {
|
||||
auth.validate(&req, &name)?;
|
||||
let path = std::path::PathBuf::from(&cli.data_robocraft).join("clanavatar").join(format!("{}.jpg", *name));
|
||||
log::debug!("Saving clanavatar for {} to {}: {}B", name, path.display(), body.len());
|
||||
std::fs::write(path, &body).map_err(super::IntercomOpError::Io)?;
|
||||
Ok(actix_web::HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
@@ -33,6 +33,22 @@ impl super::account_json::UserData {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) async fn save_clan_avatar(&self, image: Vec<u8>, clan_name: &str) -> Result<(), polariton_server::operations::SimpleOpError> {
|
||||
// seems to always be jpg
|
||||
let token = generate_token(clan_name.as_bytes(), &self.secret);
|
||||
let auth_header_val = format!("Internal {}", token);
|
||||
let url = format!("{}/clanavatar/Live/{}", self.cdn, clan_name);
|
||||
if let Err(e) = self.http_client.post(url)
|
||||
.header("Authorization", auth_header_val)
|
||||
.body(image)
|
||||
.send()
|
||||
.await {
|
||||
log::error!("Failed to update clan avatar for {} ({}): {}", self.account.public_id, self.account.id, e);
|
||||
return Err((crate::data::error_codes::SocialErrorCode::UnexpectedError as i16).into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
||||
@@ -11,7 +11,7 @@ mod inventory;
|
||||
pub use inventory::{UnlockedParts, UnlockOverride};
|
||||
|
||||
mod traits;
|
||||
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, UserLoginInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, SocialUserC, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser, FriendInviteReturn, FriendData, FriendInviteStatus, SocialInfo};
|
||||
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, UserLoginInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, SocialUserC, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser, FriendInviteReturn, FriendData, FriendInviteStatus, SocialInfo, ClanData, ClanMember, ClanMemberRank, ClanType, ClanSearchQuery, ClanInviteData};
|
||||
|
||||
pub mod intercom;
|
||||
pub use intercom::generate_token as generate_intercom_token;
|
||||
|
||||
@@ -28,6 +28,39 @@ impl UserData {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn clan_members_of_clan(&self, clan_id: i32) -> Result<Vec<super::ClanMember>, polariton_server::operations::SimpleOpError> {
|
||||
let members = self.db.clan_members_by_clan_id(clan_id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve members of clan {} by user {}: {}", clan_id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve members of clan: {}", e),
|
||||
)
|
||||
})?;
|
||||
let avatar_infos = self.db.user_auxs_by_user_ids_and_descriptor(members.iter().map(|(m, _)| m.user_id), oj_rc_database::schema::user_aux::Descriptor::AvatarId).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve clan {} member avatars by user {}: {}", clan_id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve clan member avatars: {}", e),
|
||||
)
|
||||
})?;
|
||||
let avatar_infos: std::collections::HashMap<i32, Option<i32>> = avatar_infos.into_iter()
|
||||
.map(|aux| (aux.user_id, aux.data.parse().ok()))
|
||||
.collect();
|
||||
Ok(members.into_iter()
|
||||
.map(|(member, user)| super::ClanMember {
|
||||
public_id: user.public_id,
|
||||
display_name: user.display_name,
|
||||
is_confirmed: matches!(member.status, oj_rc_database::schema::clan_member::ClanMemberStatus::Confirmed),
|
||||
avatar_id: avatar_infos.get(&user.id).copied().flatten(),
|
||||
rank: super::ClanMemberRank::db_to_core(&member.rank),
|
||||
season_xp: 0, // TODO clan seasons
|
||||
})
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -198,6 +231,829 @@ impl super::SocialUser for UserData {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
async fn my_clan_info(&self, include_members: bool) -> Result<Option<(super::ClanData, Vec<super::ClanMember>)>, polariton_server::operations::SimpleOpError> {
|
||||
let clan_opt = self.db.clan_by_user_id(self.account.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve clan for user {}: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve clan: {}", e),
|
||||
)
|
||||
})?;
|
||||
if let Some((clan, _member)) = clan_opt {
|
||||
if include_members {
|
||||
let members = self.clan_members_of_clan(clan.id).await?;
|
||||
Ok(Some((
|
||||
super::ClanData {
|
||||
name: clan.name,
|
||||
description: clan.description,
|
||||
ty: super::ClanType::db_to_core(&clan.variant),
|
||||
size: members.iter().filter(|x| x.is_confirmed).count() as _,
|
||||
},
|
||||
members,
|
||||
)))
|
||||
} else {
|
||||
Ok(Some((
|
||||
super::ClanData {
|
||||
name: clan.name,
|
||||
description: clan.description,
|
||||
ty: super::ClanType::db_to_core(&clan.variant),
|
||||
size: 0,
|
||||
},
|
||||
Vec::default(),
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn clan_info(&self, clan_name: &str) -> Result<Option<(super::ClanData, Vec<super::ClanMember>)>, polariton_server::operations::SimpleOpError> {
|
||||
let clan_opt = self.db.clan_by_name(clan_name.to_owned()).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve clan {} for user {}: {}", clan_name, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve clan {}: {}", clan_name, e),
|
||||
)
|
||||
})?;
|
||||
if let Some(clan) = clan_opt {
|
||||
let members = self.clan_members_of_clan(clan.id).await?;
|
||||
Ok(Some((
|
||||
super::ClanData {
|
||||
name: clan.name,
|
||||
description: clan.description,
|
||||
ty: super::ClanType::db_to_core(&clan.variant),
|
||||
size: members.iter().filter(|x| x.is_confirmed).count() as _,
|
||||
},
|
||||
members,
|
||||
)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn search_clan(&self, search: super::ClanSearchQuery)-> Result<Vec<super::ClanData>, polariton_server::operations::SimpleOpError> {
|
||||
// TODO support days since active (if it's actually used by client)
|
||||
let clan_results = self.db.clans_by_search(
|
||||
search.search_string,
|
||||
search.start_range as u64,
|
||||
search.end_range as u64,
|
||||
search.types.into_iter().map(super::ClanType::core_to_db)
|
||||
).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve clans for user {}: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve clans: {}", e),
|
||||
)
|
||||
})?;
|
||||
// FIXME retrieve all member counts in one database call
|
||||
let mut member_counts = std::collections::HashMap::with_capacity(clan_results.len());
|
||||
for clan in clan_results.iter() {
|
||||
let member_count = self.db.count_clan_members_by_clan_id(clan.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve clan {} member count for user {}: {}", clan.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve clan {} member count: {}", clan.id, e),
|
||||
)
|
||||
})?;
|
||||
member_counts.insert(clan.id, member_count);
|
||||
}
|
||||
Ok(clan_results.into_iter()
|
||||
.map(|clan| super::ClanData {
|
||||
name: clan.name,
|
||||
description: clan.description,
|
||||
ty: super::ClanType::db_to_core(&clan.variant),
|
||||
size: member_counts.get(&clan.id).map(|x| *x as i32).unwrap_or_default(),
|
||||
})
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
||||
async fn create_clan(&self, clan: super::ClanData, avatar: Vec<u8>)-> Result<Vec<super::ClanMember>, polariton_server::operations::SimpleOpError> {
|
||||
// TODO make this a transaction
|
||||
let existing_clan = self.db.clan_by_name(clan.name.clone()).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to do exist check on clan {} for user {}: {}", clan.name, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to do exist check on clan {}: {}", clan.name, e),
|
||||
)
|
||||
})?;
|
||||
if existing_clan.is_some() {
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::ClanAlreadyExists as i16,
|
||||
format!("Clan with name \"{}\" is already in database", clan.name),
|
||||
));
|
||||
}
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
let new_clan = oj_rc_database::schema::clan::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
||||
name: oj_rc_database::sea_orm::ActiveValue::Set(clan.name.clone()),
|
||||
description: oj_rc_database::sea_orm::ActiveValue::Set(clan.description),
|
||||
variant: oj_rc_database::sea_orm::ActiveValue::Set(clan.ty.core_to_db()),
|
||||
};
|
||||
let new_clan = self.db.insert_clan(new_clan).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to save new clan for user {}: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to save new clan: {}", e),
|
||||
)
|
||||
})?;
|
||||
let first_member = oj_rc_database::schema::clan_member::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
||||
user_id: oj_rc_database::sea_orm::ActiveValue::Set(self.account.id),
|
||||
clan_id: oj_rc_database::sea_orm::ActiveValue::Set(new_clan.id),
|
||||
rank: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::clan_member::ClanMemberRank::Leader),
|
||||
status: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::clan_member::ClanMemberStatus::Confirmed),
|
||||
};
|
||||
self.db.insert_clan_member(first_member).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to save first clan {} member of user {}: {}", new_clan.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to save first clan {} member: {}", new_clan.id, e),
|
||||
)
|
||||
})?;
|
||||
let avatar_info: Option<i32> = self.db.user_aux_by_user_id_and_descriptor(self.account.id, oj_rc_database::schema::user_aux::Descriptor::AvatarId).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve avatar info of user {}: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve avatar info: {}", e),
|
||||
)
|
||||
})?
|
||||
.map(|x| x.data.parse().ok()).flatten();
|
||||
|
||||
if !avatar.is_empty() {
|
||||
self.save_clan_avatar(avatar, &clan.name).await?;
|
||||
}
|
||||
|
||||
Ok(vec![
|
||||
super::ClanMember {
|
||||
public_id: self.account.public_id.clone(),
|
||||
display_name: self.account.display_name.clone(),
|
||||
is_confirmed: true,
|
||||
avatar_id: avatar_info,
|
||||
rank: super::ClanMemberRank::Leader,
|
||||
season_xp: 0,
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
async fn join_clan(&self, clan_name: &str) -> Result<(super::ClanData, Vec<super::ClanMember>), polariton_server::operations::SimpleOpError> {
|
||||
let current_clan = self.db.clan_by_user_id(self.account.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve clan for user {}: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve clan: {}", e),
|
||||
)
|
||||
})?;
|
||||
if current_clan.is_some() {
|
||||
log::debug!("User {} cannot join a clan; they are already in one", self.account.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::AlreadyInClan as i16,
|
||||
format!("User {} is already in clan", self.account.id),
|
||||
));
|
||||
}
|
||||
let existing_clan = self.db.clan_by_name(clan_name.to_owned()).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve existing clan {} for user {}: {}", clan_name, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve existing clan {}: {}", clan_name, e),
|
||||
)
|
||||
})?;
|
||||
if let Some(clan) = existing_clan {
|
||||
// pre-join checks
|
||||
let invited_clan_opt = self.db.clan_invited_to_for_user_id_and_clan_id(self.account.id, clan.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve clan invites of user {}: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to save new clan invites: {}", e),
|
||||
)
|
||||
})?;
|
||||
match clan.variant {
|
||||
oj_rc_database::schema::clan::ClanType::Public => {
|
||||
// nothing
|
||||
},
|
||||
oj_rc_database::schema::clan::ClanType::Private => {
|
||||
let is_invited = invited_clan_opt.is_some();
|
||||
if !is_invited {
|
||||
log::debug!("User {} tried to join clan {} without being invited", self.account.id, clan.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::ClanClosed as i16,
|
||||
format!("Clan \"{}\" is private and user {} is not invited", clan.name, self.account.id),
|
||||
));
|
||||
}
|
||||
},
|
||||
oj_rc_database::schema::clan::ClanType::Abandoned => {
|
||||
log::debug!("User {} tried to join abandoned clan {}", self.account.id, clan.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::ClanClosed as i16,
|
||||
format!("Clan \"{}\" is abandoned", clan.name),
|
||||
));
|
||||
},
|
||||
oj_rc_database::schema::clan::ClanType::Banned => {
|
||||
log::debug!("User {} tried to join banned clan {}", self.account.id, clan.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::ClanClosed as i16,
|
||||
format!("Clan \"{}\" is banned", clan.name),
|
||||
));
|
||||
},
|
||||
}
|
||||
if let Some((_invited_clan, invited_member)) = invited_clan_opt {
|
||||
self.db.update_clan_member(oj_rc_database::schema::clan_member::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::Set(invited_member.id),
|
||||
status: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::clan_member::ClanMemberStatus::Confirmed),
|
||||
..Default::default()
|
||||
}).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to update invited clan {} member of user {}: {}", clan.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to update invited clan {} member: {}", clan.id, e),
|
||||
)
|
||||
})?;
|
||||
} else {
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
let new_member = oj_rc_database::schema::clan_member::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
||||
user_id: oj_rc_database::sea_orm::ActiveValue::Set(self.account.id),
|
||||
clan_id: oj_rc_database::sea_orm::ActiveValue::Set(clan.id),
|
||||
rank: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::clan_member::ClanMemberRank::Member),
|
||||
status: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::clan_member::ClanMemberStatus::Confirmed),
|
||||
};
|
||||
self.db.insert_clan_member(new_member).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to save new clan {} member of user {}: {}", clan.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to save new clan {} member: {}", clan.id, e),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
self.db.update_clan_member_decline_all_invites(self.account.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to decline clan invites for user {}: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to decline clan invites: {}", e),
|
||||
)
|
||||
})?;
|
||||
let members = self.clan_members_of_clan(clan.id).await?;
|
||||
Ok((
|
||||
super::ClanData {
|
||||
name: clan.name,
|
||||
description: clan.description,
|
||||
ty: super::ClanType::db_to_core(&clan.variant),
|
||||
size: members.iter().filter(|x| x.is_confirmed).count() as _,
|
||||
},
|
||||
members,
|
||||
))
|
||||
} else {
|
||||
log::warn!("Failed to find clan with name \"{}\" in database", clan_name);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::ClanNotFound as i16,
|
||||
format!("Clan with name \"{}\" is not in database", clan_name),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async fn leave_clan(&self) -> Result<bool, polariton_server::operations::SimpleOpError> {
|
||||
let joined_clan_opt = self.db.clan_by_user_id(self.account.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve clan of user {}: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve clan of user: {}", e),
|
||||
)
|
||||
})?;
|
||||
if let Some((joined_clan, member)) = joined_clan_opt {
|
||||
let is_leader = matches!(member.rank, oj_rc_database::schema::clan_member::ClanMemberRank::Leader);
|
||||
if is_leader {
|
||||
log::warn!("User {} tried to leave clan {} that they lead", self.account.id, joined_clan.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::NotClanLeader as i16,
|
||||
format!("Failed to leave clan: you are the leader"),
|
||||
));
|
||||
}
|
||||
let new_model = oj_rc_database::schema::clan_member::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::Set(member.id),
|
||||
status: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::clan_member::ClanMemberStatus::Deactivated),
|
||||
..Default::default()
|
||||
};
|
||||
self.db.update_clan_member(new_model).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to update clan {} member for user {}: {}", joined_clan.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve update clan member for user: {}", e),
|
||||
)
|
||||
})?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
async fn remove_user_from_clan(&self, public_id: &str) -> Result<bool, polariton_server::operations::SimpleOpError> {
|
||||
let joined_clan_opt = self.db.clan_by_user_id(self.account.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve clan of user {}: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve clan of user: {}", e),
|
||||
)
|
||||
})?;
|
||||
if let Some((joined_clan, member)) = joined_clan_opt {
|
||||
let is_leader = matches!(member.rank, oj_rc_database::schema::clan_member::ClanMemberRank::Leader);
|
||||
let is_officer = matches!(member.rank, oj_rc_database::schema::clan_member::ClanMemberRank::Officer);
|
||||
if !(is_leader || is_officer) {
|
||||
log::warn!("User {} tried to remove user {} from clan {} without permission", self.account.id, public_id, joined_clan.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::ClanRankTooLow as i16,
|
||||
format!("Failed to remove user from clan: no permission"),
|
||||
));
|
||||
}
|
||||
let members = self.db.clan_members_by_clan_id(joined_clan.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve joined clan {} members for user {}: {}", joined_clan.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve joined clan {} members for user: {}", joined_clan.id, e),
|
||||
)
|
||||
})?;
|
||||
let target_member_opt = members.iter()
|
||||
.find(|(_mem, user)| user.public_id == public_id);
|
||||
if let Some((target_member, target_user)) = target_member_opt {
|
||||
let can_kick = match target_member.rank {
|
||||
oj_rc_database::schema::clan_member::ClanMemberRank::Member => true, // already guaranteed to be leader OR officer
|
||||
oj_rc_database::schema::clan_member::ClanMemberRank::Officer => is_leader,
|
||||
oj_rc_database::schema::clan_member::ClanMemberRank::Leader => false,
|
||||
};
|
||||
if !can_kick {
|
||||
log::warn!("User {} tried to remove user {} from clan {} with insufficient permission", self.account.id, target_user.id, joined_clan.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::ClanRankTooLow as i16,
|
||||
format!("Failed to remove user from clan: insufficient permission"),
|
||||
));
|
||||
}
|
||||
let new_model = oj_rc_database::schema::clan_member::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::Set(target_member.id),
|
||||
status: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::clan_member::ClanMemberStatus::Deactivated),
|
||||
..Default::default()
|
||||
};
|
||||
self.db.update_clan_member(new_model).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to update clan {} member for user {}: {}", joined_clan.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve update clan member for user: {}", e),
|
||||
)
|
||||
})?;
|
||||
Ok(true)
|
||||
} else {
|
||||
log::warn!("User {} tried to remove non-existent user {} from clan {}", self.account.id, public_id, joined_clan.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::UserNotFoundInClan as i16,
|
||||
format!("Failed to remove user from clan: user not in clan"),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
log::debug!("User {} (not in a clan) tried to remove user {} from a clan", self.account.id, public_id);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_clan(&self, name: Option<String>, description: Option<String>, ty: Option<super::ClanType>, avatar: Option<Vec<u8>>) -> Result<Vec<super::ClanMember>, polariton_server::operations::SimpleOpError> {
|
||||
let joined_clan_opt = self.db.clan_by_user_id(self.account.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve clan of user {}: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve clan of user: {}", e),
|
||||
)
|
||||
})?;
|
||||
if let Some((joined_clan, member)) = joined_clan_opt {
|
||||
let is_leader = matches!(member.rank, oj_rc_database::schema::clan_member::ClanMemberRank::Leader);
|
||||
if !is_leader {
|
||||
log::debug!("User {} is not leader but tried to modify clan {}", self.account.id, joined_clan.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::NotClanLeader as i16,
|
||||
format!("Failed to modify clan: user is not the leader"),
|
||||
));
|
||||
}
|
||||
let new_clan = oj_rc_database::schema::clan::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::Set(joined_clan.id),
|
||||
creation_time: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
name: if let Some(name) = name { oj_rc_database::sea_orm::ActiveValue::Set(name) } else { oj_rc_database::sea_orm::ActiveValue::NotSet },
|
||||
description: if let Some(description) = description { oj_rc_database::sea_orm::ActiveValue::Set(description) } else { oj_rc_database::sea_orm::ActiveValue::NotSet },
|
||||
variant: if let Some(ty) = ty { oj_rc_database::sea_orm::ActiveValue::Set(ty.core_to_db()) } else { oj_rc_database::sea_orm::ActiveValue::NotSet },
|
||||
};
|
||||
self.db.update_clan(new_clan).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to update clan {} for user {}: {}", joined_clan.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to update clan for user: {}", e),
|
||||
)
|
||||
})?;
|
||||
if let Some(new_avatar) = avatar {
|
||||
self.save_clan_avatar(new_avatar, &joined_clan.name).await?;
|
||||
}
|
||||
self.clan_members_of_clan(joined_clan.id).await
|
||||
} else {
|
||||
log::debug!("User {} is not in a clan but tried to modify clan", self.account.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::UserNotInClan as i16,
|
||||
format!("Failed to modify clan for user not in clan"),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
async fn invite_to_clan(&self, public_id: &str) -> Result<super::ClanMember, polariton_server::operations::SimpleOpError> {
|
||||
// TODO make this a transaction
|
||||
let my_clan_opt = self.db.clan_by_user_id(self.account.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} to invite to clan for user {}: {}", public_id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user to invite to clan for user: {}", e),
|
||||
)
|
||||
})?;
|
||||
if let Some((my_clan, my_member)) = my_clan_opt {
|
||||
if matches!(my_member.rank, oj_rc_database::schema::clan_member::ClanMemberRank::Member) {
|
||||
log::debug!("User {} cannot invite to clan {} (rank is member)", self.account.id, my_clan.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::ClanRankTooLow as i16,
|
||||
"User cannot invite to clan (rank is member)".to_owned(),
|
||||
));
|
||||
}
|
||||
let target_user = self.db.user_by_public_id(public_id.to_owned()).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} to invite to clan for user {}: {}", public_id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user to invite to clan for user: {}", e),
|
||||
)
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
log::debug!("Cannot find user {} to invite to clan {} by user {}", public_id, my_clan.id, self.account.id);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::UserDoesNotExist as i16,
|
||||
"Failed to find user to invite to clan by user".to_owned(),
|
||||
)
|
||||
})?;
|
||||
let invite_opt = self.db.clan_invited_to_for_user_id_and_clan_id(target_user.id, my_clan.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} clan invite by user {}: {}", target_user.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user clan invite by user: {}", e),
|
||||
)
|
||||
})?;
|
||||
if invite_opt.is_some() {
|
||||
log::debug!("User {} is already invited to clan {} by user {}", public_id, my_clan.id, self.account.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::AlreadyInvited as i16,
|
||||
"User is already invited to clan".to_owned(),
|
||||
));
|
||||
}
|
||||
let target_user_clan_opt = self.db.clan_by_user_id(target_user.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} clan by user {}: {}", target_user.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user clan by user: {}", e),
|
||||
)
|
||||
})?;
|
||||
if let Some((target_user_clan, _target_user_member)) = target_user_clan_opt {
|
||||
log::debug!("User {} is already in clan {}; cannot do invite by user {}", target_user.id, target_user_clan.id, self.account.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::AlreadyInClan as i16,
|
||||
"User is already in clan".to_owned(),
|
||||
));
|
||||
}
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
let new_invite = oj_rc_database::schema::clan_member::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
||||
user_id: oj_rc_database::sea_orm::ActiveValue::Set(target_user.id),
|
||||
clan_id: oj_rc_database::sea_orm::ActiveValue::Set(my_clan.id),
|
||||
rank: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::clan_member::ClanMemberRank::Member),
|
||||
status: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::clan_member::ClanMemberStatus::Invited),
|
||||
};
|
||||
self.db.insert_clan_member(new_invite).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} clan by user {}: {}", target_user.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user clan by user: {}", e),
|
||||
)
|
||||
})?;
|
||||
let avatar_id = self.db.user_aux_by_user_id_and_descriptor(target_user.id, oj_rc_database::schema::user_aux::Descriptor::AvatarId).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} avatar info by user {}: {}", target_user.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user avatar info by user: {}", e),
|
||||
)
|
||||
})?
|
||||
.and_then(|x| x.data.parse().ok());
|
||||
Ok(super::ClanMember {
|
||||
public_id: target_user.public_id,
|
||||
display_name: target_user.display_name,
|
||||
is_confirmed: false,
|
||||
avatar_id: avatar_id,
|
||||
rank: super::ClanMemberRank::Member,
|
||||
season_xp: 0,
|
||||
})
|
||||
} else {
|
||||
log::debug!("User {} tried to invite user {} to their non-existent clan", self.account.id, public_id);
|
||||
Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::UserNotInClan as i16,
|
||||
format!("Failed to invite user to clan for user: invitee is not in clan"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn my_clan_invites(&self) -> Result<Vec<super::ClanInviteData>, polariton_server::operations::SimpleOpError> {
|
||||
let invites = self.db.clans_invited_to_for_user_id(self.account.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} clan invites: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user clan invites: {}", e),
|
||||
)
|
||||
})?;
|
||||
let clan_ids = invites.iter().map(|(clan, _invite)| clan.id);
|
||||
let clan_leaders = self.db.clan_leaders_by_clan_ids(clan_ids).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} clan invite leaders: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user clan invite leaders: {}", e),
|
||||
)
|
||||
})?;
|
||||
let clan_leaders: std::collections::HashMap::<i32, oj_rc_database::schema::user::Model> = clan_leaders.into_iter()
|
||||
.map(|(member, user)| (member.clan_id, user))
|
||||
.collect();
|
||||
let clan_leader_ids = clan_leaders.values().map(|user| user.id);
|
||||
let clan_leaders_avatars = self.db.user_auxs_by_user_ids_and_descriptor(
|
||||
clan_leader_ids,
|
||||
oj_rc_database::schema::user_aux::Descriptor::AvatarId
|
||||
).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} clan invite leaders' avatars: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user clan invite leaders' avatar: {}", e),
|
||||
)
|
||||
})?;
|
||||
let clan_leaders_avatars: std::collections::HashMap::<i32, Option<i32>> = clan_leaders_avatars.into_iter()
|
||||
.map(|avatar| (avatar.user_id, avatar.data.parse().ok()))
|
||||
.collect();
|
||||
let mut clan_invites = Vec::with_capacity(invites.len());
|
||||
for (invite_clan, _invite_member) in invites {
|
||||
if let Some(invitee) = clan_leaders.get(&invite_clan.id) {
|
||||
clan_invites.push(super::ClanInviteData {
|
||||
public_id: invitee.public_id.clone(),
|
||||
display_name: invitee.display_name.clone(),
|
||||
avatar_id: clan_leaders_avatars.get(&invitee.id).copied().flatten(),
|
||||
clan_name: invite_clan.name,
|
||||
clan_description: invite_clan.description,
|
||||
size: 0, // TODO
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(clan_invites)
|
||||
}
|
||||
|
||||
async fn decline_clan_invite(&self, clan_name: &str) -> Result<Vec<super::ClanMember>, polariton_server::operations::SimpleOpError> {
|
||||
let clan_invite_opt = self.db.clan_invited_to_for_user_id_and_clan_name(self.account.id, clan_name.to_owned()).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} clan {} invite: {}", self.account.id, clan_name, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user clan invite: {}", e),
|
||||
)
|
||||
})?;
|
||||
if let Some((clan, invite)) = clan_invite_opt {
|
||||
let to_update = oj_rc_database::schema::clan_member::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::Set(invite.id),
|
||||
status: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::clan_member::ClanMemberStatus::Deactivated),
|
||||
..Default::default()
|
||||
};
|
||||
self.db.update_clan_member(to_update).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to update user {} clan {} invite {} to declined: {}", self.account.id, clan.id, invite.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to update user clan invite to declined: {}", e),
|
||||
)
|
||||
})?;
|
||||
self.clan_members_of_clan(clan.id).await
|
||||
} else {
|
||||
log::debug!("User {} cannot decline non-existent invite to clan {}", self.account.id, clan_name);
|
||||
Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::NoInvite as i16,
|
||||
format!("User cannot decline non-existent clan invite"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn decline_all_clan_invites(&self) -> Result<bool, polariton_server::operations::SimpleOpError> {
|
||||
self.db.update_clan_member_decline_all_invites(self.account.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to update all clan invites to decline for user {}: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to update all clan invites to decline for user: {}", e),
|
||||
)
|
||||
})?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn cancel_invite_to_clan(&self, public_id: &str) -> Result<(super::ClanData, Vec<super::ClanMember>), polariton_server::operations::SimpleOpError> {
|
||||
let user_opt = self.db.user_by_public_id(public_id.to_owned()).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} to cancel invite by user {}: {}", public_id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user to cancel invite by user: {}", e),
|
||||
)
|
||||
})?;
|
||||
if let Some(user) = user_opt {
|
||||
let (my_clan, _my_member) = self.db.clan_by_user_id(self.account.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} clan to cancel invite: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user clan to cancel invite by user: {}", e),
|
||||
)
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
log::debug!("Cannot find user {} clan to cancel invite", self.account.id);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::UserNotInClan as i16,
|
||||
"Cannot find user clan to cancel invite by user".to_owned(),
|
||||
)
|
||||
})?;
|
||||
let (_clan, invite) = self.db.clan_invited_to_for_user_id_and_clan_id(user.id, my_clan.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} clan invite to cancel invite for user {}: {}", user.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user clan invite to cancel invite by user: {}", e),
|
||||
)
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
log::debug!("Cannot find user {} clan invite to cancel invite for user {}", user.id, self.account.id);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::NoInvite as i16,
|
||||
"Cannot find user clan to cancel invite by user".to_owned(),
|
||||
)
|
||||
})?;
|
||||
let new_invite = oj_rc_database::schema::clan_member::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::Set(invite.id),
|
||||
status: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::clan_member::ClanMemberStatus::Deactivated),
|
||||
..Default::default()
|
||||
};
|
||||
self.db.update_clan_member(new_invite).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to update user {} clan invite to cancel invite for user {}: {}", user.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to udpate user clan invite to cancel invite by user: {}", e),
|
||||
)
|
||||
})?;
|
||||
let members = self.clan_members_of_clan(my_clan.id).await?;
|
||||
Ok((
|
||||
super::ClanData {
|
||||
name: my_clan.name,
|
||||
description: my_clan.description,
|
||||
ty: super::ClanType::db_to_core(&my_clan.variant),
|
||||
size: members.len() as _,
|
||||
},
|
||||
members,
|
||||
))
|
||||
} else {
|
||||
log::debug!("Cannot find user {} to cancel invite by user {}", public_id, self.account.id);
|
||||
Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::UserDoesNotExist as i16,
|
||||
"Cannot find user to cancel invite by user".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_clan_member(&self, public_id: &str, rank: super::ClanMemberRank) -> Result<Vec<super::ClanMember>, polariton_server::operations::SimpleOpError> {
|
||||
let user = self.db.user_by_public_id(public_id.to_owned()).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} to update clan member for user {}: {}", public_id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user to update clan member for user: {}", e),
|
||||
)
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
log::debug!("Cannot find user {} to update clan member for user {}", public_id, self.account.id);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::UserDoesNotExist as i16,
|
||||
"Cannot find user to update clan member for user".to_owned(),
|
||||
)
|
||||
})?;
|
||||
let (my_clan, my_member) = self.db.clan_by_user_id(self.account.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} clan to update clan member: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user clan to clan member: {}", e),
|
||||
)
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
log::debug!("Cannot find user {} clan to update clan member", self.account.id);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::UserNotInClan as i16,
|
||||
"Cannot find user clan to udpate clan member".to_owned(),
|
||||
)
|
||||
})?;
|
||||
let is_leader = matches!(my_member.rank, oj_rc_database::schema::clan_member::ClanMemberRank::Leader);
|
||||
let is_officer = matches!(my_member.rank, oj_rc_database::schema::clan_member::ClanMemberRank::Officer);
|
||||
let can_update = match rank {
|
||||
super::ClanMemberRank::Member => is_leader,
|
||||
super::ClanMemberRank::Officer => is_officer || is_leader,
|
||||
super::ClanMemberRank::Leader => is_leader,
|
||||
};
|
||||
if !can_update {
|
||||
log::debug!("User {} does not have enough permissions to update clan member rank of user {}", self.account.id, user.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::ClanRankTooLow as i16,
|
||||
"User does not have enough permissions to update clan member rank of user".to_owned(),
|
||||
));
|
||||
}
|
||||
let (target_clan, target_member) = self.db.clan_by_user_id(user.id).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve user {} clan to update clan member for user {}: {}", user.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to retrieve user clan to clan member: {}", e),
|
||||
)
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
log::debug!("Cannot find user {} clan to update clan member for user {}", user.id, self.account.id);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::UserNotInClan as i16,
|
||||
"Cannot find user clan to udpate clan member".to_owned(),
|
||||
)
|
||||
})?;
|
||||
if target_clan.id != my_clan.id {
|
||||
log::debug!("User {} is not in same clan as user {} to update clan member rank", self.account.id, user.id);
|
||||
return Err(polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::UserNotInClan as i16,
|
||||
"User is not in same clan as user to update clan member rank".to_owned(),
|
||||
));
|
||||
}
|
||||
let to_update = oj_rc_database::schema::clan_member::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::Set(target_member.id),
|
||||
rank: oj_rc_database::sea_orm::ActiveValue::Set(rank.core_to_db()),
|
||||
..Default::default()
|
||||
};
|
||||
self.db.update_clan_member(to_update).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to update user {} clan member rank by user {}: {}", user.id, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to udpate user clan member rank by user: {}", e),
|
||||
)
|
||||
})?;
|
||||
if matches!(rank, super::ClanMemberRank::Leader) {
|
||||
let to_update = oj_rc_database::schema::clan_member::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::Set(my_member.id),
|
||||
rank: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::clan_member::ClanMemberRank::Officer),
|
||||
..Default::default()
|
||||
};
|
||||
self.db.update_clan_member(to_update).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to update own clan member rank by user {} to demote to officer: {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::SocialErrorCode::DatabaseError as i16,
|
||||
format!("Failed to udpate own clan member rank by user to demote to officer: {}", e),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
self.clan_members_of_clan(my_clan.id).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
||||
@@ -462,6 +462,20 @@ pub trait SocialUser: Send + Sync {
|
||||
async fn has_unclaimed_match_rewards(&self) -> Result<bool, polariton_server::operations::SimpleOpError>;
|
||||
async fn get_unclaimed_match_rewards(&self) -> Result<MatchRewards, polariton_server::operations::SimpleOpError>;
|
||||
async fn claim_match_rewards(&self) -> Result<bool, polariton_server::operations::SimpleOpError>;
|
||||
async fn my_clan_info(&self, include_members: bool) -> Result<Option<(ClanData, Vec<ClanMember>)>, polariton_server::operations::SimpleOpError>;
|
||||
async fn clan_info(&self, clan_name: &str) -> Result<Option<(ClanData, Vec<ClanMember>)>, polariton_server::operations::SimpleOpError>;
|
||||
async fn search_clan(&self, search: ClanSearchQuery)-> Result<Vec<ClanData>, polariton_server::operations::SimpleOpError>;
|
||||
async fn create_clan(&self, clan: ClanData, avatar: Vec<u8>)-> Result<Vec<ClanMember>, polariton_server::operations::SimpleOpError>;
|
||||
async fn join_clan(&self, clan_name: &str) -> Result<(ClanData, Vec<ClanMember>), polariton_server::operations::SimpleOpError>;
|
||||
async fn leave_clan(&self) -> Result<bool, polariton_server::operations::SimpleOpError>;
|
||||
async fn remove_user_from_clan(&self, public_id: &str) -> Result<bool, polariton_server::operations::SimpleOpError>;
|
||||
async fn update_clan(&self, name: Option<String>, description: Option<String>, ty: Option<ClanType>, avatar: Option<Vec<u8>>) -> Result<Vec<ClanMember>, polariton_server::operations::SimpleOpError>;
|
||||
async fn invite_to_clan(&self, public_id: &str) -> Result<ClanMember, polariton_server::operations::SimpleOpError>;
|
||||
async fn my_clan_invites(&self) -> Result<Vec<ClanInviteData>, polariton_server::operations::SimpleOpError>;
|
||||
async fn decline_clan_invite(&self, clan_name: &str) -> Result<Vec<ClanMember>, polariton_server::operations::SimpleOpError>;
|
||||
async fn decline_all_clan_invites(&self) -> Result<bool, polariton_server::operations::SimpleOpError>;
|
||||
async fn cancel_invite_to_clan(&self, public_id: &str) -> Result<(ClanData, Vec<ClanMember>), polariton_server::operations::SimpleOpError>;
|
||||
async fn update_clan_member(&self, public_id: &str, rank: ClanMemberRank) -> Result<Vec<ClanMember>, polariton_server::operations::SimpleOpError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -506,6 +520,7 @@ pub struct SocialInfo {
|
||||
pub avatar_id: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum FriendInviteStatus {
|
||||
InviteSent,
|
||||
InvitePending,
|
||||
@@ -529,6 +544,92 @@ impl FriendInviteStatus {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClanData {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub ty: ClanType,
|
||||
pub size: i32,
|
||||
}
|
||||
|
||||
pub struct ClanInviteData {
|
||||
pub public_id: String,
|
||||
pub display_name: String,
|
||||
pub avatar_id: Option<i32>,
|
||||
pub clan_name: String,
|
||||
pub clan_description: String,
|
||||
pub size: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ClanType {
|
||||
Open,
|
||||
Closed,
|
||||
}
|
||||
|
||||
impl ClanType {
|
||||
#[inline]
|
||||
pub(super) fn db_to_core(status: &oj_rc_database::schema::clan::ClanType) -> Self {
|
||||
match status {
|
||||
oj_rc_database::schema::clan::ClanType::Public => Self::Open,
|
||||
oj_rc_database::schema::clan::ClanType::Private => Self::Closed,
|
||||
oj_rc_database::schema::clan::ClanType::Banned => Self::Closed,
|
||||
oj_rc_database::schema::clan::ClanType::Abandoned => Self::Closed,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn core_to_db(self) -> oj_rc_database::schema::clan::ClanType {
|
||||
match self {
|
||||
Self::Open => oj_rc_database::schema::clan::ClanType::Public,
|
||||
Self::Closed => oj_rc_database::schema::clan::ClanType::Private,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClanMember {
|
||||
pub public_id: String,
|
||||
pub display_name: String,
|
||||
pub is_confirmed: bool,
|
||||
pub avatar_id: Option<i32>,
|
||||
pub rank: ClanMemberRank,
|
||||
pub season_xp: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ClanMemberRank {
|
||||
Member,
|
||||
Officer,
|
||||
Leader,
|
||||
}
|
||||
|
||||
impl ClanMemberRank {
|
||||
#[inline]
|
||||
pub(super) fn db_to_core(status: &oj_rc_database::schema::clan_member::ClanMemberRank) -> Self {
|
||||
match status {
|
||||
oj_rc_database::schema::clan_member::ClanMemberRank::Member => Self::Member,
|
||||
oj_rc_database::schema::clan_member::ClanMemberRank::Officer => Self::Officer,
|
||||
oj_rc_database::schema::clan_member::ClanMemberRank::Leader => Self::Leader,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub(super) fn core_to_db(self) -> oj_rc_database::schema::clan_member::ClanMemberRank {
|
||||
match self {
|
||||
Self::Member => oj_rc_database::schema::clan_member::ClanMemberRank::Member,
|
||||
Self::Officer => oj_rc_database::schema::clan_member::ClanMemberRank::Officer,
|
||||
Self::Leader => oj_rc_database::schema::clan_member::ClanMemberRank::Leader,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClanSearchQuery {
|
||||
pub search_string: String,
|
||||
pub days_since_active: i32,
|
||||
pub start_range: i32,
|
||||
pub end_range: i32,
|
||||
pub types: Vec<ClanType>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait SingleplayerUser: Send + Sync {
|
||||
// regular singleplayer and campaign mode
|
||||
|
||||
@@ -23,7 +23,7 @@ impl MigrationTrait for Migration {
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::CreationTime).big_integer().not_null())
|
||||
.col(ColumnDef::new(crate::schema::friend::Column::CreationTime).big_integer().not_null())
|
||||
.col(ColumnDef::new(crate::schema::friend::Column::FriendSource).integer().not_null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
pub struct Migration;
|
||||
|
||||
impl MigrationName for Migration {
|
||||
fn name(&self) -> &str {
|
||||
"m20260221_000001_create_clan_table"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
// Define how to apply this migration: Create the Clans table.
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(crate::schema::clan::Entity)
|
||||
.col(
|
||||
ColumnDef::new(crate::schema::clan::Column::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(crate::schema::clan::Column::CreationTime).big_integer().not_null())
|
||||
.col(ColumnDef::new(crate::schema::clan::Column::Name).string().not_null())
|
||||
.col(ColumnDef::new(crate::schema::clan::Column::Description).string().not_null())
|
||||
.col(ColumnDef::new(crate::schema::clan::Column::Variant).string().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// Define how to rollback this migration: Drop the Clans table.
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(crate::schema::clan::Entity).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
pub struct Migration;
|
||||
|
||||
impl MigrationName for Migration {
|
||||
fn name(&self) -> &str {
|
||||
"m20260221_000002_create_clan_member_table"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
// Define how to apply this migration: Create the Clan Members table.
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(crate::schema::clan_member::Entity)
|
||||
.col(
|
||||
ColumnDef::new(crate::schema::clan_member::Column::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(crate::schema::clan_member::Column::CreationTime).big_integer().not_null())
|
||||
.col(ColumnDef::new(crate::schema::clan_member::Column::UserId).integer().not_null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk-clan_members-user_id")
|
||||
.from(crate::schema::clan_member::Entity, crate::schema::clan_member::Column::UserId)
|
||||
.to(crate::schema::user::Entity, crate::schema::user::Column::Id),
|
||||
)
|
||||
.col(ColumnDef::new(crate::schema::clan_member::Column::ClanId).integer().not_null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk-clan_members-clan_id")
|
||||
.from(crate::schema::clan_member::Entity, crate::schema::clan_member::Column::ClanId)
|
||||
.to(crate::schema::clan::Entity, crate::schema::clan::Column::Id),
|
||||
)
|
||||
.col(ColumnDef::new(crate::schema::clan_member::Column::Rank).string().not_null())
|
||||
.col(ColumnDef::new(crate::schema::clan_member::Column::Status).string().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// Define how to rollback this migration: Drop the Clan Members table.
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(crate::schema::clan_member::Entity).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ mod m20251228_000001_create_score_table;
|
||||
#[cfg(feature = "factory")]
|
||||
mod m20260126_000001_create_factory_vehicle_table;
|
||||
mod m20260215_000001_create_friend_table;
|
||||
mod m20260221_000001_create_clan_table;
|
||||
mod m20260221_000002_create_clan_member_table;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -39,6 +41,8 @@ impl MigratorTrait for Migrator {
|
||||
#[cfg(feature = "factory")]
|
||||
Box::new(m20260126_000001_create_factory_vehicle_table::Migration),
|
||||
Box::new(m20260215_000001_create_friend_table::Migration),
|
||||
Box::new(m20260221_000001_create_clan_table::Migration),
|
||||
Box::new(m20260221_000002_create_clan_member_table::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
35
rc_database/src/schema/clan.rs
Normal file
35
rc_database/src/schema/clan.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "clans")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub creation_time: i64, // seconds since unix epoch
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub variant: ClanType,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::clan_member::Entity")]
|
||||
ClanMember,
|
||||
}
|
||||
|
||||
impl Related<super::clan_member::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::ClanMember.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
|
||||
#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)", rename_all = "PascalCase")]
|
||||
pub enum ClanType {
|
||||
Public,
|
||||
Private,
|
||||
Banned,
|
||||
Abandoned,
|
||||
}
|
||||
59
rc_database/src/schema/clan_member.rs
Normal file
59
rc_database/src/schema/clan_member.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "clan_members")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub creation_time: i64, // seconds since unix epoch
|
||||
pub user_id: i32,
|
||||
pub clan_id: i32,
|
||||
pub rank: ClanMemberRank,
|
||||
pub status: ClanMemberStatus,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::user::Entity",
|
||||
from = "Column::UserId",
|
||||
to = "super::user::Column::Id"
|
||||
)]
|
||||
User,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::clan::Entity",
|
||||
from = "Column::ClanId",
|
||||
to = "super::clan::Column::Id"
|
||||
)]
|
||||
Clan,
|
||||
}
|
||||
|
||||
impl Related<super::user::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::User.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::clan::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Clan.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
|
||||
#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)", rename_all = "PascalCase")]
|
||||
pub enum ClanMemberRank {
|
||||
Member,
|
||||
Officer,
|
||||
Leader,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
|
||||
#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)", rename_all = "PascalCase")]
|
||||
pub enum ClanMemberStatus {
|
||||
Invited,
|
||||
Confirmed,
|
||||
Deactivated,
|
||||
}
|
||||
@@ -13,6 +13,8 @@ pub mod multiplayer_game_score;
|
||||
#[cfg(feature = "factory")]
|
||||
pub mod factory;
|
||||
pub mod friend;
|
||||
pub mod clan;
|
||||
pub mod clan_member;
|
||||
|
||||
pub fn parse_int_csv(s: &str) -> Vec<u32> {
|
||||
s.split(',').filter_map(|i_as_s| {
|
||||
|
||||
@@ -628,6 +628,154 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn clan_by_name(&self, name: String) -> Result<Option<crate::schema::clan::Model>, sea_orm::DbErr> {
|
||||
crate::schema::clan::Entity::find()
|
||||
.filter(
|
||||
sea_orm::sea_query::Func::lower(crate::schema::clan::Column::Name.into_expr())
|
||||
.eq(name.to_lowercase())
|
||||
)
|
||||
.one(self.orm.as_ref()).await
|
||||
}
|
||||
|
||||
pub async fn clan_by_user_id(&self, user_id: i32) -> Result<Option<(crate::schema::clan::Model, crate::schema::clan_member::Model)>, sea_orm::DbErr> {
|
||||
Ok(crate::schema::clan::Entity::find()
|
||||
.find_also_related(crate::schema::clan_member::Entity)
|
||||
.filter(crate::schema::clan_member::Column::UserId.eq(user_id))
|
||||
.filter(crate::schema::clan_member::Column::Status.eq(crate::schema::clan_member::ClanMemberStatus::Confirmed))
|
||||
.one(self.orm.as_ref())
|
||||
.await?
|
||||
.and_then(|(clan, member)| member.map(|member| (clan, member))))
|
||||
}
|
||||
|
||||
pub async fn clans_invited_to_for_user_id(&self, user_id: i32) -> Result<Vec<(crate::schema::clan::Model, crate::schema::clan_member::Model)>, sea_orm::DbErr> {
|
||||
Ok(crate::schema::clan::Entity::find()
|
||||
.find_also_related(crate::schema::clan_member::Entity)
|
||||
.filter(crate::schema::clan_member::Column::UserId.eq(user_id))
|
||||
.filter(crate::schema::clan_member::Column::Status.eq(crate::schema::clan_member::ClanMemberStatus::Invited))
|
||||
.all(self.orm.as_ref())
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|(clan, member)| member.map(|member| (clan, member)))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn clan_invited_to_for_user_id_and_clan_id(&self, user_id: i32, clan_id: i32) -> Result<Option<(crate::schema::clan::Model, crate::schema::clan_member::Model)>, sea_orm::DbErr> {
|
||||
Ok(crate::schema::clan::Entity::find()
|
||||
.find_also_related(crate::schema::clan_member::Entity)
|
||||
.filter(crate::schema::clan_member::Column::UserId.eq(user_id))
|
||||
.filter(crate::schema::clan_member::Column::ClanId.eq(clan_id))
|
||||
.filter(crate::schema::clan_member::Column::Status.eq(crate::schema::clan_member::ClanMemberStatus::Invited))
|
||||
.one(self.orm.as_ref())
|
||||
.await?
|
||||
.and_then(|(clan, member)| member.map(|member| (clan, member))))
|
||||
}
|
||||
|
||||
pub async fn clan_invited_to_for_user_id_and_clan_name(&self, user_id: i32, clan_name: String) -> Result<Option<(crate::schema::clan::Model, crate::schema::clan_member::Model)>, sea_orm::DbErr> {
|
||||
Ok(crate::schema::clan::Entity::find()
|
||||
.find_also_related(crate::schema::clan_member::Entity)
|
||||
.filter(crate::schema::clan_member::Column::UserId.eq(user_id))
|
||||
.filter(
|
||||
sea_orm::sea_query::Func::lower(crate::schema::clan::Column::Name.into_expr())
|
||||
.eq(clan_name.to_lowercase())
|
||||
)
|
||||
.filter(crate::schema::clan_member::Column::Status.eq(crate::schema::clan_member::ClanMemberStatus::Invited))
|
||||
.one(self.orm.as_ref())
|
||||
.await?
|
||||
.and_then(|(clan, member)| member.map(|member| (clan, member))))
|
||||
}
|
||||
|
||||
pub async fn clans_by_search(&self, s: String, start: u64, _end: u64, types: impl std::iter::Iterator<Item=crate::schema::clan::ClanType>) -> Result<Vec<crate::schema::clan::Model>, sea_orm::DbErr> {
|
||||
let lower_s_like = format!("%{}%", s.trim_matches('%').to_lowercase());
|
||||
let types: Vec<_> = types.collect();
|
||||
crate::schema::clan::Entity::find()
|
||||
.filter(
|
||||
sea_orm::sea_query::Expr::expr(
|
||||
sea_orm::sea_query::Func::lower(crate::schema::clan::Column::Name.into_expr())
|
||||
).like(&lower_s_like)
|
||||
)
|
||||
.filter(if types.is_empty() {
|
||||
crate::schema::clan::Column::Variant.is_in([
|
||||
crate::schema::clan::ClanType::Public,
|
||||
crate::schema::clan::ClanType::Private,
|
||||
])
|
||||
} else {
|
||||
crate::schema::clan::Column::Variant.is_in(types)
|
||||
})
|
||||
// FIXME don't return clans that are not strictly in the start..end range
|
||||
.paginate(self.orm.as_ref(), 50)
|
||||
.fetch_page(start / 50)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn insert_clan(&self, entity: crate::schema::clan::ActiveModel) -> Result<crate::schema::clan::Model, sea_orm::DbErr> {
|
||||
#[cfg(debug_assertions)]
|
||||
assert!(matches!(entity.id, sea_orm::ActiveValue::NotSet));
|
||||
entity.insert(self.orm.as_ref()).await
|
||||
}
|
||||
|
||||
pub async fn update_clan(&self, entity: crate::schema::clan::ActiveModel) -> Result<crate::schema::clan::Model, sea_orm::DbErr> {
|
||||
entity.update(self.orm.as_ref()).await
|
||||
}
|
||||
|
||||
pub async fn clan_members_by_clan_id(&self, clan_id: i32) -> Result<Vec<(crate::schema::clan_member::Model, crate::schema::user::Model)>, sea_orm::DbErr> {
|
||||
Ok(crate::schema::clan_member::Entity::find()
|
||||
.find_also_related(crate::schema::user::Entity)
|
||||
.filter(crate::schema::clan_member::Column::ClanId.eq(clan_id))
|
||||
.filter(crate::schema::clan_member::Column::Status.is_in([
|
||||
crate::schema::clan_member::ClanMemberStatus::Invited,
|
||||
crate::schema::clan_member::ClanMemberStatus::Confirmed,
|
||||
]))
|
||||
.all(self.orm.as_ref())
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|(member, user)| user.map(|user| (member, user)))
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn clan_leaders_by_clan_ids(&self, clan_ids: impl std::iter::Iterator<Item=i32>) -> Result<Vec<(crate::schema::clan_member::Model, crate::schema::user::Model)>, sea_orm::DbErr> {
|
||||
Ok(crate::schema::clan_member::Entity::find()
|
||||
.find_also_related(crate::schema::user::Entity)
|
||||
.filter(crate::schema::clan_member::Column::ClanId.is_in(clan_ids))
|
||||
.filter(crate::schema::clan_member::Column::Status.eq(crate::schema::clan_member::ClanMemberStatus::Confirmed))
|
||||
.filter(crate::schema::clan_member::Column::Rank.eq(crate::schema::clan_member::ClanMemberRank::Leader))
|
||||
.all(self.orm.as_ref())
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|(member, user)| user.map(|user| (member, user)))
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn insert_clan_member(&self, entity: crate::schema::clan_member::ActiveModel) -> Result<crate::schema::clan_member::Model, sea_orm::DbErr> {
|
||||
#[cfg(debug_assertions)]
|
||||
assert!(matches!(entity.id, sea_orm::ActiveValue::NotSet));
|
||||
entity.insert(self.orm.as_ref()).await
|
||||
}
|
||||
|
||||
pub async fn update_clan_member(&self, entity: crate::schema::clan_member::ActiveModel) -> Result<crate::schema::clan_member::Model, sea_orm::DbErr> {
|
||||
entity.update(self.orm.as_ref()).await
|
||||
}
|
||||
|
||||
pub async fn update_clan_member_decline_all_invites(&self, user_id: i32) -> Result<(), sea_orm::DbErr> {
|
||||
crate::schema::clan_member::Entity::update_many()
|
||||
.filter(crate::schema::clan_member::Column::UserId.eq(user_id))
|
||||
.filter(crate::schema::clan_member::Column::Status.eq(crate::schema::clan_member::ClanMemberStatus::Invited))
|
||||
.col_expr(crate::schema::clan_member::Column::Status, sea_orm::sea_query::Expr::value(crate::schema::clan_member::ClanMemberStatus::Deactivated))
|
||||
.exec(self.orm.as_ref())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn count_clan_members_by_clan_id(&self, clan_id: i32) -> Result<u64, sea_orm::DbErr> {
|
||||
crate::schema::clan_member::Entity::find()
|
||||
.find_also_related(crate::schema::user::Entity)
|
||||
.filter(crate::schema::clan_member::Column::ClanId.eq(clan_id))
|
||||
.filter(crate::schema::clan_member::Column::Status.eq(crate::schema::clan_member::ClanMemberStatus::Confirmed))
|
||||
.count(self.orm.as_ref())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn metrics(&self) -> super::DatabaseMetrics {
|
||||
self.metrics.lock().unwrap().snapshot()
|
||||
}
|
||||
|
||||
@@ -30,25 +30,84 @@ impl ClanMember {
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ClanMemberState {
|
||||
Idk0,
|
||||
Idk1,
|
||||
Idk2,
|
||||
// TODO figure out how many of these there are (and what they mean)
|
||||
Invited = 0, // Invited
|
||||
Confirmed = 1, // Confirmed
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ClanMemberRank {
|
||||
Member = 0,
|
||||
Officer = 1,
|
||||
Leader = 2,
|
||||
}
|
||||
|
||||
impl ClanMemberRank {
|
||||
#[inline]
|
||||
pub fn from_core(rank: oj_rc_core::persist::user::ClanMemberRank) -> Self {
|
||||
match rank {
|
||||
oj_rc_core::persist::user::ClanMemberRank::Member => Self::Member,
|
||||
oj_rc_core::persist::user::ClanMemberRank::Officer => Self::Officer,
|
||||
oj_rc_core::persist::user::ClanMemberRank::Leader => Self::Leader,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_core(self) -> oj_rc_core::persist::user::ClanMemberRank {
|
||||
match self {
|
||||
Self::Member => oj_rc_core::persist::user::ClanMemberRank::Member,
|
||||
Self::Officer => oj_rc_core::persist::user::ClanMemberRank::Officer,
|
||||
Self::Leader => oj_rc_core::persist::user::ClanMemberRank::Leader,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_u8(num: u8) -> Option<Self> {
|
||||
match num {
|
||||
0 => Some(Self::Member),
|
||||
1 => Some(Self::Officer),
|
||||
2 => Some(Self::Leader),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ClanType {
|
||||
Open = 1,
|
||||
Closed = 2,
|
||||
Open = 0,
|
||||
Closed = 1,
|
||||
}
|
||||
|
||||
impl ClanType {
|
||||
#[inline]
|
||||
pub fn from_u8(num: u8) -> Option<Self> {
|
||||
match num {
|
||||
0 => Some(Self::Open),
|
||||
1 => Some(Self::Closed),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_u8(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_core(self) -> oj_rc_core::persist::user::ClanType {
|
||||
match self {
|
||||
Self::Open => oj_rc_core::persist::user::ClanType::Open,
|
||||
Self::Closed => oj_rc_core::persist::user::ClanType::Closed,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_core(ty: oj_rc_core::persist::user::ClanType) -> Self {
|
||||
match ty {
|
||||
oj_rc_core::persist::user::ClanType::Open => Self::Open,
|
||||
oj_rc_core::persist::user::ClanType::Closed => Self::Closed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClanInfo {
|
||||
|
||||
@@ -21,4 +21,15 @@ impl ClanInviteInfo {
|
||||
(Typed::Str("avatarId".into()), Typed::Int(self.avatar_id)),
|
||||
].into())
|
||||
}
|
||||
|
||||
pub fn from_core(core: oj_rc_core::persist::user::ClanInviteData) -> Self {
|
||||
Self {
|
||||
username: core.public_id,
|
||||
display_name: core.display_name,
|
||||
clan_name: core.clan_name,
|
||||
clan_size: core.size,
|
||||
use_custom_avatar: core.avatar_id.is_none(),
|
||||
avatar_id: core.avatar_id.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
33
rc_social_room/src/events/clan_data_changed.rs
Normal file
33
rc_social_room/src/events/clan_data_changed.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
#[derive(Clone)]
|
||||
pub struct ClanDataUpdated {
|
||||
pub description: Option<String>,
|
||||
pub ty: Option<crate::data::clan::ClanType>,
|
||||
}
|
||||
|
||||
impl ClanDataUpdated {
|
||||
pub const CODE: u8 = 27;
|
||||
|
||||
pub fn as_event_params<C>(&self) -> polariton::operation::ParameterTable<C> {
|
||||
let mut params = std::collections::HashMap::with_capacity(2);
|
||||
if let Some(description) = &self.description {
|
||||
params.insert(32, polariton::operation::Typed::Str(description.into()));
|
||||
}
|
||||
if let Some(ty) = self.ty {
|
||||
params.insert(34, polariton::operation::Typed::Int(ty.to_u8() as _));
|
||||
}
|
||||
params.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl <C: Send + 'static> polariton_server::events::IntoEvent<C> for ClanDataUpdated {
|
||||
const CHANNEL: u8 = 0;
|
||||
const ENCRYPT: bool = true;
|
||||
const RELIABLE: bool = true;
|
||||
|
||||
fn into_event(self) -> polariton::operation::Event<C> {
|
||||
polariton::operation::Event {
|
||||
code: Self::CODE,
|
||||
params: self.as_event_params(),
|
||||
}
|
||||
}
|
||||
}
|
||||
26
rc_social_room/src/events/clan_invite_cancelled.rs
Normal file
26
rc_social_room/src/events/clan_invite_cancelled.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
pub struct ClanInviteCancelled {
|
||||
pub clan_name: String,
|
||||
}
|
||||
|
||||
impl ClanInviteCancelled {
|
||||
pub const CODE: u8 = 25;
|
||||
|
||||
pub fn as_event_params<C>(&self) -> polariton::operation::ParameterTable<C> {
|
||||
let mut params = std::collections::HashMap::with_capacity(1);
|
||||
params.insert(31, polariton::operation::Typed::Str(self.clan_name.clone().into()));
|
||||
params.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl <C: Send + 'static> polariton_server::events::IntoEvent<C> for ClanInviteCancelled {
|
||||
const CHANNEL: u8 = 0;
|
||||
const ENCRYPT: bool = true;
|
||||
const RELIABLE: bool = true;
|
||||
|
||||
fn into_event(self) -> polariton::operation::Event<C> {
|
||||
polariton::operation::Event {
|
||||
code: Self::CODE,
|
||||
params: self.as_event_params(),
|
||||
}
|
||||
}
|
||||
}
|
||||
36
rc_social_room/src/events/clan_invite_received.rs
Normal file
36
rc_social_room/src/events/clan_invite_received.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
pub struct ClanInviteReceived {
|
||||
pub inviter_public_id: String,
|
||||
pub inviter_display_name: String,
|
||||
pub clan_size: i32,
|
||||
pub clan_name: String,
|
||||
pub avatar_id: Option<i32>,
|
||||
}
|
||||
|
||||
impl ClanInviteReceived {
|
||||
pub const CODE: u8 = 21;
|
||||
|
||||
pub fn as_event_params<C>(&self) -> polariton::operation::ParameterTable<C> {
|
||||
let mut params = std::collections::HashMap::with_capacity(5);
|
||||
params.insert(1, polariton::operation::Typed::Str(self.inviter_public_id.clone().into()));
|
||||
params.insert(75, polariton::operation::Typed::Str(self.inviter_display_name.clone().into()));
|
||||
params.insert(31, polariton::operation::Typed::Str(self.clan_name.clone().into()));
|
||||
params.insert(35, polariton::operation::Typed::Int(self.clan_size));
|
||||
|
||||
params.insert(13, polariton::operation::Typed::Bool(self.avatar_id.is_none()));
|
||||
params.insert(14, polariton::operation::Typed::Int(self.avatar_id.unwrap_or_default()));
|
||||
params.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl <C: Send + 'static> polariton_server::events::IntoEvent<C> for ClanInviteReceived {
|
||||
const CHANNEL: u8 = 0;
|
||||
const ENCRYPT: bool = true;
|
||||
const RELIABLE: bool = true;
|
||||
|
||||
fn into_event(self) -> polariton::operation::Event<C> {
|
||||
polariton::operation::Event {
|
||||
code: Self::CODE,
|
||||
params: self.as_event_params(),
|
||||
}
|
||||
}
|
||||
}
|
||||
44
rc_social_room/src/events/clan_member_data_changed.rs
Normal file
44
rc_social_room/src/events/clan_member_data_changed.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
#[derive(Clone)]
|
||||
pub struct ClanMemberDataUpdated {
|
||||
pub member_public_id: String,
|
||||
pub member_display_name: String,
|
||||
pub avatar_id: Option<i32>,
|
||||
pub state: Option<crate::data::clan::ClanMemberState>,
|
||||
pub rank: Option<crate::data::clan::ClanMemberRank>,
|
||||
pub is_online: Option<bool>,
|
||||
}
|
||||
|
||||
impl ClanMemberDataUpdated {
|
||||
pub const CODE: u8 = 26;
|
||||
|
||||
pub fn as_event_params<C>(&self) -> polariton::operation::ParameterTable<C> {
|
||||
let mut params = std::collections::HashMap::with_capacity(8);
|
||||
params.insert(1, polariton::operation::Typed::Str(self.member_public_id.clone().into()));
|
||||
params.insert(75, polariton::operation::Typed::Str(self.member_display_name.clone().into()));
|
||||
params.insert(13, polariton::operation::Typed::Bool(self.avatar_id.is_none()));
|
||||
params.insert(14, polariton::operation::Typed::Int(self.avatar_id.unwrap_or_default()));
|
||||
if let Some(state) = self.state {
|
||||
params.insert(37, polariton::operation::Typed::Int(state as _));
|
||||
}
|
||||
if let Some(rank) = self.rank {
|
||||
params.insert(38, polariton::operation::Typed::Int(rank as _));
|
||||
}
|
||||
if let Some(is_online) = self.is_online {
|
||||
params.insert(2, polariton::operation::Typed::Bool(is_online));
|
||||
}
|
||||
params.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl <C: Send + 'static> polariton_server::events::IntoEvent<C> for ClanMemberDataUpdated {
|
||||
const CHANNEL: u8 = 0;
|
||||
const ENCRYPT: bool = true;
|
||||
const RELIABLE: bool = true;
|
||||
|
||||
fn into_event(self) -> polariton::operation::Event<C> {
|
||||
polariton::operation::Event {
|
||||
code: Self::CODE,
|
||||
params: self.as_event_params(),
|
||||
}
|
||||
}
|
||||
}
|
||||
36
rc_social_room/src/events/clan_member_joined.rs
Normal file
36
rc_social_room/src/events/clan_member_joined.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
#[derive(Clone)]
|
||||
pub struct ClanMemberJoined {
|
||||
pub joiner_public_id: String,
|
||||
pub joiner_display_name: String,
|
||||
pub avatar_id: Option<i32>,
|
||||
pub state: crate::data::clan::ClanMemberState,
|
||||
pub season_xp: i32,
|
||||
}
|
||||
|
||||
impl ClanMemberJoined {
|
||||
pub const CODE: u8 = 22;
|
||||
|
||||
pub fn as_event_params<C>(&self) -> polariton::operation::ParameterTable<C> {
|
||||
let mut params = std::collections::HashMap::with_capacity(6);
|
||||
params.insert(1, polariton::operation::Typed::Str(self.joiner_public_id.clone().into()));
|
||||
params.insert(75, polariton::operation::Typed::Str(self.joiner_display_name.clone().into()));
|
||||
params.insert(13, polariton::operation::Typed::Bool(self.avatar_id.is_none()));
|
||||
params.insert(14, polariton::operation::Typed::Int(self.avatar_id.unwrap_or_default()));
|
||||
params.insert(37, polariton::operation::Typed::Int(self.state as _));
|
||||
params.insert(48, polariton::operation::Typed::Int(self.season_xp));
|
||||
params.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl <C: Send + 'static> polariton_server::events::IntoEvent<C> for ClanMemberJoined {
|
||||
const CHANNEL: u8 = 0;
|
||||
const ENCRYPT: bool = true;
|
||||
const RELIABLE: bool = true;
|
||||
|
||||
fn into_event(self) -> polariton::operation::Event<C> {
|
||||
polariton::operation::Event {
|
||||
code: Self::CODE,
|
||||
params: self.as_event_params(),
|
||||
}
|
||||
}
|
||||
}
|
||||
31
rc_social_room/src/events/clan_member_left.rs
Normal file
31
rc_social_room/src/events/clan_member_left.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
#[derive(Clone)]
|
||||
pub struct ClanMemberLeft {
|
||||
pub leaver_public_id: String,
|
||||
pub new_leader_public_id: Option<String>,
|
||||
}
|
||||
|
||||
impl ClanMemberLeft {
|
||||
pub const CODE: u8 = 23;
|
||||
|
||||
pub fn as_event_params<C>(&self) -> polariton::operation::ParameterTable<C> {
|
||||
let mut params = std::collections::HashMap::with_capacity(2);
|
||||
params.insert(1, polariton::operation::Typed::Str(self.leaver_public_id.clone().into()));
|
||||
if let Some(new_leader) = &self.new_leader_public_id {
|
||||
params.insert(45, polariton::operation::Typed::Str(new_leader.into()));
|
||||
}
|
||||
params.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl <C: Send + 'static> polariton_server::events::IntoEvent<C> for ClanMemberLeft {
|
||||
const CHANNEL: u8 = 0;
|
||||
const ENCRYPT: bool = true;
|
||||
const RELIABLE: bool = true;
|
||||
|
||||
fn into_event(self) -> polariton::operation::Event<C> {
|
||||
polariton::operation::Event {
|
||||
code: Self::CODE,
|
||||
params: self.as_event_params(),
|
||||
}
|
||||
}
|
||||
}
|
||||
22
rc_social_room/src/events/clan_member_removed.rs
Normal file
22
rc_social_room/src/events/clan_member_removed.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
pub struct ClanMemberRemoved;
|
||||
|
||||
impl ClanMemberRemoved {
|
||||
pub const CODE: u8 = 24;
|
||||
|
||||
pub fn as_event_params<C>(&self) -> polariton::operation::ParameterTable<C> {
|
||||
polariton::operation::ParameterTable::with_capacity(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl <C: Send + 'static> polariton_server::events::IntoEvent<C> for ClanMemberRemoved {
|
||||
const CHANNEL: u8 = 0;
|
||||
const ENCRYPT: bool = true;
|
||||
const RELIABLE: bool = true;
|
||||
|
||||
fn into_event(self) -> polariton::operation::Event<C> {
|
||||
polariton::operation::Event {
|
||||
code: Self::CODE,
|
||||
params: self.as_event_params(),
|
||||
}
|
||||
}
|
||||
}
|
||||
30
rc_social_room/src/events/clan_renamed.rs
Normal file
30
rc_social_room/src/events/clan_renamed.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
pub struct ClanRenamed {
|
||||
pub old_name: String,
|
||||
pub new_name: String,
|
||||
pub admin_public_id: String,
|
||||
}
|
||||
|
||||
impl ClanRenamed {
|
||||
pub const CODE: u8 = 28;
|
||||
|
||||
pub fn as_event_params<C>(&self) -> polariton::operation::ParameterTable<C> {
|
||||
let mut params = std::collections::HashMap::with_capacity(3);
|
||||
params.insert(31, polariton::operation::Typed::Str(self.old_name.clone().into()));
|
||||
params.insert(44, polariton::operation::Typed::Str(self.new_name.clone().into()));
|
||||
params.insert(1, polariton::operation::Typed::Str(self.admin_public_id.clone().into()));
|
||||
params.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl <C: Send + 'static> polariton_server::events::IntoEvent<C> for ClanRenamed {
|
||||
const CHANNEL: u8 = 0;
|
||||
const ENCRYPT: bool = true;
|
||||
const RELIABLE: bool = true;
|
||||
|
||||
fn into_event(self) -> polariton::operation::Event<C> {
|
||||
polariton::operation::Event {
|
||||
code: Self::CODE,
|
||||
params: self.as_event_params(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,3 +12,11 @@ pub mod platoon_removed;
|
||||
pub mod platoon_leader_changed;
|
||||
pub mod platoon_member_kick;
|
||||
pub mod platoon_member_avatar_update;
|
||||
pub mod clan_invite_received;
|
||||
pub mod clan_member_joined;
|
||||
pub mod clan_member_left;
|
||||
pub mod clan_member_removed;
|
||||
pub mod clan_invite_cancelled;
|
||||
pub mod clan_member_data_changed;
|
||||
pub mod clan_data_changed;
|
||||
//pub mod clan_renamed;
|
||||
|
||||
@@ -50,6 +50,7 @@ async fn main() -> std::io::Result<()> {
|
||||
let start_time = chrono::Utc::now();
|
||||
START_TIMESTAMP_S.store(start_time.timestamp(), std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
log::info!("social_room ready");
|
||||
if args.once {
|
||||
log::warn!("Handling first connection and then exiting");
|
||||
let (socket, address) = listener.accept().await?;
|
||||
|
||||
76
rc_social_room/src/operations/clan_create.rs
Normal file
76
rc_social_room/src/operations/clan_create.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed, Arr};
|
||||
|
||||
use crate::data::clan::*;
|
||||
|
||||
const CODE: u8 = 31;
|
||||
|
||||
const CLAN_NAME_PARAM_KEY: u8 = 31; // str; in
|
||||
const CLAN_DESC_PARAM_KEY: u8 = 32; // str; in
|
||||
const CLAN_TYPE_PARAM_KEY: u8 = 34; // int (ClanType); in
|
||||
const CLAN_AVATAR_PARAM_KEY: u8 = 33; // bytes; in
|
||||
const MEMBERS_PARAM_KEY: u8 = 36; // arr of hashmap (ClanMember); out
|
||||
|
||||
pub(super) struct ClanCreator {
|
||||
social: std::sync::Arc<crate::SocialMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanCreator {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
if let Some(Typed::Str(clan_name)) = params.remove(&CLAN_NAME_PARAM_KEY) {
|
||||
if let Some(Typed::Str(clan_desc)) = params.remove(&CLAN_DESC_PARAM_KEY) {
|
||||
if let Some(Typed::Int(clan_ty)) = params.remove(&CLAN_TYPE_PARAM_KEY) {
|
||||
let ty = ClanType::from_u8(clan_ty as u8)
|
||||
.ok_or_else(|| SimpleOpError::with_message(
|
||||
oj_rc_core::data::error_codes::SocialErrorCode::UnexpectedError as i16,
|
||||
format!("Invalid clan type {}", clan_ty),
|
||||
))?;
|
||||
let clavatar = if let Some(Typed::Bytes(clavatar)) = params.remove(&CLAN_AVATAR_PARAM_KEY) {
|
||||
clavatar.vec
|
||||
} else {
|
||||
Vec::default()
|
||||
};
|
||||
let new_clan = oj_rc_core::persist::user::ClanData {
|
||||
name: clan_name.string,
|
||||
description: clan_desc.string,
|
||||
ty: ty.to_core(),
|
||||
size: 0,
|
||||
};
|
||||
let user_info = user.user()?;
|
||||
let new_members = user_info.create_clan(new_clan, clavatar).await?;
|
||||
let mut online_clan_members: std::collections::HashSet<String> = new_members.iter()
|
||||
.map(|mem| mem.public_id.clone())
|
||||
.collect();
|
||||
self.social.filter_online_only(&mut online_clan_members).await;
|
||||
params.insert(MEMBERS_PARAM_KEY, Typed::Arr(Arr {
|
||||
ty: polariton::serdes::TypePrefix::HashMap,
|
||||
custom_ty: None,
|
||||
items: new_members.into_iter()
|
||||
.map(|member| ClanMember {
|
||||
is_online: online_clan_members.contains(&member.public_id),
|
||||
username: member.public_id,
|
||||
display_name: member.display_name,
|
||||
member_state: if member.is_confirmed { ClanMemberState::Confirmed } else { ClanMemberState::Invited },
|
||||
use_custom_avatar: member.avatar_id.is_none(),
|
||||
avatar_id: member.avatar_id.unwrap_or_default(),
|
||||
rank: ClanMemberRank::from_core(member.rank),
|
||||
season_xp: member.season_xp,
|
||||
}.as_transmissible())
|
||||
.collect()
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn creat_clan_provider(init_ctx: &crate::InitConfig) -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanCreator> {
|
||||
SimpleOpImpl::new(ClanCreator {
|
||||
social: init_ctx.social.clone(),
|
||||
})
|
||||
}
|
||||
33
rc_social_room/src/operations/clan_experience_poll.rs
Normal file
33
rc_social_room/src/operations/clan_experience_poll.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed, Dict};
|
||||
|
||||
const CODE: u8 = 57;
|
||||
|
||||
const EXPERIENCE_TABLE_PARAM_KEY: u8 = 48; // dict string -> int; out
|
||||
const CLAN_NAME_PARAM_KEY: u8 = 31; // str; in
|
||||
|
||||
pub(super) struct ClanExperiencePoller;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanExperiencePoller {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, _user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
if let Some(Typed::Str(clan)) = params.remove(&CLAN_NAME_PARAM_KEY) {
|
||||
log::debug!("Not providing clan experience for clan {} (not implemented)", clan.string);
|
||||
params.insert(EXPERIENCE_TABLE_PARAM_KEY, Typed::Dict(Dict {
|
||||
key_ty: polariton::serdes::TypePrefix::Str,
|
||||
val_ty: polariton::serdes::TypePrefix::Int,
|
||||
items: vec![
|
||||
(Typed::Str("NGniusness".into()), Typed::Int(i32::MAX)),
|
||||
],
|
||||
}));
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clan_experience_provider() -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanExperiencePoller> {
|
||||
SimpleOpImpl::new(ClanExperiencePoller)
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
use polariton_server::operations::SimpleFunc;
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed, Arr};
|
||||
|
||||
use crate::data::clan::*;
|
||||
|
||||
const CODE: u8 = 33;
|
||||
|
||||
const CLAN_NAME_PARAM_KEY: u8 = 31; // in and out
|
||||
const MEMBERS_PARAM_KEY: u8 = 36; // out only
|
||||
const ROBITS_CONVERSION_PARAM_KEY: u8 = 51; // out only
|
||||
const CLAN_DESCRIPTION_PARAM_KEY: u8 = 32; // out only
|
||||
const CLAN_TYPE_PARAM_KEY: u8 = 32; // out only
|
||||
const CLAN_TYPE_PARAM_KEY: u8 = 34; // out only
|
||||
|
||||
pub(super) fn clan_info_provider<C: Send + Sync>() -> SimpleFunc<33, crate::UserTy, impl (Fn(ParameterTable<C>, &crate::UserTy) -> Result<ParameterTable<C>, i16>) + Sync + Sync, C> {
|
||||
/*pub(super) fn clan_info_provider<C: Send + Sync>() -> SimpleFunc<33, crate::UserTy, impl (Fn(ParameterTable<C>, &crate::UserTy) -> Result<ParameterTable<C>, i16>) + Sync + Sync, C> {
|
||||
SimpleFunc::new(|params, _| {
|
||||
let mut params = params.to_dict();
|
||||
if let Some(Typed::Str(clan_name)) = params.get(&CLAN_NAME_PARAM_KEY) {
|
||||
@@ -59,4 +61,61 @@ pub(super) fn clan_info_provider<C: Send + Sync>() -> SimpleFunc<33, crate::User
|
||||
|
||||
Ok(params.into())
|
||||
})
|
||||
}*/
|
||||
|
||||
pub(super) struct ClanInfoGetter {
|
||||
social: std::sync::Arc<crate::SocialMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanInfoGetter {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
let user_info = user.user()?;
|
||||
let clan_info = if let Some(Typed::Str(clan_name)) = params.remove(&CLAN_NAME_PARAM_KEY) {
|
||||
// retrieve info for other clan (return nothing if clan does not exist)
|
||||
log::debug!("Getting {} clan info", clan_name.string);
|
||||
user_info.clan_info(&clan_name.string).await?
|
||||
} else {
|
||||
// retrieve info for user's own clan (return nothing if user not a part of a clan)
|
||||
log::debug!("Getting own clan info");
|
||||
user_info.my_clan_info(true).await?
|
||||
};
|
||||
if let Some((clan_info, members_info)) = clan_info {
|
||||
let mut online_clan_members: std::collections::HashSet<String> = members_info.iter()
|
||||
.map(|mem| mem.public_id.clone())
|
||||
.collect();
|
||||
self.social.filter_online_only(&mut online_clan_members).await;
|
||||
log::debug!("Found clan info for {} ({} members, {} online)", clan_info.name, members_info.len(), online_clan_members.len());
|
||||
params.insert(MEMBERS_PARAM_KEY, Typed::Arr(Arr {
|
||||
ty: polariton::serdes::TypePrefix::HashMap, // hashmap
|
||||
custom_ty: None,
|
||||
items: members_info.into_iter()
|
||||
.map(|member| ClanMember {
|
||||
is_online: online_clan_members.contains(&member.public_id),
|
||||
username: member.public_id,
|
||||
display_name: member.display_name,
|
||||
member_state: if member.is_confirmed { ClanMemberState::Confirmed } else { ClanMemberState::Invited },
|
||||
use_custom_avatar: member.avatar_id.is_none(),
|
||||
avatar_id: member.avatar_id.unwrap_or_default(),
|
||||
rank: ClanMemberRank::from_core(member.rank),
|
||||
season_xp: member.season_xp,
|
||||
}.as_transmissible())
|
||||
.collect(),
|
||||
}));
|
||||
params.insert(ROBITS_CONVERSION_PARAM_KEY, Typed::Float(0.5)); // TODO ???
|
||||
params.insert(CLAN_DESCRIPTION_PARAM_KEY, Typed::Str(clan_info.description.into()));
|
||||
params.insert(CLAN_NAME_PARAM_KEY, Typed::Str(clan_info.name.into()));
|
||||
params.insert(CLAN_TYPE_PARAM_KEY, Typed::Int(ClanType::from_core(clan_info.ty).to_u8() as _));
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clan_info_provider(init_ctx: &crate::InitConfig) -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanInfoGetter> {
|
||||
SimpleOpImpl::new(ClanInfoGetter {
|
||||
social: init_ctx.social.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use polariton_server::operations::SimpleFunc;
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed, Arr};
|
||||
|
||||
use crate::data::clan_invite::*;
|
||||
|
||||
//use crate::data::clan_invite::*;
|
||||
|
||||
const PARAM_KEY: u8 = 42;
|
||||
const CODE: u8 = 39;
|
||||
|
||||
pub(super) fn clan_invites_provider<C: Send + Sync>() -> SimpleFunc<39, crate::UserTy, impl (Fn(ParameterTable<C>, &crate::UserTy) -> Result<ParameterTable<C>, i16>) + Sync + Sync, C> {
|
||||
const INVITES_PARAM_KEY: u8 = 42;
|
||||
|
||||
/*pub(super) fn clan_invites_provider<C: Send + Sync>() -> SimpleFunc<39, crate::UserTy, impl (Fn(ParameterTable<C>, &crate::UserTy) -> Result<ParameterTable<C>, i16>) + Sync + Sync, C> {
|
||||
SimpleFunc::new(|params, _| {
|
||||
let mut params = params.to_dict();
|
||||
params.insert(PARAM_KEY, Typed::<C>::Arr(Arr {
|
||||
@@ -25,4 +29,41 @@ pub(super) fn clan_invites_provider<C: Send + Sync>() -> SimpleFunc<39, crate::U
|
||||
}));
|
||||
Ok(params.into())
|
||||
})
|
||||
}*/
|
||||
|
||||
pub(super) struct ClanInviteLister {
|
||||
//social: std::sync::Arc<crate::SocialMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanInviteLister {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
let user_info = user.user()?;
|
||||
let invites = user_info.my_clan_invites().await?;
|
||||
let typed_invites = invites.into_iter()
|
||||
.map(|invite| ClanInviteInfo {
|
||||
username: invite.public_id,
|
||||
display_name: invite.display_name,
|
||||
clan_name: invite.clan_name,
|
||||
use_custom_avatar: invite.avatar_id.is_none(),
|
||||
avatar_id: invite.avatar_id.unwrap_or_default(),
|
||||
clan_size: invite.size,
|
||||
}.as_transmissible())
|
||||
.collect();
|
||||
params.insert(INVITES_PARAM_KEY, Typed::Arr(Arr {
|
||||
ty: polariton::serdes::TypePrefix::HashMap, // hashmap
|
||||
custom_ty: None,
|
||||
items: typed_invites,
|
||||
}));
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clan_invites_provider(_init_ctx: &crate::InitConfig) -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanInviteLister> {
|
||||
SimpleOpImpl::new(ClanInviteLister {
|
||||
//social: init_ctx.social.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
80
rc_social_room/src/operations/clan_invite_accept.rs
Normal file
80
rc_social_room/src/operations/clan_invite_accept.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed, Arr};
|
||||
|
||||
use crate::data::clan::*;
|
||||
|
||||
const CODE: u8 = 36;
|
||||
|
||||
const CLAN_NAME_PARAM_KEY: u8 = 31; // str; in and out
|
||||
const CLAN_DESCRIPTION_PARAM_KEY: u8 = 32; // str; out
|
||||
const CLAN_TYPE_PARAM_KEY: u8 = 34; // int; out
|
||||
const ROBITS_CONVERSION_PARAM_KEY: u8 = 51; // out only
|
||||
const MEMBERS_PARAM_KEY: u8 = 36; // hashmap (ClanMember); out
|
||||
|
||||
pub(super) struct ClanInviteAccepter {
|
||||
social: std::sync::Arc<crate::SocialMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanInviteAccepter {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
if let Some(Typed::Str(clan_name)) = params.remove(&CLAN_NAME_PARAM_KEY) {
|
||||
let user_info = user.user()?;
|
||||
log::debug!("User {} wants to accept invite to clan {}", user_info.public_id(), clan_name.string);
|
||||
let (clan_info, members_info) = user_info.join_clan(&clan_name.string).await?;
|
||||
let mut online_clan_members: std::collections::HashSet<String> = members_info.iter()
|
||||
.map(|mem| mem.public_id.clone())
|
||||
.collect();
|
||||
self.social.filter_online_only(&mut online_clan_members).await;
|
||||
log::debug!("Found clan info for {} ({} members, {} online)", clan_info.name, members_info.len(), online_clan_members.len());
|
||||
|
||||
let my_pub_id = user_info.public_id();
|
||||
let self_member = members_info.iter()
|
||||
.find(|mem| mem.public_id == my_pub_id)
|
||||
.unwrap();
|
||||
let event = crate::events::clan_member_data_changed::ClanMemberDataUpdated {
|
||||
member_public_id: my_pub_id.to_owned(),
|
||||
member_display_name: user_info.display_name().to_owned(),
|
||||
avatar_id: self_member.avatar_id,
|
||||
state: Some(crate::data::clan::ClanMemberState::Confirmed),
|
||||
rank: None,
|
||||
is_online: Some(true),
|
||||
};
|
||||
for online_member in online_clan_members.iter() {
|
||||
if online_member == my_pub_id { continue; }
|
||||
self.social.send_event_to(online_member, event.clone()).await;
|
||||
}
|
||||
|
||||
params.insert(MEMBERS_PARAM_KEY, Typed::Arr(Arr {
|
||||
ty: polariton::serdes::TypePrefix::HashMap, // hashmap
|
||||
custom_ty: None,
|
||||
items: members_info.into_iter()
|
||||
.map(|member| ClanMember {
|
||||
is_online: online_clan_members.contains(&member.public_id),
|
||||
username: member.public_id,
|
||||
display_name: member.display_name,
|
||||
member_state: if member.is_confirmed { ClanMemberState::Confirmed } else { ClanMemberState::Invited },
|
||||
use_custom_avatar: member.avatar_id.is_none(),
|
||||
avatar_id: member.avatar_id.unwrap_or_default(),
|
||||
rank: ClanMemberRank::from_core(member.rank),
|
||||
season_xp: member.season_xp,
|
||||
}.as_transmissible())
|
||||
.collect(),
|
||||
}));
|
||||
params.insert(ROBITS_CONVERSION_PARAM_KEY, Typed::Float(0.5)); // TODO
|
||||
params.insert(CLAN_DESCRIPTION_PARAM_KEY, Typed::Str(clan_info.description.into()));
|
||||
params.insert(CLAN_NAME_PARAM_KEY, Typed::Str(clan_info.name.into()));
|
||||
params.insert(CLAN_TYPE_PARAM_KEY, Typed::Int(ClanType::from_core(clan_info.ty).to_u8() as _));
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clan_accept_provider(init_ctx: &crate::InitConfig) -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanInviteAccepter> {
|
||||
SimpleOpImpl::new(ClanInviteAccepter {
|
||||
social: init_ctx.social.clone()
|
||||
})
|
||||
}
|
||||
50
rc_social_room/src/operations/clan_invite_cancel.rs
Normal file
50
rc_social_room/src/operations/clan_invite_cancel.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed};
|
||||
|
||||
const CODE: u8 = 45;
|
||||
|
||||
const PUBLIC_ID_PARAM_KEY: u8 = 1; // str; in
|
||||
|
||||
pub(super) struct ClanInviteCanceller {
|
||||
social: std::sync::Arc<crate::SocialMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanInviteCanceller {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
if let Some(Typed::Str(invitee)) = params.remove(&PUBLIC_ID_PARAM_KEY) {
|
||||
let user_info = user.user()?;
|
||||
log::debug!("User {} wants to cancel invite to user {}", user_info.public_id(), invitee.string);
|
||||
let (clan_info, members_info) = user_info.cancel_invite_to_clan(&invitee.string).await?;
|
||||
let mut online_clan_members: std::collections::HashSet<String> = members_info.iter()
|
||||
.map(|mem| mem.public_id.clone())
|
||||
.collect();
|
||||
self.social.filter_online_only(&mut online_clan_members).await;
|
||||
|
||||
let event = crate::events::clan_invite_cancelled::ClanInviteCancelled {
|
||||
clan_name: clan_info.name,
|
||||
};
|
||||
self.social.send_event_to(&invitee.string, event).await;
|
||||
|
||||
let my_pub_id = user_info.public_id();
|
||||
let event = crate::events::clan_member_left::ClanMemberLeft {
|
||||
leaver_public_id: my_pub_id.to_owned(),
|
||||
new_leader_public_id: None,
|
||||
};
|
||||
for online_member in online_clan_members.iter() {
|
||||
if online_member == my_pub_id { continue; }
|
||||
self.social.send_event_to(online_member, event.clone()).await;
|
||||
}
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clan_cancel_provider(init_ctx: &crate::InitConfig) -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanInviteCanceller> {
|
||||
SimpleOpImpl::new(ClanInviteCanceller {
|
||||
social: init_ctx.social.clone()
|
||||
})
|
||||
}
|
||||
45
rc_social_room/src/operations/clan_invite_decline.rs
Normal file
45
rc_social_room/src/operations/clan_invite_decline.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed};
|
||||
|
||||
const CODE: u8 = 37;
|
||||
|
||||
const CLAN_NAME_PARAM_KEY: u8 = 31; // str; in and out
|
||||
|
||||
pub(super) struct ClanInviteDecliner {
|
||||
social: std::sync::Arc<crate::SocialMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanInviteDecliner {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
if let Some(Typed::Str(clan_name)) = params.remove(&CLAN_NAME_PARAM_KEY) {
|
||||
let user_info = user.user()?;
|
||||
log::debug!("User {} wants to decline invite to clan {}", user_info.public_id(), clan_name.string);
|
||||
let members_info = user_info.decline_clan_invite(&clan_name.string).await?;
|
||||
let mut online_clan_members: std::collections::HashSet<String> = members_info.iter()
|
||||
.map(|mem| mem.public_id.clone())
|
||||
.collect();
|
||||
self.social.filter_online_only(&mut online_clan_members).await;
|
||||
|
||||
let my_pub_id = user_info.public_id();
|
||||
let event = crate::events::clan_member_left::ClanMemberLeft {
|
||||
leaver_public_id: my_pub_id.to_owned(),
|
||||
new_leader_public_id: None,
|
||||
};
|
||||
for online_member in online_clan_members.iter() {
|
||||
if online_member == my_pub_id { continue; }
|
||||
self.social.send_event_to(online_member, event.clone()).await;
|
||||
}
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clan_decline_provider(init_ctx: &crate::InitConfig) -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanInviteDecliner> {
|
||||
SimpleOpImpl::new(ClanInviteDecliner {
|
||||
social: init_ctx.social.clone()
|
||||
})
|
||||
}
|
||||
28
rc_social_room/src/operations/clan_invite_decline_all.rs
Normal file
28
rc_social_room/src/operations/clan_invite_decline_all.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::ParameterTable;
|
||||
|
||||
const CODE: u8 = 44;
|
||||
|
||||
pub(super) struct ClanInviteDeclineAller {
|
||||
//social: std::sync::Arc<crate::SocialMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanInviteDeclineAller {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
let user_info = user.user()?;
|
||||
log::debug!("User {} wants to decline all clan invites", user_info.public_id());
|
||||
user_info.decline_all_clan_invites().await?;
|
||||
// TODO send clan member leave events to all clans
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clan_decline_all_provider(_init_ctx: &crate::InitConfig) -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanInviteDeclineAller> {
|
||||
SimpleOpImpl::new(ClanInviteDeclineAller {
|
||||
//social: init_ctx.social.clone()
|
||||
})
|
||||
}
|
||||
64
rc_social_room/src/operations/clan_invite_to.rs
Normal file
64
rc_social_room/src/operations/clan_invite_to.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed};
|
||||
|
||||
const CODE: u8 = 35;
|
||||
|
||||
const PUBLIC_ID_PARAM_KEY: u8 = 1; // str; in
|
||||
const DISPLAY_NAME_PARAM_KEY: u8 = 75; // str; out
|
||||
const CUSTOM_AVATAR_PARAM_KEY: u8 = 13; // bool; out
|
||||
const AVATAR_ID_PARAM_KEY: u8 = 14; // int; out
|
||||
const SEASON_XP_PARAM_KEY: u8 = 48; // int; out
|
||||
|
||||
pub(super) struct ClanInviter {
|
||||
social: std::sync::Arc<crate::SocialMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanInviter {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
if let Some(Typed::Str(invitee)) = params.remove(&PUBLIC_ID_PARAM_KEY) {
|
||||
let user_info = user.user()?;
|
||||
log::debug!("User {} wants to invite {} to their clan", user_info.account_id(), invitee.string);
|
||||
let invitee = user_info.invite_to_clan(&invitee.string).await?;
|
||||
params.insert(PUBLIC_ID_PARAM_KEY, Typed::Str(invitee.public_id.clone().into()));
|
||||
params.insert(DISPLAY_NAME_PARAM_KEY, Typed::Str(invitee.display_name.clone().into()));
|
||||
params.insert(CUSTOM_AVATAR_PARAM_KEY, Typed::Bool(invitee.avatar_id.is_none()));
|
||||
params.insert(AVATAR_ID_PARAM_KEY, Typed::Int(invitee.avatar_id.unwrap_or_default()));
|
||||
params.insert(SEASON_XP_PARAM_KEY, Typed::Int(invitee.season_xp));
|
||||
if let Some((my_clan, my_clan_members)) = user_info.my_clan_info(true).await? {
|
||||
let my_pub_id = user_info.public_id();
|
||||
let my_soc_info = user_info.list_social_info(&[my_pub_id.to_owned()]).await?;
|
||||
let event = crate::events::clan_invite_received::ClanInviteReceived {
|
||||
inviter_public_id: my_pub_id.to_owned(),
|
||||
inviter_display_name: user_info.display_name().to_owned(),
|
||||
clan_size: my_clan.size,
|
||||
clan_name: my_clan.name,
|
||||
avatar_id: my_soc_info.first().and_then(|x| x.avatar_id),
|
||||
};
|
||||
self.social.send_event_to(&invitee.public_id, event).await;
|
||||
|
||||
let event = crate::events::clan_member_joined::ClanMemberJoined {
|
||||
joiner_public_id: invitee.public_id,
|
||||
joiner_display_name: invitee.display_name,
|
||||
avatar_id: invitee.avatar_id,
|
||||
state: crate::data::clan::ClanMemberState::Invited,
|
||||
season_xp: invitee.season_xp,
|
||||
};
|
||||
for member in my_clan_members {
|
||||
if member.public_id == my_pub_id { continue; }
|
||||
self.social.send_event_to(&member.public_id, event.clone()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn invite_to_clan_provider(init_ctx: &crate::InitConfig) -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanInviter> {
|
||||
SimpleOpImpl::new(ClanInviter {
|
||||
social: init_ctx.social.clone(),
|
||||
})
|
||||
}
|
||||
79
rc_social_room/src/operations/clan_join.rs
Normal file
79
rc_social_room/src/operations/clan_join.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed, Arr};
|
||||
|
||||
use crate::data::clan::*;
|
||||
|
||||
const CODE: u8 = 34;
|
||||
|
||||
const CLAN_NAME_PARAM_KEY: u8 = 31; // str; in and out
|
||||
const CLAN_DESCRIPTION_PARAM_KEY: u8 = 32; // str; out
|
||||
const CLAN_TYPE_PARAM_KEY: u8 = 34; // int; out
|
||||
const ROBITS_CONVERSION_PARAM_KEY: u8 = 51; // out only
|
||||
const MEMBERS_PARAM_KEY: u8 = 36; // hashmap (ClanMember); out
|
||||
|
||||
pub(super) struct ClanJoiner {
|
||||
social: std::sync::Arc<crate::SocialMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanJoiner {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
if let Some(Typed::Str(clan_name)) = params.remove(&CLAN_NAME_PARAM_KEY) {
|
||||
let user_info = user.user()?;
|
||||
log::debug!("User {} wants to join clan {}", user_info.public_id(), clan_name.string);
|
||||
let (clan_info, members_info) = user_info.join_clan(&clan_name.string).await?;
|
||||
let mut online_clan_members: std::collections::HashSet<String> = members_info.iter()
|
||||
.map(|mem| mem.public_id.clone())
|
||||
.collect();
|
||||
self.social.filter_online_only(&mut online_clan_members).await;
|
||||
log::debug!("Found clan info for {} ({} members, {} online)", clan_info.name, members_info.len(), online_clan_members.len());
|
||||
|
||||
let my_pub_id = user_info.public_id();
|
||||
let self_member = members_info.iter()
|
||||
.find(|mem| mem.public_id == my_pub_id)
|
||||
.unwrap();
|
||||
let event = crate::events::clan_member_joined::ClanMemberJoined {
|
||||
joiner_public_id: my_pub_id.to_owned(),
|
||||
joiner_display_name: user_info.display_name().to_owned(),
|
||||
avatar_id: self_member.avatar_id,
|
||||
state: crate::data::clan::ClanMemberState::Confirmed,
|
||||
season_xp: 0,
|
||||
};
|
||||
for online_member in online_clan_members.iter() {
|
||||
if online_member == my_pub_id { continue; }
|
||||
self.social.send_event_to(online_member, event.clone()).await;
|
||||
}
|
||||
|
||||
params.insert(MEMBERS_PARAM_KEY, Typed::Arr(Arr {
|
||||
ty: polariton::serdes::TypePrefix::HashMap, // hashmap
|
||||
custom_ty: None,
|
||||
items: members_info.into_iter()
|
||||
.map(|member| ClanMember {
|
||||
is_online: online_clan_members.contains(&member.public_id),
|
||||
username: member.public_id,
|
||||
display_name: member.display_name,
|
||||
member_state: if member.is_confirmed { ClanMemberState::Confirmed } else { ClanMemberState::Invited },
|
||||
use_custom_avatar: member.avatar_id.is_none(),
|
||||
avatar_id: member.avatar_id.unwrap_or_default(),
|
||||
rank: ClanMemberRank::from_core(member.rank),
|
||||
season_xp: member.season_xp,
|
||||
}.as_transmissible())
|
||||
.collect(),
|
||||
}));
|
||||
params.insert(ROBITS_CONVERSION_PARAM_KEY, Typed::Float(0.5)); // TODO
|
||||
params.insert(CLAN_DESCRIPTION_PARAM_KEY, Typed::Str(clan_info.description.into()));
|
||||
params.insert(CLAN_NAME_PARAM_KEY, Typed::Str(clan_info.name.into()));
|
||||
params.insert(CLAN_TYPE_PARAM_KEY, Typed::Int(ClanType::from_core(clan_info.ty).to_u8() as _));
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clan_join_provider(init_ctx: &crate::InitConfig) -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanJoiner> {
|
||||
SimpleOpImpl::new(ClanJoiner {
|
||||
social: init_ctx.social.clone()
|
||||
})
|
||||
}
|
||||
42
rc_social_room/src/operations/clan_leave.rs
Normal file
42
rc_social_room/src/operations/clan_leave.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::ParameterTable;
|
||||
|
||||
const CODE: u8 = 40;
|
||||
|
||||
pub(super) struct ClanLeaver {
|
||||
social: std::sync::Arc<crate::SocialMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanLeaver {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
let user_info = user.user()?;
|
||||
let mut members = if let Some((_clan, members)) = user_info.my_clan_info(true).await? {
|
||||
members.into_iter().map(|mem| mem.public_id).collect()
|
||||
} else {
|
||||
std::collections::HashSet::default()
|
||||
};
|
||||
log::debug!("User {} wants to leave their clan", user_info.public_id());
|
||||
user_info.leave_clan().await?;
|
||||
self.social.filter_online_only(&mut members).await;
|
||||
let my_pub_id = user_info.public_id();
|
||||
let event = crate::events::clan_member_left::ClanMemberLeft {
|
||||
leaver_public_id: my_pub_id.to_owned(),
|
||||
new_leader_public_id: None,
|
||||
};
|
||||
for member in members {
|
||||
if member == my_pub_id { continue; }
|
||||
self.social.send_event_to(&member, event.clone()).await;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clan_leave_provider(init_ctx: &crate::InitConfig) -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanLeaver> {
|
||||
SimpleOpImpl::new(ClanLeaver {
|
||||
social: init_ctx.social.clone(),
|
||||
})
|
||||
}
|
||||
48
rc_social_room/src/operations/clan_member_remove.rs
Normal file
48
rc_social_room/src/operations/clan_member_remove.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed};
|
||||
|
||||
const CODE: u8 = 41;
|
||||
|
||||
const USERNAME_PARAM_KEY: u8 = 1; // str; in
|
||||
|
||||
pub(super) struct ClanRemover {
|
||||
social: std::sync::Arc<crate::SocialMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanRemover {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
if let Some(Typed::Str(to_remove)) = params.remove(&USERNAME_PARAM_KEY) {
|
||||
let user_info = user.user()?;
|
||||
let mut members = if let Some((_clan, members)) = user_info.my_clan_info(true).await? {
|
||||
members.into_iter().map(|mem| mem.public_id).collect()
|
||||
} else {
|
||||
std::collections::HashSet::default()
|
||||
};
|
||||
log::debug!("User {} wants to remove {} from their clan", user_info.public_id(), to_remove.string);
|
||||
user_info.remove_user_from_clan(&to_remove.string).await?;
|
||||
self.social.send_event_to(&to_remove.string, crate::events::clan_member_removed::ClanMemberRemoved).await;
|
||||
self.social.filter_online_only(&mut members).await;
|
||||
let my_pub_id = user_info.public_id();
|
||||
let event = crate::events::clan_member_left::ClanMemberLeft {
|
||||
leaver_public_id: to_remove.string.clone(),
|
||||
new_leader_public_id: None,
|
||||
};
|
||||
for member in members {
|
||||
if member == to_remove.string { continue; }
|
||||
if member == my_pub_id { continue; }
|
||||
self.social.send_event_to(&member, event.clone()).await;
|
||||
}
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clan_remove_provider(init_ctx: &crate::InitConfig) -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanRemover> {
|
||||
SimpleOpImpl::new(ClanRemover {
|
||||
social: init_ctx.social.clone(),
|
||||
})
|
||||
}
|
||||
61
rc_social_room/src/operations/clan_member_rerank.rs
Normal file
61
rc_social_room/src/operations/clan_member_rerank.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed};
|
||||
|
||||
const CODE: u8 = 38;
|
||||
|
||||
const PUBLIC_ID_PARAM_KEY: u8 = 1; // str; in and out
|
||||
const MEMBER_RANK_PARAM_KEY: u8 = 38; // int enum (ClanMemberRank); in
|
||||
|
||||
pub(super) struct ClanMemberRankChanger {
|
||||
social: std::sync::Arc<crate::SocialMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanMemberRankChanger {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
if let Some(Typed::Str(member)) = params.remove(&PUBLIC_ID_PARAM_KEY) {
|
||||
if let Some(Typed::Int(new_rank)) = params.remove(&MEMBER_RANK_PARAM_KEY) {
|
||||
let new_rank = crate::data::clan::ClanMemberRank::from_u8(new_rank as _)
|
||||
.ok_or_else(|| SimpleOpError::with_message(
|
||||
oj_rc_core::data::error_codes::SocialErrorCode::UnexpectedError as i16,
|
||||
format!("Invalid clan member rank {}", new_rank),
|
||||
))?;
|
||||
let user_info = user.user()?;
|
||||
let my_pub_id = user_info.public_id();
|
||||
log::debug!("User {} wants to update user {} to clan member rank {:?}", my_pub_id, member.string, new_rank);
|
||||
let members_info = user_info.update_clan_member(&member.string, new_rank.to_core()).await?;
|
||||
let mut online_clan_members: std::collections::HashSet<String> = members_info.iter()
|
||||
.map(|mem| mem.public_id.clone())
|
||||
.collect();
|
||||
self.social.filter_online_only(&mut online_clan_members).await;
|
||||
let target_member_opt = members_info.iter()
|
||||
.find(|mem| mem.public_id == member.string);
|
||||
if let Some(target_member) = target_member_opt {
|
||||
let event = crate::events::clan_member_data_changed::ClanMemberDataUpdated {
|
||||
member_public_id: target_member.public_id.clone(),
|
||||
member_display_name: target_member.display_name.clone(),
|
||||
avatar_id: target_member.avatar_id,
|
||||
state: None,
|
||||
rank: Some(new_rank),
|
||||
is_online: Some(true),
|
||||
};
|
||||
for online_member in online_clan_members {
|
||||
if online_member == my_pub_id { continue; }
|
||||
//if online_member == member.string { continue; }
|
||||
self.social.send_event_to(&online_member, event.clone()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clan_rank_change_provider(init_ctx: &crate::InitConfig) -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanMemberRankChanger> {
|
||||
SimpleOpImpl::new(ClanMemberRankChanger {
|
||||
social: init_ctx.social.clone()
|
||||
})
|
||||
}
|
||||
86
rc_social_room/src/operations/clan_modify.rs
Normal file
86
rc_social_room/src/operations/clan_modify.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed};
|
||||
|
||||
use crate::data::clan::*;
|
||||
|
||||
const CODE: u8 = 42;
|
||||
|
||||
const CLAN_DESC_PARAM_KEY: u8 = 32; // str; in
|
||||
const CLAN_TYPE_PARAM_KEY: u8 = 34; // int (ClanType); in
|
||||
const CLAN_AVATAR_PARAM_KEY: u8 = 33; // bytes; in
|
||||
|
||||
pub(super) struct ClanDataChanger {
|
||||
social: std::sync::Arc<crate::SocialMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClanDataChanger {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
let new_ty = if let Some(Typed::Int(clan_ty)) = params.remove(&CLAN_TYPE_PARAM_KEY) {
|
||||
let ty = ClanType::from_u8(clan_ty as u8)
|
||||
.ok_or_else(|| SimpleOpError::with_message(
|
||||
oj_rc_core::data::error_codes::SocialErrorCode::UnexpectedError as i16,
|
||||
format!("Invalid clan type {}", clan_ty),
|
||||
))?;
|
||||
Some(ty)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let new_clavatar = if let Some(Typed::Bytes(clavatar)) = params.remove(&CLAN_AVATAR_PARAM_KEY) {
|
||||
if clavatar.vec.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(clavatar.vec)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let new_desc = if let Some(Typed::Str(clan_desc)) = params.remove(&CLAN_DESC_PARAM_KEY) {
|
||||
// (technically this is a client bug)
|
||||
// when the game client updates a different field, the description will get sent as a zero-length string
|
||||
// which is impossible to differentiate from someone saving the description as zero-length
|
||||
// null/non-existent *should* be different than an empty string
|
||||
// So, let's try the closest guess the server can achieve:
|
||||
// => assume a zero-length string means null *unless* all the other fields are null
|
||||
if clan_desc.string.is_empty() && (new_ty.is_some() || new_clavatar.is_some()) {
|
||||
None
|
||||
} else {
|
||||
Some(clan_desc.string)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if new_desc.is_none() && new_ty.is_none() && new_clavatar.is_none() {
|
||||
log::debug!("Got clan data change no-op (doing nothing)");
|
||||
return Ok(params);
|
||||
}
|
||||
let user_info = user.user()?;
|
||||
let members = user_info.update_clan(
|
||||
None,
|
||||
new_desc.clone(),
|
||||
new_ty.map(|x| x.to_core()),
|
||||
new_clavatar,
|
||||
).await?;
|
||||
let mut online_members = members.into_iter().map(|mem| mem.public_id).collect();
|
||||
self.social.filter_online_only(&mut online_members).await;
|
||||
let my_pub_id = user_info.public_id();
|
||||
let event = crate::events::clan_data_changed::ClanDataUpdated {
|
||||
description: new_desc,
|
||||
ty: new_ty,
|
||||
};
|
||||
for member in online_members {
|
||||
if member == my_pub_id { continue; }
|
||||
self.social.send_event_to(&member, event.clone()).await;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn update_clan_provider(init_ctx: &crate::InitConfig) -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClanDataChanger> {
|
||||
SimpleOpImpl::new(ClanDataChanger {
|
||||
social: init_ctx.social.clone(),
|
||||
})
|
||||
}
|
||||
34
rc_social_room/src/operations/clan_my_info.rs
Normal file
34
rc_social_room/src/operations/clan_my_info.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed};
|
||||
|
||||
use crate::data::clan::*;
|
||||
|
||||
const CODE: u8 = 43;
|
||||
|
||||
const CLAN_NAME_PARAM_KEY: u8 = 31; // in and out
|
||||
const CLAN_DESCRIPTION_PARAM_KEY: u8 = 32; // out only
|
||||
const CLAN_TYPE_PARAM_KEY: u8 = 34; // out only
|
||||
|
||||
pub(super) struct MyClanInfoGetter {}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for MyClanInfoGetter {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
let user_info = user.user()?;
|
||||
let clan_info = user_info.my_clan_info(false).await?;
|
||||
if let Some((clan_info, _members_info)) = clan_info {
|
||||
log::debug!("Found my clan info for {}", clan_info.name);
|
||||
params.insert(CLAN_DESCRIPTION_PARAM_KEY, Typed::Str(clan_info.description.into()));
|
||||
params.insert(CLAN_NAME_PARAM_KEY, Typed::Str(clan_info.name.into()));
|
||||
params.insert(CLAN_TYPE_PARAM_KEY, Typed::Int(ClanType::from_core(clan_info.ty).to_u8() as _));
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clan_info_provider() -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, MyClanInfoGetter> {
|
||||
SimpleOpImpl::new(MyClanInfoGetter {})
|
||||
}
|
||||
@@ -23,6 +23,19 @@ mod platoon_leave;
|
||||
mod platoon_kick;
|
||||
mod platoon_invites;
|
||||
mod platoon_status;
|
||||
mod clan_create;
|
||||
mod clan_my_info;
|
||||
mod clan_join;
|
||||
mod clan_leave;
|
||||
mod clan_member_remove;
|
||||
mod clan_modify;
|
||||
mod clan_invite_to;
|
||||
mod clan_invite_accept;
|
||||
mod clan_invite_decline;
|
||||
mod clan_invite_decline_all;
|
||||
mod clan_invite_cancel;
|
||||
mod clan_member_rerank;
|
||||
mod clan_experience_poll;
|
||||
|
||||
use polariton_server::operations::OperationsHandler;
|
||||
|
||||
@@ -30,14 +43,12 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy,
|
||||
OperationsHandler::<crate::UserTy, crate::data::custom::CustomType>::new()
|
||||
.modify(oj_rc_core::polariton::RcOpModifier)
|
||||
.add(more_auth::more_lobby_auth(init_ctx))
|
||||
//.add(polariton_server::operations::Ack::<33, _>::default()) // get user clan info (this is equivalent to not being in a clan)
|
||||
.add(friend_list::friends_provider(init_ctx)) // TODO friend object parsing Token: 0x0200169C RID: 5788
|
||||
.add(settings::settings_provider()) // TODO save settings persistently
|
||||
.add(polariton_server::operations::Ack::<43, _>::default()) // get my clan info (this is equivalent to not being in a clan)
|
||||
.add(clan_invite::clan_invites_provider())
|
||||
//.add(polariton_server::operations::Ack::<19, _>::default()) // get pending platoon invite (this is equivalent to having no pending invite)
|
||||
.add(clan_my_info::clan_info_provider())
|
||||
.add(clan_invite::clan_invites_provider(init_ctx))
|
||||
.add(platoon_invites::platoon_pending_provider(init_ctx))
|
||||
.add(clan_info::clan_info_provider())
|
||||
.add(clan_info::clan_info_provider(init_ctx))
|
||||
.add(search_clan::search_clans_provider())
|
||||
.add(polariton_server::operations::Ack::<52, _>::default()) // validate pending season rewards (this just always needs to be ack-ed)
|
||||
.add(season_rewards::season_rewards_provider())
|
||||
@@ -60,4 +71,16 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy,
|
||||
.add(platoon_leave::platoon_leave_provider(init_ctx))
|
||||
.add(platoon_kick::platoon_kick_provider(init_ctx))
|
||||
.add(platoon_status::platoon_update_provider(init_ctx))
|
||||
.add(clan_create::creat_clan_provider(init_ctx))
|
||||
.add(clan_join::clan_join_provider(init_ctx))
|
||||
.add(clan_leave::clan_leave_provider(init_ctx))
|
||||
.add(clan_member_remove::clan_remove_provider(init_ctx))
|
||||
.add(clan_modify::update_clan_provider(init_ctx))
|
||||
.add(clan_invite_to::invite_to_clan_provider(init_ctx))
|
||||
.add(clan_invite_accept::clan_accept_provider(init_ctx))
|
||||
.add(clan_invite_decline::clan_decline_provider(init_ctx))
|
||||
.add(clan_invite_decline_all::clan_decline_all_provider(init_ctx))
|
||||
.add(clan_invite_cancel::clan_cancel_provider(init_ctx))
|
||||
.add(clan_member_rerank::clan_rank_change_provider(init_ctx))
|
||||
.add(clan_experience_poll::clan_experience_provider())
|
||||
}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
use polariton_server::operations::SimpleFunc;
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed, Arr};
|
||||
|
||||
use crate::data::clan::*;
|
||||
|
||||
// params in TODO
|
||||
const CODE: u8 = 32;
|
||||
|
||||
// params in
|
||||
const STRING_PARAM_KEY: u8 = 39;
|
||||
/*const DAYS_SINCE_ACTIVE_PARAM_KEY: u8 = 40;
|
||||
const DAYS_SINCE_ACTIVE_PARAM_KEY: u8 = 40;
|
||||
const START_RANGE_PARAM_KEY: u8 = 41;
|
||||
const END_RANGE_PARAM_KEY: u8 = 43;
|
||||
const TYPES_PARAM_KEY: u8 = 34;*/
|
||||
const TYPES_PARAM_KEY: u8 = 34;
|
||||
|
||||
// params out
|
||||
const RESULTS_PARAM_KEY: u8 = 42;
|
||||
|
||||
pub(super) fn search_clans_provider<C: Send + Sync>() -> SimpleFunc<32, crate::UserTy, impl (Fn(ParameterTable<C>, &crate::UserTy) -> Result<ParameterTable<C>, i16>) + Sync + Sync, C> {
|
||||
/*pub(super) fn search_clans_provider<C: Send + Sync>() -> SimpleFunc<32, crate::UserTy, impl (Fn(ParameterTable<C>, &crate::UserTy) -> Result<ParameterTable<C>, i16>) + Sync + Sync, C> {
|
||||
SimpleFunc::new(|params, _| {
|
||||
let mut params = params.to_dict();
|
||||
if let Some(Typed::Str(s)) = params.get(&STRING_PARAM_KEY) {
|
||||
@@ -35,4 +37,69 @@ pub(super) fn search_clans_provider<C: Send + Sync>() -> SimpleFunc<32, crate::U
|
||||
}));
|
||||
Ok(params.into())
|
||||
})
|
||||
}*/
|
||||
|
||||
pub(super) struct ClansSearcher {}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<crate::data::custom::CustomType> for ClansSearcher {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<crate::data::custom::CustomType>, user: &Self::User) -> Result<ParameterTable<crate::data::custom::CustomType>, SimpleOpError> {
|
||||
let search_string = if let Some(Typed::Str(search)) = params.remove(&STRING_PARAM_KEY) {
|
||||
search.string
|
||||
} else {
|
||||
String::default()
|
||||
};
|
||||
if let Some(Typed::Int(days_since_active)) = params.remove(&DAYS_SINCE_ACTIVE_PARAM_KEY) {
|
||||
if let Some(Typed::Int(start_range)) = params.remove(&START_RANGE_PARAM_KEY) {
|
||||
if let Some(Typed::Int(end_range)) = params.remove(&END_RANGE_PARAM_KEY) {
|
||||
if let Some(Typed::Arr(clan_types)) = params.remove(&TYPES_PARAM_KEY) {
|
||||
let mut types = Vec::with_capacity(clan_types.items.len());
|
||||
for item in clan_types.items {
|
||||
if let Typed::Int(ty) = item {
|
||||
types.push(
|
||||
crate::data::clan::ClanType::from_u8(ty as u8)
|
||||
.ok_or_else(|| SimpleOpError::with_message(
|
||||
oj_rc_core::data::error_codes::SocialErrorCode::UnexpectedError as i16,
|
||||
format!("Invalid clan type {}", ty),
|
||||
))?
|
||||
.to_core()
|
||||
);
|
||||
}
|
||||
}
|
||||
let search_query = oj_rc_core::persist::user::ClanSearchQuery {
|
||||
search_string,
|
||||
days_since_active,
|
||||
start_range,
|
||||
end_range,
|
||||
types,
|
||||
};
|
||||
let user_info = user.user()?;
|
||||
log::debug!("Searching for clans with query {:?}", search_query);
|
||||
let results = user_info.search_clan(search_query).await?;
|
||||
log::debug!("Got {} clans from search", results.len());
|
||||
params.insert(RESULTS_PARAM_KEY, Typed::Arr(Arr {
|
||||
ty: polariton::serdes::TypePrefix::HashMap, // hashmap
|
||||
custom_ty: None,
|
||||
items: results.into_iter()
|
||||
.map(|clan| ClanInfo {
|
||||
clan_name: clan.name,
|
||||
clan_description: clan.description,
|
||||
clan_type: ClanType::from_core(clan.ty),
|
||||
clan_size: clan.size,
|
||||
}.as_transmissible())
|
||||
.collect(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn search_clans_provider() -> SimpleOpImpl<crate::data::custom::CustomType, crate::UserTy, ClansSearcher> {
|
||||
SimpleOpImpl::new(ClansSearcher {})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user