added frontend + securing beta server invites

This commit is contained in:
2026-01-02 22:50:02 +05:30
parent cb12b8ef75
commit 9b17a99456
52 changed files with 5409 additions and 5 deletions

View File

@@ -1,4 +1,4 @@
use actix_web::{web, App, HttpServer, HttpResponse, post, get, error, body::EitherBody, dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}};
use actix_web::{web, App, HttpServer, HttpResponse, post, get, delete, error, body::EitherBody, dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}};
use futures::future::LocalBoxFuture;
use futures::FutureExt;
use serde::{Deserialize, Serialize};
@@ -7,6 +7,7 @@ use std::sync::Arc;
use surrealdb::Surreal;
use surrealdb::engine::remote::ws::Client;
use tracing::{info, warn};
use chrono;
#[derive(Deserialize)]
pub struct IsBotThereRequest {
@@ -773,6 +774,134 @@ async fn delete_level_bridger(
})))
}
#[derive(Serialize, Deserialize, Debug)]
pub struct BetaServer {
pub guild_id: String,
pub added_at: chrono::DateTime<chrono::Utc>,
pub added_by: String,
}
#[get("/api/beta_testing")]
async fn get_beta_servers(
data: web::Data<ApiState>,
) -> Result<HttpResponse, actix_web::Error> {
info!("Processing GET /api/beta_testing request");
let servers: Vec<BetaServer> = data.db.select("beta_testing").await
.map_err(|e| {
warn!("Database query error: {}", e);
error::ErrorInternalServerError("Database query failed")
})?;
Ok(HttpResponse::Ok().json(servers))
}
#[derive(Deserialize)]
pub struct CreateBetaServerRequest {
pub guild_id: String,
}
#[post("/api/beta_testing")]
async fn create_beta_server(
body: web::Json<CreateBetaServerRequest>,
data: web::Data<ApiState>,
) -> Result<HttpResponse, actix_web::Error> {
info!("Processing POST /api/beta_testing request for {}", body.guild_id);
let beta_info = BetaServer {
guild_id: body.guild_id.clone(),
added_at: chrono::Utc::now(),
added_by: "API".to_string(),
};
let _: Option<BetaServer> = data.db
.create(("beta_testing", &body.guild_id))
.content(beta_info)
.await
.map_err(|e| {
warn!("Database create error: {}", e);
error::ErrorInternalServerError("Database create failed")
})?;
Ok(HttpResponse::Created().json(serde_json::json!({
"success": true,
"message": format!("Server {} added to beta testing", body.guild_id)
})))
}
#[delete("/api/beta_testing/{guild_id}")]
async fn delete_beta_server(
guild_id: web::Path<String>,
data: web::Data<ApiState>,
) -> Result<HttpResponse, actix_web::Error> {
let guild_id_value = guild_id.into_inner();
info!("Processing DELETE /api/beta_testing/{} request", guild_id_value);
let _: Option<BetaServer> = data.db
.delete(("beta_testing", &guild_id_value))
.await
.map_err(|e| {
warn!("Database delete error: {}", e);
error::ErrorInternalServerError("Database delete failed")
})?;
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"message": format!("Server {} removed from beta testing", guild_id_value)
})))
}
#[derive(Deserialize)]
pub struct IsBetaServerRequest {
pub guild_ids: Vec<u64>,
}
#[derive(Serialize)]
pub struct IsBetaServerResponse {
pub results: Vec<bool>,
}
#[post("/api/is_beta_server")]
async fn is_beta_server(
body: web::Json<IsBetaServerRequest>,
data: web::Data<ApiState>,
) -> Result<HttpResponse, actix_web::Error> {
info!("Processing POST /api/is_beta_server with {} IDs", body.guild_ids.len());
// Optimization: Single query to check all IDs
let ids_to_check: Vec<surrealdb::sql::Thing> = body.guild_ids
.iter()
.map(|gid| surrealdb::sql::Thing::from(("beta_testing".to_string(), gid.to_string())))
.collect();
let sql = "SELECT * FROM beta_testing WHERE id IN $ids";
let mut response = data.db.query(sql)
.bind(("ids", ids_to_check))
.await
.map_err(|e| {
warn!("Database query error: {}", e);
error::ErrorInternalServerError("Database query failed")
})?;
let found_servers: Vec<BetaServer> = response.take(0)
.map_err(|e| {
warn!("Failed to parse database response: {}", e);
error::ErrorInternalServerError("Failed to parse database response")
})?;
let found_ids: std::collections::HashSet<String> = found_servers
.into_iter()
.map(|s| s.guild_id)
.collect();
let results: Vec<bool> = body.guild_ids
.iter()
.map(|gid| found_ids.contains(&gid.to_string()))
.collect();
Ok(HttpResponse::Ok().json(IsBetaServerResponse { results }))
}
pub async fn start_api_server(
cache: Arc<Cache>,
db: Surreal<Client>,
@@ -798,6 +927,10 @@ pub async fn start_api_server(
.service(create_level_bridger)
.service(update_level_bridger)
.service(delete_level_bridger)
.service(get_beta_servers)
.service(create_beta_server)
.service(delete_beta_server)
.service(is_beta_server)
})
.bind(("0.0.0.0", port))?
.run()

89
src/commands/beta.rs Normal file
View File

@@ -0,0 +1,89 @@
use crate::{Context, Error};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct BetaInfo {
guild_id: String,
added_at: chrono::DateTime<chrono::Utc>,
added_by: String,
}
/// Add a server to beta testing (Owner only)
#[poise::command(slash_command, prefix_command, owners_only)]
pub async fn add_beta_testing(
ctx: Context<'_>,
#[description = "The server ID to add (defaults to current server)"] server_id: Option<String>,
) -> Result<(), Error> {
let guild_id = if let Some(id) = server_id {
id
} else if let Some(id) = ctx.guild_id() {
id.to_string()
} else {
ctx.say("Please provide a server ID or run this command in a server.").await?;
return Ok(());
};
let db = &ctx.data().db;
let beta_info = BetaInfo {
guild_id: guild_id.clone(),
added_at: chrono::Utc::now(),
added_by: ctx.author().id.to_string(),
};
let _: Option<BetaInfo> = db
.create(("beta_testing", &guild_id))
.content(beta_info)
.await?;
ctx.say(format!("Added server {} to beta testing.", guild_id)).await?;
Ok(())
}
/// List all beta testing servers (Owner only)
#[poise::command(slash_command, prefix_command, owners_only)]
pub async fn list_beta_testing(ctx: Context<'_>) -> Result<(), Error> {
let db = &ctx.data().db;
let servers: Vec<BetaInfo> = db.select("beta_testing").await?;
if servers.is_empty() {
ctx.say("No servers in beta testing.").await?;
return Ok(());
}
let mut response = String::from("**Beta Testing Servers:**\n");
for server in servers {
response.push_str(&format!("- {} (Added: <t:{}:R>)\n", server.guild_id, server.added_at.timestamp()));
}
ctx.say(response).await?;
Ok(())
}
/// Remove a server from beta testing (Owner only)
#[poise::command(slash_command, prefix_command, owners_only)]
pub async fn remove_beta_testing(
ctx: Context<'_>,
#[description = "The server ID to remove (defaults to current server)"] server_id: Option<String>,
) -> Result<(), Error> {
let guild_id = if let Some(id) = server_id {
id
} else if let Some(id) = ctx.guild_id() {
id.to_string()
} else {
ctx.say("Please provide a server ID or run this command in a server.").await?;
return Ok(());
};
let db = &ctx.data().db;
let _: Option<BetaInfo> = db.delete(("beta_testing", &guild_id)).await?;
ctx.say(format!("Removed server {} from beta testing.", guild_id)).await?;
Ok(())
}

View File

@@ -1,3 +1,4 @@
pub mod fun;
pub mod level;
pub mod utility;
pub mod beta;

View File

@@ -100,6 +100,9 @@ async fn main() -> Result<(), Error> {
commands::utility::delete_auto_response(),
commands::utility::edit_auto_response(),
commands::utility::summary(),
commands::beta::add_beta_testing(),
commands::beta::list_beta_testing(),
commands::beta::remove_beta_testing(),
],
prefix_options: poise::PrefixFrameworkOptions {
prefix: Some("!".into()),