diff --git a/Cargo.lock b/Cargo.lock index 4f72309..47e896e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3094,6 +3094,7 @@ dependencies = [ "actix-files", "actix-web", "actix-ws", + "chrono", "clap", "env_logger", "futures", diff --git a/assets/templates/rc_society/federation.html.hbs b/assets/templates/rc_society/federation.html.hbs new file mode 100644 index 0000000..c2ec19f --- /dev/null +++ b/assets/templates/rc_society/federation.html.hbs @@ -0,0 +1,72 @@ + + + + + + Federation - Openjam + + + + + + + + + + + + + + + + + + + + {{> stylesheet}} + + +
+
+ Welcome {{form.display_name}}! | + Home | + Dashboard | + Sign Out +
+

Federation Details

+ {{#if error}} +
+

{{error}}

+
+ {{/if}} + +
+ {{#each form.instances}} +
+

{{this.domain}}

+
+
Database ID
+
{{this.id}}
+
Auth Server
+ +
Society Server
+ +
First Seen
+
+
Last Seen
+
+
+
+ {{/each}} +
+ + +
+ + diff --git a/assets/templates/rc_society/index.html.hbs b/assets/templates/rc_society/index.html.hbs index 7af9e25..89afc39 100644 --- a/assets/templates/rc_society/index.html.hbs +++ b/assets/templates/rc_society/index.html.hbs @@ -46,7 +46,7 @@

This service allows you to manage your account settings, import and export your garage bays, view the server config, and will eventually (not yet) federate with other OpenJam rc-servers instances. - You can view other known instances here. + {{#if form.is_logged_in}}You can view other known instances here.{{/if}} This service is intended to have stable public APIs for expanding existing functionality. If you do not want any of this functionality in your instance, simply do not run this service with your instance.

diff --git a/rc_core/src/persist/user/mod.rs b/rc_core/src/persist/user/mod.rs index 22132de..3baaebb 100644 --- a/rc_core/src/persist/user/mod.rs +++ b/rc_core/src/persist/user/mod.rs @@ -11,7 +11,7 @@ mod inventory; pub use inventory::{UnlockedParts, UnlockOverride}; mod traits; -pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, FederatedAuthInfo, UserLoginInfo, UserAuthenticator, FederatedAuthenticator, 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, Userless, GameOverrides, WebUser, GarageWebInfo, GarageWebStats, SanctionWebStats, AccountWebStats, SocialWebStats}; +pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, FederatedAuthInfo, UserLoginInfo, UserAuthenticator, FederatedAuthenticator, 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, Userless, GameOverrides, WebUser, GarageWebInfo, GarageWebStats, SanctionWebStats, AccountWebStats, SocialWebStats, FederationWebData, FederationWebDetails}; pub mod intercom; pub use intercom::generate_token as generate_intercom_token; diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs index 88e9f09..7b1b56b 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -703,6 +703,7 @@ pub trait WebUser: CommonUser { async fn account_stats(&self) -> Result>; async fn sanction_stats(&self) -> Result>; async fn social_stats(&self) -> Result>; + async fn fedi_info(&self) -> Result>; } pub struct GarageWebInfo { @@ -749,6 +750,19 @@ pub struct SocialWebStats { pub chats: Vec, } +pub struct FederationWebData { + pub federations: Vec, +} + +pub struct FederationWebDetails { + pub id: i32, + pub domain: String, + pub auth: String, + pub society: String, + pub last_used: i64, + pub first_used: i64, +} + #[async_trait::async_trait] pub trait Userless: Send + Sync { async fn lobby_state_listener(&self) -> Result, reqwest_websocket::Error>; diff --git a/rc_core/src/persist/user/web.rs b/rc_core/src/persist/user/web.rs index fcaf94a..ac1c3b0 100644 --- a/rc_core/src/persist/user/web.rs +++ b/rc_core/src/persist/user/web.rs @@ -202,4 +202,20 @@ impl super::WebUser for super::account_json::UserData { chats: chat_list, }) } + + async fn fedi_info(&self) -> Result> { + let servers = self.db.federations_all().await?; + Ok(super::FederationWebData { + federations: servers.into_iter() + .map(|server| super::FederationWebDetails { + id: server.id, + domain: server.domain, + auth: server.auth, + society: server.society, + last_used: server.last_used_time, + first_used: server.creation_time, + }) + .collect() + }) + } } diff --git a/rc_database/src/wrapper.rs b/rc_database/src/wrapper.rs index ebd4387..2f807c1 100644 --- a/rc_database/src/wrapper.rs +++ b/rc_database/src/wrapper.rs @@ -943,6 +943,13 @@ impl Database { .await } + pub async fn federations_all(&self) -> Result, sea_orm::DbErr> { + crate::schema::federation::Entity::find() + .order_by_asc(crate::schema::federation::Column::Id) + .all(self.orm.as_ref()) + .await + } + pub async fn insert_federation(&self, entity: crate::schema::federation::ActiveModel) -> Result { #[cfg(debug_assertions)] assert!(matches!(entity.id, sea_orm::ActiveValue::NotSet)); diff --git a/rc_society/src/main.rs b/rc_society/src/main.rs index a23ee44..b5ba453 100644 --- a/rc_society/src/main.rs +++ b/rc_society/src/main.rs @@ -104,6 +104,7 @@ async fn main() -> std::io::Result<()> { .service(web::user_federation::post_add) .service(web::user_federation::post_off) .service(web::user_federation::post_on) + .service(web::federation::get) .service(api::config::get) .service(api::urls::get) }) diff --git a/rc_society/src/web/dashboard.rs b/rc_society/src/web/dashboard.rs index 8a01f82..f2243b4 100644 --- a/rc_society/src/web/dashboard.rs +++ b/rc_society/src/web/dashboard.rs @@ -98,7 +98,6 @@ pub async fn dashboard_impl(handlebars_ref: Data>, au match super::try_auth_user(user_opt, auth.as_ref(), &req).await? { super::LoginReturn::AuthFail(resp) => Ok(resp), super::LoginReturn::Success(user) => { - // TODO log::debug!("Rendering user's dashboard"); let creation_time = user.creation(); let creation_time_chrono = chrono::DateTime::::from_timestamp_secs(creation_time).unwrap_or_default(); diff --git a/rc_society/src/web/federation.rs b/rc_society/src/web/federation.rs new file mode 100644 index 0000000..14d848c --- /dev/null +++ b/rc_society/src/web/federation.rs @@ -0,0 +1,85 @@ +use actix_web::{get, web::Data, Responder, HttpRequest}; +use actix_identity::Identity; +use serde::{Serialize, Deserialize}; + +use crate::web::{LoginReturn, try_auth_user, render_ok, render_err}; + +const FORM_NAME: &str = "federation"; + +#[derive(Serialize, Deserialize)] +struct RenderData { + display_name: String, + public_id: String, + instances: Vec, +} + +#[derive(Serialize, Deserialize)] +struct FederatedServer { + id: i32, + domain: String, + auth: String, + society: String, + last_used_unix: i64, + last_used_iso: String, + first_used_unix: i64, + first_used_iso: String, +} + +#[get("/federation")] +pub async fn get(handlebars_ref: Data>, auth: Data>, user_opt: Option, req: HttpRequest) -> Result { + match try_auth_user(user_opt, auth.as_ref(), &req).await? { + LoginReturn::AuthFail(resp) => Ok(resp), + LoginReturn::Success(user) => { + let fedi_info = match build_fedi_info(user.as_ref()).await { + Ok(x) => x, + Err(e) => { + let html = render_err( + RenderData { + display_name: user.display_name().to_owned(), + public_id: user.public_id().to_owned(), + instances: Vec::default(), + }, + e.to_string(), + handlebars_ref.as_ref(), + FORM_NAME, + ); + return Ok( + html + .respond_to(&req) + .map_into_boxed_body() + ); + }, + }; + let html = render_ok( + RenderData { + display_name: user.display_name().to_owned(), + public_id: user.public_id().to_owned(), + instances: fedi_info, + }, + handlebars_ref.as_ref(), + FORM_NAME, + ); + Ok( + html + .respond_to(&req) + .map_into_boxed_body() + ) + } + } +} + +async fn build_fedi_info(user: &dyn oj_rc_core::persist::user::WebUser) -> Result, Box> { + let info = user.fedi_info().await?; + Ok(info.federations.into_iter() + .map(|instance| FederatedServer { + id: instance.id, + domain: instance.domain, + auth: instance.auth, + society: instance.society, + last_used_unix: instance.last_used, + last_used_iso: chrono::DateTime::::from_timestamp_secs(instance.last_used).unwrap_or_default().to_rfc3339(), + first_used_unix: instance.first_used, + first_used_iso: chrono::DateTime::::from_timestamp_secs(instance.first_used).unwrap_or_default().to_rfc3339(), + }).collect() + ) +} diff --git a/rc_society/src/web/mod.rs b/rc_society/src/web/mod.rs index b14f519..cd98acf 100644 --- a/rc_society/src/web/mod.rs +++ b/rc_society/src/web/mod.rs @@ -5,6 +5,7 @@ pub mod index; pub mod garage; pub mod user_federation; pub mod logout; +pub mod federation; use serde::Serialize; use actix_web::{web::{Html, Redirect}, Responder};