diff --git a/.woodpecker/deploy.yml b/.woodpecker/deploy.yml deleted file mode 100644 index 1fd44e2..0000000 --- a/.woodpecker/deploy.yml +++ /dev/null @@ -1,22 +0,0 @@ -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" diff --git a/Cargo.lock b/Cargo.lock index 1237a05..b8388a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,6 @@ dependencies = [ "chrono", "diesel", "diesel_migrations", - "libsqlite3-sys", "poise", "tokio", "uuid", @@ -836,7 +835,6 @@ 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 518d125..cde15dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,5 +24,3 @@ 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/affluences-api/src/models/available.rs b/affluences-api/src/models/available.rs index e10052c..29df5c5 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()); } } @@ -73,13 +73,4 @@ 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/build.rs b/src/build.rs similarity index 100% rename from build.rs rename to src/build.rs diff --git a/src/commands/affluence.rs b/src/commands/affluence.rs new file mode 100644 index 0000000..47d5f9b --- /dev/null +++ b/src/commands/affluence.rs @@ -0,0 +1,70 @@ +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 deleted file mode 100644 index 32fbbe2..0000000 --- a/src/commands/bib.rs +++ /dev/null @@ -1,165 +0,0 @@ -use crate::commands::{EmbedField, HumanNaiveDate}; -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: HumanNaiveDate) -> Result<(), Error> { - let client = &ctx.data().client; - 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 {}.", - Into::::into(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: HumanNaiveDate, - 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.clone().into(), 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: date.clone().into(), - 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!("{} {} - {}", 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))) - }) - }) - .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 32551d4..40068c5 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,61 +1,15 @@ -mod bib; +mod affluence; 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) { - 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(HumanNaiveDate) - } - } -} - -impl From for chrono::NaiveDate { - fn from(val: HumanNaiveDate) -> chrono::NaiveDate { - val.0 - } -} - pub fn commands() -> Vec> { vec![ help(), - bib::bib(), + affluence::available(), minecraft::mc(), users::register(), users::registered(), diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..e69de29