1
0
mirror of https://git.ngram.ca/OpenJam/rc-servers synced 2026-08-23 23:08:52 +00:00

Add federation webpage to society; fix dead link on home page

This commit is contained in:
NG (Graham)
2026-07-18 00:32:28 -04:00
parent 147a07c338
commit d9787734dc
11 changed files with 199 additions and 3 deletions

1
Cargo.lock generated
View File

@@ -3094,6 +3094,7 @@ dependencies = [
"actix-files",
"actix-web",
"actix-ws",
"chrono",
"clap",
"env_logger",
"futures",

View File

@@ -0,0 +1,72 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- Primary Meta Tags -->
<title>Federation - Openjam</title>
<meta name="title" content="Federation - Openjam" />
<meta name="description" content="View federation of this third-party Robocraft servers" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://git.ngram.ca/OpenJam/rc-servers" />
<meta property="og:title" content="Federation - Openjam" />
<meta property="og:description" content="View federation of this third-party Robocraft servers" />
<meta property="og:image" content="https://git.ngram.ca/OpenJam/rc-servers/raw/branch/main/assets/robocraft/favicon.jpg" />
<!-- Twitter -->
<meta property="twitter:card" content="summary" />
<meta property="twitter:url" content="https://git.ngram.ca/OpenJam/rc-servers" />
<meta property="twitter:title" content="Federation - Openjam" />
<meta property="twitter:description" content="View federation of this third-party Robocraft servers" />
<meta property="twitter:image" content="https://git.ngram.ca/OpenJam/rc-servers/raw/branch/main/assets/robocraft/favicon.jpg" />
<link rel="icon" type="image/x-icon" href="/favicon.ico">
{{> stylesheet}}
</head>
<body>
<div class="form-stripe">
<div class="form-header-right">
Welcome {{form.display_name}}! |
<a href="/">Home</a> |
<a href="/dashboard">Dashboard</a> |
<a href="/logout">Sign Out</a>
</div>
<h1 class="form-banner">Federation Details</h1>
{{#if error}}
<div class="form-error">
<p class="form-error-message">{{error}}</p>
</div>
{{/if}}
<div class="access-list">
{{#each form.instances}}
<div class="access-row-box">
<h4 class="access-box-title">{{this.domain}}</h4>
<div class="box-details">
<div class="box-details-key">Database ID</div>
<div class="box-details-value">{{this.id}}</div>
<div class="box-details-key">Auth Server</div>
<div class="box-details-value"><a class="box-details-link" href="{{this.auth}}">{{this.auth}}</a></div>
<div class="box-details-key">Society Server</div>
<div class="box-details-value"><a class="box-details-link" href="{{this.society}}">{{this.society}}</a></div>
<div class="box-details-key">First Seen</div>
<div class="box-details-value"><time datetime="{{this.first_used_iso}}">{{this.first_used_iso}}</time></div>
<div class="box-details-key">Last Seen</div>
<div class="box-details-value"><time datetime="{{this.first_used_iso}}">{{this.first_used_iso}}</time></div>
</div>
</div>
{{/each}}
</div>
<footer>
<div class="footer">
{{version}}
| <a href="{{source_url}}#readme">About</a>
| <a href="https://liberapay.com/NGram">Support Me</a>
</div>
</footer>
</div>
</body>
</html>

View File

@@ -46,7 +46,7 @@
<div class="form-text-block">
<p>
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 <a href="/federation">here</a>.
{{#if form.is_logged_in}}You can view other known instances <a href="/federation">here</a>.{{/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.
</p>

View File

@@ -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;

View File

@@ -703,6 +703,7 @@ pub trait WebUser: CommonUser {
async fn account_stats(&self) -> Result<AccountWebStats, Box<dyn std::error::Error>>;
async fn sanction_stats(&self) -> Result<SanctionWebStats, Box<dyn std::error::Error>>;
async fn social_stats(&self) -> Result<SocialWebStats, Box<dyn std::error::Error>>;
async fn fedi_info(&self) -> Result<FederationWebData, Box<dyn std::error::Error>>;
}
pub struct GarageWebInfo {
@@ -749,6 +750,19 @@ pub struct SocialWebStats {
pub chats: Vec<String>,
}
pub struct FederationWebData {
pub federations: Vec<FederationWebDetails>,
}
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<super::IntercomListener<super::intercom::IntercomLobbyStateMessage>, reqwest_websocket::Error>;

View File

@@ -202,4 +202,20 @@ impl super::WebUser for super::account_json::UserData {
chats: chat_list,
})
}
async fn fedi_info(&self) -> Result<super::FederationWebData, Box<dyn std::error::Error>> {
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()
})
}
}

View File

@@ -943,6 +943,13 @@ impl Database {
.await
}
pub async fn federations_all(&self) -> Result<Vec<crate::schema::federation::Model>, 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<crate::schema::federation::Model, sea_orm::DbErr> {
#[cfg(debug_assertions)]
assert!(matches!(entity.id, sea_orm::ActiveValue::NotSet));

View File

@@ -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)
})

View File

@@ -98,7 +98,6 @@ pub async fn dashboard_impl(handlebars_ref: Data<handlebars::Handlebars<'_>>, 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::<chrono::Utc>::from_timestamp_secs(creation_time).unwrap_or_default();

View File

@@ -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<FederatedServer>,
}
#[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<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
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<Vec<FederatedServer>, Box<dyn std::error::Error>> {
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::<chrono::Utc>::from_timestamp_secs(instance.last_used).unwrap_or_default().to_rfc3339(),
first_used_unix: instance.first_used,
first_used_iso: chrono::DateTime::<chrono::Utc>::from_timestamp_secs(instance.first_used).unwrap_or_default().to_rfc3339(),
}).collect()
)
}

View File

@@ -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};