diff --git a/Cargo.lock b/Cargo.lock index 6d1953e..41fb962 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.1.0" dependencies = [ "chrono", "futures", - "md-5", + "log", "regex", "serde", "sqlx", diff --git a/Cargo.toml b/Cargo.toml index 5d8f5b2..18f068b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,15 +5,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] - [dependencies] serde = { version = "1", features = ["derive"] } chrono = { version = "0.4", features = ["serde"] } sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite", "chrono"] } futures = "0.3" regex = "1" -md-5 = "0.10" +log="0.4" [dev-dependencies] tokio = { version = "1", features = ["full"] } diff --git a/build.rs b/build.rs index b75cdaa..9da653a 100644 --- a/build.rs +++ b/build.rs @@ -5,10 +5,17 @@ fn main() { Command::new("npx") .args(&["tailwindcss", "-i", "base.css", "-o"]) .arg(&format!( - "{}/static/styles.css", + "{}/webserver/src/static/styles.css", current_dir().unwrap().into_os_string().to_str().unwrap() )) .status() .unwrap(); - println!("cargo:rerun-if-changed=templates/") + println!("cargo:rerun-if-changed=templates/"); + + let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + if target_arch == "x86_64" && target_os == "android" { + println!("cargo:rustc-link-search=/home/forcen/Android/Sdk/ndk/26.1.10909125/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/17/lib/linux/"); + println!("cargo:rustc-link-lib=static=clang_rt.builtins-x86_64-android"); + } } diff --git a/src/lib.rs b/src/lib.rs index f7ab6d9..57bd0a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +use log::{error, info}; use sqlx::{migrate::MigrateDatabase, Sqlite, SqlitePool}; pub mod models; @@ -6,8 +7,11 @@ pub async fn create_db(db_url: &str) -> sqlx::Result { if !Sqlite::database_exists(db_url).await.unwrap_or(false) { println!("Creating database {}", db_url); match Sqlite::create_database(db_url).await { - Ok(_) => println!("create db success"), - Err(e) => panic!("Cannot create db: {e:?}"), + Ok(_) => info!("create db success"), + Err(e) => { + error!("Cannot create db at {db_url}: {e:?}"); + panic!("Cannot create db at {db_url}: {e:?}") + } }; } else { println!("Database already exists") diff --git a/static/styles.css b/static/styles.css index dd6641a..b072356 100644 --- a/static/styles.css +++ b/static/styles.css @@ -554,114 +554,6 @@ video { --tw-contain-style: ; } -.container { - width: 100%; -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 1536px) { - .container { - max-width: 1536px; - } -} - -.relative { - position: relative; -} - -.mb-2 { - margin-bottom: 0.5rem; -} - -.mb-4 { - margin-bottom: 1rem; -} - -.mb-8 { - margin-bottom: 2rem; -} - -.block { - display: block; -} - -.flex { - display: flex; -} - -.table { - display: table; -} - -.h-full { - height: 100%; -} - -.grow { - flex-grow: 1; -} - -.flex-row { - flex-direction: row; -} - -.flex-col { - flex-direction: column; -} - -.justify-evenly { - justify-content: space-evenly; -} - -.overflow-auto { - overflow: auto; -} - -.border { - border-width: 1px; -} - -.bg-stone-300 { - --tw-bg-opacity: 1; - background-color: rgb(214 211 209 / var(--tw-bg-opacity)); -} - -.p-2 { - padding: 0.5rem; -} - -.p-4 { - padding: 1rem; -} - -.text-lg { - font-size: 1.125rem; - line-height: 1.75rem; -} - .ars-input { margin: 0.25rem; margin-bottom: 0.75rem; @@ -692,13 +584,3 @@ video { background: #57534e77; cursor: pointer; } - -.hover\:bg-stone-200:hover { - --tw-bg-opacity: 1; - background-color: rgb(231 229 228 / var(--tw-bg-opacity)); -} - -.hover\:bg-stone-400:hover { - --tw-bg-opacity: 1; - background-color: rgb(168 162 158 / var(--tw-bg-opacity)); -} diff --git a/tailwind.config.js b/tailwind.config.js index 7ea262c..ba696fe 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,6 +1,6 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ["./templates/**.html"], + content: ["./webserver/src/static/**.html"], theme: { extend: {}, }, diff --git a/webserver/Cargo.toml b/webserver/Cargo.toml index 68d4ee6..0d26929 100644 --- a/webserver/Cargo.toml +++ b/webserver/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["lib"] + [dependencies] serde = { workspace = true, features = ["derive"] } chrono = { workspace = true, features = ["serde"] } diff --git a/webserver/src/lib.rs b/webserver/src/lib.rs new file mode 100644 index 0000000..bdb141f --- /dev/null +++ b/webserver/src/lib.rs @@ -0,0 +1,4 @@ +pub mod routes; +pub mod server; +pub mod static_values; +pub mod users; diff --git a/webserver/src/main.rs b/webserver/src/main.rs index 25c2f98..7069736 100644 --- a/webserver/src/main.rs +++ b/webserver/src/main.rs @@ -1,110 +1,15 @@ -use std::net::SocketAddr; -use std::sync::Arc; - -use sqlx::SqlitePool; - -use axum::{ - extract::FromRef, - routing::{get, post}, - Router, -}; -use hyper::StatusCode; -use tera::Tera; - mod routes; +mod server; +mod static_values; mod users; const DB_URL: &str = "sqlite://sqlite.db"; #[tokio::main] async fn main() { - let db = accounters::create_db(DB_URL).await.unwrap(); + let server = server::start_server("127.0.0.1:3000", DB_URL); - let mut tmpls = Tera::new("templates/*").unwrap(); - tmpls.autoescape_on(vec!["html"]); - - let state = AppState { - db: Arc::new(db), - tmpls: Arc::new(tmpls), - }; - - let exec_id: u32 = rand::random(); - - let app = Router::new() - .nest( - "/", - Router::new() - .route("/", get(routes::ui::index)) - .route("/accounts/id/:id", get(routes::ui::account::show)) - .route( - "/accounts/id/:id/transactions/add", - get(routes::ui::account::add_transactions_view) - .post(routes::ui::account::add_transactions_action), - ) - .route( - "/accounts/id/:id/transactions", - get(routes::ui::account::list_transactions), - ) - .route( - "/transaction/:id", - get(routes::ui::transaction::view).post(routes::ui::transaction::update), - ) - .route( - "/classifiers", - get(routes::ui::classifier::view_classifiers), - ) - .route( - "/classifiers/new_rule", - get(routes::ui::classifier::rules_new_view) - .post(routes::ui::classifier::rules_new_action), - ) - .route( - "/classifiers/new_category", - get(routes::ui::classifier::category_new_view) - .post(routes::ui::classifier::category_new_action), - ) - .nest( - "/static", - Router::new() - .route("/styles.css", get(routes::static_routes::styles)) - .route("/csv.js", get(routes::static_routes::csv)), - ) - .route( - "/execution", - get(move || async move { format!("{exec_id}") }), - ), - ) - .nest( - "/api/v1", - Router::new() - .route("/user", post(routes::api::create_user)) - .route("/login", post(routes::api::login)) - .route("/accounts", post(routes::api::accounts::account_create)) - .route("/accounts", get(routes::api::accounts::account_list)) - .route("/accounts/id/:id", get(routes::api::accounts::account_get)) - .route( - "/accounts/id/:id/transaction", - post(routes::api::transactions::create), - ) - .route( - "/accounts/id/:id/transaction", - get(routes::api::transactions::list), - ) - .route( - "/accounts/id/:id/recategorize", - post(routes::api::accounts::recategorize), - ) - .route("/categories", post(routes::api::categories::create)) - .route("/categories", get(routes::api::categories::list)) - .route("/rules", post(routes::api::rules::create)) - .route("/rules", get(routes::api::rules::list)), - ) - .with_state(state); - - let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); - let server = axum::Server::bind(&addr).serve(app.into_make_service()); - - let wv_task = tokio::task::spawn_blocking(|| { + /*let wv_task = tokio::task::spawn_blocking(|| { web_view::builder() .title("Test") .content(web_view::Content::Url("http://localhost:3000")) @@ -122,27 +27,6 @@ async fn main() { e = server => { println!("Axum finished with result {e:?}"); } - } -} - -#[derive(Clone)] -pub struct AppState { - db: Arc, - tmpls: Arc, -} - -impl FromRef for Arc { - fn from_ref(state: &AppState) -> Arc { - state.db.clone() - } -} - -impl FromRef for Arc { - fn from_ref(state: &AppState) -> Arc { - state.tmpls.clone() - } -} - -async fn index() -> (StatusCode, String) { - (StatusCode::OK, String::from("Hello, World!")) + }*/ + server.await.unwrap() } diff --git a/webserver/src/routes/static_routes.rs b/webserver/src/routes/static_routes.rs index 091c990..7d4815a 100644 --- a/webserver/src/routes/static_routes.rs +++ b/webserver/src/routes/static_routes.rs @@ -3,11 +3,13 @@ use std::fs; use axum::response::IntoResponse; use hyper::{header::CONTENT_TYPE, StatusCode}; +use crate::static_values; + pub async fn styles() -> impl IntoResponse { ( StatusCode::OK, [(CONTENT_TYPE, "text/css")], - fs::read_to_string("static/styles.css").unwrap(), + static_values::STYLES, ) } @@ -15,6 +17,6 @@ pub async fn csv() -> impl IntoResponse { ( StatusCode::OK, [(CONTENT_TYPE, "application/javascript")], - fs::read_to_string("static/csv.js").unwrap(), + static_values::CSV, ) } diff --git a/webserver/src/server.rs b/webserver/src/server.rs new file mode 100644 index 0000000..27d3210 --- /dev/null +++ b/webserver/src/server.rs @@ -0,0 +1,202 @@ +use std::net::{AddrParseError, SocketAddr}; +use std::sync::Arc; + +use axum::headers::ContentType; +use hyper::{header, StatusCode}; +use sqlx::SqlitePool; + +use axum::{ + extract::FromRef, + routing::{get, post}, + Router, +}; +use tera::Tera; + +use crate::{routes, static_values as templates}; + +#[derive(Debug)] +pub enum ServerError { + Db(sqlx::Error), + Tera(tera::Error), + Axum(axum::Error), + Hyper(hyper::Error), + AddrParse(AddrParseError), +} + +impl From for ServerError { + fn from(value: sqlx::Error) -> Self { + Self::Db(value) + } +} + +impl From for ServerError { + fn from(value: tera::Error) -> Self { + Self::Tera(value) + } +} + +impl From for ServerError { + fn from(value: axum::Error) -> Self { + Self::Axum(value) + } +} + +impl From for ServerError { + fn from(value: hyper::Error) -> Self { + Self::Hyper(value) + } +} + +impl From for ServerError { + fn from(value: AddrParseError) -> Self { + Self::AddrParse(value) + } +} + +pub async fn start_server(bind: &str, db_url: &str) -> Result<(), ServerError> { + let mut tmpls = Tera::default(); + tmpls + .add_raw_template("base.html", templates::BASE) + .unwrap(); + tmpls + .add_raw_template("index.html", templates::INDEX) + .unwrap(); + tmpls + .add_raw_template("account_summary.html", templates::ACCOUNT_SUMMARY) + .unwrap(); + tmpls + .add_raw_template("account_txs.html", templates::ACCOUNT_TXS) + .unwrap(); + tmpls + .add_raw_template("account_add_txs.html", templates::ACCOUNT_ADD_TXS) + .unwrap(); + tmpls + .add_raw_template("categories_new.html", templates::CATEGORIES_NEW) + .unwrap(); + tmpls + .add_raw_template("classifiers.html", templates::CLASSIFIERS) + .unwrap(); + tmpls + .add_raw_template("rules_new.html", templates::RULES_NEW) + .unwrap(); + tmpls + .add_raw_template("rules_new_success.html", templates::RULES_NEW_SUCCESS) + .unwrap(); + tmpls + .add_raw_template("transaction.html", templates::TRANSACTION) + .unwrap(); + let db = accounters::create_db(db_url).await?; + + let state = AppState { + db: Arc::new(db), + tmpls: Arc::new(tmpls), + }; + + let exec_id: u32 = rand::random(); + + let app = Router::new() + .nest( + "/", + Router::new() + .route("/", get(routes::ui::index)) + .route("/accounts/id/:id", get(routes::ui::account::show)) + .route( + "/accounts/id/:id/transactions/add", + get(routes::ui::account::add_transactions_view) + .post(routes::ui::account::add_transactions_action), + ) + .route( + "/accounts/id/:id/transactions", + get(routes::ui::account::list_transactions), + ) + .route( + "/transaction/:id", + get(routes::ui::transaction::view).post(routes::ui::transaction::update), + ) + .route( + "/classifiers", + get(routes::ui::classifier::view_classifiers), + ) + .route( + "/classifiers/new_rule", + get(routes::ui::classifier::rules_new_view) + .post(routes::ui::classifier::rules_new_action), + ) + .route( + "/classifiers/new_category", + get(routes::ui::classifier::category_new_view) + .post(routes::ui::classifier::category_new_action), + ) + .nest( + "/static", + Router::new() + .route("/styles.css", get(routes::static_routes::styles)) + .route("/csv.js", get(routes::static_routes::csv)), + ) + .route( + "/execution", + get(move || async move { format!("{exec_id}") }), + ), + ) + .nest( + "/api/v1", + Router::new() + .route( + "/healthcheck", + get(|| async { + ( + StatusCode::OK, + [(header::CONTENT_TYPE, "text/html")], + String::from("
Hello world from the healthcheck!
"), + ) + }), + ) + .route("/user", post(routes::api::create_user)) + .route("/login", post(routes::api::login)) + .route("/accounts", post(routes::api::accounts::account_create)) + .route("/accounts", get(routes::api::accounts::account_list)) + .route("/accounts/id/:id", get(routes::api::accounts::account_get)) + .route( + "/accounts/id/:id/transaction", + post(routes::api::transactions::create), + ) + .route( + "/accounts/id/:id/transaction", + get(routes::api::transactions::list), + ) + .route( + "/accounts/id/:id/recategorize", + post(routes::api::accounts::recategorize), + ) + .route("/categories", post(routes::api::categories::create)) + .route("/categories", get(routes::api::categories::list)) + .route("/rules", post(routes::api::rules::create)) + .route("/rules", get(routes::api::rules::list)), + ) + .with_state(state); + + let addr: SocketAddr = bind.parse()?; + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await?; + + Ok(()) +} + +#[derive(Clone)] +pub struct AppState { + pub db: Arc, + pub tmpls: Arc, +} + +impl FromRef for Arc { + fn from_ref(state: &AppState) -> Arc { + state.db.clone() + } +} + +impl FromRef for Arc { + fn from_ref(state: &AppState) -> Arc { + state.tmpls.clone() + } +} diff --git a/templates/account_summary.html b/webserver/src/static/account_summary.html similarity index 100% rename from templates/account_summary.html rename to webserver/src/static/account_summary.html diff --git a/templates/account_txs.html b/webserver/src/static/account_txs.html similarity index 100% rename from templates/account_txs.html rename to webserver/src/static/account_txs.html diff --git a/templates/accounts_add_txs.html b/webserver/src/static/accounts_add_txs.html similarity index 100% rename from templates/accounts_add_txs.html rename to webserver/src/static/accounts_add_txs.html diff --git a/templates/base.html b/webserver/src/static/base.html similarity index 97% rename from templates/base.html rename to webserver/src/static/base.html index c55b4fe..c910c35 100644 --- a/templates/base.html +++ b/webserver/src/static/base.html @@ -42,7 +42,7 @@ .then(data => parseInt(data)) .catch(err => {}); } - window.onload = () => { + /*window.onload = () => { check_exec().then(exec => { state.execution = exec; setInterval(()=>{ @@ -53,7 +53,7 @@ }); }, 1000); }) - } + }*/ diff --git a/templates/categories_new.html b/webserver/src/static/categories_new.html similarity index 100% rename from templates/categories_new.html rename to webserver/src/static/categories_new.html diff --git a/templates/classifiers.html b/webserver/src/static/classifiers.html similarity index 100% rename from templates/classifiers.html rename to webserver/src/static/classifiers.html diff --git a/static/csv.js b/webserver/src/static/csv.js similarity index 100% rename from static/csv.js rename to webserver/src/static/csv.js diff --git a/templates/index.html b/webserver/src/static/index.html similarity index 100% rename from templates/index.html rename to webserver/src/static/index.html diff --git a/templates/rules_new.html b/webserver/src/static/rules_new.html similarity index 100% rename from templates/rules_new.html rename to webserver/src/static/rules_new.html diff --git a/templates/rules_new_success.html b/webserver/src/static/rules_new_success.html similarity index 100% rename from templates/rules_new_success.html rename to webserver/src/static/rules_new_success.html diff --git a/webserver/src/static/styles.css b/webserver/src/static/styles.css new file mode 100644 index 0000000..dd6641a --- /dev/null +++ b/webserver/src/static/styles.css @@ -0,0 +1,704 @@ +/* +! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + letter-spacing: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +input:where([type='button']), +input:where([type='reset']), +input:where([type='submit']) { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .container { + max-width: 1536px; + } +} + +.relative { + position: relative; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.block { + display: block; +} + +.flex { + display: flex; +} + +.table { + display: table; +} + +.h-full { + height: 100%; +} + +.grow { + flex-grow: 1; +} + +.flex-row { + flex-direction: row; +} + +.flex-col { + flex-direction: column; +} + +.justify-evenly { + justify-content: space-evenly; +} + +.overflow-auto { + overflow: auto; +} + +.border { + border-width: 1px; +} + +.bg-stone-300 { + --tw-bg-opacity: 1; + background-color: rgb(214 211 209 / var(--tw-bg-opacity)); +} + +.p-2 { + padding: 0.5rem; +} + +.p-4 { + padding: 1rem; +} + +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.ars-input { + margin: 0.25rem; + margin-bottom: 0.75rem; +} + +.ars-input input { + width: 100%; + padding: 0.25rem 0.75rem; + border: solid 1px #57534e; + border-radius: 0.5rem; +} + +.ars-input select { + width: 100%; + padding: 0.25rem 0.75rem; + border: solid 1px #57534e; + border-radius: 0.5rem; + background-color: transparent; +} + +.ars-button { + padding: 0.25rem 0.75rem; + border: solid 1px #57534e; + border-radius: 0.5rem; +} + +.ars-button:hover { + background: #57534e77; + cursor: pointer; +} + +.hover\:bg-stone-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(231 229 228 / var(--tw-bg-opacity)); +} + +.hover\:bg-stone-400:hover { + --tw-bg-opacity: 1; + background-color: rgb(168 162 158 / var(--tw-bg-opacity)); +} diff --git a/templates/transaction.html b/webserver/src/static/transaction.html similarity index 100% rename from templates/transaction.html rename to webserver/src/static/transaction.html diff --git a/webserver/src/static_values.rs b/webserver/src/static_values.rs new file mode 100644 index 0000000..668979a --- /dev/null +++ b/webserver/src/static_values.rs @@ -0,0 +1,13 @@ +pub const INDEX: &str = include_str!("static/index.html"); +pub const BASE: &str = include_str!("static/base.html"); +pub const ACCOUNT_SUMMARY: &str = include_str!("static/account_summary.html"); +pub const ACCOUNT_TXS: &str = include_str!("static/account_txs.html"); +pub const ACCOUNT_ADD_TXS: &str = include_str!("static/accounts_add_txs.html"); +pub const CATEGORIES_NEW: &str = include_str!("static/categories_new.html"); +pub const CLASSIFIERS: &str = include_str!("static/classifiers.html"); +pub const RULES_NEW: &str = include_str!("static/rules_new.html"); +pub const RULES_NEW_SUCCESS: &str = include_str!("static/rules_new_success.html"); +pub const TRANSACTION: &str = include_str!("static/transaction.html"); + +pub const STYLES: &str = include_str!("static/styles.css"); +pub const CSV: &str = include_str!("static/csv.js"); diff --git a/webserver/src/users.rs b/webserver/src/users.rs index ecda457..72c1fa4 100644 --- a/webserver/src/users.rs +++ b/webserver/src/users.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use accounters::models::users::User; use axum::{ async_trait, @@ -12,7 +10,7 @@ use axum::{ }; use hyper::StatusCode; -use crate::AppState; +use crate::server::AppState; pub struct AuthRedirect;