feat: add fetching and making invites

This commit is contained in:
Radical 2025-05-09 19:26:49 +02:00
parent 773f4ca977
commit c693e89853
6 changed files with 182 additions and 0 deletions

View file

@ -27,6 +27,7 @@ tokio-tungstenite = { version = "0.26", features = ["native-tls", "url"] }
toml = "0.8"
url = { version = "2.5", features = ["serde"] }
uuid = { version = "1.16", features = ["serde", "v7"] }
random-string = "1.1"
[dependencies.tokio]
version = "1.44"

View file

@ -4,6 +4,7 @@ mod auth;
mod stats;
mod users;
mod servers;
mod invites;
pub fn web() -> Scope {
web::scope("/v1")
@ -11,4 +12,5 @@ pub fn web() -> Scope {
.service(auth::web())
.service(users::web())
.service(servers::web())
.service(invites::web())
}

View file

View file

@ -0,0 +1,100 @@
use actix_web::{get, post, web, Error, HttpRequest, HttpResponse};
use serde::Deserialize;
use uuid::Uuid;
use crate::{api::v1::auth::check_access_token, structs::{Guild, Member}, utils::get_auth_header, Data};
#[derive(Deserialize)]
struct InviteRequest {
custom_id: String,
}
#[get("{uuid}/invites")]
pub async fn get_invites(req: HttpRequest, path: web::Path<(Uuid,)>, data: web::Data<Data>) -> Result<HttpResponse, Error> {
let headers = req.headers();
let auth_header = get_auth_header(headers);
if let Err(error) = auth_header {
return Ok(error)
}
let guild_uuid = path.into_inner().0;
let authorized = check_access_token(auth_header.unwrap(), &data.pool).await;
if let Err(error) = authorized {
return Ok(error)
}
let uuid = authorized.unwrap();
let member = Member::fetch_one(&data.pool, uuid, guild_uuid).await;
if let Err(error) = member {
return Ok(error);
}
let guild_result = Guild::fetch_one(&data.pool, guild_uuid).await;
if let Err(error) = guild_result {
return Ok(error);
}
let guild = guild_result.unwrap();
let invites = guild.get_invites(&data.pool).await;
if let Err(error) = invites {
return Ok(error);
}
Ok(HttpResponse::Ok().json(invites.unwrap()))
}
#[post("{uuid}/invites")]
pub async fn create_invite(req: HttpRequest, path: web::Path<(Uuid,)>, invite_request: web::Json<Option<InviteRequest>>, data: web::Data<Data>) -> Result<HttpResponse, Error> {
let headers = req.headers();
let auth_header = get_auth_header(headers);
if let Err(error) = auth_header {
return Ok(error)
}
let guild_uuid = path.into_inner().0;
let authorized = check_access_token(auth_header.unwrap(), &data.pool).await;
if let Err(error) = authorized {
return Ok(error)
}
let uuid = authorized.unwrap();
let member_result = Member::fetch_one(&data.pool, uuid, guild_uuid).await;
if let Err(error) = member_result {
return Ok(error);
}
let member = member_result.unwrap();
let guild_result = Guild::fetch_one(&data.pool, guild_uuid).await;
if let Err(error) = guild_result {
return Ok(error);
}
let guild = guild_result.unwrap();
let custom_id = invite_request.as_ref().and_then(|ir| Some(ir.custom_id.clone()));
let invite = guild.create_invite(&data.pool, &member, custom_id).await;
if let Err(error) = invite {
return Ok(error);
}
Ok(HttpResponse::Ok().json(invite.unwrap()))
}

View file

@ -3,19 +3,26 @@ use uuid::Uuid;
mod channels;
mod roles;
mod invites;
use crate::{api::v1::auth::check_access_token, structs::{Guild, Member}, utils::get_auth_header, Data};
pub fn web() -> Scope {
web::scope("")
// Servers
.service(res)
// Channels
.service(channels::response)
.service(channels::response_post)
.service(channels::uuid::res)
.service(channels::uuid::messages::res)
// Roles
.service(roles::response)
.service(roles::response_post)
.service(roles::uuid::res)
// Invites
.service(invites::get_invites)
.service(invites::create_invite)
}
#[get("/{uuid}")]

View file

@ -296,6 +296,50 @@ impl Guild {
member_count: 1
})
}
pub async fn get_invites(&self, pool: &Pool<Postgres>) -> Result<Vec<Invite>, HttpResponse> {
let invites = sqlx::query_as(&format!("SELECT (id, guild_uuid, user_uuid) FROM invites WHERE guild_uuid = '{}'", self.uuid))
.fetch_all(pool)
.await;
if let Err(error) = invites {
error!("{}", error);
return Err(HttpResponse::InternalServerError().finish())
}
Ok(invites.unwrap().iter().map(|b: &InviteBuilder| b.build()).collect())
}
pub async fn create_invite(&self, pool: &Pool<Postgres>, member: &Member, custom_id: Option<String>) -> Result<Invite, HttpResponse> {
let invite_id;
if custom_id.is_none() {
let charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
invite_id = random_string::generate(8, charset);
} else {
invite_id = custom_id.unwrap();
if invite_id.len() > 32 {
return Err(HttpResponse::BadRequest().finish())
}
}
let result = sqlx::query(&format!("INSERT INTO invites (id, guild_uuid, user_uuid) VALUES ($1, '{}', '{}'", self.uuid, member.user_uuid))
.bind(&invite_id)
.execute(pool)
.await;
if let Err(error) = result {
error!("{}", error);
return Err(HttpResponse::InternalServerError().finish())
}
Ok(Invite {
id: invite_id,
user_uuid: member.user_uuid,
guild_uuid: self.uuid,
})
}
}
#[derive(FromRow)]
@ -468,3 +512,31 @@ pub struct Message {
user_uuid: Uuid,
message: String,
}
#[derive(FromRow)]
pub struct InviteBuilder {
id: String,
user_uuid: String,
guild_uuid: String,
}
impl InviteBuilder {
fn build(&self) -> Invite {
Invite {
id: self.id.clone(),
user_uuid: Uuid::from_str(&self.user_uuid).unwrap(),
guild_uuid: Uuid::from_str(&self.guild_uuid).unwrap(),
}
}
}
/// Server invite struct
#[derive(Serialize)]
pub struct Invite {
/// case-sensitive alphanumeric string with a fixed length of 8 characters, can be up to 32 characters for custom invites
id: String,
/// User that created the invite
user_uuid: Uuid,
/// UUID of the guild that the invite belongs to
guild_uuid: Uuid,
}