From ef7508a456f8aceac7df221d4dd94e19a7044b32 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 17 May 2023 07:59:15 +0200 Subject: [PATCH 01/14] feat: move mc stuff to subcommand --- src/commands/minecraft.rs | 12 ++++++++++-- src/commands/mod.rs | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index fbbedd9..b6250d4 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -3,9 +3,17 @@ use async_minecraft_ping::ServerDescription; const DEFAULT_SERVER: &str = "rustybever.be"; -/// Ping a minecraft server +/// Parent command for Minecraft-related actions. +/// +/// Minecraft-related commands +#[poise::command(prefix_command, slash_command, subcommands("ping"))] +pub async fn mc(_ctx: Context<'_>) -> Result<(), Error> { + Ok(()) +} + +/// Ping a minecraft server; defaults to our server. #[poise::command(prefix_command, slash_command)] -pub async fn ping_mc( +pub async fn ping( ctx: Context<'_>, #[description = "Address of the server"] address: Option, #[description = "Port the server runs on"] port: Option, diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 70a5dea..d04b01e 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -6,7 +6,7 @@ use crate::{Context, Data, Error}; type EmbedField = (String, String, bool); pub fn commands() -> Vec> { - vec![help(), affluence::available(), minecraft::ping_mc()] + vec![affluence::available(), minecraft::mc(), help()] } /// Show this help menu @@ -21,7 +21,7 @@ pub async fn help( ctx, command.as_deref(), poise::builtins::HelpConfiguration { - extra_text_at_bottom: "This is an example bot made to showcase features of my custom Discord bot framework", + extra_text_at_bottom: "Brought to you by Doofenshmirtz Evil Incorporated.", ..Default::default() }, ) From 83ff00d582d1e6e0f53578474f80b2f656eb97c2 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 17 May 2023 10:52:33 +0200 Subject: [PATCH 02/14] feat: add reservation booking --- affluences-api/src/models/available.rs | 9 ++ src/commands/affluence.rs | 70 ----------- src/commands/bib.rs | 158 +++++++++++++++++++++++++ src/commands/mod.rs | 4 +- 4 files changed, 169 insertions(+), 72 deletions(-) delete mode 100644 src/commands/affluence.rs create mode 100644 src/commands/bib.rs diff --git a/affluences-api/src/models/available.rs b/affluences-api/src/models/available.rs index 29df5c5..9042c50 100644 --- a/affluences-api/src/models/available.rs +++ b/affluences-api/src/models/available.rs @@ -73,4 +73,13 @@ impl Resource { .filter(|(hour, _)| hour.state == 1) .collect() } + + /// Returns whether a slot with the given state and time bounds is present in the list of + /// hours. + pub fn has_slot(&self, start_time: NaiveTime, end_time: NaiveTime, state: u32) -> bool { + self.condensed_hours() + .into_iter() + .filter(|(block, _)| block.state == state) + .any(|(block, duration)| start_time >= block.hour && end_time <= block.hour + duration) + } } diff --git a/src/commands/affluence.rs b/src/commands/affluence.rs deleted file mode 100644 index 47d5f9b..0000000 --- a/src/commands/affluence.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::commands::EmbedField; -use crate::{Context, Error}; - -use affluences_api::Resource; -use chrono::NaiveDate; -use uuid::{uuid, Uuid}; - -const STERRE_BIB_ID: Uuid = uuid!("4737e57a-ee05-4f7b-901a-7bb541eeb297"); -const TIME_FORMAT: &str = "%H:%M"; - -fn resource_to_embed_field(resource: Resource) -> EmbedField { - let available_hours = resource.condensed_available_hours(); - - if available_hours.is_empty() { - ( - resource.resource_name.clone(), - "Nothing available.".to_string(), - false, - ) - } else { - ( - resource.resource_name.clone(), - available_hours - .into_iter() - .map(|(start_block, duration)| { - format!( - "{} - {} ({:02}:{:02})", - start_block.hour.format(TIME_FORMAT), - (start_block.hour + duration).format(TIME_FORMAT), - duration.num_hours(), - duration.num_minutes() % 60 - ) - }) - .collect::>() - .join("\n"), - false, - ) - } -} - -/// List available timeslots for day -#[poise::command(prefix_command, slash_command)] -pub async fn available(ctx: Context<'_>, date: NaiveDate) -> Result<(), Error> { - let client = &ctx.data().client; - let resources = client.available(STERRE_BIB_ID, date, 1).await?; - - ctx.send(|f| { - f.embed(|e| { - e.description(format!("Available booking dates for {}.", date)) - .fields( - resources - .into_iter() - .map(resource_to_embed_field) - .collect::>(), - ) - }) - }) - .await?; - - Ok(()) -} - -// Create a reservation -// #[poise::command(prefix_command, slash_command)] -// pub async fn reserve( -// ctx: Context<'_>, -// date: NaiveDate, -// ) -> Result<(), Error> { - -// } diff --git a/src/commands/bib.rs b/src/commands/bib.rs new file mode 100644 index 0000000..244c623 --- /dev/null +++ b/src/commands/bib.rs @@ -0,0 +1,158 @@ +use crate::commands::EmbedField; +use crate::db::users::User; +use crate::{Context, Error}; + +use affluences_api::{Reservation, Resource}; +use chrono::{NaiveDate, NaiveTime}; +use uuid::{uuid, Uuid}; + +const STERRE_BIB_ID: Uuid = uuid!("4737e57a-ee05-4f7b-901a-7bb541eeb297"); +const TIME_FORMAT: &str = "%H:%M"; + +/// Parent command for all bib-related commands +/// +/// Commands for reservating study rooms in the bib. +#[poise::command(prefix_command, slash_command, subcommands("available", "book"))] +pub async fn bib(_ctx: Context<'_>) -> Result<(), Error> { + Ok(()) +} + +fn resource_to_embed_field(resource: Resource) -> EmbedField { + let available_hours = resource.condensed_available_hours(); + let title = format!("{} ({}p)", resource.resource_name, resource.capacity); + + if available_hours.is_empty() { + (title, "Nothing available.".to_string(), false) + } else { + ( + title, + available_hours + .into_iter() + .map(|(start_block, duration)| { + format!( + "{} - {} ({:02}:{:02})", + start_block.hour.format(TIME_FORMAT), + (start_block.hour + duration).format(TIME_FORMAT), + duration.num_hours(), + duration.num_minutes() % 60 + ) + }) + .collect::>() + .join("\n"), + false, + ) + } +} + +/// List available timeslots for day +#[poise::command(prefix_command, slash_command)] +pub async fn available(ctx: Context<'_>, date: NaiveDate) -> Result<(), Error> { + let client = &ctx.data().client; + let mut resources = client.available(STERRE_BIB_ID, date, 1).await?; + // Cloning here isn't super efficient, but this list only consists of a handful of elements so + // it's fine + resources.sort_by_key(|k| k.resource_name.clone()); + + ctx.send(|f| { + f.embed(|e| { + e.description(format!("Available booking dates for {}.", date)) + .fields( + resources + .into_iter() + .map(resource_to_embed_field) + .collect::>(), + ) + }) + }) + .await?; + + Ok(()) +} + +#[poise::command(prefix_command, slash_command)] +pub async fn book( + ctx: Context<'_>, + date: NaiveDate, + start_time: NaiveTime, + end_time: NaiveTime, + #[description = "Minimum seats the room should have."] capacity: Option, +) -> Result<(), Error> { + if ctx.guild_id().is_none() { + ctx.say("You have to send this message from a guild.") + .await?; + + return Ok(()); + } + + let guild_id = ctx.guild_id().unwrap(); + let discord_id = ctx.author().id.0 as i64; + + let user = { + let mut conn = ctx.data().pool.get()?; + User::get(&mut conn, guild_id.into(), discord_id)? + }; + + if user.is_none() { + ctx.say("You have to register before being able to book reservations.") + .await?; + + return Ok(()); + } + + let user = user.unwrap(); + + let client = &ctx.data().client; + let resources = client.available(STERRE_BIB_ID, date, 1).await?; + let chosen_resource = resources + .iter() + .filter(|r| capacity.is_none() || capacity.unwrap() <= r.capacity) + .find(|r| r.has_slot(start_time, end_time, 1)); + + if let Some(chosen_resource) = chosen_resource { + let reservation = Reservation { + auth_type: None, + email: user.email.clone(), + date, + start_time, + end_time, + note: "coworking space".to_string(), + user_firstname: user.first_name, + user_lastname: user.last_name, + user_phone: None, + person_count: capacity.unwrap_or(1), + }; + + client + .make_reservation(chosen_resource.resource_id, &reservation) + .await?; + + ctx.send(|f| { + f.embed(|e| { + e.description("A new reservation has been made.") + .field("when", format!("{} {} - {}", date, start_time.format(TIME_FORMAT), end_time.format(TIME_FORMAT)), false) + .field("where", &chosen_resource.resource_name, false) + .footer(|ft| ft.text( + format!("A confirmation mail has been sent to {}. Please check your email and confirm your reservation within two hours.", user.email))) + }) + }) + .await?; + } else { + ctx.say("No slot is available within your requested bounds.") + .await?; + } + + // let resources = if let Some(capacity) = capacity { + // resources.filter(|r| r.capacity >= capacity) + // }; + + Ok(()) +} + +// Create a reservation +// #[poise::command(prefix_command, slash_command)] +// pub async fn reserve( +// ctx: Context<'_>, +// date: NaiveDate, +// ) -> Result<(), Error> { + +// } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 40068c5..d32fb16 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,4 +1,4 @@ -mod affluence; +mod bib; mod minecraft; mod users; @@ -9,7 +9,7 @@ type EmbedField = (String, String, bool); pub fn commands() -> Vec> { vec![ help(), - affluence::available(), + bib::bib(), minecraft::mc(), users::register(), users::registered(), From 10140d879c25e87498f765ca841e79882fddc911 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 17 May 2023 12:17:41 +0200 Subject: [PATCH 03/14] feat: support weekday names & tomorrow --- src/commands/bib.rs | 35 +++++++++++++++++++------------- src/commands/mod.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/commands/bib.rs b/src/commands/bib.rs index 244c623..32fbbe2 100644 --- a/src/commands/bib.rs +++ b/src/commands/bib.rs @@ -1,4 +1,4 @@ -use crate::commands::EmbedField; +use crate::commands::{EmbedField, HumanNaiveDate}; use crate::db::users::User; use crate::{Context, Error}; @@ -46,22 +46,27 @@ fn resource_to_embed_field(resource: Resource) -> EmbedField { /// List available timeslots for day #[poise::command(prefix_command, slash_command)] -pub async fn available(ctx: Context<'_>, date: NaiveDate) -> Result<(), Error> { +pub async fn available(ctx: Context<'_>, date: HumanNaiveDate) -> Result<(), Error> { let client = &ctx.data().client; - let mut resources = client.available(STERRE_BIB_ID, date, 1).await?; + let mut resources = client + .available(STERRE_BIB_ID, date.clone().into(), 1) + .await?; // Cloning here isn't super efficient, but this list only consists of a handful of elements so // it's fine resources.sort_by_key(|k| k.resource_name.clone()); ctx.send(|f| { f.embed(|e| { - e.description(format!("Available booking dates for {}.", date)) - .fields( - resources - .into_iter() - .map(resource_to_embed_field) - .collect::>(), - ) + e.description(format!( + "Available booking dates for {}.", + Into::::into(date) + )) + .fields( + resources + .into_iter() + .map(resource_to_embed_field) + .collect::>(), + ) }) }) .await?; @@ -72,7 +77,7 @@ pub async fn available(ctx: Context<'_>, date: NaiveDate) -> Result<(), Error> { #[poise::command(prefix_command, slash_command)] pub async fn book( ctx: Context<'_>, - date: NaiveDate, + date: HumanNaiveDate, start_time: NaiveTime, end_time: NaiveTime, #[description = "Minimum seats the room should have."] capacity: Option, @@ -102,7 +107,9 @@ pub async fn book( let user = user.unwrap(); let client = &ctx.data().client; - let resources = client.available(STERRE_BIB_ID, date, 1).await?; + let resources = client + .available(STERRE_BIB_ID, date.clone().into(), 1) + .await?; let chosen_resource = resources .iter() .filter(|r| capacity.is_none() || capacity.unwrap() <= r.capacity) @@ -112,7 +119,7 @@ pub async fn book( let reservation = Reservation { auth_type: None, email: user.email.clone(), - date, + date: date.clone().into(), start_time, end_time, note: "coworking space".to_string(), @@ -129,7 +136,7 @@ pub async fn book( ctx.send(|f| { f.embed(|e| { e.description("A new reservation has been made.") - .field("when", format!("{} {} - {}", date, start_time.format(TIME_FORMAT), end_time.format(TIME_FORMAT)), false) + .field("when", format!("{} {} - {}", Into::::into(date), start_time.format(TIME_FORMAT), end_time.format(TIME_FORMAT)), false) .field("where", &chosen_resource.resource_name, false) .footer(|ft| ft.text( format!("A confirmation mail has been sent to {}. Please check your email and confirm your reservation within two hours.", user.email))) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index d32fb16..f9c81ac 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -2,10 +2,59 @@ mod bib; mod minecraft; mod users; +use chrono::Datelike; +use core::str; + use crate::{Context, Data, Error}; type EmbedField = (String, String, bool); +const DAY_TERMS: [&str; 3] = ["today", "tomorrow", "overmorrow"]; + +#[derive(Clone)] +pub struct HumanNaiveDate(chrono::NaiveDate); + +impl str::FromStr for HumanNaiveDate { + type Err = chrono::format::ParseError; + + fn from_str(s: &str) -> chrono::format::ParseResult { + if let Some(days_to_add) = DAY_TERMS + .iter() + .position(|term| s.to_lowercase() == term.to_string()) + { + let now = chrono::Local::now().naive_local().date(); + + // days_to_add will never be greater than 2 + Ok(HumanNaiveDate( + now + chrono::Duration::days(days_to_add.try_into().unwrap()), + )) + } else if let Ok(weekday) = s.parse::() { + let now = chrono::Local::now().naive_local().date(); + let cur_day = now.weekday(); + let cur_day_index = cur_day.num_days_from_monday(); + let parsed_day_index = weekday.num_days_from_monday(); + + let days_to_add = if cur_day_index <= parsed_day_index { + parsed_day_index - cur_day_index + } else { + 7 - (cur_day_index - parsed_day_index) + }; + + Ok(HumanNaiveDate( + now + chrono::Duration::days(days_to_add.into()), + )) + } else { + chrono::NaiveDate::from_str(s).map(|d| HumanNaiveDate(d)) + } + } +} + +impl Into for HumanNaiveDate { + fn into(self) -> chrono::NaiveDate { + self.0 + } +} + pub fn commands() -> Vec> { vec![ help(), From 180768b8588dd806cdc370ea14b5f5d39a64bb25 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 17 May 2023 21:14:48 +0200 Subject: [PATCH 04/14] chore: apply clippy's suggestions --- affluences-api/src/models/available.rs | 2 +- src/commands/mod.rs | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/affluences-api/src/models/available.rs b/affluences-api/src/models/available.rs index 9042c50..e10052c 100644 --- a/affluences-api/src/models/available.rs +++ b/affluences-api/src/models/available.rs @@ -57,7 +57,7 @@ impl Resource { duration = duration + Duration::minutes(hour.granularity.into()); } else { out.push((start_hour, duration)); - start_hour = &hour; + start_hour = hour; duration = Duration::minutes(hour.granularity.into()); } } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index f9c81ac..32551d4 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -18,10 +18,7 @@ impl str::FromStr for HumanNaiveDate { type Err = chrono::format::ParseError; fn from_str(s: &str) -> chrono::format::ParseResult { - if let Some(days_to_add) = DAY_TERMS - .iter() - .position(|term| s.to_lowercase() == term.to_string()) - { + if let Some(days_to_add) = DAY_TERMS.iter().position(|term| s.to_lowercase() == *term) { let now = chrono::Local::now().naive_local().date(); // days_to_add will never be greater than 2 @@ -44,14 +41,14 @@ impl str::FromStr for HumanNaiveDate { now + chrono::Duration::days(days_to_add.into()), )) } else { - chrono::NaiveDate::from_str(s).map(|d| HumanNaiveDate(d)) + chrono::NaiveDate::from_str(s).map(HumanNaiveDate) } } } -impl Into for HumanNaiveDate { - fn into(self) -> chrono::NaiveDate { - self.0 +impl From for chrono::NaiveDate { + fn from(val: HumanNaiveDate) -> chrono::NaiveDate { + val.0 } } From af828dc48e4c74d3ddce2a96088553a707314d30 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 17 May 2023 23:03:28 +0200 Subject: [PATCH 05/14] fix: bundle sqlite3 --- Cargo.lock | 2 ++ Cargo.toml | 2 ++ src/build.rs => build.rs | 0 src/models.rs | 0 4 files changed, 4 insertions(+) rename src/build.rs => build.rs (100%) delete mode 100644 src/models.rs diff --git a/Cargo.lock b/Cargo.lock index b8388a2..1237a05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,7 @@ dependencies = [ "chrono", "diesel", "diesel_migrations", + "libsqlite3-sys", "poise", "tokio", "uuid", @@ -835,6 +836,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" dependencies = [ + "cc", "pkg-config", "vcpkg", ] diff --git a/Cargo.toml b/Cargo.toml index cde15dc..518d125 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,5 @@ poise = "0.5.5" async-minecraft-ping = "0.8.0" diesel = { version = "2.0.4", features = ["sqlite", "returning_clauses_for_sqlite_3_35", "r2d2"] } diesel_migrations = { version = "2.0.0", features = [ "sqlite" ] } +# Force sqlite3 to be bundled, allowing for a fully static binary +libsqlite3-sys = { version = "*", features = ["bundled"] } diff --git a/src/build.rs b/build.rs similarity index 100% rename from src/build.rs rename to build.rs diff --git a/src/models.rs b/src/models.rs deleted file mode 100644 index e69de29..0000000 From 42d1fe82125f2da7869f9f3c58cb0fe1a6d21d28 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 17 May 2023 23:18:54 +0200 Subject: [PATCH 06/14] feat(ci): add deploy --- .woodpecker/deploy.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .woodpecker/deploy.yml diff --git a/.woodpecker/deploy.yml b/.woodpecker/deploy.yml new file mode 100644 index 0000000..1fd44e2 --- /dev/null +++ b/.woodpecker/deploy.yml @@ -0,0 +1,22 @@ +platform: 'linux/amd64' +branches: 'main' + +pipeline: + release: + image: 'plugins/docker' + settings: + registry: 'git.rustybever.be' + repo: 'git.rustybever.be/chewing_bever/affy' + tag: + - 'latest' + mtu: 1300 + secrets: + - 'docker_username' + - 'docker_password' + + deploy: + image: 'curlimages/curl' + secrets: + - 'webhook' + commands: + - curl -XPOST --fail -s "$WEBHOOK" From 4380074a75f865f12fbc429660c041dc2add238d Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 17 May 2023 23:21:15 +0200 Subject: [PATCH 07/14] chore: please linter --- build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 508b53c..3a8149e 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,3 @@ fn main() { - println!("cargo:rerun-if-changed=migrations"); + println!("cargo:rerun-if-changed=migrations"); } From c8ef995bc053276190e731c5ffe9b41c2261b6ef Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 20 May 2023 21:13:59 +0200 Subject: [PATCH 08/14] feat: smaller binaries --- Cargo.toml | 7 +++++++ src/commands/bib.rs | 13 ++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 518d125..b41350e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,10 @@ diesel = { version = "2.0.4", features = ["sqlite", "returning_clauses_for_sqlit diesel_migrations = { version = "2.0.0", features = [ "sqlite" ] } # Force sqlite3 to be bundled, allowing for a fully static binary libsqlite3-sys = { version = "*", features = ["bundled"] } + +# https://stackoverflow.com/a/54842093 +[profile.release] +lto = true # Enable link-time optimization +codegen-units = 1 # Reduce number of codegen units to increase optimizations +panic = 'abort' # Abort on panic +strip = true # Strip symbols from binary* diff --git a/src/commands/bib.rs b/src/commands/bib.rs index 32fbbe2..01e7dab 100644 --- a/src/commands/bib.rs +++ b/src/commands/bib.rs @@ -48,8 +48,10 @@ fn resource_to_embed_field(resource: Resource) -> EmbedField { #[poise::command(prefix_command, slash_command)] pub async fn available(ctx: Context<'_>, date: HumanNaiveDate) -> Result<(), Error> { let client = &ctx.data().client; + let date: NaiveDate = date.into(); + let mut resources = client - .available(STERRE_BIB_ID, date.clone().into(), 1) + .available(STERRE_BIB_ID, date, 1) .await?; // Cloning here isn't super efficient, but this list only consists of a handful of elements so // it's fine @@ -59,7 +61,7 @@ pub async fn available(ctx: Context<'_>, date: HumanNaiveDate) -> Result<(), Err f.embed(|e| { e.description(format!( "Available booking dates for {}.", - Into::::into(date) + date )) .fields( resources @@ -91,6 +93,7 @@ pub async fn book( let guild_id = ctx.guild_id().unwrap(); let discord_id = ctx.author().id.0 as i64; + let date: NaiveDate = date.into(); let user = { let mut conn = ctx.data().pool.get()?; @@ -108,7 +111,7 @@ pub async fn book( let client = &ctx.data().client; let resources = client - .available(STERRE_BIB_ID, date.clone().into(), 1) + .available(STERRE_BIB_ID, date, 1) .await?; let chosen_resource = resources .iter() @@ -119,7 +122,7 @@ pub async fn book( let reservation = Reservation { auth_type: None, email: user.email.clone(), - date: date.clone().into(), + date, start_time, end_time, note: "coworking space".to_string(), @@ -136,7 +139,7 @@ pub async fn book( ctx.send(|f| { f.embed(|e| { e.description("A new reservation has been made.") - .field("when", format!("{} {} - {}", Into::::into(date), start_time.format(TIME_FORMAT), end_time.format(TIME_FORMAT)), false) + .field("when", format!("{} {} - {}", date, start_time.format(TIME_FORMAT), end_time.format(TIME_FORMAT)), false) .field("where", &chosen_resource.resource_name, false) .footer(|ft| ft.text( format!("A confirmation mail has been sent to {}. Please check your email and confirm your reservation within two hours.", user.email))) From fa24625a1f75c168bcb107ecab1fd14f27ddfcce Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 20 May 2023 21:17:01 +0200 Subject: [PATCH 09/14] chore: conform linting --- src/commands/bib.rs | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/commands/bib.rs b/src/commands/bib.rs index 01e7dab..9f28cee 100644 --- a/src/commands/bib.rs +++ b/src/commands/bib.rs @@ -50,25 +50,20 @@ pub async fn available(ctx: Context<'_>, date: HumanNaiveDate) -> Result<(), Err let client = &ctx.data().client; let date: NaiveDate = date.into(); - let mut resources = client - .available(STERRE_BIB_ID, date, 1) - .await?; + let mut resources = client.available(STERRE_BIB_ID, date, 1).await?; // Cloning here isn't super efficient, but this list only consists of a handful of elements so // it's fine resources.sort_by_key(|k| k.resource_name.clone()); ctx.send(|f| { f.embed(|e| { - e.description(format!( - "Available booking dates for {}.", - date - )) - .fields( - resources - .into_iter() - .map(resource_to_embed_field) - .collect::>(), - ) + e.description(format!("Available booking dates for {}.", date)) + .fields( + resources + .into_iter() + .map(resource_to_embed_field) + .collect::>(), + ) }) }) .await?; @@ -110,9 +105,7 @@ pub async fn book( let user = user.unwrap(); let client = &ctx.data().client; - let resources = client - .available(STERRE_BIB_ID, date, 1) - .await?; + let resources = client.available(STERRE_BIB_ID, date, 1).await?; let chosen_resource = resources .iter() .filter(|r| capacity.is_none() || capacity.unwrap() <= r.capacity) From c33cec3af50c86b37bb4b026f5c97f5ca783c1ad Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 20 May 2023 21:13:59 +0200 Subject: [PATCH 10/14] feat: smaller binaries --- Cargo.toml | 7 +++++++ src/commands/bib.rs | 13 ++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 518d125..b41350e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,10 @@ diesel = { version = "2.0.4", features = ["sqlite", "returning_clauses_for_sqlit diesel_migrations = { version = "2.0.0", features = [ "sqlite" ] } # Force sqlite3 to be bundled, allowing for a fully static binary libsqlite3-sys = { version = "*", features = ["bundled"] } + +# https://stackoverflow.com/a/54842093 +[profile.release] +lto = true # Enable link-time optimization +codegen-units = 1 # Reduce number of codegen units to increase optimizations +panic = 'abort' # Abort on panic +strip = true # Strip symbols from binary* diff --git a/src/commands/bib.rs b/src/commands/bib.rs index 32fbbe2..01e7dab 100644 --- a/src/commands/bib.rs +++ b/src/commands/bib.rs @@ -48,8 +48,10 @@ fn resource_to_embed_field(resource: Resource) -> EmbedField { #[poise::command(prefix_command, slash_command)] pub async fn available(ctx: Context<'_>, date: HumanNaiveDate) -> Result<(), Error> { let client = &ctx.data().client; + let date: NaiveDate = date.into(); + let mut resources = client - .available(STERRE_BIB_ID, date.clone().into(), 1) + .available(STERRE_BIB_ID, date, 1) .await?; // Cloning here isn't super efficient, but this list only consists of a handful of elements so // it's fine @@ -59,7 +61,7 @@ pub async fn available(ctx: Context<'_>, date: HumanNaiveDate) -> Result<(), Err f.embed(|e| { e.description(format!( "Available booking dates for {}.", - Into::::into(date) + date )) .fields( resources @@ -91,6 +93,7 @@ pub async fn book( let guild_id = ctx.guild_id().unwrap(); let discord_id = ctx.author().id.0 as i64; + let date: NaiveDate = date.into(); let user = { let mut conn = ctx.data().pool.get()?; @@ -108,7 +111,7 @@ pub async fn book( let client = &ctx.data().client; let resources = client - .available(STERRE_BIB_ID, date.clone().into(), 1) + .available(STERRE_BIB_ID, date, 1) .await?; let chosen_resource = resources .iter() @@ -119,7 +122,7 @@ pub async fn book( let reservation = Reservation { auth_type: None, email: user.email.clone(), - date: date.clone().into(), + date, start_time, end_time, note: "coworking space".to_string(), @@ -136,7 +139,7 @@ pub async fn book( ctx.send(|f| { f.embed(|e| { e.description("A new reservation has been made.") - .field("when", format!("{} {} - {}", Into::::into(date), start_time.format(TIME_FORMAT), end_time.format(TIME_FORMAT)), false) + .field("when", format!("{} {} - {}", date, start_time.format(TIME_FORMAT), end_time.format(TIME_FORMAT)), false) .field("where", &chosen_resource.resource_name, false) .footer(|ft| ft.text( format!("A confirmation mail has been sent to {}. Please check your email and confirm your reservation within two hours.", user.email))) From 7467b1f588643962ee45dc91b8ebcf4df9c309af Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 20 May 2023 21:17:01 +0200 Subject: [PATCH 11/14] chore: conform linting --- src/commands/bib.rs | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/commands/bib.rs b/src/commands/bib.rs index 01e7dab..9f28cee 100644 --- a/src/commands/bib.rs +++ b/src/commands/bib.rs @@ -50,25 +50,20 @@ pub async fn available(ctx: Context<'_>, date: HumanNaiveDate) -> Result<(), Err let client = &ctx.data().client; let date: NaiveDate = date.into(); - let mut resources = client - .available(STERRE_BIB_ID, date, 1) - .await?; + let mut resources = client.available(STERRE_BIB_ID, date, 1).await?; // Cloning here isn't super efficient, but this list only consists of a handful of elements so // it's fine resources.sort_by_key(|k| k.resource_name.clone()); ctx.send(|f| { f.embed(|e| { - e.description(format!( - "Available booking dates for {}.", - date - )) - .fields( - resources - .into_iter() - .map(resource_to_embed_field) - .collect::>(), - ) + e.description(format!("Available booking dates for {}.", date)) + .fields( + resources + .into_iter() + .map(resource_to_embed_field) + .collect::>(), + ) }) }) .await?; @@ -110,9 +105,7 @@ pub async fn book( let user = user.unwrap(); let client = &ctx.data().client; - let resources = client - .available(STERRE_BIB_ID, date, 1) - .await?; + let resources = client.available(STERRE_BIB_ID, date, 1).await?; let chosen_resource = resources .iter() .filter(|r| capacity.is_none() || capacity.unwrap() <= r.capacity) From 83e9d3e8cfedd21cf99e7e85dd896fe1ac497123 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 20 May 2023 22:36:44 +0200 Subject: [PATCH 12/14] fix: writeable docker data directory --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index b2cbacc..9d47548 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,9 @@ FROM busybox:1.36.0 COPY --from=builder /build/dumb-init /build/affy/target/release/affy /bin/ +RUN mkdir /data && \ + chown -R www-data:www-data /data + WORKDIR /data ENV TZ=Europe/Brussels From 7fd6d55e71b84605a4a7c9c3a7349d0bd7a3804a Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 15 Feb 2024 14:00:09 +0100 Subject: [PATCH 13/14] fix: update serialization for new state string type --- .woodpecker/deploy.yml | 12 ++++++------ Cargo.lock | 2 ++ affluences-api/src/models/available.rs | 26 +++++++++++++++++++------- affluences-cli/Cargo.toml | 2 ++ affluences-cli/src/main.rs | 17 ++++++++++++++++- src/commands/bib.rs | 4 ++-- 6 files changed, 47 insertions(+), 16 deletions(-) diff --git a/.woodpecker/deploy.yml b/.woodpecker/deploy.yml index 1fd44e2..7be1e36 100644 --- a/.woodpecker/deploy.yml +++ b/.woodpecker/deploy.yml @@ -14,9 +14,9 @@ pipeline: - 'docker_username' - 'docker_password' - deploy: - image: 'curlimages/curl' - secrets: - - 'webhook' - commands: - - curl -XPOST --fail -s "$WEBHOOK" + # deploy: + # image: 'curlimages/curl' + # secrets: + # - 'webhook' + # commands: + # - curl -XPOST --fail -s "$WEBHOOK" diff --git a/Cargo.lock b/Cargo.lock index 1237a05..31b1f33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,9 +23,11 @@ name = "affluences-cli" version = "0.1.0" dependencies = [ "affluences-api", + "chrono", "clap", "serde_json", "tokio", + "uuid", ] [[package]] diff --git a/affluences-api/src/models/available.rs b/affluences-api/src/models/available.rs index e10052c..ced3836 100644 --- a/affluences-api/src/models/available.rs +++ b/affluences-api/src/models/available.rs @@ -1,12 +1,19 @@ use super::hh_mm_time_format; use chrono::{Duration, NaiveTime}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; -#[derive(Deserialize, Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum ResourceState { + Available, + Full, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub struct HourBlock { #[serde(with = "hh_mm_time_format")] pub hour: NaiveTime, - pub state: u32, + pub state: ResourceState, // reservations pub granularity: u32, pub person_count: u32, @@ -14,7 +21,7 @@ pub struct HourBlock { pub places_bookable: u32, } -#[derive(Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct Resource { pub resource_id: u32, pub resource_name: String, @@ -38,7 +45,7 @@ pub struct Resource { // max_places_per_reservation pub image_url: Option, // services - pub slots_state: u32, + pub slots_state: ResourceState, pub hours: Vec, } @@ -70,13 +77,18 @@ impl Resource { pub fn condensed_available_hours(&self) -> Vec<(&HourBlock, Duration)> { self.condensed_hours() .into_iter() - .filter(|(hour, _)| hour.state == 1) + .filter(|(hour, _)| hour.state == ResourceState::Available) .collect() } /// Returns whether a slot with the given state and time bounds is present in the list of /// hours. - pub fn has_slot(&self, start_time: NaiveTime, end_time: NaiveTime, state: u32) -> bool { + pub fn has_slot( + &self, + start_time: NaiveTime, + end_time: NaiveTime, + state: ResourceState, + ) -> bool { self.condensed_hours() .into_iter() .filter(|(block, _)| block.state == state) diff --git a/affluences-cli/Cargo.toml b/affluences-cli/Cargo.toml index f46a340..f9aa8b1 100644 --- a/affluences-cli/Cargo.toml +++ b/affluences-cli/Cargo.toml @@ -10,3 +10,5 @@ affluences-api = { path = "../affluences-api" } clap = { version = "4.2.7", features = ["derive"] } serde_json = "1.0.96" tokio = { version = "1.28.1", features = ["full"] } +uuid = "*" +chrono = "*" diff --git a/affluences-cli/src/main.rs b/affluences-cli/src/main.rs index 3b31cb5..afd9b29 100644 --- a/affluences-cli/src/main.rs +++ b/affluences-cli/src/main.rs @@ -1,5 +1,9 @@ use affluences_api::AffluencesClient; +use chrono::NaiveDate; use clap::{Parser, Subcommand}; +use uuid::{uuid, Uuid}; + +const STERRE_BIB_ID: Uuid = uuid!("4737e57a-ee05-4f7b-901a-7bb541eeb297"); #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -11,7 +15,10 @@ struct Cli { #[derive(Subcommand)] enum Commands { /// does testing things - SearchSite { query: String }, + SearchSite { + query: String, + }, + Available, } #[tokio::main] @@ -25,6 +32,14 @@ async fn main() { let s = serde_json::to_string_pretty(&res).unwrap(); println!("{}", s); } + Some(Commands::Available) => { + let res = client + .available(STERRE_BIB_ID, chrono::Utc::now().naive_utc().date(), 1) + .await + .unwrap(); + let s = serde_json::to_string_pretty(&res).unwrap(); + println!("{}", s); + } None => {} } } diff --git a/src/commands/bib.rs b/src/commands/bib.rs index 9f28cee..f5f68f7 100644 --- a/src/commands/bib.rs +++ b/src/commands/bib.rs @@ -2,7 +2,7 @@ use crate::commands::{EmbedField, HumanNaiveDate}; use crate::db::users::User; use crate::{Context, Error}; -use affluences_api::{Reservation, Resource}; +use affluences_api::{Reservation, Resource, ResourceState}; use chrono::{NaiveDate, NaiveTime}; use uuid::{uuid, Uuid}; @@ -109,7 +109,7 @@ pub async fn book( let chosen_resource = resources .iter() .filter(|r| capacity.is_none() || capacity.unwrap() <= r.capacity) - .find(|r| r.has_slot(start_time, end_time, 1)); + .find(|r| r.has_slot(start_time, end_time, ResourceState::Available)); if let Some(chosen_resource) = chosen_resource { let reservation = Reservation { From 81f052ecbc67f8dfbb1c6d583e4582e2e22b2977 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 15 Feb 2024 14:07:06 +0100 Subject: [PATCH 14/14] chore: bump rust version to 1.76 --- .woodpecker/build.yml | 2 +- .woodpecker/clippy.yml | 2 +- .woodpecker/lint.yml | 2 +- Dockerfile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml index 5779344..7adfe62 100644 --- a/.woodpecker/build.yml +++ b/.woodpecker/build.yml @@ -5,7 +5,7 @@ branches: pipeline: build: - image: 'rust:1.69' + image: 'rust:1.76' commands: - cargo build --verbose - cargo test --verbose diff --git a/.woodpecker/clippy.yml b/.woodpecker/clippy.yml index 92851e5..d34cdf6 100644 --- a/.woodpecker/clippy.yml +++ b/.woodpecker/clippy.yml @@ -5,7 +5,7 @@ branches: pipeline: clippy: - image: 'rust:1.69' + image: 'rust:1.76' commands: - rustup component add clippy - cargo clippy -- --no-deps -Dwarnings diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml index ad2b612..89c1d62 100644 --- a/.woodpecker/lint.yml +++ b/.woodpecker/lint.yml @@ -5,7 +5,7 @@ branches: pipeline: lint: - image: 'rust:1.69' + image: 'rust:1.76' commands: - rustup component add rustfmt - cargo fmt -- --check diff --git a/Dockerfile b/Dockerfile index 9d47548..12e9300 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.69-alpine AS builder +FROM rust:1.76-alpine AS builder ARG DI_VER=1.2.5