From 58de9c781dc464210f76534dd86b832ce53087d9 Mon Sep 17 00:00:00 2001 From: Sean Myers <22840861+seanpmyers@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:23:54 -0400 Subject: [PATCH 1/3] Development Synchronization (#564) * updating readme and adding constants for ssl cert paths * swapping html-escape crates * removing html-escape crate * adding redirect to webauthn login * Changing the layout from flex to a grid * Updating general styling of nav bars and main content area. * Updating styling * Trying out glass styling and updating timeline page. * Updating glass styling and updating you page. * updating timeline styling a little * updating frontend global state * upating global login functionality * updating global state * increasing max dependabot pull request count * updating gitignore * Adding dockerfile * updating dockerfile * updating dictionary schema * Updating modal functionality. * updating login/register page * restructuring project * fixing front-end build * renaming web interface package * fixing rust workspace * moving docker files for database to database folder * updating database package * adding shared library package * updating ui * updating compounding interest calculator * adding click to close functionality for modals * Bump serde from 1.0.186 to 1.0.188 in /loremaster-web-server Bumps [serde](https://github.com/serde-rs/serde) from 1.0.186 to 1.0.188. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.186...v1.0.188) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump time from 0.3.27 to 0.3.28 in /loremaster-web-server Bumps [time](https://github.com/time-rs/time) from 0.3.27 to 0.3.28. - [Release notes](https://github.com/time-rs/time/releases) - [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md) - [Commits](https://github.com/time-rs/time/compare/v0.3.27...v0.3.28) --- updated-dependencies: - dependency-name: time dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * adding rustrover config files * updating rustanalyzer project config * updating design system page * adding word to vscode dictionary * updating combobox implementation * fixing goal list * updating global state * removing redundant rustanalyzer project path * updating styling * refactoring to use new types for data entities * fixed a bug with tab component * updating dependabot path * making format changes --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/dependabot.yml | 2 +- .idea/.gitignore | 8 + .idea/loremaster.iml | 15 ++ .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .vscode/settings.json | 13 +- Cargo.lock | 9 ++ Cargo.toml | 5 +- {infrastructure => database}/Dockerfile | 0 database/README.md | 7 + .../docker-compose.yml | 0 loremaster-database/Cargo.toml | 1 + loremaster-database/src/main.rs | 37 ++++- .../src/components/combobox.rs | 29 ++-- .../src/components/container.rs | 15 +- .../src/components/icon.rs | 14 +- .../src/components/modal.rs | 24 ++- .../src/components/navigation.rs | 81 +++++----- .../src/components/navigation/side_nav_bar.rs | 70 +++++++-- .../components/navigation/tab/tab_section.rs | 7 +- .../src/components/widget/calendar/week.rs | 2 +- .../src/components/widget/goal_list.rs | 2 +- .../src/components/widget/sleep.rs | 2 +- loremaster-web-interface/src/global_state.rs | 15 +- .../src/templates/chronicle.rs | 16 +- .../src/templates/design_system.rs | 67 ++------ .../src/templates/login.rs | 4 +- .../src/templates/registration.rs | 16 +- .../static/styles/loremaster/index.css | 94 +++++++++++ loremaster-web-server/.idea/.gitignore | 8 + .../.idea/loremaster-web-server.iml | 8 + loremaster-web-server/.idea/modules.xml | 8 + loremaster-web-server/.idea/vcs.xml | 6 + loremaster-web-server/README.md | 2 +- .../src/api/handler/authentication.rs | 120 ++++++++------ .../src/api/handler/person.rs | 24 +-- .../src/api/router/authentication.rs | 10 +- .../src/api/router/chronicle.rs | 11 +- .../src/api/router/person.rs | 148 +++++++++++++++--- .../src/data/entity/action.rs | 11 +- .../src/data/entity/chronicle.rs | 15 +- .../src/data/entity/email_address.rs | 11 +- loremaster-web-server/src/data/entity/goal.rs | 17 +- .../src/data/entity/intention.rs | 17 +- .../src/data/entity/passkey.rs | 11 +- .../src/data/entity/person.rs | 19 ++- .../src/data/entity/quick_task.rs | 4 +- .../src/data/entity/sleep_schedule.rs | 11 +- .../data/entity/transfer/person_chronicle.rs | 5 +- .../entity/web_authentication_challenge.rs | 11 +- .../src/data/entity/web_authentication_key.rs | 11 +- .../add_web_authentication_key.rs | 2 +- .../add_web_authentication_login.rs | 2 +- .../add_web_authentication_register.rs | 2 +- .../get_web_authentication_register.rs | 7 +- .../src/data/query/goal/get_goal_list.rs | 5 +- .../data/query/person/action_is_related.rs | 6 +- .../src/data/query/person/add_action.rs | 6 +- .../src/data/query/person/add_goal.rs | 6 +- .../src/data/query/person/add_password.rs | 6 +- .../person/add_web_authentication_key.rs | 7 +- .../query/person/get_person_sleep_schedule.rs | 5 +- .../src/data/query/person/goal_is_related.rs | 6 +- .../src/data/query/person/meta_by_id.rs | 5 +- .../src/data/query/person/remove_one_goal.rs | 6 +- .../data/query/person/update_email_address.rs | 10 +- .../person/update_person_sleep_schedule.rs | 7 +- loremaster/Cargo.toml | 22 +++ loremaster/src/data.rs | 1 + loremaster/src/data/postgres_handler.rs | 42 +++++ loremaster/src/lib.rs | 16 ++ 71 files changed, 916 insertions(+), 310 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/loremaster.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml rename {infrastructure => database}/Dockerfile (100%) create mode 100644 database/README.md rename {infrastructure => database}/docker-compose.yml (100%) create mode 100644 loremaster-web-server/.idea/.gitignore create mode 100644 loremaster-web-server/.idea/loremaster-web-server.iml create mode 100644 loremaster-web-server/.idea/modules.xml create mode 100644 loremaster-web-server/.idea/vcs.xml create mode 100644 loremaster/Cargo.toml create mode 100644 loremaster/src/data.rs create mode 100644 loremaster/src/data/postgres_handler.rs create mode 100644 loremaster/src/lib.rs diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7ebb44bf..c9a4e528 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,7 +7,7 @@ updates: time: "05:30" open-pull-requests-limit: 50 - package-ecosystem: cargo - directory: "/loremaster-frontend" + directory: "/loremaster-web-interface" schedule: interval: daily time: "05:30" diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..1c2fda56 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/loremaster.iml b/.idea/loremaster.iml new file mode 100644 index 00000000..5561400e --- /dev/null +++ b/.idea/loremaster.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..8f460958 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..c8397c94 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index f794156a..69651b4d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,19 +1,28 @@ { "cSpell.words": [ + "bindgen", "chrono", + "Combobox", "cose", "ctap", "fidokey", + "gloo", "Hasher", + "listbox", "openapi", "reqwasm", "Rustls", - "webauthn" + "webauthn", + "YUBIKEY" ], "editor.insertSpaces": false, "editor.tabSize": 2, "git.enableCommitSigning": true, "yaml.schemas": { "openapi:v3": "file:///home/naes/github/loremaster/documentation/openapi/chronicle/openapi%3A%20%273.0.2%27.yml" - } + }, + "rust-analyzer.linkedProjects": [ + "./loremaster-web-interface/Cargo.toml", + "./loremaster-web-server/Cargo.toml" + ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a3161a26..fecee838 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1141,12 +1141,21 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "loremaster" +version = "0.1.0" +dependencies = [ + "anyhow", + "sqlx", +] + [[package]] name = "loremaster-database" version = "0.1.0" dependencies = [ "anyhow", "futures", + "loremaster", "sqlx", "thiserror", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 6c572180..a403006d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] -members = ["loremaster-database", "loremaster-web-server"] -default-members = ["loremaster-database", "loremaster-web-server"] +members = ["loremaster", "loremaster-database", "loremaster-web-server"] +default-members = ["loremaster", "loremaster-database", "loremaster-web-server"] exclude = ["loremaster-web-interface"] +resolver = "2" diff --git a/infrastructure/Dockerfile b/database/Dockerfile similarity index 100% rename from infrastructure/Dockerfile rename to database/Dockerfile diff --git a/database/README.md b/database/README.md new file mode 100644 index 00000000..8a435c7e --- /dev/null +++ b/database/README.md @@ -0,0 +1,7 @@ +# Loremaster Database + +Docker compose command + +```sh +docker-compose -f docker-compose.yml up +``` diff --git a/infrastructure/docker-compose.yml b/database/docker-compose.yml similarity index 100% rename from infrastructure/docker-compose.yml rename to database/docker-compose.yml diff --git a/loremaster-database/Cargo.toml b/loremaster-database/Cargo.toml index ea5c1ffc..cba07c42 100644 --- a/loremaster-database/Cargo.toml +++ b/loremaster-database/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" anyhow = "1.0.75" # Provides async programming foundational functionality futures = "0.3.28" +loremaster = { path = "../loremaster" } # Database client/pool/toolkit sqlx = { version = "0.6.3", features = [ "json", diff --git a/loremaster-database/src/main.rs b/loremaster-database/src/main.rs index 9981ef81..4abb1087 100644 --- a/loremaster-database/src/main.rs +++ b/loremaster-database/src/main.rs @@ -1,8 +1,43 @@ use anyhow::Result; +use loremaster::data::postgres_handler::PostgresHandler; +use sqlx::PgPool; + +const CONNECTION_STRING_ENVIRONMENT_VARIABLE_KEY: &str = "POSTGRES_CONNECTION_STRING"; +const DATABASE_FOLDER_PATH: &str = "../database/"; #[tokio::main] async fn main() -> Result<()> { - println!("Hello, world!"); + println!("Starting up..."); + + let postgres_connection_string: String = + std::env::var(CONNECTION_STRING_ENVIRONMENT_VARIABLE_KEY) + .expect("Missing postgresql connection string."); + + if postgres_connection_string.is_empty() { + panic!("Postgresql connection string is empty!"); + } + + if !std::path::Path::new(DATABASE_FOLDER_PATH).exists() { + panic!("Database folder path does not exist!"); + } + + let postgres_handler: PostgresHandler = + PostgresHandler::new(postgres_connection_string).await?; + + ping(&postgres_handler.database_pool).await?; + + Ok(()) +} + +pub async fn ping(database_pool: &PgPool) -> Result<()> { + const PING_QUERY: &str = "SELECT 1;"; + + let rows_affected: u64 = sqlx::query(PING_QUERY) + .execute(database_pool) + .await? + .rows_affected(); + + println!("{}", rows_affected); Ok(()) } diff --git a/loremaster-web-interface/src/components/combobox.rs b/loremaster-web-interface/src/components/combobox.rs index 2c4c700c..a4f335f3 100644 --- a/loremaster-web-interface/src/components/combobox.rs +++ b/loremaster-web-interface/src/components/combobox.rs @@ -51,26 +51,29 @@ pub fn ComboBox<'combobox, G: Html>( }); view! {context, - div(class=classes) { - label() { (label) } - input(type="text", bind:value=query) {} + label() { (label) } + div(class=format!("{} combobox", classes)) { + input(type="text", bind:value=query, class="combobox-input") {} input(type="hidden", value=(match selected.get().as_ref() { Some(option) => option.to_string(), None => String::new(), }), name=selected_html_input_name) - select() { + ul(class="combobox-options ", role="listbox", aria-label=label) { Keyed( iterable=filtered_options, - view= move |context, option| view! { context, - option( - class=COMBOBOX_OPTION_CSS_CLASSES, - value=option.id.to_string(), - title=option.description, - on:click=move |event: Event| { + view= move |context, option| { + let display_text = option.display_text.clone(); + view! { context, + li( + class=COMBOBOX_OPTION_CSS_CLASSES, + value=option.id.to_string(), + title=option.description + ) { button(on:click=move |event: Event| { event.prevent_default(); - selected.set(Some(option.id)) - } - ) { (option.display_text) } + selected.set(Some(option.id)); + query.set(display_text.to_owned()); + }) { (option.display_text) } } + } }, key=|option| option.id ) diff --git a/loremaster-web-interface/src/components/container.rs b/loremaster-web-interface/src/components/container.rs index 699bf325..f52da3ef 100644 --- a/loremaster-web-interface/src/components/container.rs +++ b/loremaster-web-interface/src/components/container.rs @@ -1,6 +1,10 @@ -use sycamore::prelude::*; +use perseus::reactor::Reactor; +use sycamore::{futures::spawn_local_scoped, prelude::*}; -use crate::components::navigation::{side_nav_bar::SideNavBar, top_nav_bar::TopNavBar}; +use crate::{ + components::navigation::{side_nav_bar::SideNavBar, top_nav_bar::TopNavBar}, + global_state::ApplicationStateRx, +}; #[derive(Prop)] pub struct ContainerProperties<'a, G: Html> { @@ -13,7 +17,14 @@ pub fn Container<'a, G: Html>( context: Scope<'a>, ContainerProperties { title, children }: ContainerProperties<'a, G>, ) -> View { + let user_authentication = + Reactor::::from_cx(context).get_global_state::(context); let children: View = children.call(context); + if G::IS_BROWSER { + spawn_local_scoped(context, async { + user_authentication.authentication.detect_state().await; + }); + } view! {context, div(class="glass container") { TopNavBar() diff --git a/loremaster-web-interface/src/components/icon.rs b/loremaster-web-interface/src/components/icon.rs index 315c7873..b7dddf08 100644 --- a/loremaster-web-interface/src/components/icon.rs +++ b/loremaster-web-interface/src/components/icon.rs @@ -268,7 +268,7 @@ pub const CLOSE_X_SVG_HTML: SvgIcon = r#" "#; -pub const PASSWORD_SVH_HTML: SvgIcon = r#" +pub const PASSWORD_SVG_HTML: SvgIcon = r#" "#; + +pub const LAYOUT_SVG_HTML: SvgIcon = r#" + + + + + + +"#; diff --git a/loremaster-web-interface/src/components/modal.rs b/loremaster-web-interface/src/components/modal.rs index f31498e6..5b56342a 100644 --- a/loremaster-web-interface/src/components/modal.rs +++ b/loremaster-web-interface/src/components/modal.rs @@ -18,6 +18,7 @@ pub struct ModalProperties<'modal, G: Html> { pub children: Children<'modal, G>, pub button_label: &'static str, pub modal_type: &'modal ReadSignal, + pub close_on_click_outside: &'modal ReadSignal, } #[component] @@ -28,6 +29,7 @@ pub fn Modal<'modal, G: Html>( html_class, button_label, modal_type, + close_on_click_outside, }: ModalProperties<'modal, G>, ) -> View { let children = children.call(context); @@ -41,6 +43,17 @@ pub fn Modal<'modal, G: Html>( close_dialog(&dialog_id.to_string()); }; + let on_modal_click_handler = move |event: Event| { + if !*close_on_click_outside.get() { + return; + } + if let Some(html_element) = event.target() { + if html_element.dyn_ref::().is_some() { + close_dialog(&dialog_id.to_string()) + } + } + }; + view! {context, button(on:click=open_click_handler, class=html_class) { (button_label) } dialog( @@ -49,11 +62,14 @@ pub fn Modal<'modal, G: Html>( ModalType::Default => "modal", ModalType::SidePanelRight => "modal-side-panel-right", ModalType::SidePanelLeft => "modal-side-panel-left", - }) + }), + on:click=on_modal_click_handler ) { - button(title="close",on:click=close_click_handler, class="modal-close", dangerously_set_inner_html=CLOSE_X_SVG_HTML) { } - div() { - (children) + div(class="modal-container") { + button(title="close",on:click=close_click_handler, class="modal-close", dangerously_set_inner_html=CLOSE_X_SVG_HTML) { } + div() { + (children) + } } } } diff --git a/loremaster-web-interface/src/components/navigation.rs b/loremaster-web-interface/src/components/navigation.rs index 637f206d..620b1dbb 100644 --- a/loremaster-web-interface/src/components/navigation.rs +++ b/loremaster-web-interface/src/components/navigation.rs @@ -1,6 +1,6 @@ use super::icon::{ - ARCHIVE_SVG_HTML, BOOK_SVG_HTML, CLOCK_SVG_HTML, FEATHER_SVG_HTML, HELP_CIRCLE_SVG_HTML, - LOGIN_SVG_HTML, USER_PLUS_SVG_HTML, USER_SVG_HTML, + SvgIcon, ARCHIVE_SVG_HTML, BOOK_SVG_HTML, CLOCK_SVG_HTML, FEATHER_SVG_HTML, + HELP_CIRCLE_SVG_HTML, LAYOUT_SVG_HTML, LOGIN_SVG_HTML, USER_PLUS_SVG_HTML, USER_SVG_HTML, }; pub mod side_nav_bar; @@ -8,78 +8,85 @@ pub mod tab; pub mod top_nav_bar; #[derive(Debug, Clone, PartialEq, Eq)] -pub struct NavigationLink { - pub html_id: String, - pub html_href: String, - pub display_text: String, - pub svg_html: &'static str, +pub struct NavigationLink<'a> { + pub html_id: &'a str, + pub html_href: &'a str, + pub display_text: &'a str, + pub svg_html: SvgIcon, } -pub fn get_home_link() -> NavigationLink { +pub fn get_home_link() -> NavigationLink<'static> { NavigationLink { - html_id: String::from("index-link"), - html_href: String::from("/"), - display_text: String::from("Loremaster"), + html_id: "index-link", + html_href: "/", + display_text: "Loremaster", svg_html: "", } } -pub fn get_navigation_links() -> Vec { +pub fn get_navigation_links() -> Vec> { vec![ NavigationLink { - html_id: String::from("you-link"), - html_href: String::from("/you/"), - display_text: String::from("You"), + html_id: "you-link", + html_href: "/you/", + display_text: "You", svg_html: USER_SVG_HTML, }, NavigationLink { - html_id: String::from("chronicle-link"), - html_href: String::from("/chronicle/"), - display_text: String::from("Chronicle"), + html_id: "chronicle-link", + html_href: "/chronicle/", + display_text: "Chronicle", svg_html: FEATHER_SVG_HTML, }, NavigationLink { - html_id: String::from("lore-link"), - html_href: String::from("/lore/"), - display_text: String::from("Lore"), + html_id: "lore-link", + html_href: "/lore/", + display_text: "Lore", svg_html: BOOK_SVG_HTML, }, NavigationLink { - html_id: String::from("timeline-link"), - html_href: String::from("/timeline/"), - display_text: String::from("Timeline"), + html_id: "timeline-link", + html_href: "/timeline/", + display_text: "Timeline", svg_html: CLOCK_SVG_HTML, }, NavigationLink { - html_id: String::from("ownership-link"), - html_href: String::from("/ownership/"), - display_text: String::from("Ownership"), + html_id: "ownership-link", + html_href: "/ownership/", + display_text: "Ownership", svg_html: ARCHIVE_SVG_HTML, }, NavigationLink { - html_id: String::from("registration-link"), - html_href: String::from("/registration/"), - display_text: String::from("Registration"), + html_id: "registration-link", + html_href: "/registration/", + display_text: "Registration", svg_html: USER_PLUS_SVG_HTML, }, NavigationLink { - html_id: String::from("login-link"), - html_href: String::from("/login/"), - display_text: String::from("Login"), + html_id: "login-link", + html_href: "/login/", + display_text: "Login", svg_html: LOGIN_SVG_HTML, }, NavigationLink { - html_id: String::from("about-link"), - html_href: String::from("/about/"), - display_text: String::from("About"), + html_id: "about-link", + html_href: "/about/", + display_text: "About", svg_html: HELP_CIRCLE_SVG_HTML, }, ] } -pub fn get_navigation_link_by_name(name: String) -> Option { +pub fn get_navigation_link_by_name(name: String) -> Option> { get_navigation_links() .iter() .find(|link| link.display_text == name) .map(|link| link.to_owned()) } + +pub const DESIGN_SYSTEM_LINK: NavigationLink = NavigationLink { + html_id: "design-system-link", + html_href: "/design-system/", + display_text: "Design System", + svg_html: LAYOUT_SVG_HTML, +}; diff --git a/loremaster-web-interface/src/components/navigation/side_nav_bar.rs b/loremaster-web-interface/src/components/navigation/side_nav_bar.rs index 53911bb4..44061cdb 100644 --- a/loremaster-web-interface/src/components/navigation/side_nav_bar.rs +++ b/loremaster-web-interface/src/components/navigation/side_nav_bar.rs @@ -1,36 +1,78 @@ +use perseus::reactor::Reactor; use sycamore::prelude::*; +use crate::{components::navigation::DESIGN_SYSTEM_LINK, global_state::ApplicationStateRx}; + use super::{get_navigation_links, NavigationLink}; +const NAV_CLASSES: &str = "side-nav"; +const NAV_UL_CLASSES: &str = "side-nav-container"; +const NAV_LI_CLASSES: &str = "side-nav-item"; +const A_CLASS: &str = "big-nav-button side-nav-link"; +const NAV_BUTTON_ICON_CLASS: &str = "big-nav-button-icon"; +const NAV_BUTTON_TEXT_CLASS: &str = "big-nav-button-text"; + #[component] pub fn SideNavBar(context: Scope) -> View { - let nav_classes: &str = "side-nav"; - let nav_ul_classes: &str = "side-nav-container"; - let nav_li_classes: &str = "side-nav-item"; - let a_class: &str = "big-nav-button side-nav-link"; - + let user_authentication = + Reactor::::from_cx(context).get_global_state::(context); let links: &Signal> = create_signal(context, get_navigation_links()); view! {context, - nav(class=nav_classes) { - ul(class=nav_ul_classes) { + nav(class=NAV_CLASSES) { + ul(class=NAV_UL_CLASSES) { Indexed( - iterable= links, - view= move |context, link| view! { context, - li(class=nav_li_classes) { + iterable=links, + view= move |context, link| + { + if link.html_href.eq("/you/") { + return view! { context, + li(class=NAV_LI_CLASSES) { + a( + class=A_CLASS, + id=link.html_id, + href=link.html_href + ) { + span(class=NAV_BUTTON_ICON_CLASS,dangerously_set_inner_html=link.svg_html) {} + span(class=NAV_BUTTON_TEXT_CLASS) {(user_authentication.authentication.user_alias.get())} + + } + } + }; + } + view! { context, + li(class=NAV_LI_CLASSES) { a( - class=a_class, + class=A_CLASS, id=link.html_id, href=link.html_href ) { - span(class="big-nav-button-icon",dangerously_set_inner_html=link.svg_html) {} - span(class="big-nav-button-text") {(link.display_text)} + span(class=NAV_BUTTON_ICON_CLASS,dangerously_set_inner_html=link.svg_html) {} + span(class=NAV_BUTTON_TEXT_CLASS) {(link.display_text)} } } - } + }} ) + DesignSystemLink() } } } } + +#[component] +pub fn DesignSystemLink(context: Scope) -> View { + view! { context, + li(class=NAV_LI_CLASSES) { + a( + class=A_CLASS, + id=DESIGN_SYSTEM_LINK.html_id, + href=DESIGN_SYSTEM_LINK.html_href + ) { + span(class=NAV_BUTTON_ICON_CLASS,dangerously_set_inner_html=DESIGN_SYSTEM_LINK.svg_html) {} + span(class=NAV_BUTTON_TEXT_CLASS) {(DESIGN_SYSTEM_LINK.display_text)} + + } + } + } +} diff --git a/loremaster-web-interface/src/components/navigation/tab/tab_section.rs b/loremaster-web-interface/src/components/navigation/tab/tab_section.rs index 1503b7b3..fb9f1dd0 100644 --- a/loremaster-web-interface/src/components/navigation/tab/tab_section.rs +++ b/loremaster-web-interface/src/components/navigation/tab/tab_section.rs @@ -1,5 +1,4 @@ use sycamore::prelude::*; -use web_sys::Event; use crate::components::navigation::tab::tab_panel::TabIndex; @@ -22,16 +21,12 @@ pub fn TabSection<'tab, G: Html>( }: TabSectionProperties<'tab, G>, ) -> View { let active_tab: &Signal = use_context::>(context); - let clicked = move |event: Event| { - event.prevent_default(); - create_effect(context, move || active_tab.set(index)); - }; let children: View = children.call(context); view! {context, (match active_tab.get().as_ref() == &index { true => { - view! {context, div(class=(format!("tab-section {}", classes.get())), id="", on:click=clicked) { + view! {context, div(class=(format!("tab-section {}", classes.get())), id="") { (children) }} }, diff --git a/loremaster-web-interface/src/components/widget/calendar/week.rs b/loremaster-web-interface/src/components/widget/calendar/week.rs index d36d9a03..5e9f77b1 100644 --- a/loremaster-web-interface/src/components/widget/calendar/week.rs +++ b/loremaster-web-interface/src/components/widget/calendar/week.rs @@ -33,7 +33,7 @@ pub fn Week<'a, 'b: 'a, G: Html>( view= |context, day: WeekDayInformation| { let mut day_div_classes = String::from("card"); - if &day.number == &selected_date.get().day() { day_div_classes.push_str(" active-card text-light") ;} + if day.number == selected_date.get().day() { day_div_classes.push_str(" active-card text-light") ;} else { day_div_classes.push_str(" bg-white")} view!{ context, div(class=(day_div_classes)) { diff --git a/loremaster-web-interface/src/components/widget/goal_list.rs b/loremaster-web-interface/src/components/widget/goal_list.rs index 8fd4c0b7..5aae63af 100644 --- a/loremaster-web-interface/src/components/widget/goal_list.rs +++ b/loremaster-web-interface/src/components/widget/goal_list.rs @@ -27,7 +27,7 @@ pub fn GoalList<'a, 'b: 'a, G: Html>( }); } view! { context, - (if *create_selector(context, || !goals.get().is_empty()).get() { + (if !goals.get().is_empty() { view! { context, ul(class=" goal_list", id="") { Keyed( diff --git a/loremaster-web-interface/src/components/widget/sleep.rs b/loremaster-web-interface/src/components/widget/sleep.rs index 28c598ca..7b1a64f9 100644 --- a/loremaster-web-interface/src/components/widget/sleep.rs +++ b/loremaster-web-interface/src/components/widget/sleep.rs @@ -109,7 +109,7 @@ pub fn SleepWidget(context: Scope) -> View { div(class="sleep-widget") { "No sleep schedule found." } }, ComponentState::Loading => view! { context, - div(class="sleep-widget") { "Loading..." } + div(class="sleep-widget skeleton skeleton-body") { "" } }, }) } diff --git a/loremaster-web-interface/src/global_state.rs b/loremaster-web-interface/src/global_state.rs index 70e8aa11..a20a5f6a 100644 --- a/loremaster-web-interface/src/global_state.rs +++ b/loremaster-web-interface/src/global_state.rs @@ -2,6 +2,8 @@ use perseus::{prelude::*, state::GlobalStateCreator}; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use crate::utility::http_service; + pub const LOCAL_STORAGE_KEY: &str = "chronilore_loremaster"; pub const DEFAULT_USER_ALIAS: &str = "You"; @@ -55,13 +57,18 @@ impl UserAuthenticationRx { } } - pub fn detect_state(&self) { - if let AuthenticationState::Authenticated | AuthenticationState::None = - *self.authentication_state.get() - { + pub async fn detect_state(&self) { + if let AuthenticationState::Anonymous = *self.authentication_state.get() { return; } + if let AuthenticationState::None = *self.authentication_state.get() { + //make a request for now to determine if the cookie is alive + let _ = http_service::get_endpoint("/authentication/check-session", None).await; + self.authentication_state + .set(AuthenticationState::Authenticated); + } + let storage: web_sys::Storage = web_sys::window().unwrap().local_storage().unwrap().unwrap(); diff --git a/loremaster-web-interface/src/templates/chronicle.rs b/loremaster-web-interface/src/templates/chronicle.rs index 2d56e463..6a6349ac 100644 --- a/loremaster-web-interface/src/templates/chronicle.rs +++ b/loremaster-web-interface/src/templates/chronicle.rs @@ -105,7 +105,7 @@ pub fn chronicle_page<'page, G: Html>( .set(format!("Hello, {}", state.user_alias.get())), } - IntervalStream::new(1_000) + IntervalStream::new(60_000) .for_each(|_| { let javascript_date: Date = Date::new_0(); @@ -129,12 +129,12 @@ pub fn chronicle_page<'page, G: Html>( view! {context, Container(title="Chronicle") { div(class="", id="chronicle-container") { - div(class="row flex-grow-1 text-black"){ - div(class="col-9 bg-light p-5 border-0 rounded") { - div(class="d-flex align-items-baseline") { - h2(class="fw-normal flex-grow-1") { "Chronicle - "(state.date_display.get()) } + div(class="text-black"){ + div(class="") { + div(class="") { + h2(class="") { "Chronicle - "(state.date_display.get()) } } - h3(class="fw-normal") { (state.greeting.get()) } + h3(class="") { (state.greeting.get()) } div() { Week(WeekProperties{ days: create_signal(context, vec![]), @@ -147,10 +147,10 @@ pub fn chronicle_page<'page, G: Html>( SleepWidget() div(class="chronicle-notes") { label() { "Notes" } - textarea(class="border rounded bg-white", rows="4", cols="50") {} + textarea(class="", rows="4", cols="50") {} } } - div(class="col-3 border-start") { + div(class="") { div(class="card shadow-sm border-0 rounded mt-2") { div(class="card-body") { h3(class="card-title") { "Goals" } diff --git a/loremaster-web-interface/src/templates/design_system.rs b/loremaster-web-interface/src/templates/design_system.rs index cc45858d..9f906aac 100644 --- a/loremaster-web-interface/src/templates/design_system.rs +++ b/loremaster-web-interface/src/templates/design_system.rs @@ -14,7 +14,7 @@ use crate::{ accordion::{Accordion, AccordionItem}, combobox::{ComboBox, ComboBoxOption}, container::Container, - icon::{PASSWORD_SVH_HTML, YUBIKEY_SVG_HTML}, + icon::{PASSWORD_SVG_HTML, YUBIKEY_SVG_HTML}, modal::{Modal, ModalType}, navigation::tab::tab_panel::{TabIndex, TabPanel}, navigation::tab::{tab_button::TabButton, tab_section::TabSection}, @@ -31,8 +31,6 @@ const SYCAMORE_GITHUB_URL: &str = "https://github.com/sycamore-rs/sycamore"; const PERSEUS_GITHUB_URL: &str = "https://github.com/framesurge/perseus"; pub fn design_system_page<'page, G: Html>(context: BoundedScope<'_, 'page>) -> View { - let empty_class: &Signal = create_signal(context, String::from("")); - let first_item: &Signal = create_signal(context, String::from("First")); let second_item: &Signal = create_signal(context, String::from("Second")); @@ -43,6 +41,7 @@ pub fn design_system_page<'page, G: Html>(context: BoundedScope<'_, 'page>) -> V let switch_on: &Signal = create_signal(context, false); let switch_classes: &Signal = create_signal(context, String::from("switch")); + let empty_class: &Signal = create_signal(context, String::new()); let demo_container_classes: &Signal = create_signal(context, String::from("card")); @@ -73,6 +72,7 @@ pub fn design_system_page<'page, G: Html>(context: BoundedScope<'_, 'page>) -> V ]; let modal_type: &Signal = create_signal(context, ModalType::Default); + let click_to_close: &Signal = create_signal(context, false); view! {context, Container(title="Design System") { @@ -112,7 +112,7 @@ pub fn design_system_page<'page, G: Html>(context: BoundedScope<'_, 'page>) -> V div(class=demo_container_classes) { TabPanel(active_tab=active_tab, classes=tab_panel_classes) { div(class="tab-button-group") { - TabButton(title=String::from("First"), index=0_u32, classes=tab_button_classes, icon=Some(PASSWORD_SVH_HTML)) + TabButton(title=String::from("First"), index=0_u32, classes=tab_button_classes, icon=Some(PASSWORD_SVG_HTML)) TabButton(title=String::from("Second"), index=1_u32, classes=tab_button_classes, icon=Some(YUBIKEY_SVG_HTML)) TabButton(title=String::from("Third"), index=2_u32, classes=tab_button_classes, icon=None) } @@ -168,57 +168,22 @@ pub fn design_system_page<'page, G: Html>(context: BoundedScope<'_, 'page>) -> V } div() { label() { "Current Modal Type"} - div() { (match modal_type.get().as_ref() { + input(disabled=true, type="text", value=(match modal_type.get().as_ref() { ModalType::Default => "Default".to_string(), ModalType::SidePanelRight => "Side Panel - Right".to_string(), ModalType::SidePanelLeft => "Side Panel - Left".to_string(), - }) } - } - Modal(html_class=empty_class, button_label="Open modal", modal_type=modal_type) { - div() { "Test" } + })) { } + div() { + Switch(toggled=click_to_close, classes=empty_class) + label() { "Click to Close"} + } } - } - } - div() { - h3() { "Information" } - div(class=demo_container_classes) { - - } - } - div() { - h3() { "Information" } - div(class=demo_container_classes) { - - } - } - div() { - h3() { "Information" } - div(class=demo_container_classes) { - - } - } - div() { - h3() { "Information" } - div(class=demo_container_classes) { - - } - } - div() { - h3() { "Information" } - div(class=demo_container_classes) { - - } - } - div() { - h3() { "Information" } - div(class=demo_container_classes) { - - } - } - div() { - h3() { "Information" } - div(class=demo_container_classes) { - + Modal( + html_class=empty_class, + button_label="Open modal", + modal_type=modal_type, + close_on_click_outside=click_to_close + ) { div() { "Test" } } } } div() { diff --git a/loremaster-web-interface/src/templates/login.rs b/loremaster-web-interface/src/templates/login.rs index 7dde089e..2bfcbc18 100644 --- a/loremaster-web-interface/src/templates/login.rs +++ b/loremaster-web-interface/src/templates/login.rs @@ -13,7 +13,7 @@ use web_sys::Event; use crate::components::container::Container; use crate::components::form::input_validation::{InputValidation, InputValidationProperties}; -use crate::components::icon::{PASSWORD_SVH_HTML, YUBIKEY_SVG_HTML}; +use crate::components::icon::{PASSWORD_SVG_HTML, YUBIKEY_SVG_HTML}; use crate::components::navigation::tab::tab_button::TabButton; use crate::components::navigation::tab::tab_panel::{TabIndex, TabPanel}; use crate::components::navigation::tab::tab_section::TabSection; @@ -178,7 +178,7 @@ pub fn login_page<'page, G: Html>( h3(class="") { "Login" } TabPanel(active_tab=active_tab, classes=tab_panel_classes) { div(class="tab-button-group") { - TabButton(title=String::from("Password/OPAQUE"), index=0_u32, classes=tab_button_classes, icon=Some(PASSWORD_SVH_HTML)) + TabButton(title=String::from("Password/OPAQUE"), index=0_u32, classes=tab_button_classes, icon=Some(PASSWORD_SVG_HTML)) TabButton(title=String::from("WebAuthn"), index=1_u32, classes=tab_button_classes, icon=Some(YUBIKEY_SVG_HTML)) } TabSection(title=String::from("tab1"), index=0_u32, classes=tab_section_classes){ diff --git a/loremaster-web-interface/src/templates/registration.rs b/loremaster-web-interface/src/templates/registration.rs index b54b58cc..fefa9065 100644 --- a/loremaster-web-interface/src/templates/registration.rs +++ b/loremaster-web-interface/src/templates/registration.rs @@ -11,7 +11,7 @@ use web_sys::Event; use crate::components::container::Container; use crate::components::form::input_validation::InputValidation; -use crate::components::icon::{PASSWORD_SVH_HTML, YUBIKEY_SVG_HTML}; +use crate::components::icon::{PASSWORD_SVG_HTML, YUBIKEY_SVG_HTML}; use crate::components::navigation::tab::tab_button::TabButton; use crate::components::navigation::tab::tab_panel::{TabIndex, TabPanel}; use crate::components::navigation::tab::tab_section::TabSection; @@ -117,7 +117,7 @@ pub fn registration_page<'page, G: Html>( h3(class="") {"Registration"} TabPanel(active_tab=active_tab, classes=tab_panel_classes) { div(class="tab-button-group") { - TabButton(title=String::from("Password/OPAQUE"), index=0_u32, classes=tab_button_classes, icon=Some(PASSWORD_SVH_HTML)) + TabButton(title=String::from("Password/OPAQUE"), index=0_u32, classes=tab_button_classes, icon=Some(PASSWORD_SVG_HTML)) TabButton(title=String::from("WebAuthn"), index=1_u32, classes=tab_button_classes, icon=Some(YUBIKEY_SVG_HTML)) } TabSection(title=String::from("tab1"), index=0_u32, classes=tab_section_classes){ @@ -129,15 +129,15 @@ pub fn registration_page<'page, G: Html>( input( type="email", class="form-control", - bind:value= email_address, - placeholder = "Enter your email address", + bind:value=email_address, + placeholder="Enter your email address", disabled=*loading.get() ) {} InputValidation( - content= email_address_validation_content, - visibility= email_address_validation_visibility, - validity= email_address_validity, - message_type= email_address_message_type) + content=email_address_validation_content, + visibility=email_address_validation_visibility, + validity=email_address_validity, + message_type=email_address_message_type) } div(class="input-row") { label( diff --git a/loremaster-web-interface/static/styles/loremaster/index.css b/loremaster-web-interface/static/styles/loremaster/index.css index eb4bba78..64d83ca1 100644 --- a/loremaster-web-interface/static/styles/loremaster/index.css +++ b/loremaster-web-interface/static/styles/loremaster/index.css @@ -80,6 +80,7 @@ html { background: var(--background); color: var(--default-text-color); height: 100%; + font-weight: normal; } #root, @@ -183,6 +184,10 @@ input:active { border: solid 1px var(--border-color); } +label { + font-weight:bold; +} + select { color: var(--default-text-color); background: var(--on-canvas); @@ -428,6 +433,11 @@ select { margin: .25vmin; } +.big-nav-button-icon { + width: 24px; + height: 24px; +} + .popover-button+.popover:active, .popover:hover, .popover-button:focus+.popover { @@ -931,6 +941,8 @@ form .input-row label {} min-width: 20vw; min-height: 20vh; + + padding: 0; } .modal::backdrop { @@ -946,6 +958,14 @@ form .input-row label {} width: 24px; } +.modal-container { + display: flex; + flex-direction: column; + padding: 1rem 1.5rem; + + flex-grow: 1; +} + .modal-side-panel-right:modal { margin-right: 0; @@ -1018,6 +1038,80 @@ form .input-row label {} background: rgba(36, 36, 36, 0.60); } +.skeleton { + animation: skeleton-loading 1.5s linear infinite alternate; +} + +.skeleton-text { + width: 100%; + height: 0.7rem; + margin-bottom: 0.5rem; + border-radius: 0.25rem; +} + +.skeleton-text__body { + width: 75%; +} + +.skeleton-block { + width: 400px; +} + +.skeleton-footer { + width: 30%; +} + +.combobox { + display: flex; + flex-direction: column; + width: 10rem; +} + +.combobox-options { + list-style: none; + margin: 0; + padding: 0; +} + +.combobox-options li { + display: flex; + flex-direction: column; +} + +.combobox-options li button { + margin: 0; + border-radius: 0; +} + +.month-widget { + display: flex; +} + +.month-widget .today { + width: 10rem; +} + +.month-widget .calendar { + display: flex; + flex-direction: column; +} + +.month-widget .calendar .weekdays-header { + display: flex; + flex-direction: row; + gap: 1rem; +} + +@keyframes skeleton-loading { + 0% { + background-color: var(--on-canvas); + } + + 100% { + background-color: var(--background-hover); + } +} + @keyframes placeholder-glow { 0% { diff --git a/loremaster-web-server/.idea/.gitignore b/loremaster-web-server/.idea/.gitignore new file mode 100644 index 00000000..1c2fda56 --- /dev/null +++ b/loremaster-web-server/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/loremaster-web-server/.idea/loremaster-web-server.iml b/loremaster-web-server/.idea/loremaster-web-server.iml new file mode 100644 index 00000000..ca6a26db --- /dev/null +++ b/loremaster-web-server/.idea/loremaster-web-server.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/loremaster-web-server/.idea/modules.xml b/loremaster-web-server/.idea/modules.xml new file mode 100644 index 00000000..77fb0a91 --- /dev/null +++ b/loremaster-web-server/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/loremaster-web-server/.idea/vcs.xml b/loremaster-web-server/.idea/vcs.xml new file mode 100644 index 00000000..2e3f6920 --- /dev/null +++ b/loremaster-web-server/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/loremaster-web-server/README.md b/loremaster-web-server/README.md index 8ae5b985..4de2b2fb 100644 --- a/loremaster-web-server/README.md +++ b/loremaster-web-server/README.md @@ -29,7 +29,7 @@ The application is primarily written in Rust. Docker compose command ```sh -docker-compose -f infrastructure/docker-compose.yml up +docker-compose -f ../database/docker-compose.yml up ``` You can generate a site secret for testing with the following openssl command: diff --git a/loremaster-web-server/src/api/handler/authentication.rs b/loremaster-web-server/src/api/handler/authentication.rs index 1529e011..ea790f7f 100644 --- a/loremaster-web-server/src/api/handler/authentication.rs +++ b/loremaster-web-server/src/api/handler/authentication.rs @@ -7,8 +7,9 @@ use sqlx::{Pool, Postgres}; use uuid::Uuid; use webauthn_rs::{ prelude::{ - CreationChallengeResponse, CredentialID, Passkey, PasskeyRegistration, PublicKeyCredential, - RegisterPublicKeyCredential, PasskeyAuthentication, RequestChallengeResponse, + CreationChallengeResponse, CredentialID, Passkey, PasskeyAuthentication, + PasskeyRegistration, PublicKeyCredential, RegisterPublicKeyCredential, + RequestChallengeResponse, }, Webauthn, }; @@ -18,23 +19,32 @@ use crate::{ entity::{ self, person::{Credentials, Person}, - web_authentication_challenge::{WebAuthenticationChallenge, WebAuthenticationLogin}, web_authentication_key::WebAuthenticationKey, + web_authentication_challenge::{ + WebAuthenticationChallenge, WebAuthenticationChallengeId, WebAuthenticationLogin, + }, + web_authentication_key::{WebAuthenticationKey, WebAuthenticationKeyId}, }, query::{ authentication::{ + self, add_web_authentication_login::add_web_authentication_login_query, add_web_authentication_register::add_web_authentication_register_query, get_web_authentication_by_user_name::get_web_authentication_by_user_name_query, + get_web_authentication_login::get_web_authentication_login_query, get_web_authentication_register::get_optional_web_authentication_id_by_user_name_query, - remove_stale_web_authentication_challenges_by_user_name::remove_stale_web_authentication_registers_by_user_name_query, self, get_webauthn_passkeys_by_email_address::get_webauthn_passkeys_by_email_address_query, add_web_authentication_login::add_web_authentication_login_query, remove_stale_web_authentication_login_by_user_name::remove_stale_web_authentication_login_by_user_name_query, get_web_authentication_login::get_web_authentication_login_query, + get_webauthn_passkeys_by_email_address::get_webauthn_passkeys_by_email_address_query, + remove_stale_web_authentication_challenges_by_user_name::remove_stale_web_authentication_registers_by_user_name_query, + remove_stale_web_authentication_login_by_user_name::remove_stale_web_authentication_login_by_user_name_query, }, email_address::{ create_email_address::create_email_address_query, email_address_in_use::email_address_in_use_query, }, + password, person::{ - create_person::create_person_query, - credential_by_email_address::credential_by_email_address_query, self, person_by_email_address::person_by_email_address_query, - }, password + self, create_person::create_person_query, + credential_by_email_address::credential_by_email_address_query, + person_by_email_address::person_by_email_address_query, + }, }, }, utility::password_encryption::{PasswordEncryption, PasswordEncryptionService}, @@ -86,8 +96,7 @@ pub async fn register_handler( info!("Checking for existing users with provided email address."); let existing_credentials: Option = - credential_by_email_address_query(database_pool, &valid_email_address) - .await?; + credential_by_email_address_query(database_pool, &valid_email_address).await?; if existing_credentials.is_some() { info!("Existing user found!"); @@ -100,22 +109,17 @@ pub async fn register_handler( create_email_address_query(database_pool, &valid_email_address).await?; info!("Adding new user to database."); - let person: Person = create_person_query( - database_pool, - &new_email_address.id, - None, - None, - ) - .await?; + let person: Person = + create_person_query(database_pool, &new_email_address.id.0, None, None).await?; - let encrypted_password: String = encryption_service - .encrypt_password(clean_password)?; + let encrypted_password: String = encryption_service.encrypt_password(clean_password)?; - let new_password_id: Uuid = password::add_password::add_password_query(database_pool, &encrypted_password).await?; + let new_password_id: Uuid = + password::add_password::add_password_query(database_pool, &encrypted_password).await?; //TODO:If anything fails up to here we need to handle the person/password not in use // possibly rewrite query to be one transaction instead of individual queries - person::add_password::add_password_query(database_pool,&person.id, &new_password_id).await?; + person::add_password::add_password_query(database_pool, &person.id, &new_password_id).await?; Ok(RegistrationResult::Valid) } @@ -141,8 +145,8 @@ pub async fn web_authentication_api_register_start_handler( EmailAddress::from_str(clean_email).map_err(|error| anyhow!("{}", error))?; info!("Checking if the provided email address is already in use."); - let email_in_use: bool = email_address_in_use_query(database_pool, &valid_email_address) - .await?; + let email_in_use: bool = + email_address_in_use_query(database_pool, &valid_email_address).await?; if email_in_use { info!("Existing user found!"); @@ -151,12 +155,13 @@ pub async fn web_authentication_api_register_start_handler( } info!("Email can be registered."); - let user_id: Uuid = get_optional_web_authentication_id_by_user_name_query( - database_pool, - valid_email_address.as_str(), - ) - .await? - .unwrap_or_else(Uuid::new_v4); + let user_id: WebAuthenticationChallengeId = + get_optional_web_authentication_id_by_user_name_query( + database_pool, + valid_email_address.as_str(), + ) + .await? + .unwrap_or_else(|| WebAuthenticationChallengeId(Uuid::new_v4())); remove_stale_web_authentication_registers_by_user_name_query( database_pool, @@ -170,7 +175,7 @@ pub async fn web_authentication_api_register_start_handler( let (challenge, passkey_registration): (CreationChallengeResponse, PasskeyRegistration) = web_authentication_service .start_passkey_registration( - user_id, + user_id.0, valid_email_address.as_str(), user_alias, excluded_credentials, @@ -209,8 +214,11 @@ pub async fn web_authentication_api_register_finish_handler( let valid_email_address: EmailAddress = EmailAddress::from_str(clean_email).map_err(|error| anyhow!("{}", error))?; - let Some(challenge) = get_web_authentication_by_user_name_query(database_pool, email_address).await? - else { return Ok(RegistrationResult::InvalidEmailAddress); }; + let Some(challenge) = + get_web_authentication_by_user_name_query(database_pool, email_address).await? + else { + return Ok(RegistrationResult::InvalidEmailAddress); + }; let state: PasskeyRegistration = serde_json::from_value(challenge.passkey)?; @@ -218,29 +226,32 @@ pub async fn web_authentication_api_register_finish_handler( .finish_passkey_registration(user_credential, &state) .expect("Invalid input during webauthn passkey registration finish"); - let new_email_address = create_email_address_query(database_pool, &valid_email_address).await?; let key: WebAuthenticationKey = WebAuthenticationKey { - id: Uuid::new_v4(), + id: WebAuthenticationKeyId(Uuid::new_v4()), credential_id: passkey.cred_id().0.clone(), cose_algorithm: *passkey.cred_algorithm() as i32, - passkey: serde_json::to_value(passkey)? + passkey: serde_json::to_value(passkey)?, }; - authentication::add_web_authentication_key::add_web_authentication_key_query(database_pool, &key).await?; - - info!("Adding new user to database."); - let person: Person = create_person_query( + authentication::add_web_authentication_key::add_web_authentication_key_query( database_pool, - &new_email_address.id, - None, - None, + &key, ) .await?; + info!("Adding new user to database."); + let person: Person = + create_person_query(database_pool, &new_email_address.id.0, None, None).await?; + //TODO: create relation between person and key - person::add_web_authentication_key::add_web_authentication_key_query(database_pool, &person.id,&key.id).await?; + person::add_web_authentication_key::add_web_authentication_key_query( + database_pool, + &person.id, + &key.id, + ) + .await?; Ok(RegistrationResult::Valid) } @@ -260,21 +271,27 @@ pub async fn web_authentication_api_login_start_handler( let valid_email_address: EmailAddress = EmailAddress::from_str(clean_email).map_err(|error| anyhow!("{}", error))?; - let Some(person): Option = person_by_email_address_query(database_pool, &valid_email_address).await? + let Some(_): Option = + person_by_email_address_query(database_pool, &valid_email_address).await? else { info!("Existing user not found!"); //TODO: Send an email to the specified address and indicate someone tried to re-register using that email return Ok((AuthenticationResult::InvalidEmailAddress, None)); }; - let passkeys: Vec = get_webauthn_passkeys_by_email_address_query(database_pool, &valid_email_address).await?; - remove_stale_web_authentication_login_by_user_name_query(database_pool, valid_email_address.as_str()).await?; - let (challenge, passkey_authentication): (RequestChallengeResponse, PasskeyAuthentication) = + let passkeys: Vec = + get_webauthn_passkeys_by_email_address_query(database_pool, &valid_email_address).await?; + remove_stale_web_authentication_login_by_user_name_query( + database_pool, + valid_email_address.as_str(), + ) + .await?; + let (challenge, passkey_authentication): (RequestChallengeResponse, PasskeyAuthentication) = web_authentication_service.start_passkey_authentication(&passkeys)?; add_web_authentication_login_query( database_pool, &WebAuthenticationChallenge { - id: person.id, + id: WebAuthenticationChallengeId(Uuid::new_v4()), user_name: valid_email_address.as_str().to_string(), passkey: serde_json::to_value(passkey_authentication)?, }, @@ -300,21 +317,22 @@ pub async fn web_authentication_api_login_finish_handler( let valid_email_address: EmailAddress = EmailAddress::from_str(clean_email).map_err(|error| anyhow!("{}", error))?; - let Some(person): Option = person_by_email_address_query(database_pool, &valid_email_address).await? + let Some(person): Option = + person_by_email_address_query(database_pool, &valid_email_address).await? else { info!("Existing user not found!"); //TODO: Send an email to the specified address and indicate someone tried to re-register using that email return Ok((AuthenticationResult::InvalidEmailAddress, None)); }; - let Some(challenge): Option = + let Some(challenge): Option = get_web_authentication_login_query(database_pool, valid_email_address.as_str()).await? else { //TODO: fix response - return Ok((AuthenticationResult::InvalidEmailAddress, None)); + return Ok((AuthenticationResult::InvalidEmailAddress, None)); }; let state: PasskeyAuthentication = serde_json::from_value(challenge.passkey)?; web_authentication_service.finish_passkey_authentication(public_key_credential, &state)?; - Ok((AuthenticationResult::Valid, Some(person.id))) + Ok((AuthenticationResult::Valid, Some(person.id.0))) } diff --git a/loremaster-web-server/src/api/handler/person.rs b/loremaster-web-server/src/api/handler/person.rs index 9cd65a64..5c104b00 100644 --- a/loremaster-web-server/src/api/handler/person.rs +++ b/loremaster-web-server/src/api/handler/person.rs @@ -8,7 +8,11 @@ use uuid::Uuid; use crate::{ data::{ entity::{ - self, action::Action, goal::Goal, person::PersonMeta, sleep_schedule::SleepSchedule, + self, + action::Action, + goal::{Goal, GoalId}, + person::{PersonId, PersonMeta}, + sleep_schedule::SleepSchedule, }, query::{ action::{ @@ -60,14 +64,14 @@ pub enum EmailAddressUpdateResult { pub async fn get_person_meta_data( database_pool: &Pool, - person_id: &Uuid, + person_id: &PersonId, ) -> Result> { meta_by_id_query(database_pool, person_id).await } pub async fn get_sleep_schedule_handler( database_pool: &Pool, - person_id: &Uuid, + person_id: &PersonId, ) -> Result> { let potential_sleep_schedule: Option = get_person_sleep_schedule_query(database_pool, person_id).await?; @@ -81,14 +85,14 @@ pub async fn get_action_list_handler(database_pool: &Pool) -> Result, - person_id: Option<&Uuid>, + person_id: Option<&PersonId>, ) -> Result> { get_goal_list_query(database_pool, person_id).await } pub async fn create_action( database_pool: &Pool, - person_id: &Uuid, + person_id: &PersonId, action: &String, ) -> Result { let sanitized_action: String = action.trim().to_ascii_lowercase(); @@ -122,7 +126,7 @@ pub async fn create_action( pub async fn create_goal( database_pool: &Pool, - person_id: &Uuid, + person_id: &PersonId, goal: &String, ) -> Result { let sanitized_goal: String = goal.trim().to_ascii_lowercase(); @@ -174,7 +178,7 @@ pub async fn update_person_meta_handler( pub async fn update_email_handler( database_pool: &Pool, - person_id: &Uuid, + person_id: &PersonId, email_address: &str, ) -> Result { let sanitized_email_address: String = email_address.trim().to_ascii_lowercase(); @@ -204,7 +208,7 @@ pub async fn update_email_handler( pub async fn update_sleep_schedule_handler( database_pool: &Pool, - person_id: &Uuid, + person_id: &PersonId, start_time: &str, end_time: &str, ) -> Result { @@ -231,8 +235,8 @@ pub async fn update_sleep_schedule_handler( pub async fn remove_one_goal_handler( database_pool: &Pool, - person_id: &Uuid, - goal_id: &Uuid, + person_id: &PersonId, + goal_id: &GoalId, ) -> Result { let relation_exists: bool = goal_is_related_query(database_pool, person_id, goal_id).await?; match relation_exists { diff --git a/loremaster-web-server/src/api/router/authentication.rs b/loremaster-web-server/src/api/router/authentication.rs index 4e437668..94713799 100644 --- a/loremaster-web-server/src/api/router/authentication.rs +++ b/loremaster-web-server/src/api/router/authentication.rs @@ -5,7 +5,7 @@ use axum::{ extract::{ConnectInfo, State}, http::StatusCode, response::{IntoResponse, Response}, - routing::post, + routing::{get, post}, Json, Router, }; use axum_extra::extract::{ @@ -22,6 +22,7 @@ use webauthn_rs::{ use crate::{ api::{ + guards::user::User, handler::authentication::{ register_handler, web_authentication_api_login_finish_handler, web_authentication_api_login_start_handler, @@ -126,7 +127,7 @@ async fn authenticate( } let updated_cookie_jar: PrivateCookieJar = cookie_jar.add( - Cookie::build(cookie_fields::USER_ID, person.id.to_string()) + Cookie::build(cookie_fields::USER_ID, person.id.0.to_string()) .same_site(SameSite::Strict) .path("/") .http_only(true) @@ -146,6 +147,10 @@ async fn logout(cookie_jar: PrivateCookieJar) -> Result { Ok((updated_cookie_jar, "Successfully logged out.").into_response()) } +async fn check_session(_: User) -> Result { + Ok((StatusCode::OK, "Session is active.").into_response()) +} + #[derive(Deserialize, Debug)] struct WebAuthenticationRegistrationForm { email_address: String, @@ -299,6 +304,7 @@ pub fn router() -> Router { .route("/authentication/authenticate", post(authenticate)) .route("/authentication/logout", post(logout)) .route("/authentication/register", post(register)) + .route("/authentication/check-session", get(check_session)) .route( "/authentication/webauthn/finish", post(web_authentication_api_register_finish), diff --git a/loremaster-web-server/src/api/router/chronicle.rs b/loremaster-web-server/src/api/router/chronicle.rs index d950a8d2..8895afb7 100644 --- a/loremaster-web-server/src/api/router/chronicle.rs +++ b/loremaster-web-server/src/api/router/chronicle.rs @@ -13,7 +13,8 @@ use crate::{ api::{guards::user::User, handler, response::ApiError, web_server::ApplicationState}, data::{ entity::{ - chronicle::{current_sever_time_string, Chronicle}, + chronicle::{current_sever_time_string, Chronicle, ChronicleId}, + person::PersonId, transfer::person_chronicle::PersonChronicle, }, postgres_handler::PostgresHandler, @@ -56,8 +57,8 @@ pub async fn by_date( .await .map_err(|error| anyhow!("{}", error))?; - let Some(result) = query_result else { - return Ok(Json(None)) + let Some(result) = query_result else { + return Ok(Json(None)); }; Ok(Json(Some(result))) @@ -85,9 +86,9 @@ pub async fn server_time() -> Result { pub async fn example() -> Result, ApiError> { let result: Chronicle = Chronicle { - id: Uuid::nil(), + id: ChronicleId(Uuid::nil()), date_recorded: OffsetDateTime::now_utc().date(), - person_id: Uuid::nil(), + person_id: PersonId(Uuid::nil()), notes: Some("Here are some notes".to_string()), creation_time: Some(OffsetDateTime::now_utc()), }; diff --git a/loremaster-web-server/src/api/router/person.rs b/loremaster-web-server/src/api/router/person.rs index a50d87f3..4619f7f1 100644 --- a/loremaster-web-server/src/api/router/person.rs +++ b/loremaster-web-server/src/api/router/person.rs @@ -8,7 +8,6 @@ use axum::{ use axum_extra::extract::Form; use log::info; use serde::{Deserialize, Serialize}; -use uuid::Uuid; use crate::{ api::{ @@ -23,7 +22,12 @@ use crate::{ web_server::ApplicationState, }, data::{ - entity::{action::Action, person::PersonMeta, sleep_schedule::SleepSchedule}, + entity::{ + action::Action, + goal::GoalId, + person::{PersonId, PersonMeta}, + sleep_schedule::SleepSchedule, + }, postgres_handler::PostgresHandler, }, }; @@ -33,7 +37,7 @@ pub async fn meta( user: User, ) -> Result { let result: Option = - get_person_meta_data(&postgres_service.database_pool, &user.0).await?; + get_person_meta_data(&postgres_service.database_pool, &PersonId(user.0)).await?; match result { Some(person) => Ok((StatusCode::OK, Json(person)).into_response()), None => Ok(( @@ -86,7 +90,7 @@ pub async fn update_email_address( info!("API Call: update_email_address"); match update_email_handler( &postgres_service.database_pool, - &user.0, + &PersonId(user.0), &form.email_address, ) .await? @@ -113,8 +117,12 @@ pub async fn new_action( user: User, Form(form): Form, ) -> Result { - let result: UniqueEntryResult = - create_action(&postgres_service.database_pool, &user.0, &form.action).await?; + let result: UniqueEntryResult = create_action( + &postgres_service.database_pool, + &PersonId(user.0), + &form.action, + ) + .await?; match result { UniqueEntryResult::Created => { Ok((StatusCode::CREATED, "New action successfully created!").into_response()) @@ -149,8 +157,12 @@ pub async fn new_goal( user: User, Form(form): Form, ) -> Result { - let result: UniqueEntryResult = - create_goal(&postgres_service.database_pool, &user.0, &form.goal).await?; + let result: UniqueEntryResult = create_goal( + &postgres_service.database_pool, + &PersonId(user.0), + &form.goal, + ) + .await?; match result { UniqueEntryResult::Created => { Ok((StatusCode::CREATED, "New Goal successfully created!").into_response()) @@ -169,7 +181,7 @@ pub async fn new_goal( #[derive(Deserialize, Debug)] pub struct GoalQueryParameters { - goal_id: Uuid, + goal_id: GoalId, } pub async fn remove_goal( @@ -180,7 +192,7 @@ pub async fn remove_goal( info!("API Call: remove_goal"); let result: bool = remove_one_goal_handler( &postgres_service.database_pool, - &user.0, + &PersonId(user.0), ¶meters.goal_id, ) .await?; @@ -200,7 +212,9 @@ pub async fn get_goal_list( ) -> Result { Ok(( StatusCode::OK, - Json(get_goal_list_handler(&postgres_service.database_pool, Some(&user.0)).await?), + Json( + get_goal_list_handler(&postgres_service.database_pool, Some(&PersonId(user.0))).await?, + ), ) .into_response()) } @@ -210,7 +224,7 @@ pub async fn get_sleep_schedule( user: User, ) -> Result { let result: Option = - get_sleep_schedule_handler(&postgres_service.database_pool, &user.0).await?; + get_sleep_schedule_handler(&postgres_service.database_pool, &PersonId(user.0)).await?; Ok((StatusCode::OK, Json(result)).into_response()) } @@ -227,7 +241,7 @@ pub async fn update_sleep_schedule( ) -> Result { let result: SleepSchedule = update_sleep_schedule_handler( &postgres_service.database_pool, - &user.0, + &PersonId(user.0), &form.start_time, &form.end_time, ) @@ -237,23 +251,119 @@ pub async fn update_sleep_schedule( } #[derive(Deserialize, Serialize, Debug)] +pub enum InvestmentFrequency { + Monthly, + Annually, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] pub struct CompoundingInterestInputs { pub duration_in_years: u16, - pub start_age: u8, - pub initial_amount: f32, // TODO: need 64? - pub annual_percent_interest: f32, + pub initial_amount: f32, + pub percent_interest: f32, + pub interest_frequency: InvestmentFrequency, + pub contribution_amount: f32, + pub contribution_frequency: InvestmentFrequency, + pub percent_inflation: f32, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct InvestmentStatus { + pub total_value: f32, + pub total_principal: f32, + pub interest_value: f32, + pub contribution_value: f32, + pub deflated_value: f32, + pub deflation_difference: f32, } #[derive(Deserialize, Serialize, Debug)] pub struct CompoundingInterestResult { - pub duration_in_years: u16, + pub inputs: CompoundingInterestInputs, + pub results: Vec, } pub async fn compounding_interest_calculator( - Form(input_form): Form, + Json(input_form): Json, ) -> Result { + if input_form.duration_in_years == 0 { + return Ok((StatusCode::BAD_REQUEST, "Years cannot be zero.").into_response()); + } + let years = (input_form.duration_in_years + 1) as u32; + + const MONTHS_IN_A_YEAR: u32 = 12; + const ITERATION_START: u32 = 1; + const PERCENTAGE_MULTIPLIER: f32 = 0.01_f32; + + let mut results: Vec = Vec::with_capacity(years as usize); + + let mut current_value: f32 = input_form.initial_amount; + let mut current_interest_value: f32 = 0_f32; + let mut current_contribution_value: f32 = 0_f32; + + let iterations: u32 = match ( + &input_form.contribution_frequency, + &input_form.interest_frequency, + ) { + (_, InvestmentFrequency::Monthly) => years * MONTHS_IN_A_YEAR, + (InvestmentFrequency::Monthly, _) => years * MONTHS_IN_A_YEAR, + (_, _) => years, + }; + + for index in ITERATION_START..iterations { + let is_year = match ( + &input_form.contribution_frequency, + &input_form.interest_frequency, + ) { + (InvestmentFrequency::Annually, InvestmentFrequency::Annually) => true, + (_, _) => index % MONTHS_IN_A_YEAR == 0 && index != 0, + }; + + match (&input_form.interest_frequency, is_year) { + (InvestmentFrequency::Annually, true) => { + current_interest_value += + current_value * input_form.percent_interest * PERCENTAGE_MULTIPLIER; + current_value += + current_value * input_form.percent_interest * PERCENTAGE_MULTIPLIER; + } + (InvestmentFrequency::Annually, false) => (), + (_, _) => { + current_interest_value += + current_value * input_form.percent_interest * PERCENTAGE_MULTIPLIER; + current_value += + current_value * input_form.percent_interest * PERCENTAGE_MULTIPLIER; + } + } + + match (&input_form.contribution_frequency, is_year) { + (InvestmentFrequency::Annually, true) => { + current_contribution_value += &input_form.contribution_amount; + current_value += &input_form.contribution_amount; + } + (InvestmentFrequency::Annually, false) => (), + (_, _) => { + current_contribution_value += &input_form.contribution_amount; + current_value += &input_form.contribution_amount; + } + } + + results.push(InvestmentStatus { + total_value: current_value, + total_principal: current_value - current_interest_value, + interest_value: current_interest_value, + contribution_value: current_contribution_value, + deflated_value: current_value + - current_value * input_form.percent_inflation * PERCENTAGE_MULTIPLIER, + deflation_difference: current_value + - (current_value + - current_value * input_form.percent_inflation * PERCENTAGE_MULTIPLIER), + }); + } + let result: CompoundingInterestResult = CompoundingInterestResult { - duration_in_years: input_form.duration_in_years, + inputs: input_form, + results, }; Ok((StatusCode::OK, Json(result)).into_response()) } diff --git a/loremaster-web-server/src/data/entity/action.rs b/loremaster-web-server/src/data/entity/action.rs index bf287e4a..751d235b 100644 --- a/loremaster-web-server/src/data/entity/action.rs +++ b/loremaster-web-server/src/data/entity/action.rs @@ -3,6 +3,15 @@ use uuid::Uuid; #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] pub struct Action { - pub id: Uuid, + pub id: ActionId, pub name: String, } + +#[derive(Deserialize, Serialize, Debug, Clone, sqlx::Decode, sqlx::Encode)] +pub struct ActionId(pub Uuid); + +impl sqlx::Type for ActionId { + fn type_info() -> ::TypeInfo { + >::type_info() + } +} diff --git a/loremaster-web-server/src/data/entity/chronicle.rs b/loremaster-web-server/src/data/entity/chronicle.rs index 0941e348..45b9c337 100644 --- a/loremaster-web-server/src/data/entity/chronicle.rs +++ b/loremaster-web-server/src/data/entity/chronicle.rs @@ -4,16 +4,27 @@ use time::{format_description::well_known::Rfc3339, Date, Duration, OffsetDateTi use time_tz::OffsetDateTimeExt; use uuid::Uuid; +use super::person::PersonId; + #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] #[serde(rename_all = "camelCase")] pub struct Chronicle { - pub id: Uuid, - pub person_id: Uuid, + pub id: ChronicleId, + pub person_id: PersonId, pub date_recorded: Date, pub notes: Option, pub creation_time: Option, } +#[derive(Deserialize, Serialize, Debug, Clone, sqlx::Decode, sqlx::Encode)] +pub struct ChronicleId(pub Uuid); + +impl sqlx::Type for ChronicleId { + fn type_info() -> ::TypeInfo { + >::type_info() + } +} + pub fn current_server_time() -> Result { Ok(OffsetDateTime::now_utc()) } diff --git a/loremaster-web-server/src/data/entity/email_address.rs b/loremaster-web-server/src/data/entity/email_address.rs index dd2843ff..04716113 100644 --- a/loremaster-web-server/src/data/entity/email_address.rs +++ b/loremaster-web-server/src/data/entity/email_address.rs @@ -5,7 +5,7 @@ use uuid::Uuid; #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] #[serde(rename_all = "camelCase")] pub struct EmailAddress { - pub id: Uuid, + pub id: EmailAddressId, pub display: String, pub local_part: String, pub domain: String, @@ -13,3 +13,12 @@ pub struct EmailAddress { pub validation_date: Option, pub creation_date: OffsetDateTime, } + +#[derive(Deserialize, Serialize, Debug, Clone, sqlx::Decode, sqlx::Encode)] +pub struct EmailAddressId(pub Uuid); + +impl sqlx::Type for EmailAddressId { + fn type_info() -> ::TypeInfo { + >::type_info() + } +} diff --git a/loremaster-web-server/src/data/entity/goal.rs b/loremaster-web-server/src/data/entity/goal.rs index e293b87b..38955162 100644 --- a/loremaster-web-server/src/data/entity/goal.rs +++ b/loremaster-web-server/src/data/entity/goal.rs @@ -1,17 +1,28 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; +use super::person::PersonId; + #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] #[serde(rename_all = "camelCase")] pub struct Goal { - pub id: Uuid, + pub id: GoalId, pub name: String, } +#[derive(Deserialize, Serialize, Debug, Clone, sqlx::Decode, sqlx::Encode)] +pub struct GoalId(pub Uuid); + +impl sqlx::Type for GoalId { + fn type_info() -> ::TypeInfo { + >::type_info() + } +} + #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] #[serde(rename_all = "camelCase")] pub struct PersonGoal { - pub person_id: Uuid, - pub goal_id: Uuid, + pub person_id: PersonId, + pub goal_id: GoalId, pub goal_name: String, } diff --git a/loremaster-web-server/src/data/entity/intention.rs b/loremaster-web-server/src/data/entity/intention.rs index 08093e03..793ff353 100644 --- a/loremaster-web-server/src/data/entity/intention.rs +++ b/loremaster-web-server/src/data/entity/intention.rs @@ -2,13 +2,24 @@ use serde::{Deserialize, Serialize}; use time::{Duration, OffsetDateTime}; use uuid::Uuid; +use super::{chronicle::ChronicleId, person::PersonId}; + #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] #[serde(rename_all = "camelCase")] pub struct Intention { - pub id: Uuid, + pub id: IntentionId, pub action_id: Uuid, - pub person_id: Uuid, - pub chronicle_id: Option, + pub person_id: PersonId, + pub chronicle_id: Option, pub intended_time: Option, pub expected_duration: Option, } + +#[derive(Deserialize, Serialize, Debug, Clone, sqlx::Decode, sqlx::Encode)] +pub struct IntentionId(pub Uuid); + +impl sqlx::Type for IntentionId { + fn type_info() -> ::TypeInfo { + >::type_info() + } +} diff --git a/loremaster-web-server/src/data/entity/passkey.rs b/loremaster-web-server/src/data/entity/passkey.rs index 7da116cb..9a3a8fa5 100644 --- a/loremaster-web-server/src/data/entity/passkey.rs +++ b/loremaster-web-server/src/data/entity/passkey.rs @@ -3,7 +3,16 @@ use uuid::Uuid; #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] pub struct Passkey { - pub id: Uuid, + pub id: PasskeyId, pub credential_id: Vec, pub cose_algorithm: i32, } + +#[derive(Deserialize, Serialize, Debug, Clone, sqlx::Decode, sqlx::Encode)] +pub struct PasskeyId(pub Uuid); + +impl sqlx::Type for PasskeyId { + fn type_info() -> ::TypeInfo { + >::type_info() + } +} diff --git a/loremaster-web-server/src/data/entity/person.rs b/loremaster-web-server/src/data/entity/person.rs index 159a24b6..42188f57 100644 --- a/loremaster-web-server/src/data/entity/person.rs +++ b/loremaster-web-server/src/data/entity/person.rs @@ -2,20 +2,31 @@ use serde::{Deserialize, Serialize}; use sqlx::types::time::OffsetDateTime; use uuid::Uuid; +use super::email_address::EmailAddressId; + #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] #[serde(rename_all = "camelCase")] pub struct Person { - pub id: Uuid, - pub email_address_id: Uuid, + pub id: PersonId, + pub email_address_id: EmailAddressId, pub registration_date: OffsetDateTime, pub alias: Option, pub chronicle_id: Option, } +#[derive(Deserialize, Serialize, Debug, Clone, sqlx::Decode, sqlx::Encode)] +pub struct PersonId(pub Uuid); + +impl sqlx::Type for PersonId { + fn type_info() -> ::TypeInfo { + >::type_info() + } +} + #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] #[serde(rename_all = "camelCase")] pub struct Credentials { - pub id: Uuid, + pub id: PersonId, pub email_address: String, pub encrypted_password: String, } @@ -23,7 +34,7 @@ pub struct Credentials { #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] #[serde(rename_all = "camelCase")] pub struct PersonMeta { - pub id: Uuid, + pub id: PersonId, pub email_address: String, pub registration_date: OffsetDateTime, pub alias: Option, diff --git a/loremaster-web-server/src/data/entity/quick_task.rs b/loremaster-web-server/src/data/entity/quick_task.rs index 3e34fb6b..c0403bc4 100644 --- a/loremaster-web-server/src/data/entity/quick_task.rs +++ b/loremaster-web-server/src/data/entity/quick_task.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; +use super::person::PersonId; + #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] pub struct QuickTask { pub id: Uuid, @@ -12,7 +14,7 @@ pub struct QuickTask { #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] pub struct PersonQuickTask { pub quick_task_id: Uuid, - pub person_id: Uuid, + pub person_id: PersonId, pub title: String, pub description: Option, pub completed: bool, diff --git a/loremaster-web-server/src/data/entity/sleep_schedule.rs b/loremaster-web-server/src/data/entity/sleep_schedule.rs index a0885fc1..ebacbd7b 100644 --- a/loremaster-web-server/src/data/entity/sleep_schedule.rs +++ b/loremaster-web-server/src/data/entity/sleep_schedule.rs @@ -5,7 +5,16 @@ use uuid::Uuid; #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] #[serde(rename_all = "camelCase")] pub struct SleepSchedule { - pub id: Uuid, + pub id: SleepScheduleId, pub start_time: Time, pub end_time: Time, } + +#[derive(Deserialize, Serialize, Debug, Clone, sqlx::Decode, sqlx::Encode)] +pub struct SleepScheduleId(pub Uuid); + +impl sqlx::Type for SleepScheduleId { + fn type_info() -> ::TypeInfo { + >::type_info() + } +} diff --git a/loremaster-web-server/src/data/entity/transfer/person_chronicle.rs b/loremaster-web-server/src/data/entity/transfer/person_chronicle.rs index 9c9aa83b..ddb179ca 100644 --- a/loremaster-web-server/src/data/entity/transfer/person_chronicle.rs +++ b/loremaster-web-server/src/data/entity/transfer/person_chronicle.rs @@ -1,10 +1,11 @@ use serde::{Deserialize, Serialize}; use time::Date; -use uuid::Uuid; + +use crate::data::entity::chronicle::ChronicleId; #[derive(Deserialize, Serialize, Debug, Clone)] pub struct PersonChronicle { - pub chronicle_id: Uuid, + pub chronicle_id: ChronicleId, pub chronicle_date: Date, pub person_alias: Option, } diff --git a/loremaster-web-server/src/data/entity/web_authentication_challenge.rs b/loremaster-web-server/src/data/entity/web_authentication_challenge.rs index 0e949549..82b869ab 100644 --- a/loremaster-web-server/src/data/entity/web_authentication_challenge.rs +++ b/loremaster-web-server/src/data/entity/web_authentication_challenge.rs @@ -4,10 +4,19 @@ use uuid::Uuid; #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] pub struct WebAuthenticationChallenge { - pub id: Uuid, + pub id: WebAuthenticationChallengeId, pub user_name: String, pub passkey: Value, } +#[derive(Deserialize, Serialize, Debug, Clone, sqlx::Decode, sqlx::Encode)] +pub struct WebAuthenticationChallengeId(pub Uuid); + +impl sqlx::Type for WebAuthenticationChallengeId { + fn type_info() -> ::TypeInfo { + >::type_info() + } +} + pub type WebAuthenticationRegister = WebAuthenticationChallenge; pub type WebAuthenticationLogin = WebAuthenticationChallenge; diff --git a/loremaster-web-server/src/data/entity/web_authentication_key.rs b/loremaster-web-server/src/data/entity/web_authentication_key.rs index 3310da93..9651e6b8 100644 --- a/loremaster-web-server/src/data/entity/web_authentication_key.rs +++ b/loremaster-web-server/src/data/entity/web_authentication_key.rs @@ -4,8 +4,17 @@ use uuid::Uuid; #[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] pub struct WebAuthenticationKey { - pub id: Uuid, + pub id: WebAuthenticationKeyId, pub credential_id: Vec, pub cose_algorithm: i32, pub passkey: Value, } + +#[derive(Deserialize, Serialize, Debug, Clone, sqlx::Decode, sqlx::Encode)] +pub struct WebAuthenticationKeyId(pub Uuid); + +impl sqlx::Type for WebAuthenticationKeyId { + fn type_info() -> ::TypeInfo { + >::type_info() + } +} diff --git a/loremaster-web-server/src/data/query/authentication/add_web_authentication_key.rs b/loremaster-web-server/src/data/query/authentication/add_web_authentication_key.rs index 1158b6b0..618af567 100644 --- a/loremaster-web-server/src/data/query/authentication/add_web_authentication_key.rs +++ b/loremaster-web-server/src/data/query/authentication/add_web_authentication_key.rs @@ -23,7 +23,7 @@ pub async fn add_web_authentication_key_query( info!("QUERY CALL: add_web_authentication_key_query"); query(QUERY) - .bind(web_authentication_key.id) + .bind(&web_authentication_key.id) .bind(&web_authentication_key.credential_id) .bind(web_authentication_key.cose_algorithm) .bind(&web_authentication_key.passkey) diff --git a/loremaster-web-server/src/data/query/authentication/add_web_authentication_login.rs b/loremaster-web-server/src/data/query/authentication/add_web_authentication_login.rs index d1aa7afe..02e60f04 100644 --- a/loremaster-web-server/src/data/query/authentication/add_web_authentication_login.rs +++ b/loremaster-web-server/src/data/query/authentication/add_web_authentication_login.rs @@ -23,7 +23,7 @@ pub async fn add_web_authentication_login_query( web_authentication_challenge: &WebAuthenticationLogin, ) -> Result { let query_result: WebAuthenticationLogin = query_as::<_, WebAuthenticationLogin>(QUERY) - .bind(web_authentication_challenge.id) + .bind(&web_authentication_challenge.id) .bind(&web_authentication_challenge.user_name) .bind(&web_authentication_challenge.passkey) .fetch_one(database_connection) diff --git a/loremaster-web-server/src/data/query/authentication/add_web_authentication_register.rs b/loremaster-web-server/src/data/query/authentication/add_web_authentication_register.rs index 2a0a4d60..a1c1b63c 100644 --- a/loremaster-web-server/src/data/query/authentication/add_web_authentication_register.rs +++ b/loremaster-web-server/src/data/query/authentication/add_web_authentication_register.rs @@ -25,7 +25,7 @@ pub async fn add_web_authentication_register_query( ) -> Result { info!("QUERY CALL: add_web_authentication_register_query"); let query_result: WebAuthenticationRegister = query_as::<_, WebAuthenticationRegister>(QUERY) - .bind(web_authentication_register.id) + .bind(&web_authentication_register.id) .bind(&web_authentication_register.user_name) .bind(&web_authentication_register.passkey) .fetch_one(database_connection) diff --git a/loremaster-web-server/src/data/query/authentication/get_web_authentication_register.rs b/loremaster-web-server/src/data/query/authentication/get_web_authentication_register.rs index 7eae25eb..7bf55b7c 100644 --- a/loremaster-web-server/src/data/query/authentication/get_web_authentication_register.rs +++ b/loremaster-web-server/src/data/query/authentication/get_web_authentication_register.rs @@ -1,7 +1,8 @@ use anyhow::Result; use log::info; use sqlx::{query_scalar, PgPool}; -use uuid::Uuid; + +use crate::data::entity::web_authentication_challenge::WebAuthenticationChallengeId; const QUERY: &str = " SELECT @@ -15,9 +16,9 @@ const QUERY: &str = " pub async fn get_optional_web_authentication_id_by_user_name_query( database_connection: &PgPool, user_name: &str, -) -> Result> { +) -> Result> { info!("QUERY CALL: get_optional_web_authentication_id_by_user_name_query"); - let query_result: Option = query_scalar(QUERY) + let query_result: Option = query_scalar(QUERY) .bind(user_name) .fetch_optional(database_connection) .await?; diff --git a/loremaster-web-server/src/data/query/goal/get_goal_list.rs b/loremaster-web-server/src/data/query/goal/get_goal_list.rs index 764aac21..274c6ffb 100644 --- a/loremaster-web-server/src/data/query/goal/get_goal_list.rs +++ b/loremaster-web-server/src/data/query/goal/get_goal_list.rs @@ -1,9 +1,8 @@ use anyhow::Result; use log::info; use sqlx::{query_as, PgPool}; -use uuid::Uuid; -use crate::data::entity::goal::Goal; +use crate::data::entity::{goal::Goal, person::PersonId}; const QUERY: &str = " SELECT @@ -27,7 +26,7 @@ const PERSON_QUERY: &str = " pub async fn get_goal_list_query( database_connection: &PgPool, - person_id: Option<&Uuid>, + person_id: Option<&PersonId>, ) -> Result> { info!("QUERY CALL: get_goal_list_query"); diff --git a/loremaster-web-server/src/data/query/person/action_is_related.rs b/loremaster-web-server/src/data/query/person/action_is_related.rs index b2bdf7f5..ea8de416 100644 --- a/loremaster-web-server/src/data/query/person/action_is_related.rs +++ b/loremaster-web-server/src/data/query/person/action_is_related.rs @@ -2,7 +2,7 @@ use anyhow::Result; use log::info; use sqlx::{postgres::PgRow, query, PgPool}; -use uuid::Uuid; +use crate::data::entity::{action::ActionId, person::PersonId}; const QUERY: &str = " SELECT @@ -18,8 +18,8 @@ const QUERY: &str = " pub async fn action_is_related_query( database_connection: &PgPool, - person_id: &Uuid, - action_id: &Uuid, + person_id: &PersonId, + action_id: &ActionId, ) -> Result { info!("QUERY CALL: action_is_related_query"); diff --git a/loremaster-web-server/src/data/query/person/add_action.rs b/loremaster-web-server/src/data/query/person/add_action.rs index 37ef510d..e986e3a8 100644 --- a/loremaster-web-server/src/data/query/person/add_action.rs +++ b/loremaster-web-server/src/data/query/person/add_action.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Result}; use log::info; use sqlx::{query, PgPool}; -use uuid::Uuid; +use crate::data::entity::{action::ActionId, person::PersonId}; const QUERY: &str = " INSERT INTO @@ -16,8 +16,8 @@ const QUERY: &str = " pub async fn add_action_query( database_connection: &PgPool, - person_id: &Uuid, - action_id: &Uuid, + person_id: &PersonId, + action_id: &ActionId, ) -> Result<()> { info!("QUERY CALL: add_action_query"); diff --git a/loremaster-web-server/src/data/query/person/add_goal.rs b/loremaster-web-server/src/data/query/person/add_goal.rs index 76403fe0..3e4c5d9c 100644 --- a/loremaster-web-server/src/data/query/person/add_goal.rs +++ b/loremaster-web-server/src/data/query/person/add_goal.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Result}; use log::info; use sqlx::{query, PgPool}; -use uuid::Uuid; +use crate::data::entity::{goal::GoalId, person::PersonId}; const QUERY: &str = " INSERT INTO @@ -16,8 +16,8 @@ const QUERY: &str = " pub async fn add_goal_query( database_connection: &PgPool, - person_id: &Uuid, - goal_id: &Uuid, + person_id: &PersonId, + goal_id: &GoalId, ) -> Result<()> { info!("QUERY CALL: add_goal_query"); diff --git a/loremaster-web-server/src/data/query/person/add_password.rs b/loremaster-web-server/src/data/query/person/add_password.rs index 74e867d8..c8b100a6 100644 --- a/loremaster-web-server/src/data/query/person/add_password.rs +++ b/loremaster-web-server/src/data/query/person/add_password.rs @@ -3,6 +3,8 @@ use log::info; use sqlx::{query, PgPool}; use uuid::Uuid; +use crate::data::entity::person::PersonId; + const QUERY: &str = " INSERT INTO public.person_password ( @@ -15,13 +17,13 @@ const QUERY: &str = " pub async fn add_password_query( database_connection: &PgPool, - person_id: &Uuid, + person_id: &PersonId, password_id: &Uuid, ) -> Result<()> { info!("QUERY CALL: add_password_query"); query(QUERY) - .bind(person_id) + .bind(person_id.0) .bind(password_id) .execute(database_connection) .await?; diff --git a/loremaster-web-server/src/data/query/person/add_web_authentication_key.rs b/loremaster-web-server/src/data/query/person/add_web_authentication_key.rs index 43ea30e8..d79da83d 100644 --- a/loremaster-web-server/src/data/query/person/add_web_authentication_key.rs +++ b/loremaster-web-server/src/data/query/person/add_web_authentication_key.rs @@ -1,7 +1,8 @@ use anyhow::Result; use log::info; use sqlx::{query, PgPool}; -use uuid::Uuid; + +use crate::data::entity::{person::PersonId, web_authentication_key::WebAuthenticationKeyId}; const QUERY: &str = " INSERT INTO @@ -15,8 +16,8 @@ const QUERY: &str = " pub async fn add_web_authentication_key_query( database_connection: &PgPool, - person_id: &Uuid, - web_authentication_key_id: &Uuid, + person_id: &PersonId, + web_authentication_key_id: &WebAuthenticationKeyId, ) -> Result<()> { info!("QUERY CALL: add_web_authentication_key_query"); diff --git a/loremaster-web-server/src/data/query/person/get_person_sleep_schedule.rs b/loremaster-web-server/src/data/query/person/get_person_sleep_schedule.rs index e4ae78c8..b145b1d2 100644 --- a/loremaster-web-server/src/data/query/person/get_person_sleep_schedule.rs +++ b/loremaster-web-server/src/data/query/person/get_person_sleep_schedule.rs @@ -1,9 +1,8 @@ use anyhow::Result; use log::info; use sqlx::{query_as, PgPool}; -use uuid::Uuid; -use crate::data::entity::sleep_schedule::SleepSchedule; +use crate::data::entity::{person::PersonId, sleep_schedule::SleepSchedule}; const QUERY: &str = " SELECT @@ -22,7 +21,7 @@ LIMIT pub async fn get_person_sleep_schedule_query( database_connection: &PgPool, - person_id: &Uuid, + person_id: &PersonId, ) -> Result> { info!("QUERY CALL: get_person_sleep_schedule_query"); let query_result: Option = query_as::<_, SleepSchedule>(QUERY) diff --git a/loremaster-web-server/src/data/query/person/goal_is_related.rs b/loremaster-web-server/src/data/query/person/goal_is_related.rs index d48f75dc..23b50000 100644 --- a/loremaster-web-server/src/data/query/person/goal_is_related.rs +++ b/loremaster-web-server/src/data/query/person/goal_is_related.rs @@ -2,7 +2,7 @@ use anyhow::Result; use log::info; use sqlx::{query, PgPool}; -use uuid::Uuid; +use crate::data::entity::{goal::GoalId, person::PersonId}; const QUERY: &str = " SELECT @@ -18,8 +18,8 @@ const QUERY: &str = " pub async fn goal_is_related_query( database_connection: &PgPool, - person_id: &Uuid, - goal_id: &Uuid, + person_id: &PersonId, + goal_id: &GoalId, ) -> Result { info!("QUERY CALL: goal_is_related_query"); diff --git a/loremaster-web-server/src/data/query/person/meta_by_id.rs b/loremaster-web-server/src/data/query/person/meta_by_id.rs index 2285234e..3bcac86a 100644 --- a/loremaster-web-server/src/data/query/person/meta_by_id.rs +++ b/loremaster-web-server/src/data/query/person/meta_by_id.rs @@ -1,8 +1,7 @@ use anyhow::Result; use sqlx::{query_as, PgPool}; -use uuid::Uuid; -use crate::data::entity::person::PersonMeta; +use crate::data::entity::person::{PersonId, PersonMeta}; const QUERY: &str = " SELECT @@ -22,7 +21,7 @@ LIMIT pub async fn meta_by_id_query( database_connection: &PgPool, - person_id: &Uuid, + person_id: &PersonId, ) -> Result> { let query_result: Option = query_as::<_, PersonMeta>(QUERY) .bind(person_id) diff --git a/loremaster-web-server/src/data/query/person/remove_one_goal.rs b/loremaster-web-server/src/data/query/person/remove_one_goal.rs index 7f18bdce..ba3b1541 100644 --- a/loremaster-web-server/src/data/query/person/remove_one_goal.rs +++ b/loremaster-web-server/src/data/query/person/remove_one_goal.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Result}; use log::info; use sqlx::{query, PgPool}; -use uuid::Uuid; +use crate::data::entity::{goal::GoalId, person::PersonId}; const QUERY: &str = " DELETE FROM @@ -14,8 +14,8 @@ const QUERY: &str = " pub async fn remove_one_goal_query( database_connection: &PgPool, - person_id: &Uuid, - goal_id: &Uuid, + person_id: &PersonId, + goal_id: &GoalId, ) -> Result<()> { info!("QUERY CALL: remove_one_goal_query"); diff --git a/loremaster-web-server/src/data/query/person/update_email_address.rs b/loremaster-web-server/src/data/query/person/update_email_address.rs index 1cb7a585..f61f8299 100644 --- a/loremaster-web-server/src/data/query/person/update_email_address.rs +++ b/loremaster-web-server/src/data/query/person/update_email_address.rs @@ -1,8 +1,10 @@ use anyhow::Result; use sqlx::{query_as, PgPool}; -use uuid::Uuid; -use crate::data::entity::person::Person; +use crate::data::entity::{ + email_address::EmailAddressId, + person::{Person, PersonId}, +}; const QUERY: &str = " UPDATE @@ -22,8 +24,8 @@ RETURNING pub async fn update_email_address_query( database_connection: &PgPool, - person_id: &Uuid, - email_address_id: &Uuid, + person_id: &PersonId, + email_address_id: &EmailAddressId, ) -> Result { let query_result: Person = query_as::<_, Person>(QUERY) .bind(person_id) diff --git a/loremaster-web-server/src/data/query/person/update_person_sleep_schedule.rs b/loremaster-web-server/src/data/query/person/update_person_sleep_schedule.rs index 36551e58..73e42648 100644 --- a/loremaster-web-server/src/data/query/person/update_person_sleep_schedule.rs +++ b/loremaster-web-server/src/data/query/person/update_person_sleep_schedule.rs @@ -1,7 +1,8 @@ use anyhow::{anyhow, Result}; use log::info; use sqlx::{query, PgPool}; -use uuid::Uuid; + +use crate::data::entity::{person::PersonId, sleep_schedule::SleepScheduleId}; const QUERY: &str = " INSERT INTO @@ -15,8 +16,8 @@ VALUES pub async fn update_person_sleep_schedule_query( database_connection: &PgPool, - schedule_id: &Uuid, - person_id: &Uuid, + schedule_id: &SleepScheduleId, + person_id: &PersonId, ) -> Result<()> { info!("QUERY CALL: update_person_sleep_schedule_query"); diff --git a/loremaster/Cargo.toml b/loremaster/Cargo.toml new file mode 100644 index 00000000..0a65e792 --- /dev/null +++ b/loremaster/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "loremaster" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# Error handling +anyhow = "1.0.72" +# Database client/pool/toolkit +sqlx = { version = "0.6.3", features = [ + "json", + "macros", + "migrate", + # "offline", + "postgres", + "runtime-tokio-native-tls", + "time", + "tls", + "uuid", +] } diff --git a/loremaster/src/data.rs b/loremaster/src/data.rs new file mode 100644 index 00000000..b2e6f1b8 --- /dev/null +++ b/loremaster/src/data.rs @@ -0,0 +1 @@ +pub mod postgres_handler; diff --git a/loremaster/src/data/postgres_handler.rs b/loremaster/src/data/postgres_handler.rs new file mode 100644 index 00000000..1911bfa1 --- /dev/null +++ b/loremaster/src/data/postgres_handler.rs @@ -0,0 +1,42 @@ +use anyhow::{anyhow, Result}; +use sqlx::{pool::PoolOptions, postgres::PgPoolOptions, PgPool}; +use std::time::Duration; + +const DB_POOL_MAX_OPEN: u32 = 32; +const DB_POOL_MAX_IDLE: u64 = 8; +const DB_POOL_TIMEOUT_SECONDS: u64 = 15; + +#[derive(Clone)] +pub struct PostgresHandler { + pub connection_string: String, + pub database_pool: PgPool, +} + +impl PostgresHandler { + pub async fn new(connection_string: String) -> Result { + let database_pool: PgPool = create_database_pool(&connection_string) + .await + .map_err(|error| anyhow!("{}", error))?; + + let new_handler: PostgresHandler = PostgresHandler { + connection_string, + database_pool, + }; + + Ok(new_handler) + } +} + +pub async fn create_database_pool(connection_string: &str) -> Result { + let options: PoolOptions = PgPoolOptions::new() + .idle_timeout(Duration::from_secs(DB_POOL_MAX_IDLE)) + .max_connections(DB_POOL_MAX_OPEN) + .acquire_timeout(Duration::from_secs(DB_POOL_TIMEOUT_SECONDS)); + + let result: PgPool = options + .connect(connection_string) + .await + .map_err(|error| anyhow!("{}", error))?; + + Ok(result) +} diff --git a/loremaster/src/lib.rs b/loremaster/src/lib.rs new file mode 100644 index 00000000..2a86aad4 --- /dev/null +++ b/loremaster/src/lib.rs @@ -0,0 +1,16 @@ +pub mod data; + +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} From e0fdaff7c0c4b8980953b189a93309b2cdbdff6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 23:24:28 +0000 Subject: [PATCH 2/3] Bump tokio from 1.32.0 to 1.33.0 in /loremaster-database Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.32.0 to 1.33.0. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.32.0...tokio-1.33.0) --- updated-dependencies: - dependency-name: tokio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- loremaster-database/Cargo.lock | 13 +++++++++++-- loremaster-database/Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/loremaster-database/Cargo.lock b/loremaster-database/Cargo.lock index 410ac05f..9d79f009 100644 --- a/loremaster-database/Cargo.lock +++ b/loremaster-database/Cargo.lock @@ -594,12 +594,21 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +[[package]] +name = "loremaster" +version = "0.1.0" +dependencies = [ + "anyhow", + "sqlx", +] + [[package]] name = "loremaster-database" version = "0.1.0" dependencies = [ "anyhow", "futures", + "loremaster", "sqlx", "thiserror", "tokio", @@ -1284,9 +1293,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ "backtrace", "bytes", diff --git a/loremaster-database/Cargo.toml b/loremaster-database/Cargo.toml index cba07c42..26058718 100644 --- a/loremaster-database/Cargo.toml +++ b/loremaster-database/Cargo.toml @@ -26,4 +26,4 @@ sqlx = { version = "0.6.3", features = [ # Error/exception handling thiserror = "1.0.49" # Backend async I/O functionality -tokio = { version = "1.32.0", features = ["full"] } +tokio = { version = "1.33.0", features = ["full"] } From 1958d60af948fd64ccda3a43009b46b488bb7116 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 23:24:35 +0000 Subject: [PATCH 3/3] Bump tokio from 1.32.0 to 1.33.0 in /loremaster-web-server Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.32.0 to 1.33.0. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.32.0...tokio-1.33.0) --- updated-dependencies: - dependency-name: tokio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- loremaster-web-server/Cargo.lock | 4 ++-- loremaster-web-server/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/loremaster-web-server/Cargo.lock b/loremaster-web-server/Cargo.lock index 87415718..dbedb98c 100644 --- a/loremaster-web-server/Cargo.lock +++ b/loremaster-web-server/Cargo.lock @@ -2189,9 +2189,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ "backtrace", "bytes", diff --git a/loremaster-web-server/Cargo.toml b/loremaster-web-server/Cargo.toml index cb365e55..524796cc 100644 --- a/loremaster-web-server/Cargo.toml +++ b/loremaster-web-server/Cargo.toml @@ -57,7 +57,7 @@ thiserror = "1.0.49" time = { version = "0.3.29", features = ["serde", "std", "parsing"] } time-tz = { version = "1.0.3" } # Backend async I/O functionality -tokio = { version = "1.32.0", features = ["full"] } +tokio = { version = "1.33.0", features = ["full"] } tokio-stream = { version = "0.1.14" } tokio-test = { version = "0.4.3" } # Tower middleware and utilities for HTTP clients and servers