From 1de68f86ae915e6b4150d53293ec92a072d710fa Mon Sep 17 00:00:00 2001 From: Benjamin Sherriff Date: Sun, 12 May 2024 09:05:59 -0400 Subject: [PATCH] Formatting code --- service/docker-compose.yml | 2 - service/src/auth/model.rs | 38 ++-- service/src/auth/routes.rs | 90 +++++---- service/src/auth/session.rs | 25 ++- service/src/bot/commands/audio/mod.rs | 126 ++++++++++--- service/src/bot/commands/audio/pause.rs | 6 +- service/src/bot/commands/audio/play.rs | 90 ++++++--- service/src/bot/commands/audio/resume.rs | 10 +- service/src/bot/commands/audio/skip.rs | 6 +- service/src/bot/commands/audio/stop.rs | 10 +- service/src/bot/commands/audio/volume.rs | 34 ++-- service/src/bot/commands/chat.rs | 73 ++++---- service/src/bot/commands/help.rs | 1 + service/src/bot/commands/mod.rs | 4 +- service/src/bot/commands/ping.rs | 7 +- service/src/bot/commands/roll.rs | 63 ++++--- service/src/bot/commands/schedule.rs | 1 + service/src/bot/guilds/mod.rs | 2 +- service/src/bot/guilds/model.rs | 12 +- service/src/bot/guilds/routes.rs | 224 +++++++++++++++-------- service/src/bot/handler.rs | 83 ++++++--- service/src/bot/messages/model.rs | 14 +- service/src/bot/messages/routes.rs | 47 ++--- service/src/bot/oai/model.rs | 36 ++-- service/src/bot/ytdlp/mod.rs | 9 +- service/src/bot/ytdlp/model.rs | 3 +- service/src/dnd/backgrounds/mod.rs | 1 + service/src/dnd/bestiary/mod.rs | 1 + service/src/dnd/classes/mod.rs | 2 +- service/src/dnd/classes/model.rs | 8 +- service/src/dnd/conditions/mod.rs | 7 +- service/src/dnd/feats/mod.rs | 1 + service/src/dnd/items/mod.rs | 1 + service/src/dnd/mod.rs | 2 +- service/src/dnd/options/mod.rs | 1 + service/src/dnd/races/mod.rs | 1 + service/src/dnd/spells/mod.rs | 19 +- service/src/dnd/spells/model.rs | 220 ++++++++++++++++------ service/src/dnd/spells/routes.rs | 119 +++++++----- service/src/dnd/spells/types.rs | 105 +++++------ service/src/lib.rs | 51 +++--- service/src/main.rs | 34 ++-- service/src/storage/mod.rs | 33 ++-- service/src/storage/schema.rs | 2 +- service/src/users/mod.rs | 2 +- service/src/users/routes.rs | 92 +++++----- 46 files changed, 1109 insertions(+), 609 deletions(-) diff --git a/service/docker-compose.yml b/service/docker-compose.yml index ca93fb3..bda44aa 100644 --- a/service/docker-compose.yml +++ b/service/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - x-env_file: &env - path: .env required: true diff --git a/service/src/auth/model.rs b/service/src/auth/model.rs index 17f78d0..60840cb 100644 --- a/service/src/auth/model.rs +++ b/service/src/auth/model.rs @@ -86,7 +86,10 @@ impl InsertUser { Ok(user) } - pub fn update_profile(email: &str, profile_picture: Option<&str>) -> Result { + pub fn update_profile( + email: &str, + profile_picture: Option<&str>, + ) -> Result { let mut conn = connection()?; let user = diesel::update(users::table) .filter(users::email.eq(&email)) @@ -120,7 +123,7 @@ impl From for ResponseUser { #[derive(Debug, Serialize, Deserialize)] pub struct Auth { pub id: String, - pub user: ResponseUser + pub user: ResponseUser, } impl FromRequest for Auth { @@ -131,21 +134,30 @@ impl FromRequest for Auth { .cookie(SESSION_COOKIE_NAME) .map(|c| c.value().to_string()) .or_else(|| { - req.headers().get(http::header::AUTHORIZATION) - .map(|h| h.to_str().unwrap().split_at(7).1.to_string()) + req + .headers() + .get(http::header::AUTHORIZATION) + .map(|h| h.to_str().unwrap().split_at(7).1.to_string()) }) { - Some(token) => token, - None => return ready(Err(ActixError::from(ServiceError { + Some(token) => token, + None => { + return ready(Err(ActixError::from(ServiceError { status: 401, - message: "Unauthorized".to_string() + message: "Unauthorized".to_string(), }))) - }; - + } + }; + let ip_address = req.peer_addr().unwrap().ip().to_string(); - + match Session::verify(&session_id, &ip_address) { - Ok(v) => return ready(Ok(Auth { id: v.0.id, user: v.1.into() })), - Err(err) => return ready(Err(ActixError::from(err))) + Ok(v) => { + return ready(Ok(Auth { + id: v.0.id, + user: v.1.into(), + })) + } + Err(err) => return ready(Err(ActixError::from(err))), } } } @@ -156,7 +168,7 @@ pub fn verify_role(auth: &Auth, role: &str) -> Result<(), ServiceError> { } else { Err(ServiceError { status: 403, - message: "Forbidden".to_string() + message: "Forbidden".to_string(), }) } } diff --git a/service/src/auth/routes.rs b/service/src/auth/routes.rs index 085c393..88444f7 100644 --- a/service/src/auth/routes.rs +++ b/service/src/auth/routes.rs @@ -1,11 +1,18 @@ use std::env; -use actix_web::{get, post, web, HttpResponse, ResponseError, cookie::{Cookie, time::Duration}, HttpRequest}; +use actix_web::{ + get, post, web, HttpResponse, ResponseError, + cookie::{Cookie, time::Duration}, + HttpRequest, +}; use log::error; use redis::AsyncCommands; use siren::ServiceError; -use crate::{auth::{InsertUser, Auth, LoginRequest, QueryUser, RegisterUser, Session, SESSION_COOKIE_NAME}, storage::{self}}; +use crate::{ + auth::{InsertUser, Auth, LoginRequest, QueryUser, RegisterUser, Session, SESSION_COOKIE_NAME}, + storage::{self}, +}; use super::verify_hash; @@ -14,12 +21,10 @@ async fn register(user: web::Json) -> HttpResponse { let register_user = user.0; let insert_user: InsertUser = match register_user.convert_to_insert() { Ok(user) => user, - Err(err) => return ResponseError::error_response(&err) + Err(err) => return ResponseError::error_response(&err), }; match InsertUser::insert(insert_user) { - Ok(_) => { - HttpResponse::Created().finish() - }, + Ok(_) => HttpResponse::Created().finish(), Err(err) => { // Obfuscate the service error message to prevent leaking database details if err.status == 409 { @@ -39,10 +44,12 @@ async fn login(request: HttpRequest, login_request: web::Json) -> let query_user = match QueryUser::get_by_email(&email) { Ok(query_user) => query_user, - Err(_) => return ResponseError::error_response(&ServiceError { - status: 401, - message: "The email or password was incorrect.".to_string() - }) + Err(_) => { + return ResponseError::error_response(&ServiceError { + status: 401, + message: "The email or password was incorrect.".to_string(), + }) + } }; // Verify password if verify_hash(&login_request.password, &query_user.hash) { @@ -52,7 +59,7 @@ async fn login(request: HttpRequest, login_request: web::Json) -> Ok(conn) => conn, Err(err) => { error!("Failed to get redis connection: {}", err); - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); } }; @@ -61,10 +68,16 @@ async fn login(request: HttpRequest, login_request: web::Json) -> .parse::() .expect("SESSION_TTL must be an integer"); - let session_result: redis::RedisResult<()> = conn.set_ex(session.id.to_string(), &serde_json::to_string(&session).unwrap(), (session_ttl * 60) as usize).await; + let session_result: redis::RedisResult<()> = conn + .set_ex( + session.id.to_string(), + &serde_json::to_string(&session).unwrap(), + (session_ttl * 60) as usize, + ) + .await; if let Err(err) = session_result { error!("Failed to set access token in redis: {}", err); - return ResponseError::error_response(&ServiceError::from(err)) + return ResponseError::error_response(&ServiceError::from(err)); }; let session_cookie = Cookie::build(SESSION_COOKIE_NAME, session.id.clone()) @@ -82,12 +95,15 @@ async fn login(request: HttpRequest, login_request: web::Json) -> HttpResponse::Ok() .cookie(session_cookie) .cookie(user_id_cookie) - .json(Auth { id: session.id, user: query_user.into() }) + .json(Auth { + id: session.id, + user: query_user.into(), + }) } else { return ResponseError::error_response(&ServiceError { status: 401, - message: "The email or password was incorrect.".to_string() - }) + message: "The email or password was incorrect.".to_string(), + }); } } @@ -99,7 +115,7 @@ async fn refresh(req: HttpRequest, auth: Auth) -> HttpResponse { Ok(conn) => conn, Err(err) => { error!("Failed to get redis connection: {}", err); - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); } }; @@ -113,10 +129,16 @@ async fn refresh(req: HttpRequest, auth: Auth) -> HttpResponse { // Create new session let session = Session::new(&auth.user.email, &ip_address); - let session_result: redis::RedisResult<()> = conn.set_ex(session.id.to_string(), &serde_json::to_string(&session).unwrap(), (session_ttl * 60) as usize).await; + let session_result: redis::RedisResult<()> = conn + .set_ex( + session.id.to_string(), + &serde_json::to_string(&session).unwrap(), + (session_ttl * 60) as usize, + ) + .await; if let Err(err) = session_result { error!("Failed to set session id in redis: {}", err); - return ResponseError::error_response(&ServiceError::from(err)) + return ResponseError::error_response(&ServiceError::from(err)); }; // Create cookies @@ -130,7 +152,10 @@ async fn refresh(req: HttpRequest, auth: Auth) -> HttpResponse { HttpResponse::Ok() .cookie(session_cookie) .cookie(user_id_cookie) - .json(Auth { id: session.id, user: auth.user }) + .json(Auth { + id: session.id, + user: auth.user, + }) } #[post("/logout")] @@ -139,14 +164,14 @@ async fn logout(auth: Auth) -> HttpResponse { Ok(conn) => conn, Err(err) => { error!("Failed to get redis connection: {}", err); - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); } }; let session_result: redis::RedisResult<()> = conn.del(&auth.id.to_string()).await; if let Err(err) = session_result { error!("Failed to remove session id in redis: {}", err); - return ResponseError::error_response(&ServiceError::from(err)) + return ResponseError::error_response(&ServiceError::from(err)); }; let session_cookie = Cookie::build(SESSION_COOKIE_NAME, "") @@ -160,7 +185,7 @@ async fn logout(auth: Auth) -> HttpResponse { .max_age(Duration::new(-1, 0)) .http_only(true) .finish(); - + HttpResponse::Ok() .cookie(session_cookie) .cookie(user_id_cookie) @@ -189,12 +214,13 @@ pub fn init_routes(config: &mut web::ServiceConfig) { u.role = "admin".to_string(); u.verified = true; let _ = InsertUser::insert(u); - config.service(web::scope("auth") - .service(register) - .service(login) - .service(refresh) - .service(logout) - .service(me) - .service(roles) - ); -} \ No newline at end of file + config.service( + web::scope("auth") + .service(register) + .service(login) + .service(refresh) + .service(logout) + .service(me) + .service(roles), + ); +} diff --git a/service/src/auth/session.rs b/service/src/auth/session.rs index 50bfa8c..e664deb 100644 --- a/service/src/auth/session.rs +++ b/service/src/auth/session.rs @@ -1,7 +1,10 @@ use std::env; use actix_web::cookie::{time::Duration, Cookie}; -use argon2::{password_hash::{rand_core::OsRng, SaltString}, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; +use argon2::{ + password_hash::{rand_core::OsRng, SaltString}, + Argon2, PasswordHash, PasswordHasher, PasswordVerifier, +}; use rand::prelude::*; use rand_chacha::ChaCha20Rng; use redis::Commands; @@ -31,7 +34,7 @@ impl Session { id: csprng_128bit(), user_id: user_id.to_string(), ip_address: hash(&ip_address).unwrap(), - expiration: (now + chrono::Duration::minutes(ttl)).timestamp() + expiration: (now + chrono::Duration::minutes(ttl)).timestamp(), } } @@ -40,7 +43,7 @@ impl Session { // Check if the session exists let session = match conn.get::<_, String>(session_id) { Ok(session) => session, - Err(_) => return Err(ServiceError::new(401, "Unauthorized".to_string())) + Err(_) => return Err(ServiceError::new(401, "Unauthorized".to_string())), }; let session: Self = serde_json::from_str(&session)?; // Check if the IP address matches @@ -51,12 +54,12 @@ impl Session { // Check if the user exists let user = match crate::auth::model::QueryUser::get_by_email(&email) { Ok(user) => user, - Err(_) => return Err(ServiceError::new(401, "Unauthorized".to_string())) + Err(_) => return Err(ServiceError::new(401, "Unauthorized".to_string())), }; // Check if the session has expired let now = chrono::Utc::now().timestamp(); if now < session.expiration { - return Ok((session, user)) + return Ok((session, user)); } } Err(ServiceError::new(401, "Unauthorized".to_string())) @@ -79,7 +82,11 @@ impl Session { fn csprng_128bit() -> String { // Generate a CSPRNG 128-bit (16 byte) ID using alphanumeric characters (a-z, A-Z, 0-9) let rng = ChaCha20Rng::from_entropy(); - rng.sample_iter(rand::distributions::Alphanumeric).take(16).map(char::from).collect() + rng + .sample_iter(rand::distributions::Alphanumeric) + .take(16) + .map(char::from) + .collect() } pub fn hash(str: &str) -> Result { @@ -93,10 +100,10 @@ pub fn verify_hash(str: &str, hash: &str) -> bool { let bytes = str.as_bytes(); let parsed_hash = match PasswordHash::new(hash) { Ok(h) => h, - Err(_) => return false + Err(_) => return false, }; match Argon2::default().verify_password(bytes, &parsed_hash) { Ok(_) => true, - Err(_) => false + Err(_) => false, } -} \ No newline at end of file +} diff --git a/service/src/bot/commands/audio/mod.rs b/service/src/bot/commands/audio/mod.rs index c82f5b7..5f94cbc 100644 --- a/service/src/bot/commands/audio/mod.rs +++ b/service/src/bot/commands/audio/mod.rs @@ -4,7 +4,9 @@ use log::{debug, warn}; use reqwest::Url; use serenity::client::Cache; -use serenity::model::application::interaction::{InteractionResponseType, application_command::ApplicationCommandInteraction}; +use serenity::model::application::interaction::{ + InteractionResponseType, application_command::ApplicationCommandInteraction, +}; use serenity::model::prelude::{GuildId, ChannelId}; use serenity::model::user::User; use serenity::prelude::*; @@ -21,33 +23,60 @@ pub mod skip; pub mod stop; pub mod volume; -pub async fn join_by_user(cache: &Arc, manager: Arc, guild_id_option: &Option, user: &User) -> Result<(), ServiceError> { +pub async fn join_by_user( + cache: &Arc, + manager: Arc, + guild_id_option: &Option, + user: &User, +) -> Result<(), ServiceError> { let guild_id = match guild_id_option { Some(g) => g, - None => return Err(ServiceError { status: 422, message: format!("{}", "No guild ID set") }) + None => { + return Err(ServiceError { + status: 422, + message: format!("{}", "No guild ID set"), + }) + } }; let channel_id = match find_voice_channel(cache, &guild_id, &user) { Ok(channel) => channel, - Err(err) => return Err(ServiceError { status: 500, message: err.to_string() }) + Err(err) => { + return Err(ServiceError { + status: 500, + message: err.to_string(), + }) + } }; join(manager, guild_id, &channel_id).await } -pub async fn join(manager: Arc, guild_id: &GuildId, channel_id: &ChannelId) -> Result<(), ServiceError> { +pub async fn join( + manager: Arc, + guild_id: &GuildId, + channel_id: &ChannelId, +) -> Result<(), ServiceError> { debug!("<{}> Joining channel {}", guild_id.0, channel_id.0); - let (_handle_lock, success) = manager.join(guild_id.to_owned(), channel_id.to_owned()).await; + let (_handle_lock, success) = manager + .join(guild_id.to_owned(), channel_id.to_owned()) + .await; match success { Ok(s) => Ok(s), Err(err) => { warn!("Failed to join channel: {:?}", err); - Err(ServiceError { status: 500, message: err.to_string() }) + Err(ServiceError { + status: 500, + message: err.to_string(), + }) } } } -pub async fn leave(manager: Arc, guild_id_option: &Option) -> Result<(), String> { +pub async fn leave( + manager: Arc, + guild_id_option: &Option, +) -> Result<(), String> { let guild_id = match guild_id_option { Some(g) => g, None => { @@ -58,39 +87,72 @@ pub async fn leave(manager: Arc, guild_id_option: &Option) -> if manager.get(*guild_id).is_some() { debug!("<{}> Disconnecting from channel", guild_id.0); if let Err(e) = manager.remove(*guild_id).await { - return Err(format!("{}", e)) + return Err(format!("{}", e)); } } Ok(()) } -fn find_voice_channel(cache: &Arc, guild_id: &GuildId, user: &User) -> Result { +fn find_voice_channel( + cache: &Arc, + guild_id: &GuildId, + user: &User, +) -> Result { let guild = match guild_id.to_guild_cached(cache.to_owned()) { Some(g) => g, - None => return Err(format!("Guild not found")) + None => return Err(format!("Guild not found")), }; - match guild.voice_states.get(&user.id).and_then(|voice_state| voice_state.channel_id) { + match guild + .voice_states + .get(&user.id) + .and_then(|voice_state| voice_state.channel_id) + { Some(channel) => Ok(channel), - None => return Err(format!("User is not in a voice channel")) + None => return Err(format!("User is not in a voice channel")), } } -pub async fn create_response(ctx: &Context, command: &ApplicationCommandInteraction, content: String) -> Result<(), SerenityError> { - command.create_interaction_response(&ctx.http, |response: &mut serenity::builder::CreateInteractionResponse<'_>| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message: &mut serenity::builder::CreateInteractionResponseData<'_>| message.content(content)) - }).await +pub async fn create_response( + ctx: &Context, + command: &ApplicationCommandInteraction, + content: String, +) -> Result<(), SerenityError> { + command + .create_interaction_response( + &ctx.http, + |response: &mut serenity::builder::CreateInteractionResponse<'_>| { + response + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data( + |message: &mut serenity::builder::CreateInteractionResponseData<'_>| { + message.content(content) + }, + ) + }, + ) + .await } -pub async fn edit_response(ctx: &Context, command: &ApplicationCommandInteraction, content: String) -> Result { - command.edit_original_interaction_response(&ctx.http, |response: &mut serenity::builder::EditInteractionResponse| { - response.content(content) - }).await +pub async fn edit_response( + ctx: &Context, + command: &ApplicationCommandInteraction, + content: String, +) -> Result { + command + .edit_original_interaction_response( + &ctx.http, + |response: &mut serenity::builder::EditInteractionResponse| response.content(content), + ) + .await } -pub async fn add_song(call: Arc>, url: &str, lazy: bool, volume: Option) -> Result { +pub async fn add_song( + call: Arc>, + url: &str, + lazy: bool, + volume: Option, +) -> Result { let source = Restartable::ytdl(url.to_owned(), lazy).await?; let mut handler = call.lock().await; let track: Input = source.into(); @@ -115,9 +177,10 @@ pub fn get_playlist_urls(url: &str) -> Result, ServiceError> { None } else { Some( - serde_json::from_slice::(line.as_bytes()).map_err( - |err| ServiceError { status: 500, message: err.to_string() } - ) + serde_json::from_slice::(line.as_bytes()).map_err(|err| ServiceError { + status: 500, + message: err.to_string(), + }), ) } }) @@ -134,11 +197,16 @@ pub fn get_playlist_urls(url: &str) -> Result, ServiceError> { fn is_valid_url(url: &str) -> (bool, bool) { Url::parse(url).ok().map_or((false, false), |valid_url| { - let is_playlist: bool = valid_url.query_pairs().find(|(key, _)| key == "list").map_or(false, |_| true); + let is_playlist: bool = valid_url + .query_pairs() + .find(|(key, _)| key == "list") + .map_or(false, |_| true); (true, is_playlist) }) } pub async fn get_songbird(ctx: &Context) -> Arc { - songbird::get(ctx).await.expect("Songbird Voice client placed in at initialization") + songbird::get(ctx) + .await + .expect("Songbird Voice client placed in at initialization") } diff --git a/service/src/bot/commands/audio/pause.rs b/service/src/bot/commands/audio/pause.rs index 4423f67..213bfa9 100644 --- a/service/src/bot/commands/audio/pause.rs +++ b/service/src/bot/commands/audio/pause.rs @@ -16,7 +16,9 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { let guild_id = match command.guild_id { Some(g) => g, None => { - if let Err(why) = edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await { + if let Err(why) = + edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await + { error!("Failed to edit response message: {}", why); } return; @@ -40,4 +42,4 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("pause").description("Pause the current track") -} \ No newline at end of file +} diff --git a/service/src/bot/commands/audio/play.rs b/service/src/bot/commands/audio/play.rs index 855d84d..cfcd80f 100644 --- a/service/src/bot/commands/audio/play.rs +++ b/service/src/bot/commands/audio/play.rs @@ -10,7 +10,10 @@ use siren::ServiceError; use songbird::{EventHandler, Songbird}; use crate::bot::ytdlp::PlaylistItem; -use crate::bot::{guilds::QueryGuild, commands::audio::{leave, get_playlist_urls, add_song, get_songbird}}; +use crate::bot::{ + guilds::QueryGuild, + commands::audio::{leave, get_playlist_urls, add_song, get_songbird}, +}; use super::{create_response, edit_response, is_valid_url, join_by_user}; @@ -22,20 +25,23 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { Some(s) => s.to_owned(), None => { warn!("Missing track option"); - if let Err(why) = create_response(&ctx, &command, format!("Track option is missing")).await { + if let Err(why) = + create_response(&ctx, &command, format!("Track option is missing")).await + { error!("Failed to create response message: {}", why); } return; } - } + }, None => { warn!("Missing track option"); - if let Err(why) = create_response(&ctx, &command, format!("Track option is missing")).await { + if let Err(why) = create_response(&ctx, &command, format!("Track option is missing")).await + { error!("Failed to create response message: {}", why); } return; } - } + }, None => { warn!("Missing track option"); if let Err(why) = create_response(&ctx, &command, format!("Track option is missing")).await { @@ -52,12 +58,14 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { } let manager = get_songbird(ctx).await; - match join_by_user(&ctx.cache, manager,&command.guild_id, &command.user).await { + match join_by_user(&ctx.cache, manager, &command.guild_id, &command.user).await { Ok(_) => { let guild_id = match command.guild_id { Some(g) => g, None => { - if let Err(why) = edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await { + if let Err(why) = + edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await + { error!("Failed to edit response message: {}", why); } return; @@ -76,15 +84,17 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { if let Err(why) = edit_response(&ctx, &command, message).await { error!("Failed to edit response message: {}", why); } - }, + } Err(err) => { warn!("Failed to play track: {}", err); - if let Err(why) = edit_response(&ctx, &command, format!("Failed to play track: {}", err)).await { + if let Err(why) = + edit_response(&ctx, &command, format!("Failed to play track: {}", err)).await + { error!("Failed to edit response message: {}", why); } } }; - }, + } Err(err) => { warn!("{}", err); if let Err(why) = edit_response(&ctx, &command, format!("{}", err)).await { @@ -94,7 +104,11 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { } } -pub async fn play_track(manager: Arc, guild_id: GuildId, track_url: String) -> Result { +pub async fn play_track( + manager: Arc, + guild_id: GuildId, + track_url: String, +) -> Result { let mut track_count = 0; if let Some(handler_lock) = manager.get(guild_id) { let is_queue_empty = { @@ -105,7 +119,10 @@ pub async fn play_track(manager: Arc, guild_id: GuildId, track_url: St let valid = is_valid_url(&track_url); if !valid.0 { warn!("Invalid track url: {}", track_url); - return Err(ServiceError { status: 422, message: format!("Invalid track url: {}", track_url) }) + return Err(ServiceError { + status: 422, + message: format!("Invalid track url: {}", track_url), + }); } let mut playlist_items: Vec = Vec::new(); if valid.1 { @@ -113,7 +130,10 @@ pub async fn play_track(manager: Arc, guild_id: GuildId, track_url: St Ok(items) => items, Err(err) => { warn!("Failed to get playlist urls: {}", err); - return Err(ServiceError { status: 422, message: err.to_string() }) + return Err(ServiceError { + status: 422, + message: err.to_string(), + }); } }; } else { @@ -122,26 +142,42 @@ pub async fn play_track(manager: Arc, guild_id: GuildId, track_url: St url: track_url, title: "".to_string(), duration: 0, - playlist_index: 0 + playlist_index: 0, }; playlist_items.push(playlist_item); } for item in playlist_items { - match add_song(handler_lock.clone(), &item.url, is_queue_empty, Some(guild.volume as f32 / 100.0)).await { + match add_song( + handler_lock.clone(), + &item.url, + is_queue_empty, + Some(guild.volume as f32 / 100.0), + ) + .await + { Ok(added_song) => { let track_title = added_song.title.unwrap(); debug!("Added track: {}", track_title); let mut handler = handler_lock.lock().await; handler.remove_all_global_events(); - handler.add_global_event(songbird::Event::Track(songbird::TrackEvent::End), TrackEndNotifier { guild_id, call: manager.clone() }); + handler.add_global_event( + songbird::Event::Track(songbird::TrackEvent::End), + TrackEndNotifier { + guild_id, + call: manager.clone(), + }, + ); track_count += 1; - }, + } Err(err) => { warn!("Failed to add song: {}", err); if let Err(why) = leave(manager, &Some(guild_id)).await { error!("Failed to leave voice channel: {}", why); } - return Err(ServiceError { status: 422, message: err.to_string() }) + return Err(ServiceError { + status: 422, + message: err.to_string(), + }); } } } @@ -150,17 +186,21 @@ pub async fn play_track(manager: Arc, guild_id: GuildId, track_url: St } pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command.name("play").description("Plays the given track").create_option(|option| { option - .name("track") - .description("The track to be played") - .kind(serenity::model::prelude::command::CommandOptionType::String) - .required(true) - }) + command + .name("play") + .description("Plays the given track") + .create_option(|option| { + option + .name("track") + .description("The track to be played") + .kind(serenity::model::prelude::command::CommandOptionType::String) + .required(true) + }) } struct TrackEndNotifier { pub call: std::sync::Arc, - pub guild_id: serenity::model::id::GuildId + pub guild_id: serenity::model::id::GuildId, } #[async_trait] diff --git a/service/src/bot/commands/audio/resume.rs b/service/src/bot/commands/audio/resume.rs index d97a592..0a10a20 100644 --- a/service/src/bot/commands/audio/resume.rs +++ b/service/src/bot/commands/audio/resume.rs @@ -16,7 +16,9 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { let guild_id = match command.guild_id { Some(g) => g, None => { - if let Err(why) = edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await { + if let Err(why) = + edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await + { error!("Failed to edit response message: {}", why); } return; @@ -39,5 +41,7 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { } pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command.name("resume").description("Resume the current track") -} \ No newline at end of file + command + .name("resume") + .description("Resume the current track") +} diff --git a/service/src/bot/commands/audio/skip.rs b/service/src/bot/commands/audio/skip.rs index cd95d91..c91ffdd 100644 --- a/service/src/bot/commands/audio/skip.rs +++ b/service/src/bot/commands/audio/skip.rs @@ -16,7 +16,9 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { let guild_id = match command.guild_id { Some(g) => g, None => { - if let Err(why) = edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await { + if let Err(why) = + edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await + { error!("Failed to edit response message: {}", why); } return; @@ -40,4 +42,4 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("skip").description("Skip the current track") -} \ No newline at end of file +} diff --git a/service/src/bot/commands/audio/stop.rs b/service/src/bot/commands/audio/stop.rs index 32dec6f..b4943dd 100644 --- a/service/src/bot/commands/audio/stop.rs +++ b/service/src/bot/commands/audio/stop.rs @@ -16,7 +16,9 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { let guild_id = match command.guild_id { Some(g) => g, None => { - if let Err(why) = edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await { + if let Err(why) = + edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await + { error!("Failed to edit response message: {}", why); } return; @@ -34,5 +36,7 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { } pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command.name("stop").description("Stop the current track and clear the queue") -} \ No newline at end of file + command + .name("stop") + .description("Stop the current track and clear the queue") +} diff --git a/service/src/bot/commands/audio/volume.rs b/service/src/bot/commands/audio/volume.rs index 5a60c0e..624cbdd 100644 --- a/service/src/bot/commands/audio/volume.rs +++ b/service/src/bot/commands/audio/volume.rs @@ -19,20 +19,23 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { Some(p) => p as i32, None => { warn!("Unable to get volume option as a string"); - if let Err(why) = create_response(&ctx, &command, format!("Volume option is missing")).await { + if let Err(why) = + create_response(&ctx, &command, format!("Volume option is missing")).await + { error!("Failed to create response message: {}", why); } return; } - } + }, None => { warn!("Missing volume option value"); - if let Err(why) = create_response(&ctx, &command, format!("Volume option is missing")).await { + if let Err(why) = create_response(&ctx, &command, format!("Volume option is missing")).await + { error!("Failed to create response message: {}", why); } return; } - } + }, None => { warn!("Missing volume option"); if let Err(why) = create_response(&ctx, &command, format!("Volume option is missing")).await { @@ -51,7 +54,9 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { let guild_id = match command.guild_id { Some(g) => g, None => { - if let Err(why) = edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await { + if let Err(why) = + edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await + { error!("Failed to edit response message: {}", why); } return; @@ -59,7 +64,8 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { }; let manager = get_songbird(ctx).await; set_volume(manager, guild_id, volume).await; - if let Err(why) = edit_response(&ctx, &command, format!("Setting the volume to {}", volume)).await { + if let Err(why) = edit_response(&ctx, &command, format!("Setting the volume to {}", volume)).await + { error!("Failed to set the volume: {}", why); } } @@ -79,10 +85,14 @@ pub async fn set_volume(manager: Arc, guild_id: GuildId, volume: i32) } pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command.name("volume").description("Set the audio player volume").create_option(|option| { option + command .name("volume") - .description("Volume between 0 and 100") - .kind(serenity::model::prelude::command::CommandOptionType::Integer) - .required(true) - }) -} \ No newline at end of file + .description("Set the audio player volume") + .create_option(|option| { + option + .name("volume") + .description("Volume between 0 and 100") + .kind(serenity::model::prelude::command::CommandOptionType::Integer) + .required(true) + }) +} diff --git a/service/src/bot/commands/chat.rs b/service/src/bot/commands/chat.rs index e41e628..cb3f92b 100644 --- a/service/src/bot/commands/chat.rs +++ b/service/src/bot/commands/chat.rs @@ -26,31 +26,34 @@ pub async fn generate_response(ctx: &Context, msg: &Message, oai: &OAI) { }, ]; - match QueryMessage::get_all(&QueryFilters { - by_guild_id: Some(guild_id.0 as i64), - by_channel_id: Some(channel_id.0 as i64), - by_user_id: Some(author_id.0 as i64), - ..Default::default() - }, 100, 1) { + match QueryMessage::get_all( + &QueryFilters { + by_guild_id: Some(guild_id.0 as i64), + by_channel_id: Some(channel_id.0 as i64), + by_user_id: Some(author_id.0 as i64), + ..Default::default() + }, + 100, + 1, + ) { Ok(m) => { for message in m { - messages.push( - ChatCompletionMessage { - role: GPTRole::User, - content: format!("{}", message.request) - } - ); - messages.push( - ChatCompletionMessage { - role: GPTRole::Assistant, - content: format!("{}", message.response) - } - ); + messages.push(ChatCompletionMessage { + role: GPTRole::User, + content: format!("{}", message.request), + }); + messages.push(ChatCompletionMessage { + role: GPTRole::Assistant, + content: format!("{}", message.response), + }); } - }, - Err(err) => warn!("Could not load previous messages: {}", err) + } + Err(err) => warn!("Could not load previous messages: {}", err), }; - messages.push(ChatCompletionMessage { role: GPTRole::User, content: parsed_content.clone() }); + messages.push(ChatCompletionMessage { + role: GPTRole::User, + content: parsed_content.clone(), + }); let request = ChatCompletionRequest { model: oai.default_model.clone(), @@ -61,14 +64,18 @@ pub async fn generate_response(ctx: &Context, msg: &Message, oai: &OAI) { max_tokens: Some(oai.max_tokens), presence_penalty: Some(0.6), frequency_penalty: Some(0.0), - user: Some(msg.author.name.clone()) + user: Some(msg.author.name.clone()), }; // Get the thread channel ID let thread_name = generate_thread_name(oai, &parsed_content, 99).await; - let response_channel = match msg.channel_id.create_private_thread(&ctx.http, |thread| { - thread.name(thread_name).kind(ChannelType::PublicThread) - }).await { + let response_channel = match msg + .channel_id + .create_private_thread(&ctx.http, |thread| { + thread.name(thread_name).kind(ChannelType::PublicThread) + }) + .await + { Ok(c) => { let allow = Permissions::SEND_MESSAGES; let deny = Permissions::SEND_TTS_MESSAGES | Permissions::ATTACH_FILES; @@ -80,9 +87,7 @@ pub async fn generate_response(ctx: &Context, msg: &Message, oai: &OAI) { let _ = c.create_permission(&ctx.http, &overwrite).await; c.id } - Err(_) => { - channel_id - } + Err(_) => channel_id, }; let typing = response_channel.start_typing(&ctx.http).unwrap(); @@ -144,7 +149,10 @@ pub async fn generate_response(ctx: &Context, msg: &Message, oai: &OAI) { async fn generate_thread_name(oai: &OAI, s: &str, max_chars: usize) -> String { let message = ChatCompletionMessage { role: GPTRole::User, - content: format!("---\n{}\n---\nSummarize the message above into a concise Discord thread title", s) + content: format!( + "---\n{}\n---\nSummarize the message above into a concise Discord thread title", + s + ), }; let request = ChatCompletionRequest { model: oai.default_model.clone(), @@ -155,13 +163,14 @@ async fn generate_thread_name(oai: &OAI, s: &str, max_chars: usize) -> String { max_tokens: Some(oai.max_tokens), presence_penalty: Some(0.6), frequency_penalty: Some(0.0), - user: None + user: None, }; // Truncate the response to the max number of characters let mut response = match s.char_indices().nth(max_chars) { None => s, - Some((idx, _)) => &s[..idx] - }.to_string(); + Some((idx, _)) => &s[..idx], + } + .to_string(); // Set the response to the OAI response match oai.chat_completion(request).await { Ok(r) => { diff --git a/service/src/bot/commands/help.rs b/service/src/bot/commands/help.rs index e69de29..8b13789 100644 --- a/service/src/bot/commands/help.rs +++ b/service/src/bot/commands/help.rs @@ -0,0 +1 @@ + diff --git a/service/src/bot/commands/mod.rs b/service/src/bot/commands/mod.rs index 89262ed..e01634b 100644 --- a/service/src/bot/commands/mod.rs +++ b/service/src/bot/commands/mod.rs @@ -1,6 +1,6 @@ pub mod audio; -pub mod help; pub mod chat; +pub mod help; pub mod ping; -pub mod schedule; pub mod roll; +pub mod schedule; diff --git a/service/src/bot/commands/ping.rs b/service/src/bot/commands/ping.rs index 4524e09..951f712 100644 --- a/service/src/bot/commands/ping.rs +++ b/service/src/bot/commands/ping.rs @@ -1,5 +1,8 @@ use log::debug; -use serenity::{model::prelude::interaction::application_command::CommandDataOption, builder::CreateApplicationCommand}; +use serenity::{ + model::prelude::interaction::application_command::CommandDataOption, + builder::CreateApplicationCommand, +}; pub fn run(_options: &[CommandDataOption]) -> String { debug!("Ping command executed"); @@ -8,4 +11,4 @@ pub fn run(_options: &[CommandDataOption]) -> String { pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("ping").description("Replies with pong") -} \ No newline at end of file +} diff --git a/service/src/bot/commands/roll.rs b/service/src/bot/commands/roll.rs index 456d232..e929bf5 100644 --- a/service/src/bot/commands/roll.rs +++ b/service/src/bot/commands/roll.rs @@ -1,6 +1,12 @@ use log::{error, warn}; use rand::Rng; -use serenity::{builder::CreateApplicationCommand, client::Context, model::application::{command::CommandOptionType, interaction::application_command::ApplicationCommandInteraction}}; +use serenity::{ + builder::CreateApplicationCommand, + client::Context, + model::application::{ + command::CommandOptionType, interaction::application_command::ApplicationCommandInteraction, + }, +}; use crate::bot::commands::audio::edit_response; @@ -49,10 +55,17 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { total += roll; rolls.push(roll); } - let response = format!("{}d{}{} = {}", + let response = format!( + "{}d{}{} = {}", count, - sides, - if modifier > 0 { format!("+{}", modifier) } else if modifier < 0 { format!("-{}", modifier) } else { "".to_string() }, + sides, + if modifier > 0 { + format!("+{}", modifier) + } else if modifier < 0 { + format!("-{}", modifier) + } else { + "".to_string() + }, total + (modifier as u32) ); if let Err(why) = edit_response(&ctx, &command, response).await { @@ -60,13 +73,12 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { } } Err(why) => { - if let Err(why) = edit_response(&ctx, &command, format!("Invalid dice string: {}", why)).await { + if let Err(why) = edit_response(&ctx, &command, format!("Invalid dice string: {}", why)).await + { error!("Failed to create response message: {}", why); } } } - - } fn parse_dice(dice: &str) -> Result<(u32, u32, i32), String> { @@ -74,9 +86,9 @@ fn parse_dice(dice: &str) -> Result<(u32, u32, i32), String> { let count = match parts.next() { Some(c) => match c.parse::() { Ok(n) => n, - Err(_) => return Err(format!("Invalid dice count: {}", c)) + Err(_) => return Err(format!("Invalid dice count: {}", c)), }, - None => return Err(format!("Invalid dice string: {}", dice)) + None => return Err(format!("Invalid dice string: {}", dice)), }; let mut positive_modifier = true; let mut parts = match parts.next() { @@ -91,8 +103,8 @@ fn parse_dice(dice: &str) -> Result<(u32, u32, i32), String> { } else { p.split("+") } - }, - None => return Err(format!("Invalid dice string: {}", dice)) + } + None => return Err(format!("Invalid dice string: {}", dice)), }; let sides = match parts.next() { Some(s) => match s.parse::() { @@ -103,9 +115,9 @@ fn parse_dice(dice: &str) -> Result<(u32, u32, i32), String> { return Err(format!("Invalid dice sides: {}", s)); } } - Err(_) => return Err(format!("Invalid dice sides: {}", s)) + Err(_) => return Err(format!("Invalid dice sides: {}", s)), }, - None => return Err(format!("Invalid dice string: {}", dice)) + None => return Err(format!("Invalid dice string: {}", dice)), }; let modifier = match parts.next() { Some(m) => match m.parse::() { @@ -115,20 +127,23 @@ fn parse_dice(dice: &str) -> Result<(u32, u32, i32), String> { } else { n * -1 } - }, - Err(_) => return Err(format!("Invalid dice modifier: {}", m)) + } + Err(_) => return Err(format!("Invalid dice modifier: {}", m)), }, - None => 0 + None => 0, }; Ok((count, sides, modifier)) } pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command.name("roll").description("Rolls D&D dice").create_option(|option| { - option - .name("dice") - .description("Dice to roll") - .kind(CommandOptionType::String) - .required(true) - }) -} \ No newline at end of file + command + .name("roll") + .description("Rolls D&D dice") + .create_option(|option| { + option + .name("dice") + .description("Dice to roll") + .kind(CommandOptionType::String) + .required(true) + }) +} diff --git a/service/src/bot/commands/schedule.rs b/service/src/bot/commands/schedule.rs index e69de29..8b13789 100644 --- a/service/src/bot/commands/schedule.rs +++ b/service/src/bot/commands/schedule.rs @@ -0,0 +1 @@ + diff --git a/service/src/bot/guilds/mod.rs b/service/src/bot/guilds/mod.rs index 6666fdc..6fbb137 100644 --- a/service/src/bot/guilds/mod.rs +++ b/service/src/bot/guilds/mod.rs @@ -2,4 +2,4 @@ mod model; mod routes; pub use model::*; -pub use routes::init_routes; \ No newline at end of file +pub use routes::init_routes; diff --git a/service/src/bot/guilds/model.rs b/service/src/bot/guilds/model.rs index ea89714..d79bcb3 100644 --- a/service/src/bot/guilds/model.rs +++ b/service/src/bot/guilds/model.rs @@ -9,7 +9,7 @@ use crate::storage::{schema::guilds, connection}; pub struct QueryGuild { pub id: i64, pub bot_id: i64, - pub volume: i32 + pub volume: i32, } impl QueryGuild { @@ -25,19 +25,23 @@ impl QueryGuild { pub struct InsertGuild { pub id: i64, pub bot_id: i64, - pub volume: i32 + pub volume: i32, } impl InsertGuild { pub fn insert(guild: Self) -> Result { let mut conn = connection()?; - let guild = diesel::insert_into(guilds::table).values(guild).get_result(&mut conn)?; + let guild = diesel::insert_into(guilds::table) + .values(guild) + .get_result(&mut conn)?; Ok(guild) } pub fn update_audio(id: i64, volume: i32) -> Result { let mut conn = connection()?; - let guild = diesel::update(guilds::table.filter(guilds::id.eq(id))).set(guilds::volume.eq(volume)).get_result(&mut conn)?; + let guild = diesel::update(guilds::table.filter(guilds::id.eq(id))) + .set(guilds::volume.eq(volume)) + .get_result(&mut conn)?; Ok(guild) } } diff --git a/service/src/bot/guilds/routes.rs b/service/src/bot/guilds/routes.rs index 7b17aef..e1cf6ad 100644 --- a/service/src/bot/guilds/routes.rs +++ b/service/src/bot/guilds/routes.rs @@ -5,74 +5,104 @@ use serde::{Serialize, Deserialize}; use serenity::model::prelude::{GuildChannel, ChannelType}; use siren::{ServiceError, Response}; -use crate::{AppState, bot::commands::audio::{play::play_track, join}, bot::guilds::QueryGuild, auth::{Auth, verify_role}}; +use crate::{ + AppState, + bot::commands::audio::{play::play_track, join}, + bot::guilds::QueryGuild, + auth::{Auth, verify_role}, +}; #[get("/guilds")] async fn get_guilds(data: web::Data>, auth: Auth) -> HttpResponse { if let Err(err) = verify_role(&auth, "admin") { - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); }; let guild_results = &data.http.get_guilds(None, None).await; let guilds = match guild_results { Ok(guilds) => guilds, - Err(err) => return ResponseError::error_response(&ServiceError { - status: 422, - message: err.to_string() - }) + Err(err) => { + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) + } }; HttpResponse::Ok().json(Response { data: guilds, - metadata: None + metadata: None, }) } #[get("/{id}/text")] -async fn get_text_channels(id: web::Path, data: web::Data>, auth: Auth) -> HttpResponse { +async fn get_text_channels( + id: web::Path, + data: web::Data>, + auth: Auth, +) -> HttpResponse { if let Err(err) = verify_role(&auth, "admin") { - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); }; let channel_results = &data.http.get_channels(id.parse::().unwrap()).await; let channels = match channel_results { - Ok(channels) => channels.iter().filter(|c| c.kind == ChannelType::Text).collect::>(), - Err(err) => return ResponseError::error_response(&ServiceError { - status: 422, - message: err.to_string() - }) + Ok(channels) => channels + .iter() + .filter(|c| c.kind == ChannelType::Text) + .collect::>(), + Err(err) => { + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) + } }; HttpResponse::Ok().json(Response { data: channels, - metadata: None + metadata: None, }) } #[get("/{id}/voice")] -async fn get_voice_channels(id: web::Path, data: web::Data>, auth: Auth) -> HttpResponse { +async fn get_voice_channels( + id: web::Path, + data: web::Data>, + auth: Auth, +) -> HttpResponse { if let Err(err) = verify_role(&auth, "admin") { - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); }; let channel_results = &data.http.get_channels(id.parse::().unwrap()).await; let channels = match channel_results { - Ok(channels) => channels.iter().filter(|c| c.kind == ChannelType::Voice).collect::>(), - Err(err) => return ResponseError::error_response(&ServiceError { - status: 422, - message: err.to_string() - }) + Ok(channels) => channels + .iter() + .filter(|c| c.kind == ChannelType::Voice) + .collect::>(), + Err(err) => { + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) + } }; HttpResponse::Ok().json(Response { data: channels, - metadata: None + metadata: None, }) } #[derive(Serialize, Deserialize)] struct ChannelMessage { - message: String + message: String, } #[post("/{guild_id}/text/{channel_id}/message")] -async fn send_message(path: web::Path<(String, String)>, text: web::Json, data: web::Data>, auth: Auth) -> HttpResponse { +async fn send_message( + path: web::Path<(String, String)>, + text: web::Json, + data: web::Data>, + auth: Auth, +) -> HttpResponse { if let Err(err) = verify_role(&auth, "admin") { - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); }; let (guild_id, channel_id) = path.into_inner(); let guild_id = match guild_id.parse::() { @@ -80,7 +110,7 @@ async fn send_message(path: web::Path<(String, String)>, text: web::Json { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() + message: err.to_string(), }) } }; @@ -89,7 +119,7 @@ async fn send_message(path: web::Path<(String, String)>, text: web::Json { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() + message: err.to_string(), }) } }; @@ -99,26 +129,29 @@ async fn send_message(path: web::Path<(String, String)>, text: web::Json { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() + message: err.to_string(), }) } }; - + let channel = match channels.iter().find(|c| c.id.0 == channel_id) { Some(channel) => channel, None => { return ResponseError::error_response(&ServiceError { status: 422, - message: format!("Could not find channel with id {}", channel_id) + message: format!("Could not find channel with id {}", channel_id), }) } }; - if let Err(err) = channel.say(&Pin::new(&data.http).get_ref(), &text.message).await { + if let Err(err) = channel + .say(&Pin::new(&data.http).get_ref(), &text.message) + .await + { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() - }) + message: err.to_string(), + }); }; HttpResponse::Ok().finish() @@ -126,38 +159,55 @@ async fn send_message(path: web::Path<(String, String)>, text: web::Json, play_request: web::Json, data: web::Data>, auth: Auth) -> HttpResponse { +async fn play( + path: web::Path<(String, String)>, + play_request: web::Json, + data: web::Data>, + auth: Auth, +) -> HttpResponse { if let Err(err) = verify_role(&auth, "admin") { - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); }; let (guild_id, channel_id) = path.into_inner(); let guild_id = match guild_id.parse::() { Ok(id) => id, Err(err) => { - return ResponseError::error_response(&ServiceError { status: 422, message: err.to_string() }) + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) } }; let channel_id = match channel_id.parse::() { Ok(id) => id, Err(err) => { - return ResponseError::error_response(&ServiceError { status: 422, message: err.to_string() }) + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) } }; let http = Pin::new(&data.http).get_ref(); let guild = match http.get_guild(guild_id).await { Ok(guild) => guild, Err(err) => { - return ResponseError::error_response(&ServiceError { status: 422, message: err.to_string() }) + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) } }; let channel = match http.get_channel(channel_id).await { Ok(channel) => channel, Err(err) => { - return ResponseError::error_response(&ServiceError { status: 422, message: err.to_string() }) + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) } }; @@ -165,15 +215,22 @@ async fn play(path: web::Path<(String, String)>, play_request: web::Json { - match play_track(Arc::clone(&data.songbird), guild.id, play_request.track_url.to_string()).await { + match play_track( + Arc::clone(&data.songbird), + guild.id, + play_request.track_url.to_string(), + ) + .await + { Ok(_) => HttpResponse::Ok().finish(), - Err(err) => { - return ResponseError::error_response(&err) - } + Err(err) => return ResponseError::error_response(&err), } - }, + } Err(err) => { - return ResponseError::error_response(&ServiceError { status: 500, message: err.to_string() }) + return ResponseError::error_response(&ServiceError { + status: 500, + message: err.to_string(), + }) } } } @@ -181,7 +238,7 @@ async fn play(path: web::Path<(String, String)>, play_request: web::Json, data: web::Data>, auth: Auth) -> HttpResponse { if let Err(err) = verify_role(&auth, "admin") { - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); }; let guild_id = path.into_inner(); let guild_id = match guild_id.parse::() { @@ -189,7 +246,7 @@ async fn stop(path: web::Path, data: web::Data>, auth: Aut Err(err) => { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() + message: err.to_string(), }) } }; @@ -203,9 +260,13 @@ async fn stop(path: web::Path, data: web::Data>, auth: Aut } #[post("/{guild_id}/voice/resume")] -async fn resume(path: web::Path, data: web::Data>, auth: Auth) -> HttpResponse { +async fn resume( + path: web::Path, + data: web::Data>, + auth: Auth, +) -> HttpResponse { if let Err(err) = verify_role(&auth, "admin") { - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); }; let guild_id = path.into_inner(); let guild_id = match guild_id.parse::() { @@ -213,7 +274,7 @@ async fn resume(path: web::Path, data: web::Data>, auth: A Err(err) => { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() + message: err.to_string(), }) } }; @@ -223,8 +284,8 @@ async fn resume(path: web::Path, data: web::Data>, auth: A if let Err(err) = handler.queue().resume() { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() - }) + message: err.to_string(), + }); } } @@ -232,9 +293,13 @@ async fn resume(path: web::Path, data: web::Data>, auth: A } #[post("/{guild_id}/voice/pause")] -async fn pause(path: web::Path, data: web::Data>, auth: Auth) -> HttpResponse { +async fn pause( + path: web::Path, + data: web::Data>, + auth: Auth, +) -> HttpResponse { if let Err(err) = verify_role(&auth, "admin") { - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); }; let guild_id = path.into_inner(); let guild_id = match guild_id.parse::() { @@ -242,7 +307,7 @@ async fn pause(path: web::Path, data: web::Data>, auth: Au Err(err) => { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() + message: err.to_string(), }) } }; @@ -252,8 +317,8 @@ async fn pause(path: web::Path, data: web::Data>, auth: Au if let Err(err) = handler.queue().pause() { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() - }) + message: err.to_string(), + }); } } @@ -262,13 +327,13 @@ async fn pause(path: web::Path, data: web::Data>, auth: Au #[derive(Serialize, Deserialize)] struct SetVolume { - volume: String + volume: String, } #[get("/{guild_id}/voice/volume")] async fn get_volume(path: web::Path, auth: Auth) -> HttpResponse { if let Err(err) = verify_role(&auth, "admin") { - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); }; let guild_id = path.into_inner(); let guild_id = match guild_id.parse::() { @@ -276,7 +341,7 @@ async fn get_volume(path: web::Path, auth: Auth) -> HttpResponse { Err(err) => { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() + message: err.to_string(), }) } }; @@ -286,7 +351,7 @@ async fn get_volume(path: web::Path, auth: Auth) -> HttpResponse { Err(err) => { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() + message: err.to_string(), }) } }; @@ -295,9 +360,14 @@ async fn get_volume(path: web::Path, auth: Auth) -> HttpResponse { } #[post("/{guild_id}/voice/volume")] -async fn set_volume(path: web::Path, volume: web::Json::, data: web::Data>, auth: Auth) -> HttpResponse { +async fn set_volume( + path: web::Path, + volume: web::Json, + data: web::Data>, + auth: Auth, +) -> HttpResponse { if let Err(err) = verify_role(&auth, "admin") { - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); }; let guild_id = path.into_inner(); let guild_id = match guild_id.parse::() { @@ -305,7 +375,7 @@ async fn set_volume(path: web::Path, volume: web::Json::, dat Err(err) => { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() + message: err.to_string(), }) } }; @@ -316,7 +386,10 @@ async fn set_volume(path: web::Path, volume: web::Json::, dat let guild = match http.get_guild(guild_id).await { Ok(guild) => guild, Err(err) => { - return ResponseError::error_response(&ServiceError { status: 422, message: err.to_string() }) + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) } }; crate::bot::commands::audio::volume::set_volume(manager, guild.id, volume).await; @@ -327,7 +400,7 @@ async fn set_volume(path: web::Path, volume: web::Json::, dat #[post("/{guild_id}/voice/skip")] async fn skip(path: web::Path, data: web::Data>, auth: Auth) -> HttpResponse { if let Err(err) = verify_role(&auth, "admin") { - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); }; let guild_id = path.into_inner(); let guild_id = match guild_id.parse::() { @@ -335,7 +408,7 @@ async fn skip(path: web::Path, data: web::Data>, auth: Aut Err(err) => { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() + message: err.to_string(), }) } }; @@ -345,8 +418,8 @@ async fn skip(path: web::Path, data: web::Data>, auth: Aut if let Err(err) = handler.queue().skip() { return ResponseError::error_response(&ServiceError { status: 422, - message: err.to_string() - }) + message: err.to_string(), + }); } } @@ -354,9 +427,8 @@ async fn skip(path: web::Path, data: web::Data>, auth: Aut } pub fn init_routes(config: &mut web::ServiceConfig) { - config - .service(get_guilds) - .service(web::scope("guilds") + config.service(get_guilds).service( + web::scope("guilds") .service(get_text_channels) .service(get_voice_channels) .service(send_message) @@ -366,6 +438,6 @@ pub fn init_routes(config: &mut web::ServiceConfig) { .service(pause) .service(set_volume) .service(get_volume) - .service(skip) + .service(skip), ); -} \ No newline at end of file +} diff --git a/service/src/bot/handler.rs b/service/src/bot/handler.rs index fef8a3b..e3f256a 100644 --- a/service/src/bot/handler.rs +++ b/service/src/bot/handler.rs @@ -12,7 +12,7 @@ use super::commands::audio::create_response; pub struct Handler { // Open AI Config - pub oai: Option + pub oai: Option, } #[async_trait] @@ -28,18 +28,21 @@ impl EventHandler for Handler { Ok(mentioned) => { let bot_in_thread = match msg.channel_id.get_thread_members(&ctx.http).await { Ok(t) => { - match t.iter().find(|t| t.user_id.unwrap().0 == ctx.cache.current_user_id().0) { + match t + .iter() + .find(|t| t.user_id.unwrap().0 == ctx.cache.current_user_id().0) + { Some(_) => true, - None => false + None => false, } } - Err(_) => false + Err(_) => false, }; if mentioned || bot_in_thread { commands::chat::generate_response(&ctx, &msg, oai).await; } } - Err(why) => warn!("Could not check mentions: {:?}", why) + Err(why) => warn!("Could not check mentions: {:?}", why), }; } None => {} @@ -59,9 +62,9 @@ impl EventHandler for Handler { _ => { let content: String = match command.data.name.as_str() { "ping" => commands::ping::run(&command.data.options), - _ => "Unknown command".to_string() + _ => "Unknown command".to_string(), }; - + if let Err(why) = create_response(&ctx, &command, content).await { warn!("Cannot respond to slash command: {}", why); } @@ -78,23 +81,61 @@ impl EventHandler for Handler { let _ = InsertGuild::insert(InsertGuild { id: (guild.id.0 as i64), bot_id: ctx.cache.current_user().id.0 as i64, - volume: 100 + volume: 100, }); - let commands = guild.id.set_application_commands(&ctx.http, |commands| { - commands - .create_application_command(|command: &mut serenity::builder::CreateApplicationCommand| { commands::ping::register(command) }) - .create_application_command(|command: &mut serenity::builder::CreateApplicationCommand| { commands::roll::register(command) }) - .create_application_command(|command: &mut serenity::builder::CreateApplicationCommand| { commands::audio::play::register(command) }) - .create_application_command(|command: &mut serenity::builder::CreateApplicationCommand| { commands::audio::stop::register(command) }) - .create_application_command(|command: &mut serenity::builder::CreateApplicationCommand| { commands::audio::pause::register(command) }) - .create_application_command(|command: &mut serenity::builder::CreateApplicationCommand| { commands::audio::resume::register(command) }) - .create_application_command(|command: &mut serenity::builder::CreateApplicationCommand| { commands::audio::skip::register(command) }) - .create_application_command(|command: &mut serenity::builder::CreateApplicationCommand| { commands::audio::volume::register(command) }) - }).await; + let commands = guild + .id + .set_application_commands(&ctx.http, |commands| { + commands + .create_application_command( + |command: &mut serenity::builder::CreateApplicationCommand| { + commands::ping::register(command) + }, + ) + .create_application_command( + |command: &mut serenity::builder::CreateApplicationCommand| { + commands::roll::register(command) + }, + ) + .create_application_command( + |command: &mut serenity::builder::CreateApplicationCommand| { + commands::audio::play::register(command) + }, + ) + .create_application_command( + |command: &mut serenity::builder::CreateApplicationCommand| { + commands::audio::stop::register(command) + }, + ) + .create_application_command( + |command: &mut serenity::builder::CreateApplicationCommand| { + commands::audio::pause::register(command) + }, + ) + .create_application_command( + |command: &mut serenity::builder::CreateApplicationCommand| { + commands::audio::resume::register(command) + }, + ) + .create_application_command( + |command: &mut serenity::builder::CreateApplicationCommand| { + commands::audio::skip::register(command) + }, + ) + .create_application_command( + |command: &mut serenity::builder::CreateApplicationCommand| { + commands::audio::volume::register(command) + }, + ) + }) + .await; match commands { Ok(c) => info!("Registered {} commands for guild {}", c.len(), guild.id.0), - Err(why) => error!("Could not register commands for guild {}: {:?}", guild.id.0, why) + Err(why) => error!( + "Could not register commands for guild {}: {:?}", + guild.id.0, why + ), }; } } -} \ No newline at end of file +} diff --git a/service/src/bot/messages/model.rs b/service/src/bot/messages/model.rs index 1ce6b17..4cc4e7c 100644 --- a/service/src/bot/messages/model.rs +++ b/service/src/bot/messages/model.rs @@ -2,7 +2,10 @@ use diesel::prelude::*; use serde::{Deserialize, Serialize}; use siren::ServiceError; -use crate::storage::{schema::messages::{self}, connection}; +use crate::storage::{ + schema::messages::{self}, + connection, +}; #[derive(Queryable, Selectable, Insertable, AsChangeset, Serialize, Deserialize)] #[diesel(table_name = messages)] @@ -28,7 +31,7 @@ pub struct QueryFilters { pub by_request: Option, pub by_response: Option, pub by_request_tags: Option>, - pub by_response_tags: Option> + pub by_response_tags: Option>, } impl Default for QueryFilters { @@ -42,7 +45,7 @@ impl Default for QueryFilters { by_request: None, by_response: None, by_request_tags: None, - by_response_tags: None + by_response_tags: None, } } } @@ -50,7 +53,10 @@ impl Default for QueryFilters { impl QueryMessage { pub fn get_all(filters: &QueryFilters, limit: i32, page: i32) -> Result, ServiceError> { let mut conn = connection()?; - let mut query = messages::table.limit(limit as i64).order(messages::created.asc()).into_boxed(); + let mut query = messages::table + .limit(limit as i64) + .order(messages::created.asc()) + .into_boxed(); // Limit query to page and limit let offset = (page - 1) * limit; query = query.offset(offset as i64); diff --git a/service/src/bot/messages/routes.rs b/service/src/bot/messages/routes.rs index e1d9e42..caf2676 100644 --- a/service/src/bot/messages/routes.rs +++ b/service/src/bot/messages/routes.rs @@ -3,7 +3,10 @@ use log::error; use serde::{Serialize, Deserialize}; use siren::{Response, Metadata, ServiceError}; -use crate::{bot::messages::{QueryMessage, QueryFilters}, auth::{Auth, verify_role}}; +use crate::{ + bot::messages::{QueryMessage, QueryFilters}, + auth::{Auth, verify_role}, +}; #[derive(Serialize, Deserialize)] struct GetAllParams { @@ -23,15 +26,17 @@ struct GetAllParams { #[get("/messages")] async fn get_all(req: HttpRequest, auth: Auth) -> HttpResponse { let _ = match verify_role(&auth, "admin") { - Ok(_) => {}, - Err(err) => return ResponseError::error_response(&err) + Ok(_) => {} + Err(err) => return ResponseError::error_response(&err), }; let params = match web::Query::::from_query(req.query_string()) { Ok(params) => params, - Err(err) => return ResponseError::error_response(&ServiceError { - status: 422, - message: err.to_string() - }) + Err(err) => { + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) + } }; let mut filters = QueryFilters::default(); filters.by_id = params.id.clone(); @@ -47,19 +52,17 @@ async fn get_all(req: HttpRequest, auth: Auth) -> HttpResponse { let total_count = QueryMessage::get_count(&filters).unwrap(); let max_page = std::cmp::max((total_count as f64 / limit as f64).ceil() as i32, 1); let page = std::cmp::min(std::cmp::max(params.page.unwrap_or(1), 1), max_page); - + match QueryMessage::get_all(&filters, limit, page) { - Ok(messages) => { - HttpResponse::Ok().json(Response { - data: messages, - metadata: Some(Metadata { - total: total_count as i32, - limit, - page, - pages: max_page - }) - }) - }, + Ok(messages) => HttpResponse::Ok().json(Response { + data: messages, + metadata: Some(Metadata { + total: total_count as i32, + limit, + page, + pages: max_page, + }), + }), Err(err) => { error!("{:?}", err.message); ResponseError::error_response(&err) @@ -70,8 +73,8 @@ async fn get_all(req: HttpRequest, auth: Auth) -> HttpResponse { #[post("/messages")] async fn create(message: web::Json, auth: Auth) -> HttpResponse { let _ = match verify_role(&auth, "admin") { - Ok(_) => {}, - Err(err) => return ResponseError::error_response(&err) + Ok(_) => {} + Err(err) => return ResponseError::error_response(&err), }; match QueryMessage::insert(message.into_inner()) { Ok(message) => HttpResponse::Created().json(message), @@ -85,4 +88,4 @@ async fn create(message: web::Json, auth: Auth) -> HttpResponse { pub fn init_routes(config: &mut web::ServiceConfig) { config.service(get_all); config.service(create); -} \ No newline at end of file +} diff --git a/service/src/bot/oai/model.rs b/service/src/bot/oai/model.rs index 2f42644..f9181c7 100644 --- a/service/src/bot/oai/model.rs +++ b/service/src/bot/oai/model.rs @@ -11,7 +11,7 @@ pub enum GPTRole { #[serde(rename = "assistant")] Assistant, #[serde(rename = "function")] - Function + Function, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -41,7 +41,7 @@ pub struct ChatCompletionRequest { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatCompletionMessage { pub role: GPTRole, - pub content: String + pub content: String, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -52,14 +52,14 @@ pub struct ChatCompletionResponse { pub created: i64, pub model: String, pub usage: Usage, - pub choices: Vec + pub choices: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Usage { pub prompt_tokens: i64, pub completion_tokens: i64, - pub total_tokens: i64 + pub total_tokens: i64, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -67,13 +67,13 @@ pub struct Choice { pub message: ChatCompletionMessage, pub finish_reason: String, pub index: i64, - pub logprobs: Option + pub logprobs: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] enum ResponseEvent { ChatCompletionResponse(ChatCompletionResponse), - ResponseError(ResponseError) + ResponseError(ResponseError), } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -82,13 +82,13 @@ struct ResponseError { message: Option, param: Option, #[serde(rename = "type")] - error_type: Option + error_type: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] struct ErrorDetails { code: Option, - message: Option + message: Option, } pub struct OAI { @@ -99,13 +99,18 @@ pub struct OAI { pub token: String, pub max_tokens: i64, pub default_model: String, - pub max_context_questions: i64 + pub max_context_questions: i64, } impl OAI { - pub async fn chat_completion(&self, request: ChatCompletionRequest) -> Result { + pub async fn chat_completion( + &self, + request: ChatCompletionRequest, + ) -> Result { let url = format!("{}/chat/completions", self.base_url); - let response = self.client.post(&url) + let response = self + .client + .post(&url) .bearer_auth(&self.token) .header("Content-Type", "application/json".to_string()) .json(&request) @@ -121,8 +126,13 @@ impl OAI { // } let res = serde_json::from_value::(value)?; return Ok(res); - }, - Err(err) => return Err(ServiceError { status: 500, message: format!("Error: {}", err) }) + } + Err(err) => { + return Err(ServiceError { + status: 500, + message: format!("Error: {}", err), + }) + } } } } diff --git a/service/src/bot/ytdlp/mod.rs b/service/src/bot/ytdlp/mod.rs index e544a7a..170a2a8 100644 --- a/service/src/bot/ytdlp/mod.rs +++ b/service/src/bot/ytdlp/mod.rs @@ -4,7 +4,6 @@ use std::process::{Child, Command, Output, Stdio}; pub use model::*; - const YOUTUBE_DL_COMMAND: &str = "yt-dlp"; pub struct YtDlp { @@ -15,7 +14,8 @@ pub struct YtDlp { impl YtDlp { pub fn new() -> Self { let mut cmd = Command::new(YOUTUBE_DL_COMMAND); - cmd.env("LC_ALL", "en_US.UTF-8") + cmd + .env("LC_ALL", "en_US.UTF-8") .stdout(Stdio::piped()) .stdin(Stdio::piped()) .stderr(Stdio::piped()); @@ -31,9 +31,10 @@ impl YtDlp { } pub fn execute(&mut self) -> std::io::Result { - self.command + self + .command .args(self.args.clone()) .spawn() .and_then(Child::wait_with_output) } -} \ No newline at end of file +} diff --git a/service/src/bot/ytdlp/model.rs b/service/src/bot/ytdlp/model.rs index 1d7ced9..8f83b98 100644 --- a/service/src/bot/ytdlp/model.rs +++ b/service/src/bot/ytdlp/model.rs @@ -1,6 +1,5 @@ use serde::{Deserialize, Serialize}; - #[derive(Debug, Serialize, Deserialize)] pub struct PlaylistItem { pub id: String, @@ -8,4 +7,4 @@ pub struct PlaylistItem { pub title: String, pub duration: i32, pub playlist_index: i32, -} \ No newline at end of file +} diff --git a/service/src/dnd/backgrounds/mod.rs b/service/src/dnd/backgrounds/mod.rs index e69de29..8b13789 100644 --- a/service/src/dnd/backgrounds/mod.rs +++ b/service/src/dnd/backgrounds/mod.rs @@ -0,0 +1 @@ + diff --git a/service/src/dnd/bestiary/mod.rs b/service/src/dnd/bestiary/mod.rs index e69de29..8b13789 100644 --- a/service/src/dnd/bestiary/mod.rs +++ b/service/src/dnd/bestiary/mod.rs @@ -0,0 +1 @@ + diff --git a/service/src/dnd/classes/mod.rs b/service/src/dnd/classes/mod.rs index 24e3024..4a7ebf6 100644 --- a/service/src/dnd/classes/mod.rs +++ b/service/src/dnd/classes/mod.rs @@ -1,3 +1,3 @@ mod model; -pub use model::*; \ No newline at end of file +pub use model::*; diff --git a/service/src/dnd/classes/model.rs b/service/src/dnd/classes/model.rs index 7a42411..600034d 100644 --- a/service/src/dnd/classes/model.rs +++ b/service/src/dnd/classes/model.rs @@ -14,7 +14,7 @@ pub enum AbilityType { #[serde(rename = "wisdom")] Wisdom, #[serde(rename = "charisma")] - Charisma + Charisma, } impl AbilityType { @@ -25,7 +25,7 @@ impl AbilityType { AbilityType::Constitution => "Constitution".to_string(), AbilityType::Intelligence => "Intelligence".to_string(), AbilityType::Wisdom => "Wisdom".to_string(), - AbilityType::Charisma => "Charisma".to_string() + AbilityType::Charisma => "Charisma".to_string(), } } } @@ -40,7 +40,7 @@ impl FromStr for AbilityType { "Intelligence" => Ok(AbilityType::Intelligence), "Wisdom" => Ok(AbilityType::Wisdom), "Charisma" => Ok(AbilityType::Charisma), - _ => Err(()) + _ => Err(()), } } -} \ No newline at end of file +} diff --git a/service/src/dnd/conditions/mod.rs b/service/src/dnd/conditions/mod.rs index e281d3e..ed82734 100644 --- a/service/src/dnd/conditions/mod.rs +++ b/service/src/dnd/conditions/mod.rs @@ -1,7 +1,6 @@ use std::str::FromStr; use serde::{Deserialize, Serialize}; - #[derive(Debug, Serialize, Deserialize)] pub enum ConditionType { #[serde(rename = "blinded")] @@ -33,7 +32,7 @@ pub enum ConditionType { #[serde(rename = "stunned")] Stunned, #[serde(rename = "unconscious")] - Unconscious + Unconscious, } impl ConditionType { @@ -53,7 +52,7 @@ impl ConditionType { ConditionType::Prone => "Prone".to_string(), ConditionType::Restrained => "Restrained".to_string(), ConditionType::Stunned => "Stunned".to_string(), - ConditionType::Unconscious => "Unconscious".to_string() + ConditionType::Unconscious => "Unconscious".to_string(), } } } @@ -77,7 +76,7 @@ impl FromStr for ConditionType { "Restrained" => Ok(ConditionType::Restrained), "Stunned" => Ok(ConditionType::Stunned), "Unconscious" => Ok(ConditionType::Unconscious), - _ => Err(()) + _ => Err(()), } } } diff --git a/service/src/dnd/feats/mod.rs b/service/src/dnd/feats/mod.rs index e69de29..8b13789 100644 --- a/service/src/dnd/feats/mod.rs +++ b/service/src/dnd/feats/mod.rs @@ -0,0 +1 @@ + diff --git a/service/src/dnd/items/mod.rs b/service/src/dnd/items/mod.rs index e69de29..8b13789 100644 --- a/service/src/dnd/items/mod.rs +++ b/service/src/dnd/items/mod.rs @@ -0,0 +1 @@ + diff --git a/service/src/dnd/mod.rs b/service/src/dnd/mod.rs index d8a2b34..bcbfa92 100644 --- a/service/src/dnd/mod.rs +++ b/service/src/dnd/mod.rs @@ -10,4 +10,4 @@ pub mod spells; pub fn load_data(data_dir_path: &str) { spells::load_data(data_dir_path); -} \ No newline at end of file +} diff --git a/service/src/dnd/options/mod.rs b/service/src/dnd/options/mod.rs index e69de29..8b13789 100644 --- a/service/src/dnd/options/mod.rs +++ b/service/src/dnd/options/mod.rs @@ -0,0 +1 @@ + diff --git a/service/src/dnd/races/mod.rs b/service/src/dnd/races/mod.rs index e69de29..8b13789 100644 --- a/service/src/dnd/races/mod.rs +++ b/service/src/dnd/races/mod.rs @@ -0,0 +1 @@ + diff --git a/service/src/dnd/spells/mod.rs b/service/src/dnd/spells/mod.rs index 4ab082a..90156a2 100644 --- a/service/src/dnd/spells/mod.rs +++ b/service/src/dnd/spells/mod.rs @@ -2,7 +2,11 @@ mod model; mod routes; mod types; -use std::{fs::{metadata, File, read_dir}, path::Path, io::BufReader}; +use std::{ + fs::{metadata, File, read_dir}, + path::Path, + io::BufReader, +}; use log::{warn, trace}; pub use model::*; @@ -35,7 +39,7 @@ pub fn load_data(data_dir_path: &str) { trace!("Spell '{}' already exists", spell.name); continue; } - }, + } Err(err) => { warn!("Error checking if spell '{}' exists: {}", spell.name, err); continue; @@ -44,15 +48,18 @@ pub fn load_data(data_dir_path: &str) { let spell = InsertSpell::insert(spell.into()).unwrap(); trace!("Inserted spell: {}", spell.name); } - }, - Err(err) => warn!("Error reading spells from file: {}", err) - }; + } + Err(err) => warn!("Error reading spells from file: {}", err), + }; } } } } } } else { - warn!("Data path '{}' does not exist, no data imported", data_dir_path); + warn!( + "Data path '{}' does not exist, no data imported", + data_dir_path + ); } } diff --git a/service/src/dnd/spells/model.rs b/service/src/dnd/spells/model.rs index 9604178..611483e 100644 --- a/service/src/dnd/spells/model.rs +++ b/service/src/dnd/spells/model.rs @@ -6,7 +6,10 @@ use crate::storage::connection; use crate::storage::schema::spells::{self}; use crate::dnd::{classes::AbilityType, conditions::ConditionType}; -use super::{SchoolType, CastingTime, SpellAttackType, SpellDamageType, Range, Area, Components, Duration, Source, Description, DurationType, Effect}; +use super::{ + SchoolType, CastingTime, SpellAttackType, SpellDamageType, Range, Area, Components, Duration, + Source, Description, DurationType, Effect, +}; #[derive(Debug, Queryable, QueryableByName, Serialize, Deserialize)] #[diesel(table_name = spells)] @@ -75,7 +78,14 @@ impl QuerySpell { query = query.filter(spells::name.ilike(format!("%{}%", name))); } if let Some(schools) = &filters.by_schools { - query = query.filter(spells::school.eq_any(schools.iter().map(|school| school.to_string()).collect::>())); + query = query.filter( + spells::school.eq_any( + schools + .iter() + .map(|school| school.to_string()) + .collect::>(), + ), + ); } if let Some(levels) = &filters.by_levels { query = query.filter(spells::level.eq_any(levels)); @@ -90,16 +100,44 @@ impl QuerySpell { query = query.filter(spells::classes.overlaps_with(classes)); } if let Some(damage_inflict) = &filters.by_damage_inflict { - query = query.filter(spells::damage_inflict.overlaps_with(damage_inflict.iter().map(|damage_inflict| damage_inflict.to_string()).collect::>())); + query = query.filter( + spells::damage_inflict.overlaps_with( + damage_inflict + .iter() + .map(|damage_inflict| damage_inflict.to_string()) + .collect::>(), + ), + ); } if let Some(damage_resist) = &filters.by_damage_resist { - query = query.filter(spells::damage_resist.overlaps_with(damage_resist.iter().map(|damage_resist| damage_resist.to_string()).collect::>())); + query = query.filter( + spells::damage_resist.overlaps_with( + damage_resist + .iter() + .map(|damage_resist| damage_resist.to_string()) + .collect::>(), + ), + ); } if let Some(conditions) = &filters.by_conditions { - query = query.filter(spells::conditions.overlaps_with(conditions.iter().map(|condition| condition.to_string()).collect::>())); + query = query.filter( + spells::conditions.overlaps_with( + conditions + .iter() + .map(|condition| condition.to_string()) + .collect::>(), + ), + ); } if let Some(saving_throw) = &filters.by_saving_throw { - query = query.filter(spells::saving_throw.overlaps_with(saving_throw.iter().map(|saving_throw| saving_throw.to_string()).collect::>())); + query = query.filter( + spells::saving_throw.overlaps_with( + saving_throw + .iter() + .map(|saving_throw| saving_throw.to_string()) + .collect::>(), + ), + ); } if let Some(attack_type) = &filters.by_attack_type { query = query.filter(spells::attack_type.eq(attack_type.to_string())); @@ -116,7 +154,14 @@ impl QuerySpell { query = query.filter(spells::name.ilike(format!("%{}%", name))); } if let Some(schools) = &filters.by_schools { - query = query.filter(spells::school.eq_any(schools.iter().map(|school| school.to_string()).collect::>())); + query = query.filter( + spells::school.eq_any( + schools + .iter() + .map(|school| school.to_string()) + .collect::>(), + ), + ); } if let Some(levels) = &filters.by_levels { query = query.filter(spells::level.eq_any(levels)); @@ -131,21 +176,49 @@ impl QuerySpell { query = query.filter(spells::classes.overlaps_with(classes)); } if let Some(damage_inflict) = &filters.by_damage_inflict { - query = query.filter(spells::damage_inflict.overlaps_with(damage_inflict.iter().map(|damage_inflict| damage_inflict.to_string()).collect::>())); + query = query.filter( + spells::damage_inflict.overlaps_with( + damage_inflict + .iter() + .map(|damage_inflict| damage_inflict.to_string()) + .collect::>(), + ), + ); } if let Some(damage_resist) = &filters.by_damage_resist { - query = query.filter(spells::damage_resist.overlaps_with(damage_resist.iter().map(|damage_resist| damage_resist.to_string()).collect::>())); + query = query.filter( + spells::damage_resist.overlaps_with( + damage_resist + .iter() + .map(|damage_resist| damage_resist.to_string()) + .collect::>(), + ), + ); } if let Some(conditions) = &filters.by_conditions { - query = query.filter(spells::conditions.overlaps_with(conditions.iter().map(|condition| condition.to_string()).collect::>())); + query = query.filter( + spells::conditions.overlaps_with( + conditions + .iter() + .map(|condition| condition.to_string()) + .collect::>(), + ), + ); } if let Some(saving_throw) = &filters.by_saving_throw { - query = query.filter(spells::saving_throw.overlaps_with(saving_throw.iter().map(|saving_throw| saving_throw.to_string()).collect::>())); + query = query.filter( + spells::saving_throw.overlaps_with( + saving_throw + .iter() + .map(|saving_throw| saving_throw.to_string()) + .collect::>(), + ), + ); } if let Some(attack_type) = &filters.by_attack_type { query = query.filter(spells::attack_type.eq(attack_type.to_string())); } - + let count = query.get_result(&mut conn)?; Ok(count) } @@ -179,19 +252,23 @@ pub struct InsertSpell { pub conditions: Vec, pub saving_throw: Vec, pub attack_type: Option, - pub data: serde_json::Value + pub data: serde_json::Value, } impl InsertSpell { pub fn insert(spell: Self) -> Result { let mut conn = connection()?; - let spell = diesel::insert_into(spells::table).values(spell).get_result(&mut conn)?; + let spell = diesel::insert_into(spells::table) + .values(spell) + .get_result(&mut conn)?; Ok(spell) } pub fn update(id: i32, spell: Self) -> Result { let mut conn = connection()?; - let spell = diesel::update(spells::table.filter(spells::id.eq(id))).set(spell).get_result(&mut conn)?; + let spell = diesel::update(spells::table.filter(spells::id.eq(id))) + .set(spell) + .get_result(&mut conn)?; Ok(spell) } } @@ -226,7 +303,7 @@ pub struct Spell { #[serde(skip_serializing_if = "Option::is_none")] pub tags: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option + pub description: Option, } impl From for Spell { @@ -236,29 +313,44 @@ impl From for Spell { Err(err) => { log::error!("Failed to parse spell: {}", err); Self { - id: None, - name: "".to_string(), - school: SchoolType::Abjuration, - level: 0, - ritual: false, - casting_time: CastingTime { value: 0, casting_type: "".to_string(), note: None }, - effect: None, - saving_throw: None, - attack_type: None, - damage_inflict: None, - damage_resist: None, - conditions: None, - range: Range { range_type: "".to_string(), value: None, unit: None }, - area: None, - components: Components { verbal: false, somatic: false, material: false, materials_needed: None, materials_cost: None, materials_consumed: None }, - durations: vec![], - classes: vec![], - sources: vec![], - tags: None, - description: None, + id: None, + name: "".to_string(), + school: SchoolType::Abjuration, + level: 0, + ritual: false, + casting_time: CastingTime { + value: 0, + casting_type: "".to_string(), + note: None, + }, + effect: None, + saving_throw: None, + attack_type: None, + damage_inflict: None, + damage_resist: None, + conditions: None, + range: Range { + range_type: "".to_string(), + value: None, + unit: None, + }, + area: None, + components: Components { + verbal: false, + somatic: false, + material: false, + materials_needed: None, + materials_cost: None, + materials_consumed: None, + }, + durations: vec![], + classes: vec![], + sources: vec![], + tags: None, + description: None, } } - } + }; } } @@ -269,35 +361,57 @@ impl Into for Spell { school: self.school.to_string(), level: self.level, ritual: self.ritual, - concentration: self.durations.iter().any(|duration| match duration.duration_type { - DurationType::Concentration => true, - _ => false - }), - classes: self.classes.iter().map(|class| class.to_string()).collect::>(), + concentration: self + .durations + .iter() + .any(|duration| match duration.duration_type { + DurationType::Concentration => true, + _ => false, + }), + classes: self + .classes + .iter() + .map(|class| class.to_string()) + .collect::>(), damage_inflict: match &self.damage_inflict { - Some(damage_inflict) => damage_inflict.iter().map(|damage_inflict| damage_inflict.to_string()).collect(), - None => vec![] + Some(damage_inflict) => damage_inflict + .iter() + .map(|damage_inflict| damage_inflict.to_string()) + .collect(), + None => vec![], }, damage_resist: match &self.damage_resist { - Some(damage_resist) => damage_resist.iter().map(|damage_resist| damage_resist.to_string()).collect(), - None => vec![] + Some(damage_resist) => damage_resist + .iter() + .map(|damage_resist| damage_resist.to_string()) + .collect(), + None => vec![], }, conditions: match &self.conditions { - Some(conditions) => conditions.iter().map(|condition| condition.to_string()).collect(), - None => vec![] + Some(conditions) => conditions + .iter() + .map(|condition| condition.to_string()) + .collect(), + None => vec![], }, saving_throw: match &self.saving_throw { - Some(saving_throw) => saving_throw.iter().map(|saving_throw| saving_throw.to_string()).collect(), - None => vec![] + Some(saving_throw) => saving_throw + .iter() + .map(|saving_throw| saving_throw.to_string()) + .collect(), + None => vec![], }, - attack_type: self.attack_type.as_ref().map(|attack_type| attack_type.to_string()), + attack_type: self + .attack_type + .as_ref() + .map(|attack_type| attack_type.to_string()), data: match serde_json::to_value(&self) { Ok(data) => data, Err(err) => { log::error!("Failed to serialize spell: {}", err); serde_json::Value::Null } - } - } + }, + }; } } diff --git a/service/src/dnd/spells/routes.rs b/service/src/dnd/spells/routes.rs index 5ed0e7b..579ad7f 100644 --- a/service/src/dnd/spells/routes.rs +++ b/service/src/dnd/spells/routes.rs @@ -3,7 +3,10 @@ use log::error; use serde::{Serialize, Deserialize}; use siren::{Response, Metadata, ServiceError}; -use crate::{dnd::spells::{QuerySpell, QueryFilters}, auth::{Auth, verify_role}}; +use crate::{ + dnd::spells::{QuerySpell, QueryFilters}, + auth::{Auth, verify_role}, +}; use super::{Spell, InsertSpell}; @@ -29,50 +32,57 @@ struct GetAllParams { async fn get_all(req: HttpRequest) -> HttpResponse { let params = match web::Query::::from_query(req.query_string()) { Ok(params) => params, - Err(err) => return ResponseError::error_response(&ServiceError { - status: 422, - message: err.to_string() - }) + Err(err) => { + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) + } }; let mut filters = QueryFilters::default(); filters.by_name = params.name.clone(); filters.like_name = params.like_name.clone(); filters.by_schools = match ¶ms.schools { Some(schools) => Some(schools.split(",").map(|s| s.to_string()).collect()), - None => None + None => None, }; filters.by_levels = match ¶ms.levels { - Some(levels) => Some(levels.split(",").map(|s| match s.to_string().parse::() { - Ok(level) => level, - Err(_) => 0 - }).collect()), - None => None + Some(levels) => Some( + levels + .split(",") + .map(|s| match s.to_string().parse::() { + Ok(level) => level, + Err(_) => 0, + }) + .collect(), + ), + None => None, }; filters.by_ritual = params.ritual; filters.by_concentration = params.concentration; filters.by_classes = match ¶ms.classes { Some(classes) => Some(classes.split(",").map(|s| s.to_string()).collect()), - None => None + None => None, }; filters.by_damage_inflict = match ¶ms.damage_inflict { Some(damage_inflict) => Some(damage_inflict.split(",").map(|s| s.to_string()).collect()), - None => None + None => None, }; filters.by_damage_resist = match ¶ms.damage_resist { Some(damage_resist) => Some(damage_resist.split(",").map(|s| s.to_string()).collect()), - None => None + None => None, }; filters.by_conditions = match ¶ms.conditions { Some(conditions) => Some(conditions.split(",").map(|s| s.to_string()).collect()), - None => None + None => None, }; filters.by_saving_throw = match ¶ms.saving_throw { Some(saving_throw) => Some(saving_throw.split(",").map(|s| s.to_string()).collect()), - None => None + None => None, }; filters.by_attack_type = match ¶ms.attack_type { Some(attack_type) => Some(attack_type.split(",").map(|s| s.to_string()).collect()), - None => None + None => None, }; // Limit must be between 1 and 100 let limit = std::cmp::min(std::cmp::max(params.limit.unwrap_or(100), 1), 100); @@ -81,7 +91,10 @@ async fn get_all(req: HttpRequest) -> HttpResponse { // Page must be between 1 and max_page let page = std::cmp::min(std::cmp::max(params.page.unwrap_or(1), 1), max_page); - match web::block(move || QuerySpell::get_all(&filters, limit, page)).await.unwrap() { + match web::block(move || QuerySpell::get_all(&filters, limit, page)) + .await + .unwrap() + { Ok(spells) => { let mut response: Vec = Vec::new(); for query_spell in spells { @@ -96,10 +109,10 @@ async fn get_all(req: HttpRequest) -> HttpResponse { total: total_count as i32, limit, page, - pages: max_page - }) + pages: max_page, + }), }) - }, + } Err(err) => { error!("{:?}", err.message); ResponseError::error_response(&err) @@ -111,10 +124,12 @@ async fn get_all(req: HttpRequest) -> HttpResponse { async fn get_by_id(id: web::Path) -> HttpResponse { let id = match id.parse::() { Ok(id) => id, - Err(err) => return ResponseError::error_response(&ServiceError { - status: 422, - message: err.to_string() - }) + Err(err) => { + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) + } }; match web::block(move || QuerySpell::get_by_id(id)).await.unwrap() { Ok(query_spell) => { @@ -123,9 +138,9 @@ async fn get_by_id(id: web::Path) -> HttpResponse { spell.id = Some(id); HttpResponse::Ok().json(Response { data: spell, - metadata: None + metadata: None, }) - }, + } Err(err) => { error!("{:?}", err.message); ResponseError::error_response(&err) @@ -136,8 +151,8 @@ async fn get_by_id(id: web::Path) -> HttpResponse { #[post("/spells")] async fn create(spell: web::Json, auth: Auth) -> HttpResponse { let _ = match verify_role(&auth, "admin") { - Ok(_) => {}, - Err(err) => return ResponseError::error_response(&err) + Ok(_) => {} + Err(err) => return ResponseError::error_response(&err), }; match InsertSpell::insert(spell.into_inner().into()) { Ok(spell) => HttpResponse::Created().json(Spell::from(spell)), @@ -151,17 +166,22 @@ async fn create(spell: web::Json, auth: Auth) -> HttpResponse { #[put("/spells/{id}")] async fn update(id: web::Path, spell: web::Json, auth: Auth) -> HttpResponse { let _ = match verify_role(&auth, "admin") { - Ok(_) => {}, - Err(err) => return ResponseError::error_response(&err) + Ok(_) => {} + Err(err) => return ResponseError::error_response(&err), }; let id = match id.parse::() { Ok(id) => id, - Err(err) => return ResponseError::error_response(&ServiceError { - status: 422, - message: err.to_string() - }) + Err(err) => { + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) + } }; - match web::block(move || InsertSpell::update(id, spell.into_inner().into())).await.unwrap() { + match web::block(move || InsertSpell::update(id, spell.into_inner().into())) + .await + .unwrap() + { Ok(spell) => HttpResponse::Ok().json(Spell::from(spell)), Err(err) => { error!("{:?}", err.message); @@ -173,15 +193,17 @@ async fn update(id: web::Path, spell: web::Json, auth: Auth) -> H #[delete("/spells/{id}")] async fn delete(id: web::Path, auth: Auth) -> HttpResponse { let _ = match verify_role(&auth, "admin") { - Ok(_) => {}, - Err(err) => return ResponseError::error_response(&err) + Ok(_) => {} + Err(err) => return ResponseError::error_response(&err), }; let id = match id.parse::() { Ok(id) => id, - Err(err) => return ResponseError::error_response(&ServiceError { - status: 422, - message: err.to_string() - }) + Err(err) => { + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) + } }; match web::block(move || QuerySpell::delete(id)).await.unwrap() { Ok(spell) => HttpResponse::Ok().json(Spell::from(spell)), @@ -193,10 +215,11 @@ async fn delete(id: web::Path, auth: Auth) -> HttpResponse { } pub fn init_routes(config: &mut web::ServiceConfig) { - config.service(web::scope("dnd") - .service(get_all) - .service(get_by_id) - .service(create) - .service(update) + config.service( + web::scope("dnd") + .service(get_all) + .service(get_by_id) + .service(create) + .service(update), ); -} \ No newline at end of file +} diff --git a/service/src/dnd/spells/types.rs b/service/src/dnd/spells/types.rs index 9c9d9a2..fc357ad 100644 --- a/service/src/dnd/spells/types.rs +++ b/service/src/dnd/spells/types.rs @@ -18,7 +18,7 @@ pub enum SchoolType { #[serde(rename = "necromancy")] Necromancy, #[serde(rename = "transmutation")] - Transmutation + Transmutation, } impl SchoolType { @@ -31,7 +31,7 @@ impl SchoolType { SchoolType::Evocation => "evocation".to_string(), SchoolType::Illusion => "illusion".to_string(), SchoolType::Necromancy => "necromancy".to_string(), - SchoolType::Transmutation => "transmutation".to_string() + SchoolType::Transmutation => "transmutation".to_string(), } } } @@ -49,7 +49,7 @@ impl FromStr for SchoolType { "illusion" => Ok(SchoolType::Illusion), "necromancy" => Ok(SchoolType::Necromancy), "transmutation" => Ok(SchoolType::Transmutation), - _ => Err(()) + _ => Err(()), } } } @@ -59,7 +59,7 @@ pub struct CastingTime { pub value: i32, #[serde(rename = "unit")] pub casting_type: String, - pub note: Option + pub note: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -74,7 +74,7 @@ impl SpellAttackType { pub fn to_string(&self) -> String { match self { SpellAttackType::Melee => "melee".to_string(), - SpellAttackType::Ranged => "ranged".to_string() + SpellAttackType::Ranged => "ranged".to_string(), } } } @@ -86,7 +86,7 @@ impl FromStr for SpellAttackType { match s { "melee" => Ok(SpellAttackType::Melee), "ranged" => Ok(SpellAttackType::Ranged), - _ => Err(()) + _ => Err(()), } } } @@ -118,7 +118,7 @@ pub enum SpellDamageType { #[serde(rename = "slashing")] Slashing, #[serde(rename = "thunder")] - Thunder + Thunder, } impl SpellDamageType { @@ -136,7 +136,7 @@ impl SpellDamageType { SpellDamageType::Psychic => "psychic".to_string(), SpellDamageType::Radiant => "radiant".to_string(), SpellDamageType::Slashing => "slashing".to_string(), - SpellDamageType::Thunder => "thunder".to_string() + SpellDamageType::Thunder => "thunder".to_string(), } } } @@ -159,7 +159,7 @@ impl FromStr for SpellDamageType { "radiant" => Ok(SpellDamageType::Radiant), "slashing" => Ok(SpellDamageType::Slashing), "thunder" => Ok(SpellDamageType::Thunder), - _ => Err(()) + _ => Err(()), } } } @@ -171,7 +171,7 @@ pub struct Range { #[serde(skip_serializing_if = "Option::is_none")] pub value: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub unit: Option + pub unit: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -181,7 +181,7 @@ pub struct Area { #[serde(skip_serializing_if = "Option::is_none")] pub value: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub unit: Option + pub unit: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -191,7 +191,7 @@ pub struct Duration { #[serde(skip_serializing_if = "Option::is_none")] pub value: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub unit: Option + pub unit: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -205,36 +205,39 @@ pub enum DurationType { #[serde(rename = "dispelled")] UntilDispelled, #[serde(rename = "special")] - Special + Special, } #[derive(Debug, Serialize, Deserialize)] pub struct Source { pub source: String, #[serde(skip_serializing_if = "Option::is_none")] - pub page: Option + pub page: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct Description { - pub entries: Vec + pub entries: Vec, } #[derive(Debug)] pub struct Entry { pub text: Option, pub list: Option>, - pub table: Option + pub table: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct EntryTable { pub headers: Vec, - pub rows: Vec> + pub rows: Vec>, } impl<'de> Deserialize<'de> for Entry { - fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { let value = serde_json::Value::deserialize(deserializer)?; match value { serde_json::Value::String(s) => Ok(Entry { @@ -246,9 +249,9 @@ impl<'de> Deserialize<'de> for Entry { let text = match o.get("text") { Some(t) => match t.as_str() { Some(s) => Some(s.to_string()), - None => return Err(serde::de::Error::custom("Invalid entry text")) + None => return Err(serde::de::Error::custom("Invalid entry text")), }, - None => None + None => None, }; let list = match o.get("list") { Some(i) => match i.as_array() { @@ -257,14 +260,14 @@ impl<'de> Deserialize<'de> for Entry { for item in a { match item.as_str() { Some(s) => list.push(s.to_string()), - None => return Err(serde::de::Error::custom("Invalid entry list item")) + None => return Err(serde::de::Error::custom("Invalid entry list item")), } } Some(list) - }, - None => return Err(serde::de::Error::custom("Invalid entry list items")) + } + None => return Err(serde::de::Error::custom("Invalid entry list items")), }, - None => None + None => None, }; let table = match o.get("table") { Some(t) => match t.as_object() { @@ -277,13 +280,13 @@ impl<'de> Deserialize<'de> for Entry { for item in a { match item.as_str() { Some(s) => headers.push(s.to_string()), - None => return Err(serde::de::Error::custom("Invalid entry table header")) + None => return Err(serde::de::Error::custom("Invalid entry table header")), } } - }, - None => return Err(serde::de::Error::custom("Invalid entry table headers")) + } + None => return Err(serde::de::Error::custom("Invalid entry table headers")), }, - None => return Err(serde::de::Error::custom("Missing entry table headers")) + None => return Err(serde::de::Error::custom("Missing entry table headers")), }; match o.get("rows") { Some(r) => match r.as_array() { @@ -295,41 +298,41 @@ impl<'de> Deserialize<'de> for Entry { for item in a { match item.as_str() { Some(s) => row.push(s.to_string()), - None => return Err(serde::de::Error::custom("Invalid entry table row item")) + None => { + return Err(serde::de::Error::custom( + "Invalid entry table row item", + )) + } } } rows.push(row); - }, - None => return Err(serde::de::Error::custom("Invalid entry table row")) + } + None => return Err(serde::de::Error::custom("Invalid entry table row")), } } - }, - None => return Err(serde::de::Error::custom("Invalid entry table rows")) + } + None => return Err(serde::de::Error::custom("Invalid entry table rows")), }, - None => return Err(serde::de::Error::custom("Missing entry table rows")) + None => return Err(serde::de::Error::custom("Missing entry table rows")), }; - Some(EntryTable { - headers, - rows - }) - }, - None => return Err(serde::de::Error::custom("Invalid entry table")) + Some(EntryTable { headers, rows }) + } + None => return Err(serde::de::Error::custom("Invalid entry table")), }, - None => None + None => None, }; - Ok(Entry { - text, - list, - table - }) - }, - _ => Err(serde::de::Error::custom("Invalid entry")) + Ok(Entry { text, list, table }) + } + _ => Err(serde::de::Error::custom("Invalid entry")), } } } impl Serialize for Entry { - fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { let mut map = serializer.serialize_map(Some(1))?; if let Some(text) = &self.text { map.serialize_entry("text", text)?; @@ -354,10 +357,10 @@ pub struct Components { #[serde(skip_serializing_if = "Option::is_none")] pub materials_cost: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub materials_consumed: Option + pub materials_consumed: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct Effect { - pub effect_type: Option + pub effect_type: Option, } diff --git a/service/src/lib.rs b/service/src/lib.rs index f5870b9..ecde4ea 100644 --- a/service/src/lib.rs +++ b/service/src/lib.rs @@ -22,7 +22,7 @@ pub struct Message { pub struct Response { pub data: T, #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option + pub metadata: Option, } #[derive(Serialize, Deserialize)] @@ -30,13 +30,13 @@ pub struct Metadata { pub total: i32, pub limit: i32, pub page: i32, - pub pages: i32 + pub pages: i32, } #[derive(Debug, Deserialize, Serialize)] pub struct ServiceError { - pub status: u16, - pub message: String, + pub status: u16, + pub message: String, } impl ServiceError { @@ -69,20 +69,14 @@ impl From for ServiceError { impl From for ServiceError { fn from(error: DieselError) -> ServiceError { match error { - DieselError::DatabaseError(kind, err) => { - match kind { - diesel::result::DatabaseErrorKind::UniqueViolation => { - ServiceError::new(409, err.message().to_string()) - }, - _ => ServiceError::new(500, err.message().to_string()) + DieselError::DatabaseError(kind, err) => match kind { + diesel::result::DatabaseErrorKind::UniqueViolation => { + ServiceError::new(409, err.message().to_string()) } + _ => ServiceError::new(500, err.message().to_string()), }, - DieselError::NotFound => { - ServiceError::new(404, "The record was not found".to_string()) - }, - DieselError::SerializationError(err) => { - ServiceError::new(422, err.to_string()) - }, + DieselError::NotFound => ServiceError::new(404, "The record was not found".to_string()), + DieselError::SerializationError(err) => ServiceError::new(422, err.to_string()), err => ServiceError::new(500, format!("Unknown database error: {}", err)), } } @@ -121,10 +115,8 @@ impl From for ServiceError { impl From for ServiceError { fn from(error: s3::error::S3Error) -> ServiceError { match error { - s3::error::S3Error::Http(code, message) => { - ServiceError::new(code, message) - }, - _ => ServiceError::new(500, format!("Unknown s3 error: {}", error)) + s3::error::S3Error::Http(code, message) => ServiceError::new(code, message), + _ => ServiceError::new(500, format!("Unknown s3 error: {}", error)), } } } @@ -155,16 +147,17 @@ impl From for ServiceError { impl ResponseError for ServiceError { fn error_response(&self) -> HttpResponse { - let status_code = match StatusCode::from_u16(self.status) { - Ok(status_code) => status_code, - Err(_) => StatusCode::INTERNAL_SERVER_ERROR, - }; + let status_code = match StatusCode::from_u16(self.status) { + Ok(status_code) => status_code, + Err(_) => StatusCode::INTERNAL_SERVER_ERROR, + }; - let error_message = match status_code.as_u16() < 500 { - true => self.message.clone(), - false => "Internal server error".to_string(), - }; + let error_message = match status_code.as_u16() < 500 { + true => self.message.clone(), + false => "Internal server error".to_string(), + }; - HttpResponse::build(status_code).json(serde_json::json!({ "status": status_code.as_u16(), "message": error_message })) + HttpResponse::build(status_code) + .json(serde_json::json!({ "status": status_code.as_u16(), "message": error_message })) } } diff --git a/service/src/main.rs b/service/src/main.rs index f510479..4ba816d 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -17,8 +17,8 @@ use actix_web::{HttpServer, App, web}; use crate::bot::handler::Handler; mod auth; -mod dnd; mod bot; +mod dnd; mod storage; mod users; @@ -29,7 +29,7 @@ async fn main() -> std::io::Result<()> { storage::init().await; match env::var("DATA_DIR_PATH") { Ok(data_dir_path) => dnd::load_data(&data_dir_path), - Err(err) => warn!("Unable to load initial database data: {}", err) + Err(err) => warn!("Unable to load initial database data: {}", err), }; let token: String = env::var("DISCORD_TOKEN").expect("Expected a token in the environment"); @@ -46,12 +46,12 @@ async fn main() -> std::io::Result<()> { } match http.get_current_user().await { Ok(bot) => (owners, bot.id), - Err(why) => panic!("Could not access the bot id: {:?}", why) + Err(why) => panic!("Could not access the bot id: {:?}", why), } - }, - Err(why) => panic!("Could not access application info: {:?}", why) + } + Err(why) => panic!("Could not access application info: {:?}", why), }; - + let handler = match env::var("OPENAI_API_KEY") { Ok(token) => { info!("Loaded OpenAI token"); @@ -66,7 +66,7 @@ async fn main() -> std::io::Result<()> { max_context_questions: 30, max_tokens: 2048, default_model, - }) + }), } } Err(err) => { @@ -79,8 +79,7 @@ async fn main() -> std::io::Result<()> { let mut client = Client::builder(token, intents) .event_handler(handler) - .framework(StandardFramework::new() - .configure(|c| c.owners(owners))) + .framework(StandardFramework::new().configure(|c| c.owners(owners))) .register_songbird_with(Arc::clone(&songbird)) .await .expect("Error creating client"); @@ -91,14 +90,15 @@ async fn main() -> std::io::Result<()> { let app_data = Arc::new(AppState { http, cache, - songbird: Arc::clone(&songbird) + songbird: Arc::clone(&songbird), }); - let shard_manager = Arc::clone(&client.shard_manager); tokio::spawn(async move { - tokio::signal::ctrl_c().await.expect("Could not register ctrl+c handler"); + tokio::signal::ctrl_c() + .await + .expect("Could not register ctrl+c handler"); shard_manager.lock().await.shutdown_all().await; }); @@ -127,23 +127,23 @@ async fn main() -> std::io::Result<()> { .configure(crate::bot::guilds::init_routes) .configure(crate::bot::messages::init_routes) }) - .bind(format!("{}:{}", host, port)) { + .bind(format!("{}:{}", host, port)) + { Ok(b) => { info!("Binding server to {}:{}", host, port); b - }, + } Err(err) => { error!("Could not bind server: {}", err); return Err(err); } }; - server.run() - .await + server.run().await } pub struct AppState { pub http: Arc, pub cache: Arc, - pub songbird: Arc + pub songbird: Arc, } diff --git a/service/src/storage/mod.rs b/service/src/storage/mod.rs index 404625e..840f732 100644 --- a/service/src/storage/mod.rs +++ b/service/src/storage/mod.rs @@ -22,9 +22,15 @@ lazy_static! { let host = env::var("DATABASE_HOST").unwrap_or("localhost".to_string()); let name = env::var("DATABASE_NAME").expect("DATABASE_NAME is not set"); let port = env::var("DATABASE_PORT").unwrap_or("5432".to_string()); - let url = format!("postgres://{}:{}@{}:{}/{}", username, password, host, port, name); + let url = format!( + "postgres://{}:{}@{}:{}/{}", + username, password, host, port, name + ); let manager = DieselConnectionManager::::new(url); - DbPool::builder().test_on_check_out(true).build(manager).expect("Failed to create db pool") + DbPool::builder() + .test_on_check_out(true) + .build(manager) + .expect("Failed to create db pool") }; static ref REDIS: RedisClient = { let host = env::var("REDIS_HOST").unwrap_or("localhost".to_string()); @@ -38,21 +44,23 @@ lazy_static! { let user = env::var("MINIO_ROOT_USER").expect("MINIO_ROOT_USER is not set"); let password = env::var("MINIO_ROOT_PASSWORD").expect("MINIO_ROOT_PASSWORD is not set"); let base_url = format!("http://{}:{}", url, port); - + let region = Region::Custom { region: "".to_string(), endpoint: base_url, }; - + let credentials = Credentials { access_key: Some(user), secret_key: Some(password), security_token: None, session_token: None, - expiration: None + expiration: None, }; - - Bucket::new("siren", region.clone(), credentials.clone()).expect("Failed to create S3 Bucket").with_path_style() + + Bucket::new("siren", region.clone(), credentials.clone()) + .expect("Failed to create S3 Bucket") + .with_path_style() }; } @@ -64,12 +72,13 @@ pub async fn init() { let mut pool: DbConnection = connection().expect("Failed to get db connection"); match pool.run_pending_migrations(MIGRATIONS) { Ok(_) => info!("Database initialized"), - Err(err) => error!("Failed to initialize database; {}", err) + Err(err) => error!("Failed to initialize database; {}", err), }; } pub fn connection() -> Result { - POOL.get() + POOL + .get() .map_err(|e| ServiceError::new(500, format!("Failed getting db connection: {}", e))) } @@ -100,9 +109,11 @@ async fn create_bucket() { secret_key: Some(password), security_token: None, session_token: None, - expiration: None + expiration: None, }; - let _ = Bucket::create_with_path_style("siren", region, credentials, BucketConfiguration::default()).await; + let _ = + Bucket::create_with_path_style("siren", region, credentials, BucketConfiguration::default()) + .await; } pub async fn upload_file(path: &str, content: &[u8]) -> Result { diff --git a/service/src/storage/schema.rs b/service/src/storage/schema.rs index 1a64dad..29f6538 100644 --- a/service/src/storage/schema.rs +++ b/service/src/storage/schema.rs @@ -51,4 +51,4 @@ diesel::table! { profile_picture -> Nullable, verified -> Bool, } -} \ No newline at end of file +} diff --git a/service/src/users/mod.rs b/service/src/users/mod.rs index 1688bdf..fa26829 100644 --- a/service/src/users/mod.rs +++ b/service/src/users/mod.rs @@ -1,3 +1,3 @@ mod routes; -pub use routes::init_routes; \ No newline at end of file +pub use routes::init_routes; diff --git a/service/src/users/routes.rs b/service/src/users/routes.rs index a878bda..de3287c 100644 --- a/service/src/users/routes.rs +++ b/service/src/users/routes.rs @@ -4,7 +4,10 @@ use log::error; use serenity::futures::StreamExt; use siren::ServiceError; -use crate::{auth::{Auth, InsertUser, QueryUser}, storage::{upload_file, get_file, delete_file}}; +use crate::{ + auth::{Auth, InsertUser, QueryUser}, + storage::{upload_file, get_file, delete_file}, +}; #[post("/picture")] async fn set_picture(mut payload: Multipart, auth: Auth) -> HttpResponse { @@ -12,7 +15,7 @@ async fn set_picture(mut payload: Multipart, auth: Auth) -> HttpResponse { let mut bytes = web::BytesMut::new(); let mut field = match item { Ok(field) => field, - Err(err) => return ResponseError::error_response(&err) + Err(err) => return ResponseError::error_response(&err), }; let content_type = field.content_disposition(); // Get file name and construct the file path @@ -20,25 +23,29 @@ async fn set_picture(mut payload: Multipart, auth: Auth) -> HttpResponse { Some(name) => { // Verify extension is supported match name.split(".").last() { - Some(ext) => { - match ext { - "png" | "jpg" | "jpeg" => name, - _ => return ResponseError::error_response(&ServiceError { + Some(ext) => match ext { + "png" | "jpg" | "jpeg" => name, + _ => { + return ResponseError::error_response(&ServiceError { status: 400, - message: "File extension is not supported".to_string() + message: "File extension is not supported".to_string(), }) } }, - None => return ResponseError::error_response(&ServiceError { - status: 400, - message: "Unknown file extension".to_string() - }) + None => { + return ResponseError::error_response(&ServiceError { + status: 400, + message: "Unknown file extension".to_string(), + }) + } } - }, - None => return ResponseError::error_response(&ServiceError { - status: 400, - message: "File name is not provided".to_string() - }) + } + None => { + return ResponseError::error_response(&ServiceError { + status: 400, + message: "File name is not provided".to_string(), + }) + } }; let path = format!("users/{}/{}", auth.user.email, file_name); @@ -62,13 +69,13 @@ async fn set_picture(mut payload: Multipart, auth: Auth) -> HttpResponse { return ResponseError::error_response(&err); } }; - }, + } Err(err) => { error!("Failed to upload file: {}", err); return ResponseError::error_response(&err); } } - }; + } return HttpResponse::Ok().finish(); } @@ -97,27 +104,25 @@ async fn get_picture(auth: Auth) -> HttpResponse { #[delete("/picture")] async fn delete_picture(auth: Auth) -> HttpResponse { match QueryUser::get_by_email(&auth.user.email) { - Ok(user) => { - match user.profile_picture { - Some(path) => { - match delete_file(&path).await { - Ok(_) => { - match InsertUser::update_profile(&auth.user.email, None) { - Ok(_) => {} - Err(err) => { - error!("Failed to update user profile: {}", err); - return ResponseError::error_response(&err); - } - }; - } - Err(err) => { - error!("Failed to delete file: {}", err); - return ResponseError::error_response(&err); - } - }; - }, - None => {} + Ok(user) => match user.profile_picture { + Some(path) => { + match delete_file(&path).await { + Ok(_) => { + match InsertUser::update_profile(&auth.user.email, None) { + Ok(_) => {} + Err(err) => { + error!("Failed to update user profile: {}", err); + return ResponseError::error_response(&err); + } + }; + } + Err(err) => { + error!("Failed to delete file: {}", err); + return ResponseError::error_response(&err); + } + }; } + None => {} }, Err(err) => { error!("Failed to get user: {}", err); @@ -128,9 +133,10 @@ async fn delete_picture(auth: Auth) -> HttpResponse { } pub fn init_routes(config: &mut web::ServiceConfig) { - config.service(web::scope("users") - .service(set_picture) - .service(get_picture) - .service(delete_picture) + config.service( + web::scope("users") + .service(set_picture) + .service(get_picture) + .service(delete_picture), ); -} \ No newline at end of file +}