Implement more tables, add more page templates, initial working db example
This commit is contained in:
parent
bcbbef1a82
commit
1ecbdde2c0
71
Cargo.lock
generated
71
Cargo.lock
generated
|
|
@ -466,6 +466,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
|
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -482,6 +483,7 @@ dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"libc",
|
"libc",
|
||||||
"pq-sys",
|
"pq-sys",
|
||||||
|
"r2d2",
|
||||||
"time",
|
"time",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
@ -625,8 +627,10 @@ dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"log",
|
"log",
|
||||||
|
"r2d2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"tower",
|
"tower",
|
||||||
|
|
@ -1024,6 +1028,15 @@ version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
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]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.29"
|
version = "0.4.29"
|
||||||
|
|
@ -1127,6 +1140,29 @@ version = "1.70.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
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]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
|
@ -1225,6 +1261,17 @@ version = "5.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
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]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
|
|
@ -1254,6 +1301,15 @@ dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.2"
|
version = "1.12.2"
|
||||||
|
|
@ -1301,6 +1357,21 @@ version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
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]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,15 @@ askama = "0.15.1"
|
||||||
axum = { version = "0.8.8", features = ["macros", "ws"] }
|
axum = { version = "0.8.8", features = ["macros", "ws"] }
|
||||||
camino = "1.2.2"
|
camino = "1.2.2"
|
||||||
clap = { version = "4.5.54", features = ["derive"] }
|
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"] }
|
diesel_migrations = { version = "2.3.1", features = ["postgres"] }
|
||||||
env_logger = "0.11.8"
|
env_logger = "0.11.8"
|
||||||
ipnet = "2.11.0"
|
ipnet = "2.11.0"
|
||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
|
r2d2 = "0.8.10"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.149"
|
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"] }
|
tokio = { version = "1.49.0", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] }
|
||||||
toml = "0.9.11"
|
toml = "0.9.11"
|
||||||
tower = { version = "0.5.3", features = ["full"] }
|
tower = { version = "0.5.3", features = ["full"] }
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,6 @@ CREATE TABLE IF NOT EXISTS users (
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
last_login_at TIMESTAMPTZ,
|
last_login_at TIMESTAMPTZ,
|
||||||
|
|
||||||
-- u128 bitfield for permissions
|
-- i64 bitfield for permissions
|
||||||
permissions NUMERIC(39,0) NOT NULL DEFAULT 0
|
permissions BIGINT NOT NULL DEFAULT 0
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,15 @@ CREATE TABLE IF NOT EXISTS clients (
|
||||||
first_name TEXT NOT NULL,
|
first_name TEXT NOT NULL,
|
||||||
last_name TEXT NOT NULL,
|
last_name TEXT NOT NULL,
|
||||||
|
|
||||||
date_of_birth DATE,
|
date_of_birth DATE NOT NULL,
|
||||||
phone_number TEXT,
|
phone_number TEXT NOT NULL,
|
||||||
gov_id_number TEXT,
|
gov_id_number TEXT NOT NULL,
|
||||||
house_number TEXT,
|
house_number TEXT NOT NULL,
|
||||||
address_line TEXT,
|
address_line TEXT NOT NULL,
|
||||||
city TEXT,
|
city TEXT NOT NULL,
|
||||||
state TEXT,
|
state TEXT NOT NULL,
|
||||||
postal_code TEXT,
|
postal_code TEXT NOT NULL,
|
||||||
country TEXT,
|
country TEXT NOT NULL,
|
||||||
|
|
||||||
worker_user_id BIGINT,
|
worker_user_id BIGINT,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,6 @@ CREATE TABLE IF NOT EXISTS inventory_catalog (
|
||||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
|
code TEXT NOT NULL,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE IF EXISTS service_catalog;
|
||||||
7
migrations/2026-01-13-095853-0000_service_catalog/up.sql
Normal file
7
migrations/2026-01-13-095853-0000_service_catalog/up.sql
Normal file
|
|
@ -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()
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
DROP TABLE IF EXISTS assigned_services;
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
@ -1,3 +1,11 @@
|
||||||
CREATE TABLE IF NOT EXISTS tickets (
|
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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
DROP TABLE IF EXISTS services;
|
|
||||||
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE IF EXISTS ticket_comments;
|
||||||
11
migrations/2026-01-13-190735-0000_ticket_comments/up.sql
Normal file
11
migrations/2026-01-13-190735-0000_ticket_comments/up.sql
Normal file
|
|
@ -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
|
||||||
|
)
|
||||||
1
migrations/2026-01-13-191646-0000_attachments/down.sql
Normal file
1
migrations/2026-01-13-191646-0000_attachments/down.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE IF EXISTS attachments;
|
||||||
9
migrations/2026-01-13-191646-0000_attachments/up.sql
Normal file
9
migrations/2026-01-13-191646-0000_attachments/up.sql
Normal file
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
@ -1,19 +1,31 @@
|
||||||
use diesel::{Connection, PgConnection};
|
use diesel::{Connection, PgConnection, r2d2::ConnectionManager};
|
||||||
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
|
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
|
||||||
|
use r2d2::Pool;
|
||||||
|
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
|
pub mod models;
|
||||||
|
|
||||||
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations");
|
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations");
|
||||||
|
|
||||||
fn run_migrations(conn: &mut impl MigrationHarness<diesel::pg::Pg>) {
|
|
||||||
|
pub type DbPool = Pool<ConnectionManager<PgConnection>>;
|
||||||
|
|
||||||
|
fn run_migrations(pool: &mut DbPool) {
|
||||||
|
let mut conn = pool
|
||||||
|
.get()
|
||||||
|
.expect("failed to get db connection :3");
|
||||||
conn.run_pending_migrations(MIGRATIONS).unwrap();
|
conn.run_pending_migrations(MIGRATIONS).unwrap();
|
||||||
log::info!("Running migrations");
|
log::info!("Running migrations");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn start(cfg: &crate::config::Config) -> anyhow::Result<PgConnection> {
|
pub fn start(cfg: &crate::config::Config) -> anyhow::Result<DbPool> {
|
||||||
let mut connection = PgConnection::establish(&cfg.database_url()?.to_string())?;
|
let manager = ConnectionManager::<PgConnection>::new(&cfg.database_url()?.to_string());
|
||||||
run_migrations(&mut connection);
|
let mut pool = Pool::builder()
|
||||||
Ok(connection)
|
.build(manager)
|
||||||
|
.expect("failed to create pool :3");
|
||||||
|
|
||||||
|
run_migrations(&mut pool);
|
||||||
|
Ok(pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
171
src/db/models.rs
Normal file
171
src/db/models.rs
Normal file
|
|
@ -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<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<String>,
|
||||||
|
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<String>,
|
||||||
|
pub value_string: Option<String>,
|
||||||
|
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<time::OffsetDateTime>,
|
||||||
|
pub content: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<String>,
|
||||||
|
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<String>,
|
||||||
|
pub date_of_birth: Option<time::Date>,
|
||||||
|
pub phone_number: Option<String>,
|
||||||
|
pub created_at: time::OffsetDateTime,
|
||||||
|
pub last_login_at: Option<time::OffsetDateTime>,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
// @generated automatically by Diesel CLI.
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
assigned_services (id) {
|
||||||
|
id -> Int8,
|
||||||
|
name -> Text,
|
||||||
|
client_id -> Int8,
|
||||||
|
catalog_id -> Int8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
assigned_warehouse_managers (id) {
|
assigned_warehouse_managers (id) {
|
||||||
id -> Int8,
|
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! {
|
diesel::table! {
|
||||||
clients (id) {
|
clients (id) {
|
||||||
id -> Int8,
|
id -> Int8,
|
||||||
email -> Text,
|
email -> Text,
|
||||||
first_name -> Text,
|
first_name -> Text,
|
||||||
last_name -> Text,
|
last_name -> Text,
|
||||||
date_of_birth -> Nullable<Date>,
|
date_of_birth -> Date,
|
||||||
phone_number -> Nullable<Text>,
|
phone_number -> Text,
|
||||||
gov_id_number -> Nullable<Text>,
|
gov_id_number -> Text,
|
||||||
house_number -> Nullable<Text>,
|
house_number -> Text,
|
||||||
address_line -> Nullable<Text>,
|
address_line -> Text,
|
||||||
city -> Nullable<Text>,
|
city -> Text,
|
||||||
state -> Nullable<Text>,
|
state -> Text,
|
||||||
postal_code -> Nullable<Text>,
|
postal_code -> Text,
|
||||||
country -> Nullable<Text>,
|
country -> Text,
|
||||||
worker_user_id -> Nullable<Int8>,
|
worker_user_id -> Nullable<Int8>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -42,6 +60,7 @@ diesel::table! {
|
||||||
id -> Int8,
|
id -> Int8,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
description -> Nullable<Text>,
|
description -> Nullable<Text>,
|
||||||
|
code -> Text,
|
||||||
created_at -> Timestamptz,
|
created_at -> Timestamptz,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -55,16 +74,34 @@ diesel::table! {
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
services (id) {
|
service_catalog (id) {
|
||||||
id -> Int8,
|
id -> Int8,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
client_id -> Int8,
|
description -> Nullable<Text>,
|
||||||
|
value_string -> Nullable<Text>,
|
||||||
|
created_at -> Timestamptz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
ticket_comments (id) {
|
||||||
|
id -> Int8,
|
||||||
|
user_id -> Int8,
|
||||||
|
ticket_id -> Int8,
|
||||||
|
created_at -> Timestamptz,
|
||||||
|
modified_at -> Nullable<Timestamptz>,
|
||||||
|
content -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
tickets (id) {
|
tickets (id) {
|
||||||
id -> Int8,
|
id -> Int8,
|
||||||
|
title -> Text,
|
||||||
|
description -> Nullable<Text>,
|
||||||
|
created_at -> Timestamptz,
|
||||||
|
service_id -> Int8,
|
||||||
|
created_by_user_id -> Int8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +119,7 @@ diesel::table! {
|
||||||
phone_number -> Nullable<Text>,
|
phone_number -> Nullable<Text>,
|
||||||
created_at -> Timestamptz,
|
created_at -> Timestamptz,
|
||||||
last_login_at -> Nullable<Timestamptz>,
|
last_login_at -> Nullable<Timestamptz>,
|
||||||
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 -> users (user_id));
|
||||||
diesel::joinable!(assigned_warehouse_managers -> warehouses (warehouse_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!(clients -> users (worker_user_id));
|
||||||
diesel::joinable!(inventory -> inventory_catalog (catalog_id));
|
diesel::joinable!(inventory -> inventory_catalog (catalog_id));
|
||||||
diesel::joinable!(inventory -> warehouses (warehouse_id));
|
diesel::joinable!(inventory -> warehouses (warehouse_id));
|
||||||
diesel::joinable!(invoices -> clients (client_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 -> users (user_id));
|
||||||
diesel::joinable!(warehouse_actions -> warehouses (warehouse_id));
|
diesel::joinable!(warehouse_actions -> warehouses (warehouse_id));
|
||||||
|
|
||||||
diesel::allow_tables_to_appear_in_same_query!(
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
|
assigned_services,
|
||||||
assigned_warehouse_managers,
|
assigned_warehouse_managers,
|
||||||
|
attachments,
|
||||||
clients,
|
clients,
|
||||||
inventory,
|
inventory,
|
||||||
inventory_catalog,
|
inventory_catalog,
|
||||||
invoices,
|
invoices,
|
||||||
services,
|
service_catalog,
|
||||||
|
ticket_comments,
|
||||||
tickets,
|
tickets,
|
||||||
users,
|
users,
|
||||||
warehouse_actions,
|
warehouse_actions,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,6 @@ async fn main() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let db = db::start(&cfg)?;
|
let db = db::start(&cfg)?;
|
||||||
web::start(&cfg).await?;
|
web::start(&cfg, db).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,27 @@
|
||||||
use axum::{Router, routing::get};
|
use axum::{Router, routing::get};
|
||||||
|
use diesel::PgConnection;
|
||||||
use tower::ServiceBuilder;
|
use tower::ServiceBuilder;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
|
|
||||||
|
use crate::db::DbPool;
|
||||||
|
|
||||||
pub mod pages;
|
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 addr = format!("{}:{}", cfg.web.host, cfg.web.port);
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(pages::home::get_page))
|
.route("/", get(pages::home::get_page))
|
||||||
.route("/login", get(pages::login::get_page))
|
.route("/login", get(pages::login::get_page))
|
||||||
|
.route("/clients", get(pages::clients::get_page))
|
||||||
.nest_service(
|
.nest_service(
|
||||||
"/static",
|
"/static",
|
||||||
ServiceBuilder::new()
|
ServiceBuilder::new()
|
||||||
.service(ServeDir::new("static"))
|
.service(ServeDir::new("static"))
|
||||||
);
|
)
|
||||||
|
.with_state(db);
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||||
log::info!("Listening on http://{addr}");
|
log::info!("Listening on http://{addr}");
|
||||||
|
|
|
||||||
55
src/web/pages/clients/mod.rs
Normal file
55
src/web/pages/clients/mod.rs
Normal file
|
|
@ -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<Client>
|
||||||
|
}
|
||||||
|
|
||||||
|
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<DbPool>) -> 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::<Client>(&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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +1,15 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
use axum::extract::State;
|
||||||
use axum::response::{Html, IntoResponse, Response};
|
use axum::response::{Html, IntoResponse, Response};
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
|
||||||
use axum::{
|
use crate::db::DbPool;
|
||||||
routing::{get, post},
|
|
||||||
http::StatusCode,
|
|
||||||
Json, Router,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::web::pages::{BaseTemplate, BaseTemplateCtx};
|
use crate::web::pages::{BaseTemplate, BaseTemplateCtx};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "home.html")]
|
#[template(path = "inventory/index.html")]
|
||||||
pub struct PageTemplate {
|
pub struct PageTemplate {
|
||||||
pub ctx: BaseTemplateCtx,
|
pub ctx: BaseTemplateCtx,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BaseTemplate for PageTemplate {
|
impl BaseTemplate for PageTemplate {
|
||||||
|
|
@ -30,18 +23,18 @@ impl BaseTemplate for PageTemplate {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn get_page() -> Response {
|
pub async fn get_page(State(pool): State<DbPool>) -> Response {
|
||||||
fn inner() -> anyhow::Result<(StatusCode, String)> {
|
async fn inner(_pool: &DbPool) -> anyhow::Result<(StatusCode, String)> {
|
||||||
let mut template = PageTemplate {
|
let mut template = PageTemplate {
|
||||||
ctx: Default::default()
|
ctx: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
template.set_title("Home");
|
template.set_title("Clients");
|
||||||
|
|
||||||
Ok((StatusCode::OK, template.render()?))
|
Ok((StatusCode::OK, template.render()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
match inner() {
|
match inner(&pool).await {
|
||||||
Ok((status, s)) => (status, Html(s)).into_response(),
|
Ok((status, s)) => (status, Html(s)).into_response(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let s = crate::web::pages::error::get_error_page(e.to_string()).await;
|
let s = crate::web::pages::error::get_error_page(e.to_string()).await;
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
use axum::extract::State;
|
||||||
use axum::response::{Html, IntoResponse, Response};
|
use axum::response::{Html, IntoResponse, Response};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
|
|
@ -7,6 +8,7 @@ use axum::{
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::db::DbPool;
|
||||||
use crate::web::pages::{BaseTemplate, BaseTemplateCtx};
|
use crate::web::pages::{BaseTemplate, BaseTemplateCtx};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
|
|
@ -27,7 +29,7 @@ impl BaseTemplate for PageTemplate {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn get_page() -> Response {
|
pub async fn get_page(State(_db): State<DbPool>) -> Response {
|
||||||
fn inner() -> anyhow::Result<(StatusCode, String)> {
|
fn inner() -> anyhow::Result<(StatusCode, String)> {
|
||||||
let mut template = PageTemplate {
|
let mut template = PageTemplate {
|
||||||
ctx: Default::default()
|
ctx: Default::default()
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,15 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
use axum::extract::State;
|
||||||
use axum::response::{Html, IntoResponse, Response};
|
use axum::response::{Html, IntoResponse, Response};
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
|
||||||
use axum::{
|
use crate::db::DbPool;
|
||||||
routing::{get, post},
|
|
||||||
http::StatusCode,
|
|
||||||
Json, Router,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::web::pages::{BaseTemplate, BaseTemplateCtx};
|
use crate::web::pages::{BaseTemplate, BaseTemplateCtx};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "home.html")]
|
#[template(path = "tickets/index.html")]
|
||||||
pub struct PageTemplate {
|
pub struct PageTemplate {
|
||||||
pub ctx: BaseTemplateCtx,
|
pub ctx: BaseTemplateCtx,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BaseTemplate for PageTemplate {
|
impl BaseTemplate for PageTemplate {
|
||||||
|
|
@ -30,18 +23,18 @@ impl BaseTemplate for PageTemplate {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn get_page() -> Response {
|
pub async fn get_page(State(pool): State<DbPool>) -> Response {
|
||||||
fn inner() -> anyhow::Result<(StatusCode, String)> {
|
async fn inner(_pool: &DbPool) -> anyhow::Result<(StatusCode, String)> {
|
||||||
let mut template = PageTemplate {
|
let mut template = PageTemplate {
|
||||||
ctx: Default::default()
|
ctx: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
template.set_title("Tickets");
|
template.set_title("Clients");
|
||||||
|
|
||||||
Ok((StatusCode::OK, template.render()?))
|
Ok((StatusCode::OK, template.render()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
match inner() {
|
match inner(&pool).await {
|
||||||
Ok((status, s)) => (status, Html(s)).into_response(),
|
Ok((status, s)) => (status, Html(s)).into_response(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let s = crate::web::pages::error::get_error_page(e.to_string()).await;
|
let s = crate::web::pages::error::get_error_page(e.to_string()).await;
|
||||||
BIN
static/arch.png
BIN
static/arch.png
Binary file not shown.
|
Before Width: | Height: | Size: 71 KiB |
|
|
@ -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;
|
|
||||||
}
|
|
||||||
18
static/login.css
Normal file
18
static/login.css
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>{{ self.title() }}</title>
|
<title>{{ self.title() }}</title>
|
||||||
<link rel="stylesheet" href="/static/index.css">
|
<link rel="stylesheet" href="/static/base.css">
|
||||||
|
{% block headers %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% include "header.html" %}
|
{% include "header.html" %}
|
||||||
|
|
|
||||||
7
templates/clients/client.html
Normal file
7
templates/clients/client.html
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block headers %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Hewwo wowld!!!!
|
||||||
|
{% endblock %}
|
||||||
31
templates/clients/index.html
Normal file
31
templates/clients/index.html
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block headers %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>First Name</td>
|
||||||
|
<td>Last Name</td>
|
||||||
|
<td>Address</td>
|
||||||
|
<td>Number</td>
|
||||||
|
<td>Email</td>
|
||||||
|
</tr>
|
||||||
|
{% for client in clients %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ client.first_name }}</td>
|
||||||
|
<td>{{ client.last_name }}</td>
|
||||||
|
<td>
|
||||||
|
{{ client.country}}
|
||||||
|
{{ client.city}}
|
||||||
|
{{ client.state}}
|
||||||
|
{{ client.address_line}}
|
||||||
|
{{ client.house_number}}
|
||||||
|
{{ client.postal_code}}
|
||||||
|
</td>
|
||||||
|
<td>{{ client.phone_number }}</td>
|
||||||
|
<td>{{ client.email }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block headers %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
A server error happened: {{ error }}
|
A server error happened: {{ error }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block headers %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
Hewo world
|
Hewo world
|
||||||
|
|
|
||||||
7
templates/inventory/catalog.html
Normal file
7
templates/inventory/catalog.html
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block headers %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Hewwo wowld!!!!
|
||||||
|
{% endblock %}
|
||||||
7
templates/inventory/index.html
Normal file
7
templates/inventory/index.html
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block headers %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Hewwo wowld!!!!
|
||||||
|
{% endblock %}
|
||||||
7
templates/inventory/transfer.html
Normal file
7
templates/inventory/transfer.html
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block headers %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Hewwo wowld!!!!
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block headers %}
|
||||||
|
<link rel="stylesheet" href="/static/login.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<form class="login-form" action_url="/login" method="POST">
|
<form class="login-form" action_url="/login" method="POST">
|
||||||
|
|
|
||||||
7
templates/tickets/closed.html
Normal file
7
templates/tickets/closed.html
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block headers %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Hewwo wowld!!!!
|
||||||
|
{% endblock %}
|
||||||
7
templates/tickets/create.html
Normal file
7
templates/tickets/create.html
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block headers %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Hewwo wowld!!!!
|
||||||
|
{% endblock %}
|
||||||
7
templates/tickets/index.html
Normal file
7
templates/tickets/index.html
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block headers %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Hewwo wowld!!!!
|
||||||
|
{% endblock %}
|
||||||
Loading…
Reference in New Issue
Block a user