Implement more tables, add more page templates, initial working db example

This commit is contained in:
Gvidas Juknevičius 2026-01-14 00:00:12 +02:00
parent bcbbef1a82
commit 1ecbdde2c0
41 changed files with 583 additions and 147 deletions

71
Cargo.lock generated
View File

@ -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"

View File

@ -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"] }

View File

@ -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
)

View File

@ -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,

View File

@ -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()
)

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS service_catalog;

View 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()
)

View File

@ -0,0 +1,2 @@
DROP TABLE IF EXISTS assigned_services;

View File

@ -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
)

View File

@ -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
)

View File

@ -1,2 +0,0 @@
DROP TABLE IF EXISTS services;

View File

@ -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
)

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS ticket_comments;

View 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
)

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS attachments;

View 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
)

View File

@ -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<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();
log::info!("Running migrations");
}
pub fn start(cfg: &crate::config::Config) -> anyhow::Result<PgConnection> {
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<DbPool> {
let manager = ConnectionManager::<PgConnection>::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)
}

171
src/db/models.rs Normal file
View 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,
}

View File

@ -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<Date>,
phone_number -> Nullable<Text>,
gov_id_number -> Nullable<Text>,
house_number -> Nullable<Text>,
address_line -> Nullable<Text>,
city -> Nullable<Text>,
state -> Nullable<Text>,
postal_code -> Nullable<Text>,
country -> Nullable<Text>,
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<Int8>,
}
}
@ -42,6 +60,7 @@ diesel::table! {
id -> Int8,
name -> Text,
description -> Nullable<Text>,
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<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! {
tickets (id) {
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>,
created_at -> 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 -> 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,

View File

@ -19,6 +19,6 @@ async fn main() -> anyhow::Result<()> {
}
let db = db::start(&cfg)?;
web::start(&cfg).await?;
web::start(&cfg, db).await?;
Ok(())
}

View File

@ -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}");

View 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()
}
}
}

View File

@ -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()
}
}
}

View File

@ -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<DbPool>) -> 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;

View File

@ -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<DbPool>) -> Response {
fn inner() -> anyhow::Result<(StatusCode, String)> {
let mut template = PageTemplate {
ctx: Default::default()

View File

@ -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<DbPool>) -> 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;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

View File

@ -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
View 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;
}

View File

@ -3,7 +3,8 @@
<head>
<meta charset="utf-8">
<title>{{ self.title() }}</title>
<link rel="stylesheet" href="/static/index.css">
<link rel="stylesheet" href="/static/base.css">
{% block headers %}{% endblock %}
</head>
<body>
{% include "header.html" %}

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block headers %}
{% endblock %}
{% block content %}
Hewwo wowld!!!!
{% endblock %}

View 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 %}

View File

@ -1,5 +1,10 @@
{% extends "base.html" %}
{% block headers %}
{% endblock %}
{% block content %}
A server error happened: {{ error }}
{% endblock %}

View File

@ -1,5 +1,10 @@
{% extends "base.html" %}
{% block headers %}
{% endblock %}
{% block content %}
Hewo world

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block headers %}
{% endblock %}
{% block content %}
Hewwo wowld!!!!
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block headers %}
{% endblock %}
{% block content %}
Hewwo wowld!!!!
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block headers %}
{% endblock %}
{% block content %}
Hewwo wowld!!!!
{% endblock %}

View File

@ -1,5 +1,9 @@
{% extends "base.html" %}
{% block headers %}
<link rel="stylesheet" href="/static/login.css">
{% endblock %}
{% block content %}
<form class="login-form" action_url="/login" method="POST">

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block headers %}
{% endblock %}
{% block content %}
Hewwo wowld!!!!
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block headers %}
{% endblock %}
{% block content %}
Hewwo wowld!!!!
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block headers %}
{% endblock %}
{% block content %}
Hewwo wowld!!!!
{% endblock %}