adding the queries for the scheduler
This commit is contained in:
@@ -0,0 +1 @@
|
||||
DATABASE_URL=postgres://postgres:password@localhost/rust_manager_employees
|
||||
+14
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod company_queries;
|
||||
pub mod storage;
|
||||
pub mod user_queries;
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
pub conflicts;
|
||||
pub scheduler;
|
||||
pub validator;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
pub employee;
|
||||
pub schedule;
|
||||
pub shift;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
pub event;
|
||||
pub ui;
|
||||
pub screens;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
pub employees;
|
||||
pub menu;
|
||||
pub preferences;
|
||||
pub schedule;
|
||||
|
||||
Reference in New Issue
Block a user