diff --git a/Cargo.lock b/Cargo.lock index 901c20a..177f5be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -466,6 +466,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", + "serde_core", ] [[package]] @@ -482,6 +483,7 @@ dependencies = [ "itoa", "libc", "pq-sys", + "r2d2", "time", "uuid", ] @@ -625,8 +627,10 @@ dependencies = [ "env_logger", "ipnet", "log", + "r2d2", "serde", "serde_json", + "time", "tokio", "toml", "tower", @@ -1024,6 +1028,15 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -1127,6 +1140,29 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1225,6 +1261,17 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + [[package]] name = "rand" version = "0.9.2" @@ -1254,6 +1301,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.12.2" @@ -1301,6 +1357,21 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.228" diff --git a/Cargo.toml b/Cargo.toml index aa636da..14a0701 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,15 @@ askama = "0.15.1" axum = { version = "0.8.8", features = ["macros", "ws"] } camino = "1.2.2" clap = { version = "4.5.54", features = ["derive"] } -diesel = { version = "2.3.5", features = ["uuid", "time", "postgres", "ipnet-address"] } +diesel = { version = "2.3.5", features = ["uuid", "time", "postgres", "ipnet-address", "r2d2"] } diesel_migrations = { version = "2.3.1", features = ["postgres"] } env_logger = "0.11.8" ipnet = "2.11.0" log = "0.4.29" +r2d2 = "0.8.10" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" +time = { version = "0.3.45", features = ["macros", "serde"] } tokio = { version = "1.49.0", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } toml = "0.9.11" tower = { version = "0.5.3", features = ["full"] } diff --git a/migrations/2026-01-13-095817-0000_users/up.sql b/migrations/2026-01-13-095817-0000_users/up.sql index 3cb8c4a..0e89ac8 100644 --- a/migrations/2026-01-13-095817-0000_users/up.sql +++ b/migrations/2026-01-13-095817-0000_users/up.sql @@ -13,6 +13,6 @@ CREATE TABLE IF NOT EXISTS users ( created_at TIMESTAMPTZ NOT NULL DEFAULT now(), last_login_at TIMESTAMPTZ, - -- u128 bitfield for permissions - permissions NUMERIC(39,0) NOT NULL DEFAULT 0 + -- i64 bitfield for permissions + permissions BIGINT NOT NULL DEFAULT 0 ) diff --git a/migrations/2026-01-13-095838-0000_clients/up.sql b/migrations/2026-01-13-095838-0000_clients/up.sql index b2ed366..99a688e 100644 --- a/migrations/2026-01-13-095838-0000_clients/up.sql +++ b/migrations/2026-01-13-095838-0000_clients/up.sql @@ -5,15 +5,15 @@ CREATE TABLE IF NOT EXISTS clients ( first_name TEXT NOT NULL, last_name TEXT NOT NULL, - date_of_birth DATE, - phone_number TEXT, - gov_id_number TEXT, - house_number TEXT, - address_line TEXT, - city TEXT, - state TEXT, - postal_code TEXT, - country TEXT, + date_of_birth DATE NOT NULL, + phone_number TEXT NOT NULL, + gov_id_number TEXT NOT NULL, + house_number TEXT NOT NULL, + address_line TEXT NOT NULL, + city TEXT NOT NULL, + state TEXT NOT NULL, + postal_code TEXT NOT NULL, + country TEXT NOT NULL, worker_user_id BIGINT, diff --git a/migrations/2026-01-13-095843-0000_inventory_catalog/up.sql b/migrations/2026-01-13-095843-0000_inventory_catalog/up.sql index 4c77ca4..c896864 100644 --- a/migrations/2026-01-13-095843-0000_inventory_catalog/up.sql +++ b/migrations/2026-01-13-095843-0000_inventory_catalog/up.sql @@ -2,5 +2,6 @@ CREATE TABLE IF NOT EXISTS inventory_catalog ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name TEXT NOT NULL, description TEXT, + code TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ) diff --git a/migrations/2026-01-13-095853-0000_service_catalog/down.sql b/migrations/2026-01-13-095853-0000_service_catalog/down.sql new file mode 100644 index 0000000..a828564 --- /dev/null +++ b/migrations/2026-01-13-095853-0000_service_catalog/down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS service_catalog; diff --git a/migrations/2026-01-13-095853-0000_service_catalog/up.sql b/migrations/2026-01-13-095853-0000_service_catalog/up.sql new file mode 100644 index 0000000..ac91547 --- /dev/null +++ b/migrations/2026-01-13-095853-0000_service_catalog/up.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS service_catalog ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name TEXT NOT NULL, + description TEXT, + value_string TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +) diff --git a/migrations/2026-01-13-095854-0000_assigned_services/down.sql b/migrations/2026-01-13-095854-0000_assigned_services/down.sql new file mode 100644 index 0000000..77711f5 --- /dev/null +++ b/migrations/2026-01-13-095854-0000_assigned_services/down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS assigned_services; + diff --git a/migrations/2026-01-13-095854-0000_assigned_services/up.sql b/migrations/2026-01-13-095854-0000_assigned_services/up.sql new file mode 100644 index 0000000..6602078 --- /dev/null +++ b/migrations/2026-01-13-095854-0000_assigned_services/up.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS assigned_services ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + client_id BIGINT NOT NULL, + catalog_id BIGINT NOT NULL, + + CONSTRAINT fk_client_id FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE, + CONSTRAINT fk_catalog_id FOREIGN KEY (catalog_id) REFERENCES service_catalog(id) ON DELETE CASCADE +) diff --git a/migrations/2026-01-13-095855-0000_tickets/up.sql b/migrations/2026-01-13-095855-0000_tickets/up.sql index c5053d7..a5ddd28 100644 --- a/migrations/2026-01-13-095855-0000_tickets/up.sql +++ b/migrations/2026-01-13-095855-0000_tickets/up.sql @@ -1,3 +1,11 @@ CREATE TABLE IF NOT EXISTS tickets ( - id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + title TEXT NOT NULL, + description TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + service_id BIGINT NOT NULL, + created_by_user_id BIGINT NOT NULL, + + CONSTRAINT fk_service_id FOREIGN KEY (service_id) REFERENCES assigned_services(id) ON DELETE CASCADE, + CONSTRAINT fk_user_id FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE CASCADE ) diff --git a/migrations/2026-01-13-101601-0000_services/down.sql b/migrations/2026-01-13-101601-0000_services/down.sql deleted file mode 100644 index 49ef0d0..0000000 --- a/migrations/2026-01-13-101601-0000_services/down.sql +++ /dev/null @@ -1,2 +0,0 @@ -DROP TABLE IF EXISTS services; - diff --git a/migrations/2026-01-13-101601-0000_services/up.sql b/migrations/2026-01-13-101601-0000_services/up.sql deleted file mode 100644 index af48d55..0000000 --- a/migrations/2026-01-13-101601-0000_services/up.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS services ( - id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - name TEXT NOT NULL, - client_id BIGINT NOT NULL, - - CONSTRAINT fk_client_id FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE -) diff --git a/migrations/2026-01-13-190735-0000_ticket_comments/down.sql b/migrations/2026-01-13-190735-0000_ticket_comments/down.sql new file mode 100644 index 0000000..3207a1a --- /dev/null +++ b/migrations/2026-01-13-190735-0000_ticket_comments/down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS ticket_comments; diff --git a/migrations/2026-01-13-190735-0000_ticket_comments/up.sql b/migrations/2026-01-13-190735-0000_ticket_comments/up.sql new file mode 100644 index 0000000..f864a8e --- /dev/null +++ b/migrations/2026-01-13-190735-0000_ticket_comments/up.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS ticket_comments ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + user_id BIGINT NOT NULL, + ticket_id BIGINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + modified_at TIMESTAMPTZ, + content TEXT, + + CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + CONSTRAINT fk_ticket_id FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE +) diff --git a/migrations/2026-01-13-191646-0000_attachments/down.sql b/migrations/2026-01-13-191646-0000_attachments/down.sql new file mode 100644 index 0000000..318e120 --- /dev/null +++ b/migrations/2026-01-13-191646-0000_attachments/down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS attachments; diff --git a/migrations/2026-01-13-191646-0000_attachments/up.sql b/migrations/2026-01-13-191646-0000_attachments/up.sql new file mode 100644 index 0000000..e9ad158 --- /dev/null +++ b/migrations/2026-01-13-191646-0000_attachments/up.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS attachments ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + user_id BIGINT NOT NULL, + comment_id BIGINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + -- stored in file storage by attachment id + CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + CONSTRAINT fk_comment_id FOREIGN KEY (comment_id) REFERENCES ticket_comments(id) ON DELETE CASCADE +) diff --git a/src/db/mod.rs b/src/db/mod.rs index a6a27b2..5541c21 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,19 +1,31 @@ -use diesel::{Connection, PgConnection}; +use diesel::{Connection, PgConnection, r2d2::ConnectionManager}; use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations}; +use r2d2::Pool; pub mod schema; +pub mod models; pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations"); -fn run_migrations(conn: &mut impl MigrationHarness) { + +pub type DbPool = Pool>; + +fn run_migrations(pool: &mut DbPool) { + let mut conn = pool + .get() + .expect("failed to get db connection :3"); conn.run_pending_migrations(MIGRATIONS).unwrap(); log::info!("Running migrations"); } -pub fn start(cfg: &crate::config::Config) -> anyhow::Result { - let mut connection = PgConnection::establish(&cfg.database_url()?.to_string())?; - run_migrations(&mut connection); - Ok(connection) +pub fn start(cfg: &crate::config::Config) -> anyhow::Result { + let manager = ConnectionManager::::new(&cfg.database_url()?.to_string()); + let mut pool = Pool::builder() + .build(manager) + .expect("failed to create pool :3"); + + run_migrations(&mut pool); + Ok(pool) } diff --git a/src/db/models.rs b/src/db/models.rs new file mode 100644 index 0000000..b73246b --- /dev/null +++ b/src/db/models.rs @@ -0,0 +1,171 @@ +use diesel::prelude::*; + +#[derive(Queryable, Selectable, Associations)] +#[diesel(belongs_to(User, foreign_key=worker_user_id))] +#[diesel(table_name = super::schema::clients)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct Client { + pub id: i64, + pub email: String, + pub first_name: String, + pub last_name: String, + pub date_of_birth: time::Date, + pub phone_number: String, + pub gov_id_number: String, + pub house_number: String, + pub address_line: String, + pub city: String, + pub state: String, + pub postal_code: String, + pub country: String, + pub worker_user_id: Option, +} + +#[derive(Queryable, Selectable, Associations)] +#[diesel(belongs_to(User))] +#[diesel(belongs_to(Warehouse))] +#[diesel(table_name = super::schema::assigned_warehouse_managers)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct AssignedWarehouseManager { + pub id: i64, + pub user_id: i64, + pub warehouse_id: i64, + pub assigned_at: time::OffsetDateTime, +} + +#[derive(Queryable, Selectable, Associations)] +#[diesel(belongs_to(User))] +#[diesel(table_name = super::schema::attachments)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct Attachment { + pub id: i64, + pub user_id: i64, + pub comment_id: i64, + pub created_at: time::OffsetDateTime, +} + +#[derive(Queryable, Selectable, Associations)] +#[diesel(belongs_to(Warehouse))] +#[diesel(belongs_to(InventoryCatalogEntry, foreign_key=catalog_id))] +#[diesel(table_name = super::schema::inventory)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct Inventory { + pub id: i64, + pub warehouse_id: i64, + pub catalog_id: i64, + pub count: i64 +} + +#[derive(Queryable, Selectable)] +#[diesel(table_name = super::schema::inventory_catalog)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct InventoryCatalogEntry { + pub id: i64, + pub name: String, + pub code: String, + pub description: Option, + pub created_at: time::OffsetDateTime, +} + +#[derive(Queryable, Selectable, Associations)] +#[diesel(belongs_to(Client))] +#[diesel(table_name = super::schema::invoices)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct Invoice { + pub id: i64, + pub client_id: i64, + pub amount: f32 +} + +#[derive(Queryable, Selectable)] +#[diesel(table_name = super::schema::service_catalog)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct ServiceCatalogEntry { + pub id: i64, + pub name: String, + pub description: Option, + pub value_string: Option, + pub created_at: time::OffsetDateTime, +} + +#[derive(Queryable, Selectable, Associations)] +#[diesel(belongs_to(Client))] +#[diesel(belongs_to(ServiceCatalogEntry, foreign_key=catalog_id))] +#[diesel(table_name = super::schema::assigned_services)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct AssignedService { + pub id: i64, + pub client_id: i64, + pub catalog_id: i64, +} + +#[derive(Queryable, Selectable, Associations)] +#[diesel(belongs_to(User))] +#[diesel(belongs_to(Ticket))] +#[diesel(table_name = super::schema::ticket_comments)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct TicketComment { + pub id: i64, + pub user_id: i64, + pub ticket_id: i64, + pub created_at: time::OffsetDateTime, + pub modified_at: Option, + pub content: Option +} + +#[derive(Queryable, Selectable, Associations)] +#[diesel(belongs_to(AssignedService, foreign_key=service_id))] +#[diesel(table_name = super::schema::tickets)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct Ticket { + pub id: i64, + pub title: String, + pub description: Option, + pub created_at: time::OffsetDateTime, + pub created_by_user_id: i64, + pub service_id: i64 +} + +#[derive(Queryable, Selectable)] +#[diesel(table_name = super::schema::users)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct User { + pub id: i64, + pub username: String, + pub email: String, + pub password_hash: String, + pub password_salt: String, + pub first_name: String, + pub last_name: String, + pub display_name: Option, + pub date_of_birth: Option, + pub phone_number: Option, + pub created_at: time::OffsetDateTime, + pub last_login_at: Option, + pub permissions: i64, +} + +#[derive(Queryable, Selectable, Associations)] +#[diesel(belongs_to(User))] +#[diesel(belongs_to(Warehouse))] +#[diesel(table_name = super::schema::warehouse_actions)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct WarehouseAction { + pub id: i64, + pub user_id: i64, + pub warehouse_id: i64, + pub count: i64, + pub reason: String, + pub timestamp: time::OffsetDateTime, +} + +#[derive(Queryable, Selectable)] +#[diesel(table_name = super::schema::warehouses)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct Warehouse { + pub id: i64, + pub name: String, + pub created_at: time::OffsetDateTime, +} + + diff --git a/src/db/schema.rs b/src/db/schema.rs index fc0768c..df11c30 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -1,5 +1,14 @@ // @generated automatically by Diesel CLI. +diesel::table! { + assigned_services (id) { + id -> Int8, + name -> Text, + client_id -> Int8, + catalog_id -> Int8, + } +} + diesel::table! { assigned_warehouse_managers (id) { id -> Int8, @@ -9,21 +18,30 @@ diesel::table! { } } +diesel::table! { + attachments (id) { + id -> Int8, + user_id -> Int8, + comment_id -> Int8, + created_at -> Timestamptz, + } +} + diesel::table! { clients (id) { id -> Int8, email -> Text, first_name -> Text, last_name -> Text, - date_of_birth -> Nullable, - phone_number -> Nullable, - gov_id_number -> Nullable, - house_number -> Nullable, - address_line -> Nullable, - city -> Nullable, - state -> Nullable, - postal_code -> Nullable, - country -> Nullable, + date_of_birth -> Date, + phone_number -> Text, + gov_id_number -> Text, + house_number -> Text, + address_line -> Text, + city -> Text, + state -> Text, + postal_code -> Text, + country -> Text, worker_user_id -> Nullable, } } @@ -42,6 +60,7 @@ diesel::table! { id -> Int8, name -> Text, description -> Nullable, + code -> Text, created_at -> Timestamptz, } } @@ -55,16 +74,34 @@ diesel::table! { } diesel::table! { - services (id) { + service_catalog (id) { id -> Int8, name -> Text, - client_id -> Int8, + description -> Nullable, + value_string -> Nullable, + created_at -> Timestamptz, + } +} + +diesel::table! { + ticket_comments (id) { + id -> Int8, + user_id -> Int8, + ticket_id -> Int8, + created_at -> Timestamptz, + modified_at -> Nullable, + content -> Nullable, } } diesel::table! { tickets (id) { id -> Int8, + title -> Text, + description -> Nullable, + created_at -> Timestamptz, + service_id -> Int8, + created_by_user_id -> Int8, } } @@ -82,7 +119,7 @@ diesel::table! { phone_number -> Nullable, created_at -> Timestamptz, last_login_at -> Nullable, - permissions -> Numeric, + permissions -> Int8, } } @@ -105,23 +142,33 @@ diesel::table! { } } +diesel::joinable!(assigned_services -> clients (client_id)); +diesel::joinable!(assigned_services -> service_catalog (catalog_id)); diesel::joinable!(assigned_warehouse_managers -> users (user_id)); diesel::joinable!(assigned_warehouse_managers -> warehouses (warehouse_id)); +diesel::joinable!(attachments -> ticket_comments (comment_id)); +diesel::joinable!(attachments -> users (user_id)); diesel::joinable!(clients -> users (worker_user_id)); diesel::joinable!(inventory -> inventory_catalog (catalog_id)); diesel::joinable!(inventory -> warehouses (warehouse_id)); diesel::joinable!(invoices -> clients (client_id)); -diesel::joinable!(services -> clients (client_id)); +diesel::joinable!(ticket_comments -> tickets (ticket_id)); +diesel::joinable!(ticket_comments -> users (user_id)); +diesel::joinable!(tickets -> assigned_services (service_id)); +diesel::joinable!(tickets -> users (created_by_user_id)); diesel::joinable!(warehouse_actions -> users (user_id)); diesel::joinable!(warehouse_actions -> warehouses (warehouse_id)); diesel::allow_tables_to_appear_in_same_query!( + assigned_services, assigned_warehouse_managers, + attachments, clients, inventory, inventory_catalog, invoices, - services, + service_catalog, + ticket_comments, tickets, users, warehouse_actions, diff --git a/src/main.rs b/src/main.rs index 6f840c6..4171e42 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,6 @@ async fn main() -> anyhow::Result<()> { } let db = db::start(&cfg)?; - web::start(&cfg).await?; + web::start(&cfg, db).await?; Ok(()) } diff --git a/src/web/mod.rs b/src/web/mod.rs index ba6e387..ee1cca8 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -1,22 +1,27 @@ use axum::{Router, routing::get}; +use diesel::PgConnection; use tower::ServiceBuilder; use tower_http::services::ServeDir; +use crate::db::DbPool; + pub mod pages; -pub async fn start(cfg: &crate::config::Config) -> anyhow::Result<()> { +pub async fn start(cfg: &crate::config::Config, db: DbPool) -> anyhow::Result<()> { let addr = format!("{}:{}", cfg.web.host, cfg.web.port); let app = Router::new() .route("/", get(pages::home::get_page)) .route("/login", get(pages::login::get_page)) + .route("/clients", get(pages::clients::get_page)) .nest_service( "/static", ServiceBuilder::new() .service(ServeDir::new("static")) - ); + ) + .with_state(db); let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); log::info!("Listening on http://{addr}"); diff --git a/src/web/pages/clients/mod.rs b/src/web/pages/clients/mod.rs new file mode 100644 index 0000000..c0749a9 --- /dev/null +++ b/src/web/pages/clients/mod.rs @@ -0,0 +1,55 @@ +use askama::Template; +use axum::extract::State; +use axum::response::{Html, IntoResponse, Response}; +use axum::http::StatusCode; + +use crate::db::DbPool; +use crate::db::models::Client; +use crate::web::pages::{BaseTemplate, BaseTemplateCtx}; + +#[derive(Template)] +#[template(path = "clients/index.html")] +pub struct PageTemplate { + pub ctx: BaseTemplateCtx, + pub clients: Vec +} + +impl BaseTemplate for PageTemplate { + fn ctx(&self) -> &BaseTemplateCtx { + &self.ctx + } + fn ctx_mut(&mut self) -> &mut BaseTemplateCtx { + &mut self.ctx + } + +} + +#[axum::debug_handler] +pub async fn get_page(State(pool): State) -> Response { + async fn inner(pool: &DbPool) -> anyhow::Result<(StatusCode, String)> { + use diesel::prelude::*; + use crate::db::schema::clients::dsl::*; + + let results = clients + .order(id.asc()) + .limit(50) + .load::(&mut pool.get()?)?; + + let mut template = PageTemplate { + ctx: Default::default(), + clients: results + }; + + template.set_title("Clients"); + + Ok((StatusCode::OK, template.render()?)) + } + + match inner(&pool).await { + Ok((status, s)) => (status, Html(s)).into_response(), + Err(e) => { + let s = crate::web::pages::error::get_error_page(e.to_string()).await; + (StatusCode::INTERNAL_SERVER_ERROR, Html(s)).into_response() + } + } +} diff --git a/src/web/pages/inventory.rs b/src/web/pages/inventory.rs deleted file mode 100644 index 60466b7..0000000 --- a/src/web/pages/inventory.rs +++ /dev/null @@ -1,51 +0,0 @@ - - - -use askama::Template; -use axum::response::{Html, IntoResponse, Response}; - -use axum::{ - routing::{get, post}, - http::StatusCode, - Json, Router, -}; - -use crate::web::pages::{BaseTemplate, BaseTemplateCtx}; - -#[derive(Template)] -#[template(path = "home.html")] -pub struct HomeTemplate { - pub ctx: BaseTemplateCtx, - -} - -impl BaseTemplate for HomeTemplate { - fn ctx(&self) -> &BaseTemplateCtx { - &self.ctx - } - fn ctx_mut(&mut self) -> &mut BaseTemplateCtx { - &mut self.ctx - } - -} - -#[axum::debug_handler] -pub async fn get_page() -> Response { - fn inner() -> anyhow::Result<(StatusCode, String)> { - let mut template = HomeTemplate { - ctx: Default::default() - }; - - template.set_title("Home"); - - Ok((StatusCode::OK, template.render()?)) - } - - match inner() { - Ok((status, s)) => (status, Html(s)).into_response(), - Err(e) => { - let s = crate::web::pages::error::get_error_page(e.to_string()).await; - (StatusCode::INTERNAL_SERVER_ERROR, Html(s)).into_response() - } - } -} diff --git a/src/web/pages/clients.rs b/src/web/pages/inventory/mod.rs similarity index 68% rename from src/web/pages/clients.rs rename to src/web/pages/inventory/mod.rs index 4c73322..c67aa11 100644 --- a/src/web/pages/clients.rs +++ b/src/web/pages/inventory/mod.rs @@ -1,22 +1,15 @@ - - - use askama::Template; +use axum::extract::State; use axum::response::{Html, IntoResponse, Response}; +use axum::http::StatusCode; -use axum::{ - routing::{get, post}, - http::StatusCode, - Json, Router, -}; - +use crate::db::DbPool; use crate::web::pages::{BaseTemplate, BaseTemplateCtx}; #[derive(Template)] -#[template(path = "home.html")] +#[template(path = "inventory/index.html")] pub struct PageTemplate { pub ctx: BaseTemplateCtx, - } impl BaseTemplate for PageTemplate { @@ -30,18 +23,18 @@ impl BaseTemplate for PageTemplate { } #[axum::debug_handler] -pub async fn get_page() -> Response { - fn inner() -> anyhow::Result<(StatusCode, String)> { +pub async fn get_page(State(pool): State) -> Response { + async fn inner(_pool: &DbPool) -> anyhow::Result<(StatusCode, String)> { let mut template = PageTemplate { - ctx: Default::default() + ctx: Default::default(), }; - template.set_title("Home"); + template.set_title("Clients"); Ok((StatusCode::OK, template.render()?)) } - match inner() { + match inner(&pool).await { Ok((status, s)) => (status, Html(s)).into_response(), Err(e) => { let s = crate::web::pages::error::get_error_page(e.to_string()).await; diff --git a/src/web/pages/login.rs b/src/web/pages/login.rs index b1dfcbd..d7aaabc 100644 --- a/src/web/pages/login.rs +++ b/src/web/pages/login.rs @@ -1,4 +1,5 @@ use askama::Template; +use axum::extract::State; use axum::response::{Html, IntoResponse, Response}; use axum::{ @@ -7,6 +8,7 @@ use axum::{ Json, Router, }; +use crate::db::DbPool; use crate::web::pages::{BaseTemplate, BaseTemplateCtx}; #[derive(Template)] @@ -27,7 +29,7 @@ impl BaseTemplate for PageTemplate { } #[axum::debug_handler] -pub async fn get_page() -> Response { +pub async fn get_page(State(_db): State) -> Response { fn inner() -> anyhow::Result<(StatusCode, String)> { let mut template = PageTemplate { ctx: Default::default() diff --git a/src/web/pages/tickets.rs b/src/web/pages/tickets/mod.rs similarity index 68% rename from src/web/pages/tickets.rs rename to src/web/pages/tickets/mod.rs index 0ecb5a3..f5460dd 100644 --- a/src/web/pages/tickets.rs +++ b/src/web/pages/tickets/mod.rs @@ -1,22 +1,15 @@ - - - use askama::Template; +use axum::extract::State; use axum::response::{Html, IntoResponse, Response}; +use axum::http::StatusCode; -use axum::{ - routing::{get, post}, - http::StatusCode, - Json, Router, -}; - +use crate::db::DbPool; use crate::web::pages::{BaseTemplate, BaseTemplateCtx}; #[derive(Template)] -#[template(path = "home.html")] +#[template(path = "tickets/index.html")] pub struct PageTemplate { pub ctx: BaseTemplateCtx, - } impl BaseTemplate for PageTemplate { @@ -30,18 +23,18 @@ impl BaseTemplate for PageTemplate { } #[axum::debug_handler] -pub async fn get_page() -> Response { - fn inner() -> anyhow::Result<(StatusCode, String)> { +pub async fn get_page(State(pool): State) -> Response { + async fn inner(_pool: &DbPool) -> anyhow::Result<(StatusCode, String)> { let mut template = PageTemplate { - ctx: Default::default() + ctx: Default::default(), }; - template.set_title("Tickets"); + template.set_title("Clients"); Ok((StatusCode::OK, template.render()?)) } - match inner() { + match inner(&pool).await { Ok((status, s)) => (status, Html(s)).into_response(), Err(e) => { let s = crate::web::pages::error::get_error_page(e.to_string()).await; diff --git a/static/arch.png b/static/arch.png deleted file mode 100755 index 01fc39d..0000000 Binary files a/static/arch.png and /dev/null differ diff --git a/static/index.css b/static/base.css similarity index 69% rename from static/index.css rename to static/base.css index 3eda125..eae63e1 100644 --- a/static/index.css +++ b/static/base.css @@ -50,21 +50,4 @@ footer { } -.login-form { - background: gray; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - padding: 10px; -} - -.login-form input[type="email"],input[type="password"] { - width: 95%; -} - -.login-form input[type="submit"] { - width: 98%; - margin-top: 10px; -} diff --git a/static/login.css b/static/login.css new file mode 100644 index 0000000..8c9bc5a --- /dev/null +++ b/static/login.css @@ -0,0 +1,18 @@ +.login-form { + + background: gray; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + padding: 10px; +} + +.login-form input[type="email"],input[type="password"] { + width: 95%; +} + +.login-form input[type="submit"] { + width: 98%; + margin-top: 10px; +} diff --git a/templates/base.html b/templates/base.html index 86574ca..22cf4a4 100644 --- a/templates/base.html +++ b/templates/base.html @@ -3,7 +3,8 @@ {{ self.title() }} - + + {% block headers %}{% endblock %} {% include "header.html" %} diff --git a/templates/clients/client.html b/templates/clients/client.html new file mode 100644 index 0000000..5a84a91 --- /dev/null +++ b/templates/clients/client.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} +{% block headers %} +{% endblock %} + +{% block content %} +Hewwo wowld!!!! +{% endblock %} diff --git a/templates/clients/index.html b/templates/clients/index.html new file mode 100644 index 0000000..f00c234 --- /dev/null +++ b/templates/clients/index.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} +{% block headers %} +{% endblock %} + +{% block content %} + + + + + + + + + {% for client in clients %} + + + + + + + + {% endfor %} +
First NameLast NameAddressNumberEmail
{{ client.first_name }}{{ client.last_name }} + {{ client.country}} + {{ client.city}} + {{ client.state}} + {{ client.address_line}} + {{ client.house_number}} + {{ client.postal_code}} + {{ client.phone_number }}{{ client.email }}
+{% endblock %} diff --git a/templates/error.html b/templates/error.html index 23b7ba1..b49081a 100644 --- a/templates/error.html +++ b/templates/error.html @@ -1,5 +1,10 @@ {% extends "base.html" %} +{% block headers %} +{% endblock %} + + + {% block content %} A server error happened: {{ error }} {% endblock %} diff --git a/templates/home.html b/templates/home.html index 3a2bea3..3fa596b 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,5 +1,10 @@ {% extends "base.html" %} +{% block headers %} +{% endblock %} + + + {% block content %} Hewo world diff --git a/templates/inventory/catalog.html b/templates/inventory/catalog.html new file mode 100644 index 0000000..5a84a91 --- /dev/null +++ b/templates/inventory/catalog.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} +{% block headers %} +{% endblock %} + +{% block content %} +Hewwo wowld!!!! +{% endblock %} diff --git a/templates/inventory/index.html b/templates/inventory/index.html new file mode 100644 index 0000000..5a84a91 --- /dev/null +++ b/templates/inventory/index.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} +{% block headers %} +{% endblock %} + +{% block content %} +Hewwo wowld!!!! +{% endblock %} diff --git a/templates/inventory/transfer.html b/templates/inventory/transfer.html new file mode 100644 index 0000000..5a84a91 --- /dev/null +++ b/templates/inventory/transfer.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} +{% block headers %} +{% endblock %} + +{% block content %} +Hewwo wowld!!!! +{% endblock %} diff --git a/templates/login.html b/templates/login.html index 44f83f6..b95cde4 100644 --- a/templates/login.html +++ b/templates/login.html @@ -1,5 +1,9 @@ {% extends "base.html" %} +{% block headers %} + +{% endblock %} + {% block content %}