use chrono::{NaiveDate, NaiveDateTime}; use sqlx::{PgPool, query, query_file}; use super::{Area, AvailabilityChangeset, Result, Role, User, UserFunction}; #[derive(Clone, Debug)] pub struct Availability { pub id: i32, pub user_id: i32, pub user: Option, pub start: NaiveDateTime, pub end: NaiveDateTime, pub comment: Option, } impl Availability { pub async fn create( pool: &PgPool, user_id: i32, changeset: AvailabilityChangeset, ) -> Result<()> { query_file!( "sql/availability/create.sql", user_id, changeset.time.0.and_utc(), changeset.time.1.and_utc(), changeset.comment ) .execute(pool) .await?; Ok(()) } pub async fn read_all_by_date_and_area_including_user( pool: &PgPool, date: NaiveDate, area_id: i32, ) -> Result> { let records = query_file!( "sql/availability/read_all_by_date_and_area_including_user.sql", date, area_id ) .fetch_all(pool) .await?; let availabilities = records .iter() .map(|r| Availability { id: r.id, user_id: r.userid, user: Some(User { id: r.userid, name: r.name.clone(), email: r.email.clone(), password: r.password.clone(), salt: r.salt.clone(), role: r.role, function: r.function.clone(), area_id: r.areaid, area: None, locked: r.locked, last_login: r.lastlogin, receive_notifications: r.receivenotifications, }), start: r.starttimestamp.naive_utc(), end: r.endtimestamp.naive_utc(), comment: r.comment.clone(), }) .collect(); Ok(availabilities) } /// loads availabilities for the area and the same day as the start date and which fully lie inside the daterange pub async fn read_all_by_daterange_and_area_including_user_for_event_planning( pool: &PgPool, date_range: (NaiveDateTime, NaiveDateTime), area_id: i32, ) -> Result> { let records = query_file!( "sql/availability/read_all_by_daterange_and_area_including_user_for_event_planning.sql", date_range.0.date(), area_id, date_range.0.and_utc(), date_range.1.and_utc() ) .fetch_all(pool) .await?; let availabilities = records .iter() .map(|r| Availability { id: r.id, user_id: r.userid, user: Some(User { id: r.userid, name: r.name.clone(), email: r.email.clone(), password: r.password.clone(), salt: r.salt.clone(), role: r.role, function: r.function.clone(), area_id: r.areaid, area: None, locked: r.locked, last_login: r.lastlogin, receive_notifications: r.receivenotifications, }), start: r.starttimestamp.naive_utc(), end: r.endtimestamp.naive_utc(), comment: r.comment.clone(), }) .collect(); Ok(availabilities) } pub async fn read_including_user(pool: &PgPool, id: i32) -> Result> { let record = query_file!("sql/availability/read_including_user.sql", id) .fetch_optional(pool) .await?; let availability = record.map(|r| Availability { id: r.id, user_id: r.userid, user: Some(User { id: r.userid, name: r.name.clone(), email: r.email.clone(), password: r.password.clone(), salt: r.salt.clone(), role: r.role, function: r.function.clone(), area_id: r.areaid, area: None, locked: r.locked, last_login: r.lastlogin, receive_notifications: r.receivenotifications, }), start: r.starttimestamp.naive_utc(), end: r.endtimestamp.naive_utc(), comment: r.comment.clone(), }); Ok(availability) } pub async fn read(pool: &PgPool, id: i32) -> Result> { let record = query!("SELECT * FROM availability WHERE id = $1", id) .fetch_optional(pool) .await?; let availability = record.map(|record| Availability { id: record.id, user_id: record.userid, user: None, start: record.starttimestamp.naive_utc(), end: record.endtimestamp.naive_utc(), comment: record.comment.clone(), }); Ok(availability) } pub async fn read_all_by_daterange_and_area_including_user_for_export( pool: &PgPool, date_range: (NaiveDate, NaiveDate), area_id: i32, ) -> Result> { let records = query_file!( "sql/availability/read_all_by_daterange_and_area_including_user_for_export.sql", area_id, date_range.0, date_range.1 ) .fetch_all(pool) .await?; let availabilities = records .iter() .map(|r| Availability { id: r.id, user_id: r.userid, user: Some(User { id: r.userid, name: r.name.clone(), email: r.email.clone(), password: r.password.clone(), salt: r.salt.clone(), role: r.role, function: r.function.clone(), area_id: r.areaid, area: Some(Area { id: r.areaid, name: r.areaname.clone(), }), locked: r.locked, last_login: r.lastlogin, receive_notifications: r.receivenotifications, }), start: r.starttimestamp.naive_utc(), end: r.endtimestamp.naive_utc(), comment: r.comment.clone(), }) .collect(); Ok(availabilities) } pub async fn read_all_by_user_and_date( pool: &PgPool, user_id: i32, date: &NaiveDate, ) -> Result> { let records = query_file!( "sql/availability/read_all_by_user_and_date.sql", user_id, date ) .fetch_all(pool) .await?; let availabilities = records .iter() .map(|r| Availability { id: r.id, user_id: r.userid, user: None, start: r.starttimestamp.naive_utc(), end: r.endtimestamp.naive_utc(), comment: r.comment.clone(), }) .collect(); Ok(availabilities) } pub async fn read_all_by_user_and_daterange( pool: &PgPool, user_id: i32, date_range: (&NaiveDate, &NaiveDate), ) -> Result> { let records = query_file!( "sql/availability/read_all_by_user_and_daterange.sql", user_id, date_range.0, date_range.1 ) .fetch_all(pool) .await?; let availabilities = records .iter() .map(|r| Availability { id: r.id, user_id: r.userid, user: None, start: r.starttimestamp.naive_utc(), end: r.endtimestamp.naive_utc(), comment: r.comment.clone(), }) .collect(); Ok(availabilities) } pub async fn find_adjacent_by_time_for_user( pool: &PgPool, start: &NaiveDateTime, end: &NaiveDateTime, user: i32, availability_to_ignore: Option, ) -> Result> { let records = query!( r##" SELECT availability.id, availability.userId, availability.startTimestamp, availability.endTimestamp, availability.comment FROM availability WHERE availability.userId = $1 AND (availability.endtimestamp = $2 OR availability.starttimestamp = $3) AND (availability.id <> $4 OR $4 IS NULL); "##, user, start.and_utc(), end.and_utc(), availability_to_ignore ) .fetch_all(pool) // possible to find up to two availabilities (upper and lower), for now we only pick one and extend it .await?; let adjacent_avaialability = records.first().and_then(|r| { Some(Availability { id: r.id, user_id: r.userid, user: None, start: r.starttimestamp.naive_utc(), end: r.endtimestamp.naive_utc(), comment: r.comment.clone(), }) }); Ok(adjacent_avaialability) } pub async fn update(pool: &PgPool, id: i32, changeset: AvailabilityChangeset) -> Result<()> { query!( "UPDATE availability SET startTimestamp = $1, endTimestamp = $2, comment = $3 WHERE id = $4", changeset.time.0.and_utc(), changeset.time.1.and_utc(), changeset.comment, id ) .execute(pool) .await?; Ok(()) } pub async fn delete(pool: &PgPool, id: i32) -> Result<()> { query!("DELETE FROM availability WHERE id = $1", id) .execute(pool) .await?; Ok(()) } }