use crate::db::pool::DbPool;
use crate::errors::{AppError, AppResult};
use crate::models::event::Event;
use crate::models::event_type::EventType;
use crate::models::location::Location;
use chrono::{NaiveDate, NaiveTime};
use rusqlite::{Connection, Result, Row, params};
pub fn load_events_by_date(pool: &mut DbPool, date: &NaiveDate) -> AppResult<Vec<Event>> {
let mut stmt = pool.conn.prepare(
"SELECT * FROM events
WHERE date = ?1
ORDER BY time ASC",
)?;
let date_str = date.format("%Y-%m-%d").to_string();
let rows = stmt.query_map([date_str], map_row)?;
let mut out = Vec::new();
for r in rows {
out.push(r?);
}
Ok(out)
}
pub fn map_row(row: &Row) -> Result<Event> {
let date_str: String = row.get("date")?;
let time_str: String = row.get("time")?;
let date = NaiveDate::parse_from_str(&date_str, "%Y-%m-%d").map_err(|_| {
rusqlite::Error::FromSqlConversionFailure(
0,
rusqlite::types::Type::Text,
Box::new(AppError::InvalidDate(date_str.clone())),
)
})?;
let time = NaiveTime::parse_from_str(&time_str, "%H:%M").map_err(|_| {
rusqlite::Error::FromSqlConversionFailure(
0,
rusqlite::types::Type::Text,
Box::new(AppError::InvalidTime(time_str.clone())),
)
})?;
let kind_str: String = row.get("kind")?;
let kind = EventType::from_db_str(&kind_str).ok_or_else(|| {
rusqlite::Error::FromSqlConversionFailure(
0,
rusqlite::types::Type::Text,
Box::new(AppError::InvalidEventType(format!(
"Invalid kind: {}",
kind_str
))),
)
})?;
let loc_str: String = row.get("position")?;
let location = Location::from_db_str(&loc_str).ok_or_else(|| {
rusqlite::Error::FromSqlConversionFailure(
0,
rusqlite::types::Type::Text,
Box::new(AppError::InvalidPosition(format!(
"Invalid location: {}",
loc_str
))),
)
})?;
Ok(Event {
id: row.get("id")?,
date,
time,
kind,
location,
lunch: row.get("lunch_break")?,
work_gap: row.get::<_, i32>("work_gap")? == 1,
pair: row.get("pair")?,
source: row.get("source")?,
meta: row.get("meta")?,
created_at: row.get("created_at")?,
})
}
pub fn insert_event(conn: &Connection, ev: &Event) -> AppResult<()> {
conn.execute(
"INSERT INTO events (date, time, kind, position, lunch_break, work_gap, pair, source, meta, created_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
params![
ev.date.format("%Y-%m-%d").to_string(),
ev.time.format("%H:%M").to_string(),
ev.kind.to_db_str(),
ev.location.to_db_str(),
ev.lunch.unwrap_or(0),
if ev.work_gap { 1 } else { 0 },
ev.pair,
ev.source,
ev.meta,
ev.created_at,
],
)?;
Ok(())
}
pub fn update_event(conn: &Connection, ev: &Event) -> AppResult<()> {
conn.execute(
"UPDATE events
SET date = ?1, time = ?2, kind = ?3,
position = ?4, lunch_break = ?5,
work_gap = ?6, pair = ?7,
source = ?8, meta = ?9, created_at = ?10
WHERE id = ?11",
params![
ev.date.to_string(),
ev.time.format("%H:%M").to_string(),
ev.kind.to_db_str(),
ev.location.to_db_str(),
ev.lunch.unwrap_or(0),
if ev.work_gap { 1 } else { 0 },
ev.pair,
ev.source,
ev.meta,
ev.created_at,
ev.id,
],
)?;
Ok(())
}
pub fn delete_event(pool: &mut DbPool, id: i32) -> Result<()> {
pool.conn.execute("DELETE FROM events WHERE id = ?", [id])?;
Ok(())
}
pub fn load_pair_by_index(
conn: &Connection,
date: &NaiveDate,
pair_index: usize, ) -> AppResult<(Option<Event>, Option<Event>)> {
let mut stmt = conn.prepare("SELECT * FROM events WHERE date = ?1 ORDER BY time ASC")?;
let rows = stmt.query_map([date.to_string()], map_row)?;
let mut events: Vec<Event> = Vec::new();
for r in rows {
events.push(r?);
}
if events.is_empty() {
return Err(AppError::InvalidPair(pair_index));
}
let mut pairs: Vec<(Option<Event>, Option<Event>)> = Vec::new();
for ev in events.into_iter() {
match ev.kind {
EventType::In => pairs.push((Some(ev), None)),
EventType::Out => {
if let Some(last) = pairs.last_mut()
&& last.1.is_none()
{
last.1 = Some(ev);
continue;
}
pairs.push((None, Some(ev)));
}
}
}
if pair_index == 0 {
return Err(AppError::InvalidPair(0));
}
let idx = pair_index - 1;
if idx >= pairs.len() {
return Err(AppError::InvalidPair(pair_index));
}
Ok(pairs[idx].clone())
}
pub fn date_has_events(conn: &Connection, date: &NaiveDate) -> AppResult<bool> {
let date_str = date.to_string();
let exists: i64 = conn.query_row(
"SELECT EXISTS(SELECT 1 FROM events WHERE date = ?1 LIMIT 1)",
rusqlite::params![date_str],
|r| r.get(0),
)?;
Ok(exists == 1)
}