adding the queries for the scheduler

This commit is contained in:
2026-09-17 20:58:15 -04:00
parent 1fbdfb5d28
commit ed18d4f931
13 changed files with 3044 additions and 2 deletions
+1
View File
@@ -0,0 +1 @@
DATABASE_URL=postgres://postgres:password@localhost/rust_manager_employees
+14
View File
@@ -1 +1,15 @@
### Rust ###
# Generated by Cargo
# will have compiled files and executables
debug/
target/
/target
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
Generated
+2567
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -4,3 +4,9 @@ version = "0.1.0"
edition = "2024"
[dependencies]
chrono = "0.4.42"
crossterm = "0.29.0"
ratatui = "0.30.2"
runtime-tokio = "0.0.0"
sqlx = { version = "0.9.0", features = ["chrono", "derive", "postgres", "runtime-tokio"] }
tokio = "1.53.1"
+47
View File
@@ -0,0 +1,47 @@
use sqlx::PgPool;
use crate::database::storage::{Company, CompanyUser};
pub async fn add_company(pool: &PgPool, name: &str) -> Result<Company, sqlx::Error> {
sqlx::query_as::<_, Company>(
r#"
INSERT INTO companies (name)
VALUES ($1)
RETURNING id, name, date_created, date_updated
"#,
)
.bind(name)
.fetch_one(pool)
.await
}
pub async fn remove_company(pool: &PgPool, id: i32) -> Result<Company, sqlx::Error> {
sqlx::query_as::<_, Company>(
r#"
DELETE FROM companies
WHERE id = $1
RETURNING id, name, date_created, date_updated
"#,
)
.bind(id)
.fetch_one(pool)
.await
}
pub async fn add_company_user(
pool: &PgPool,
company_id: i32,
user_id: i32,
) -> Result<CompanyUser, sqlx::Error> {
sqlx::query_as::<_, CompanyUser>(
r#"
INSERT INTO company_users (user_id, company_id)
VALUES ($1, $2)
RETURNING id, user_id, company_id, date_created, date_updated
"#,
)
.bind(user_id)
.bind(company_id)
.fetch_one(pool)
.await
}
+3
View File
@@ -0,0 +1,3 @@
pub mod company_queries;
pub mod storage;
pub mod user_queries;
+127
View File
@@ -0,0 +1,127 @@
use chrono::NaiveDateTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq, sqlx::Type)]
#[sqlx(type_name = "work_time", rename_all = "snake_case")]
pub enum WorkTime {
Morning,
Evening,
Night,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, sqlx::Type)]
#[sqlx(type_name = "day_of_week", rename_all = "snake_case")]
pub enum DayOfWeek {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}
#[derive(Debug, sqlx::FromRow)]
pub struct User {
pub id: i32,
pub name: String,
pub last_name: String,
pub preference: WorkTime,
pub date_created: NaiveDateTime,
pub date_updated: NaiveDateTime,
}
#[derive(Debug, sqlx::FromRow)]
pub struct CompanyUser {
pub id: i32,
pub user_id: i32,
pub company_id: i32,
pub date_created: NaiveDateTime,
pub date_updated: NaiveDateTime,
}
#[derive(Debug, sqlx::FromRow)]
pub struct Company {
pub id: i32,
pub name: String,
pub date_created: NaiveDateTime,
pub date_updated: NaiveDateTime,
}
#[derive(Debug, sqlx::FromRow)]
pub struct Schedule {
pub id: i32,
pub user_id: i32,
pub enter_time: NaiveDateTime,
pub day_of_week: DayOfWeek,
pub out_time: NaiveDateTime,
pub date_created: NaiveDateTime,
pub date_updated: NaiveDateTime,
}
pub const CREATE_WORK_TIME_ENUM: &str = r#"
CREATE TYPE work_time AS ENUM ('morning', 'evening', 'night');
"#;
pub const CREATE_DAY_OF_WEEK_ENUM: &str = r#"
CREATE TYPE day_of_week AS ENUM (
'sunday',
'monday',
'tuesday',
'wednesday',
'thursday',
'friday',
'saturday'
);
"#;
pub const CREATE_COMPANIES_TABLE: &str = r#"
CREATE TABLE companies (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
date_created TIMESTAMP NOT NULL DEFAULT NOW(),
date_updated TIMESTAMP NOT NULL DEFAULT NOW()
);
"#;
pub const CREATE_USERS_TABLE: &str = r#"
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
last_name TEXT NOT NULL,
preference work_time NOT NULL,
date_created TIMESTAMP NOT NULL DEFAULT NOW(),
date_updated TIMESTAMP NOT NULL DEFAULT NOW()
);
"#;
pub const CREATE_COMPANY_USERS_TABLE: &str = r#"
CREATE TABLE company_users (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
company_id INTEGER NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
date_created TIMESTAMP NOT NULL DEFAULT NOW(),
date_updated TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE (user_id, company_id)
);
"#;
pub const CREATE_SCHEDULES_TABLE: &str = r#"
CREATE TABLE schedules (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
enter_time TIMESTAMP NOT NULL,
day_of_week day_of_week NOT NULL,
out_time TIMESTAMP NOT NULL,
date_created TIMESTAMP NOT NULL DEFAULT NOW(),
date_updated TIMESTAMP NOT NULL DEFAULT NOW()
);
"#;
pub const CREATE_SCHEMA_QUERIES: [&str; 6] = [
CREATE_WORK_TIME_ENUM,
CREATE_DAY_OF_WEEK_ENUM,
CREATE_COMPANIES_TABLE,
CREATE_USERS_TABLE,
CREATE_COMPANY_USERS_TABLE,
CREATE_SCHEDULES_TABLE,
];
+123
View File
@@ -0,0 +1,123 @@
use chrono::NaiveDateTime;
use sqlx::PgPool;
use crate::database::storage::{DayOfWeek, Schedule, User, WorkTime};
pub async fn add_user(
pool: &PgPool,
name: &str,
last_name: &str,
preference: WorkTime,
) -> Result<User, sqlx::Error> {
sqlx::query_as::<_, User>(
r#"
INSERT INTO users (name, last_name, preference)
VALUES ($1, $2, $3)
RETURNING id, name, last_name, preference, date_created, date_updated
"#,
)
.bind(name)
.bind(last_name)
.bind(preference)
.fetch_one(pool)
.await
}
pub async fn remove_user(pool: &PgPool, id: i32) -> Result<User, sqlx::Error> {
sqlx::query_as::<_, User>(
r#"
DELETE FROM users
WHERE id = $1
RETURNING id, name, last_name, preference, date_created, date_updated
"#,
)
.bind(id)
.fetch_one(pool)
.await
}
pub async fn add_schedule(
pool: &PgPool,
user_id: i32,
enter_time: NaiveDateTime,
day_of_week: DayOfWeek,
out_time: NaiveDateTime,
) -> Result<Schedule, sqlx::Error> {
sqlx::query_as::<_, Schedule>(
r#"
INSERT INTO schedules (user_id, enter_time, day_of_week, out_time)
VALUES ($1, $2, $3, $4)
RETURNING id, user_id, enter_time, day_of_week, out_time, date_created, date_updated
"#,
)
.bind(user_id)
.bind(enter_time)
.bind(day_of_week)
.bind(out_time)
.fetch_one(pool)
.await
}
pub async fn get_days_worked_by_user(
pool: &PgPool,
user_id: i32,
) -> Result<Vec<DayOfWeek>, sqlx::Error> {
sqlx::query_scalar::<_, DayOfWeek>(
r#"
SELECT DISTINCT day_of_week
FROM schedules
WHERE user_id = $1
ORDER BY day_of_week
"#,
)
.bind(user_id)
.fetch_all(pool)
.await
}
pub async fn get_user_enter_time_by_day(
pool: &PgPool,
user_id: i32,
day_of_week: DayOfWeek,
) -> Result<Option<NaiveDateTime>, sqlx::Error> {
sqlx::query_scalar::<_, NaiveDateTime>(
r#"
SELECT enter_time
FROM schedules
WHERE user_id = $1 AND day_of_week = $2
ORDER BY date_created DESC
LIMIT 1
"#,
)
.bind(user_id)
.bind(day_of_week)
.fetch_optional(pool)
.await
}
pub async fn update_day_end_by_user_and_day(
pool: &PgPool,
user_id: i32,
day_of_week: DayOfWeek,
out_time: NaiveDateTime,
) -> Result<Schedule, sqlx::Error> {
sqlx::query_as::<_, Schedule>(
r#"
UPDATE schedules
SET out_time = $3, date_updated = NOW()
WHERE id = (
SELECT id
FROM schedules
WHERE user_id = $1 AND day_of_week = $2
ORDER BY date_created DESC
LIMIT 1
)
RETURNING id, user_id, enter_time, day_of_week, out_time, date_created, date_updated
"#,
)
.bind(user_id)
.bind(day_of_week)
.bind(out_time)
.fetch_one(pool)
.await
}
+143 -2
View File
@@ -1,3 +1,144 @@
fn main() {
println!("Hello, world!");
use std::io;
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
use ratatui::{
DefaultTerminal, Frame,
buffer::Buffer,
layout::Rect,
style::Stylize,
symbols::border,
text::{Line, Text},
widgets::{Block, Paragraph, Widget},
};
fn main() -> io::Result<()> {
ratatui::run(|terminal| App::default().run(terminal))
}
#[derive(Debug, Default)]
pub struct App {
counter: u8,
exit: bool,
}
impl App {
/// runs the application's main loop until the user quits
pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
while !self.exit {
terminal.draw(|frame| self.draw(frame))?;
self.handle_events()?;
}
Ok(())
}
fn draw(&self, frame: &mut Frame) {
frame.render_widget(self, frame.area());
}
/// updates the application's state based on user input
fn handle_events(&mut self) -> io::Result<()> {
match event::read()? {
// it's important to check that the event is a key press event as
// crossterm also emits key release and repeat events on Windows.
Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
self.handle_key_event(key_event)
}
_ => {}
};
Ok(())
}
fn handle_key_event(&mut self, key_event: KeyEvent) {
match key_event.code {
KeyCode::Char('q') => self.exit(),
KeyCode::Left => self.decrement_counter(),
KeyCode::Right => self.increment_counter(),
_ => {}
}
}
fn exit(&mut self) {
self.exit = true;
}
fn increment_counter(&mut self) {
self.counter += 1;
}
fn decrement_counter(&mut self) {
self.counter -= 1;
}
}
impl Widget for &App {
fn render(self, area: Rect, buf: &mut Buffer) {
let title = Line::from(" Counter App Tutorial ".bold());
let instructions = Line::from(vec![
" Decrement ".into(),
"<Left>".blue().bold(),
" Increment ".into(),
"<Right>".blue().bold(),
" Quit ".into(),
"<Q> ".blue().bold(),
]);
let block = Block::bordered()
.title(title.centered())
.title_bottom(instructions.centered())
.border_set(border::THICK);
let counter_text = Text::from(vec![Line::from(vec![
"Value: ".into(),
self.counter.to_string().yellow(),
])]);
Paragraph::new(counter_text)
.centered()
.block(block)
.render(area, buf);
}
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui::style::Style;
#[test]
fn render() {
let app = App::default();
let mut buf = Buffer::empty(Rect::new(0, 0, 50, 4));
app.render(buf.area, &mut buf);
let mut expected = Buffer::with_lines(vec![
"┏━━━━━━━━━━━━━ Counter App Tutorial ━━━━━━━━━━━━━┓",
"┃ Value: 0 ┃",
"┃ ┃",
"┗━ Decrement <Left> Increment <Right> Quit <Q> ━━┛",
]);
let title_style = Style::new().bold();
let counter_style = Style::new().yellow();
let key_style = Style::new().blue().bold();
expected.set_style(Rect::new(14, 0, 22, 1), title_style);
expected.set_style(Rect::new(28, 1, 1, 1), counter_style);
expected.set_style(Rect::new(13, 3, 6, 1), key_style);
expected.set_style(Rect::new(30, 3, 7, 1), key_style);
expected.set_style(Rect::new(43, 3, 4, 1), key_style);
assert_eq!(buf, expected);
}
#[test]
fn handle_key_event() {
let mut app = App::default();
app.handle_key_event(KeyCode::Right.into());
assert_eq!(app.counter, 1);
app.handle_key_event(KeyCode::Left.into());
assert_eq!(app.counter, 0);
let mut app = App::default();
app.handle_key_event(KeyCode::Char('q').into());
assert!(app.exit);
}
}
+3
View File
@@ -0,0 +1,3 @@
pub conflicts;
pub scheduler;
pub validator;
+3
View File
@@ -0,0 +1,3 @@
pub employee;
pub schedule;
pub shift;
+3
View File
@@ -0,0 +1,3 @@
pub event;
pub ui;
pub screens;
+4
View File
@@ -0,0 +1,4 @@
pub employees;
pub menu;
pub preferences;
pub schedule;