mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Friends functionality (#90)
### Description Implements and closes #86 ### Game Robocraft ### Please confirm - [x] I am the legal owner or represent the owner of all work submitted - [x] I consent to my submission being added to this FOSS project - [ ] This PR used LLMs to generate some or all of the code changes Reviewed-on: https://git.ngram.ca/OpenJam/rc-servers/pulls/90 Co-authored-by: NG (Graham) <ngniusness@gmail.com> Co-committed-by: NG (Graham) <ngniusness@gmail.com>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
pub struct Migration;
|
||||
|
||||
impl MigrationName for Migration {
|
||||
fn name(&self) -> &str {
|
||||
"m20260215_000001_create_friend_table"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
// Define how to apply this migration: Create the Friends table.
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(crate::schema::friend::Entity)
|
||||
.col(
|
||||
ColumnDef::new(crate::schema::friend::Column::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::CreationTime).big_integer().not_null())
|
||||
.col(ColumnDef::new(crate::schema::friend::Column::FriendSource).integer().not_null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk-friends-friend_source")
|
||||
.from(crate::schema::friend::Entity, crate::schema::friend::Column::FriendSource)
|
||||
.to(crate::schema::user::Entity, crate::schema::user::Column::Id),
|
||||
)
|
||||
.col(ColumnDef::new(crate::schema::friend::Column::FriendTarget).integer().not_null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk-friends-friend_target")
|
||||
.from(crate::schema::friend::Entity, crate::schema::friend::Column::FriendTarget)
|
||||
.to(crate::schema::user::Entity, crate::schema::user::Column::Id),
|
||||
)
|
||||
.col(ColumnDef::new(crate::schema::friend::Column::State).string().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// Define how to rollback this migration: Drop the Friends table.
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(crate::schema::friend::Entity).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ mod m20250918_000001_add_player_variant;
|
||||
mod m20251228_000001_create_score_table;
|
||||
#[cfg(feature = "factory")]
|
||||
mod m20260126_000001_create_factory_vehicle_table;
|
||||
mod m20260215_000001_create_friend_table;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -37,6 +38,7 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20251228_000001_create_score_table::Migration),
|
||||
#[cfg(feature = "factory")]
|
||||
Box::new(m20260126_000001_create_factory_vehicle_table::Migration),
|
||||
Box::new(m20260215_000001_create_friend_table::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
59
rc_database/src/schema/friend.rs
Normal file
59
rc_database/src/schema/friend.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "friends")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub creation_time: i64, // seconds since unix epoch
|
||||
pub friend_source: i32,
|
||||
pub friend_target: i32,
|
||||
pub state: FriendStatus,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::user::Entity",
|
||||
from = "Column::FriendSource",
|
||||
to = "super::user::Column::Id"
|
||||
)]
|
||||
Source,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::user::Entity",
|
||||
from = "Column::FriendTarget",
|
||||
to = "super::user::Column::Id"
|
||||
)]
|
||||
Target,
|
||||
}
|
||||
|
||||
/*impl Related<super::user::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Source.def()
|
||||
}
|
||||
}*/
|
||||
|
||||
impl Related<super::user::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Target.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 FriendStatus {
|
||||
InviteSent,
|
||||
InvitePending,
|
||||
Accepted,
|
||||
Declined,
|
||||
Cancelled,
|
||||
Removed,
|
||||
}
|
||||
|
||||
pub const FINAL_STATUSES: [FriendStatus; 3] = [
|
||||
FriendStatus::Declined,
|
||||
FriendStatus::Cancelled,
|
||||
FriendStatus::Removed,
|
||||
];
|
||||
@@ -12,6 +12,7 @@ pub mod game_event;
|
||||
pub mod multiplayer_game_score;
|
||||
#[cfg(feature = "factory")]
|
||||
pub mod factory;
|
||||
pub mod friend;
|
||||
|
||||
pub fn parse_int_csv(s: &str) -> Vec<u32> {
|
||||
s.split(',').filter_map(|i_as_s| {
|
||||
|
||||
@@ -27,6 +27,8 @@ pub enum Relation {
|
||||
Player,
|
||||
#[sea_orm(has_many = "super::factory::vehicle::Entity")]
|
||||
FactoryUploads,
|
||||
#[sea_orm(has_many = "super::friend::Entity")]
|
||||
Friends, // this will probably join the wrong column (i.e. in the wrong direction)
|
||||
}
|
||||
|
||||
impl Related<super::permissions::Entity> for Entity {
|
||||
@@ -65,4 +67,10 @@ impl Related<super::factory::vehicle::Entity> for Entity {
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::friend::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Friends.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use sea_orm_migration::MigratorTrait;
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, TransactionTrait};
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, TransactionTrait, sea_query::ExprTrait};
|
||||
|
||||
pub struct Database {
|
||||
orm: std::sync::Arc<sea_orm::DatabaseConnection>,
|
||||
@@ -39,6 +39,30 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn user_by_public_id(&self, public_id: String) -> Result<Option<crate::schema::user::Model>, sea_orm::DbErr> {
|
||||
crate::schema::user::Entity::find()
|
||||
.filter(crate::schema::user::Column::PublicId.eq(public_id))
|
||||
.one(self.orm.as_ref())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn user_by_some_social_id(&self, public_id: String) -> Result<Option<crate::schema::user::Model>, sea_orm::DbErr> {
|
||||
let lower_public_id = public_id.to_lowercase();
|
||||
crate::schema::user::Entity::find()
|
||||
.filter(sea_orm::sea_query::Expr::expr(
|
||||
sea_orm::sea_query::Func::lower(crate::schema::user::Column::DisplayName.into_expr())
|
||||
).eq(&lower_public_id).or(
|
||||
sea_orm::sea_query::Func::lower(crate::schema::user::Column::PublicId.into_expr())
|
||||
.eq(&lower_public_id)
|
||||
).or(
|
||||
sea_orm::sea_query::Func::lower(crate::schema::user::Column::Email.into_expr())
|
||||
.eq(&lower_public_id)
|
||||
)
|
||||
)
|
||||
.one(self.orm.as_ref())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn user_by_steam_id(&self, steam_id: u64) -> Result<Option<crate::schema::user::Model>, sea_orm::DbErr> {
|
||||
crate::schema::user::Entity::find()
|
||||
.filter(crate::schema::user::Column::SteamId.eq(Some(steam_id.to_string())))
|
||||
@@ -89,6 +113,14 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn user_auxs_by_user_ids_and_descriptor(&self, user_ids: impl std::iter::IntoIterator<Item=i32>, descriptor: crate::schema::user_aux::Descriptor) -> Result<Vec<crate::schema::user_aux::Model>, sea_orm::DbErr> {
|
||||
crate::schema::user_aux::Entity::find()
|
||||
.filter(crate::schema::user_aux::Column::UserId.is_in(user_ids))
|
||||
.filter(crate::schema::user_aux::Column::Descriptor.eq(descriptor))
|
||||
.all(self.orm.as_ref())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn insert_user_aux(&self, entities: Vec<crate::schema::user_aux::ActiveModel>) -> Result<(), sea_orm::DbErr> {
|
||||
crate::schema::user_aux::Entity::insert_many(entities.into_iter()).exec(self.orm.as_ref()).await?;
|
||||
Ok(())
|
||||
@@ -552,6 +584,43 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn friends_by_user_id(&self, user_id: i32, status_not_in: impl IntoIterator<Item=crate::schema::friend::FriendStatus>) -> Result<Vec<(crate::schema::friend::Model, crate::schema::user::Model)>, sea_orm::DbErr> {
|
||||
Ok(crate::schema::friend::Entity::find()
|
||||
.find_also_related(crate::schema::user::Entity)
|
||||
.filter(crate::schema::friend::Column::FriendSource.eq(user_id))
|
||||
.filter(crate::schema::friend::Column::State.is_not_in(status_not_in))
|
||||
.order_by_asc(crate::schema::friend::Column::CreationTime)
|
||||
.all(self.orm.as_ref())
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|(friend, user_opt)| user_opt.map(|user| (friend, user)))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn insert_friends(&self, entities: impl std::iter::IntoIterator<Item=crate::schema::friend::ActiveModel>) -> Result<(), sea_orm::DbErr> {
|
||||
crate::schema::friend::Entity::insert_many(entities).exec(self.orm.as_ref()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_friends_state(&self, user_id_1: i32, user_id_2: i32, state: crate::schema::friend::FriendStatus) -> Result<(), sea_orm::DbErr> {
|
||||
crate::schema::friend::Entity::update_many()
|
||||
.filter(
|
||||
sea_orm::sea_query::Condition::any()
|
||||
.add(
|
||||
sea_orm::sea_query::Expr::expr(crate::schema::friend::Column::FriendSource.eq(user_id_1))
|
||||
.and(crate::schema::friend::Column::FriendTarget.eq(user_id_2))
|
||||
).add(
|
||||
sea_orm::sea_query::Expr::expr(crate::schema::friend::Column::FriendSource.eq(user_id_2))
|
||||
.and(crate::schema::friend::Column::FriendTarget.eq(user_id_1))
|
||||
)
|
||||
)
|
||||
.filter(crate::schema::friend::Column::State.is_not_in(crate::schema::friend::FINAL_STATUSES))
|
||||
.col_expr(crate::schema::friend::Column::State, sea_orm::sea_query::Expr::value(state))
|
||||
.exec(self.orm.as_ref())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn metrics(&self) -> super::DatabaseMetrics {
|
||||
self.metrics.lock().unwrap().snapshot()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user