From 5445ab6bf7550ca8719aefd9d04b7b2b95f812a4 Mon Sep 17 00:00:00 2001 From: RileyChampion Date: Wed, 9 Apr 2025 16:19:14 -0700 Subject: [PATCH 01/12] Add admin route and page with basic view. Looking to expand on what admins will see --- frontend/templates/admin/dashboard.html | 32 +++++++++++++++++++++++++ src/app/admin.rs | 29 ++++++++++++++++++++++ src/app/mod.rs | 2 ++ src/db/user.rs | 26 ++++++++++++++++++++ src/views/admin.rs | 9 +++++++ src/views/mod.rs | 1 + 6 files changed, 99 insertions(+) create mode 100644 frontend/templates/admin/dashboard.html create mode 100644 src/app/admin.rs create mode 100644 src/views/admin.rs diff --git a/frontend/templates/admin/dashboard.html b/frontend/templates/admin/dashboard.html new file mode 100644 index 0000000..bec51c7 --- /dev/null +++ b/frontend/templates/admin/dashboard.html @@ -0,0 +1,32 @@ +{% extends "layout.html" %} + +{% block title %}light and sound - admin{% endblock title %} + +{% block styles %} + {% call super() %} +{% endblock styles %} + +{% block content %} +
+

Admin View

+
+
Monthly New Users: 24
+ + + + + + + + {% for user in users %} + + + + + + {% endfor %} + +
First NameLast NameEmail
{{ user.first_name }}{{ user.last_name }}{{ user.email }}
+
+
+{% endblock content %} diff --git a/src/app/admin.rs b/src/app/admin.rs new file mode 100644 index 0000000..a6f958c --- /dev/null +++ b/src/app/admin.rs @@ -0,0 +1,29 @@ +use askama::Template; +use axum::{ + extract::{Query, State}, + response::{Html, IntoResponse, Response}, + routing::get, +}; + +use crate::utils::types::{AppResult, AppRouter, SharedAppState}; +use crate::{ + db::user::{ListUserQuery, User}, + views, +}; + +/// Add all `admins` routes to the router. +pub fn register_routes(router: AppRouter) -> AppRouter { + router.route("/admin/dashboard", get(admin_dashboard)) +} + +/// Display admin dashboard +async fn admin_dashboard( + State(state): State, + Query(query): Query, +) -> AppResult { + let users = User::list(&state.db, &query).await?; + + let dashboard_template = views::admin::AdminDashboard { users }; + + Ok(Html(dashboard_template.render()?).into_response()) +} diff --git a/src/app/mod.rs b/src/app/mod.rs index 03e4b35..cb46ef9 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -6,6 +6,7 @@ use tower_http::services::ServeDir; use crate::db::Db; use crate::utils::{self, config::*, emailer::Emailer}; +mod admin; mod auth; mod emails; mod events; @@ -38,6 +39,7 @@ pub async fn build(config: Config) -> Result { let r = events::register_routes(r); let r = lists::register_routes(r); let r = emails::register_routes(r); + let r = admin::register_routes(r); let r = auth::register(r, Arc::clone(&state)); diff --git a/src/db/user.rs b/src/db/user.rs index ee37efd..7f3380c 100644 --- a/src/db/user.rs +++ b/src/db/user.rs @@ -27,6 +27,14 @@ pub struct UpdateUser { pub email: String, } +#[derive(Debug, serde::Deserialize)] +pub struct ListUserQuery { + // month: String, + // year: String, + pub page: i64, + pub page_size: i64, +} + impl User { /// Full access to everything. pub const ADMIN: &'static str = "admin"; @@ -101,4 +109,22 @@ impl User { .await?; Ok(row.is_some()) } + + //Query users based on params + pub async fn list(db: &Db, query: &ListUserQuery) -> Result> { + let current_page = (query.page - 1) * query.page_size; + let users = sqlx::query_as!( + User, + r#"SELECT u.* + FROM users u + LIMIT ? + OFFSET ?"#, + query.page_size, + current_page, + ) + .fetch_all(db) + .await?; + + Ok(users) + } } diff --git a/src/views/admin.rs b/src/views/admin.rs new file mode 100644 index 0000000..7510dbc --- /dev/null +++ b/src/views/admin.rs @@ -0,0 +1,9 @@ +use crate::db::user::User; +// use crate::views::filters; +use askama::Template; + +#[derive(Template)] +#[template(path = "admin/dashboard.html")] +pub struct AdminDashboard { + pub users: Vec, +} diff --git a/src/views/mod.rs b/src/views/mod.rs index f704d8b..60c1373 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -1,3 +1,4 @@ +pub mod admin; pub mod auth; pub mod events; pub mod filters; From cb5146e3aaef200821708ecd8b768e403cd3fe63 Mon Sep 17 00:00:00 2001 From: RileyChampion Date: Thu, 10 Apr 2025 02:27:47 -0700 Subject: [PATCH 02/12] update query cache --- ...6322b98a16fa81e362e138d7ce99c4a020b4f.json | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .sqlx/query-d23b45d169c59a7dd2c4756b49e6322b98a16fa81e362e138d7ce99c4a020b4f.json diff --git a/.sqlx/query-d23b45d169c59a7dd2c4756b49e6322b98a16fa81e362e138d7ce99c4a020b4f.json b/.sqlx/query-d23b45d169c59a7dd2c4756b49e6322b98a16fa81e362e138d7ce99c4a020b4f.json new file mode 100644 index 0000000..ebd67c8 --- /dev/null +++ b/.sqlx/query-d23b45d169c59a7dd2c4756b49e6322b98a16fa81e362e138d7ce99c4a020b4f.json @@ -0,0 +1,44 @@ +{ + "db_name": "SQLite", + "query": "SELECT u.*\n FROM users u\n LIMIT ?\n OFFSET ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "first_name", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "last_name", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "email", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 4, + "type_info": "Datetime" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "d23b45d169c59a7dd2c4756b49e6322b98a16fa81e362e138d7ce99c4a020b4f" +} From 9a65b6629a9c31219727ca6755f2aa0638d6df93 Mon Sep 17 00:00:00 2001 From: RileyChampion Date: Thu, 10 Apr 2025 02:35:42 -0700 Subject: [PATCH 03/12] Testing fix for cross-compile --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index db6a0f8..cf06ed1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -66,5 +66,5 @@ jobs: targets: aarch64-unknown-linux-gnu - uses: Swatinem/rust-cache@v2 - name: install gcc-aarch64-linux-gnu - run: sudo apt install -y gcc-aarch64-linux-gnu + run: sudo apt update && sudo apt install -y gcc-aarch64-linux-gnu - run: cargo build --target aarch64-unknown-linux-gnu From 2d28cd8a32f31ae05404c17b628d100f9026beff Mon Sep 17 00:00:00 2001 From: RileyChampion Date: Fri, 18 Apr 2025 04:00:53 -0700 Subject: [PATCH 04/12] Fixed prittier, lint and clippy formatting --- frontend/styles/admin/dashboard.css | 47 +++++++------ frontend/styles/main.css | 2 +- frontend/templates/admin/dashboard.html | 92 ++++++++++++++++++++----- src/app/admin.rs | 5 +- src/db/user.rs | 2 +- src/views/admin.rs | 4 +- 6 files changed, 105 insertions(+), 47 deletions(-) diff --git a/frontend/styles/admin/dashboard.css b/frontend/styles/admin/dashboard.css index dde82ce..f669772 100644 --- a/frontend/styles/admin/dashboard.css +++ b/frontend/styles/admin/dashboard.css @@ -1,30 +1,29 @@ #admin\/ { - @apply flex flex-row h-full; + @apply flex h-full flex-row; } #sidebar { - @apply fixed min-h-screen relative z-30 min-w-64 h-auto flex flex-col border-r-1 border-neutral-800 p-2; - > a { - @apply flex items-center text-lg no-underline m-1 p-2 rounded-sm cursor-pointer text-gray-400 hover:bg-gray-800 hover:text-white; - &.active { - @apply bg-gray-800 text-white; - svg { - @apply text-white; - } - } - span { - @apply ms-2; - } - svg { - @apply text-gray-500 fill-current - } - &:hover { - svg { - @apply text-white; - } - } + @apply fixed relative z-30 flex h-auto min-h-screen min-w-64 flex-col border-r-1 border-neutral-800 p-2; + > a { + @apply m-1 flex cursor-pointer items-center rounded-sm p-2 text-lg text-gray-400 no-underline hover:bg-gray-800 hover:text-white; + &.active { + @apply bg-gray-800 text-white; + svg { + @apply text-white; + } } - + span { + @apply ms-2; + } + svg { + @apply fill-current text-gray-500; + } + &:hover { + svg { + @apply text-white; + } + } + } } #admin\/dashboard { - @apply p-4 w-full; -} \ No newline at end of file + @apply w-full p-4; +} diff --git a/frontend/styles/main.css b/frontend/styles/main.css index d4c1694..8e23459 100644 --- a/frontend/styles/main.css +++ b/frontend/styles/main.css @@ -77,4 +77,4 @@ form { } } -@import "./admin/dashboard.css" \ No newline at end of file +@import "./admin/dashboard.css"; diff --git a/frontend/templates/admin/dashboard.html b/frontend/templates/admin/dashboard.html index b61a173..ed9cda6 100644 --- a/frontend/templates/admin/dashboard.html +++ b/frontend/templates/admin/dashboard.html @@ -7,32 +7,92 @@
diff --git a/src/app/admin.rs b/src/app/admin.rs index 6e365d0..8ee4443 100644 --- a/src/app/admin.rs +++ b/src/app/admin.rs @@ -6,7 +6,7 @@ use axum::{ use crate::utils::{ error::AppResult, - types::{AppRouter, SharedAppState} + types::{AppRouter, SharedAppState}, }; use crate::{ db::user::{ListUserQuery, User}, @@ -15,8 +15,7 @@ use crate::{ /// Add all `admins` routes to the router. pub fn routes() -> AppRouter { - AppRouter::new() - .route("/dashboard", get(admin_dashboard)) + AppRouter::new().route("/dashboard", get(admin_dashboard)) } /// Display admin dashboard diff --git a/src/db/user.rs b/src/db/user.rs index 0f81675..2a116e2 100644 --- a/src/db/user.rs +++ b/src/db/user.rs @@ -29,7 +29,7 @@ pub struct UpdateUser { #[derive(Debug, serde::Deserialize)] pub struct UserView { - pub first_name: String, + pub first_name: String, pub last_name: String, pub email: String, } diff --git a/src/views/admin.rs b/src/views/admin.rs index 8614225..bfa0ef6 100644 --- a/src/views/admin.rs +++ b/src/views/admin.rs @@ -1,4 +1,4 @@ -use crate::{db::user::{UserView}, views::filters}; +use crate::db::user::UserView; use askama::Template; use askama_web::WebTemplate; @@ -6,4 +6,4 @@ use askama_web::WebTemplate; #[template(path = "admin/dashboard.html")] pub struct AdminDashboard { pub users: Vec, -} \ No newline at end of file +} From b3909b30e0fa7b38bfa895ef6edbb6b17e7909fe Mon Sep 17 00:00:00 2001 From: RileyChampion Date: Sun, 20 Apr 2025 13:00:17 -0700 Subject: [PATCH 05/12] more changes to overview dashboard, added users dashboard, styling to widgets --- frontend/styles/admin/dashboard.css | 30 ++++- frontend/styles/admin/dashboard/overview.css | 8 ++ frontend/templates/admin/dashboard.html | 77 ++++++++---- .../templates/admin/dashboard/events.html | 4 + .../templates/admin/dashboard/newsletter.html | 4 + .../templates/admin/dashboard/overview.html | 110 ++++++++++++++++++ frontend/templates/admin/dashboard/users.html | 21 ++++ src/app/admin.rs | 19 ++- src/views/admin.rs | 10 +- 9 files changed, 248 insertions(+), 35 deletions(-) create mode 100644 frontend/styles/admin/dashboard/overview.css create mode 100644 frontend/templates/admin/dashboard/events.html create mode 100644 frontend/templates/admin/dashboard/newsletter.html create mode 100644 frontend/templates/admin/dashboard/overview.html create mode 100644 frontend/templates/admin/dashboard/users.html diff --git a/frontend/styles/admin/dashboard.css b/frontend/styles/admin/dashboard.css index f669772..b1e13bb 100644 --- a/frontend/styles/admin/dashboard.css +++ b/frontend/styles/admin/dashboard.css @@ -1,12 +1,12 @@ #admin\/ { - @apply flex h-full flex-row; + @apply flex max-h-[calc(100vh-58px)] min-h-[calc(100vh-58px)] flex-row; } #sidebar { - @apply fixed relative z-30 flex h-auto min-h-screen min-w-64 flex-col border-r-1 border-neutral-800 p-2; + @apply relative z-30 flex min-w-64 flex-col border-r-1 border-neutral-800 p-2; > a { - @apply m-1 flex cursor-pointer items-center rounded-sm p-2 text-lg text-gray-400 no-underline hover:bg-gray-800 hover:text-white; + @apply m-1 flex cursor-pointer items-center rounded-sm border border-transparent p-2 text-sm text-neutral-600 no-underline hover:bg-neutral-800 hover:text-white; &.active { - @apply bg-gray-800 text-white; + @apply border border-neutral-700 bg-neutral-900 text-white hover:bg-neutral-800; svg { @apply text-white; } @@ -15,7 +15,7 @@ @apply ms-2; } svg { - @apply fill-current text-gray-500; + @apply fill-current text-neutral-600; } &:hover { svg { @@ -27,3 +27,23 @@ #admin\/dashboard { @apply w-full p-4; } + +.widget-container { + @apply rounded-sm border border-neutral-800 bg-neutral-950 p-4; + a { + svg { + @apply ml-auto h-7 w-7 cursor-pointer rounded-sm p-1 hover:bg-neutral-800; + } + } + .widget-content-stat { + @apply flex flex-col items-center; + h1 { + @apply text-5xl; + } + h2 { + @apply text-xl; + } + } +} + +@import "./dashboard/overview.css"; diff --git a/frontend/styles/admin/dashboard/overview.css b/frontend/styles/admin/dashboard/overview.css new file mode 100644 index 0000000..3cfe097 --- /dev/null +++ b/frontend/styles/admin/dashboard/overview.css @@ -0,0 +1,8 @@ +#admin\/dashboard\/overview { + @apply grid h-[94%] w-full grid-cols-3 gap-2; + grid-template-rows: 0.5fr 1fr 1fr; +} + +#widget-attendees { + @apply col-start-1 col-end-4 row-start-2 row-end-4; +} diff --git a/frontend/templates/admin/dashboard.html b/frontend/templates/admin/dashboard.html index ed9cda6..ed09a2f 100644 --- a/frontend/templates/admin/dashboard.html +++ b/frontend/templates/admin/dashboard.html @@ -6,7 +6,7 @@ {% block content %} + {% endblock content %} diff --git a/frontend/templates/admin/dashboard/events.html b/frontend/templates/admin/dashboard/events.html new file mode 100644 index 0000000..3897be2 --- /dev/null +++ b/frontend/templates/admin/dashboard/events.html @@ -0,0 +1,4 @@ +{% extends "admin/dashboard.html" %} +{% block title %}{% call super() %} overview{% endblock title %} +{% block panel %} +{% endblock panel %} diff --git a/frontend/templates/admin/dashboard/newsletter.html b/frontend/templates/admin/dashboard/newsletter.html new file mode 100644 index 0000000..3897be2 --- /dev/null +++ b/frontend/templates/admin/dashboard/newsletter.html @@ -0,0 +1,4 @@ +{% extends "admin/dashboard.html" %} +{% block title %}{% call super() %} overview{% endblock title %} +{% block panel %} +{% endblock panel %} diff --git a/frontend/templates/admin/dashboard/overview.html b/frontend/templates/admin/dashboard/overview.html new file mode 100644 index 0000000..ea0ab35 --- /dev/null +++ b/frontend/templates/admin/dashboard/overview.html @@ -0,0 +1,110 @@ +{% extends "admin/dashboard.html" %} +{% block title %}{% call super() %} overview{% endblock title %} +{% block panel %} +

Overview

+
+
+ + + +
+

+ {{ users_count }} +

+

# of Monthly Users

+
+
+
+ + + +
+

+ 7000 +

+

# of Newsletters Opened

+
+
+
+ + + +
+

+ 15 +

+

# of Events

+
+
+
+ + + +
+
+
+{% endblock panel %} diff --git a/frontend/templates/admin/dashboard/users.html b/frontend/templates/admin/dashboard/users.html new file mode 100644 index 0000000..0a9af7c --- /dev/null +++ b/frontend/templates/admin/dashboard/users.html @@ -0,0 +1,21 @@ +{% extends "admin/dashboard.html" %} +{% block title %}{% call super() %} overview{% endblock title %} +{% block panel %} + + + + + + + + {% for user in users %} + + + + + + {% endfor %} + +
+
First NameLast NameEmail
{{ user.first_name }}{{ user.last_name }}{{ user.email }}
+{% endblock panel %} diff --git a/src/app/admin.rs b/src/app/admin.rs index 8ee4443..2f690a2 100644 --- a/src/app/admin.rs +++ b/src/app/admin.rs @@ -15,15 +15,26 @@ use crate::{ /// Add all `admins` routes to the router. pub fn routes() -> AppRouter { - AppRouter::new().route("/dashboard", get(admin_dashboard)) + AppRouter::new() + .route("/dashboard/overview", get(admin_overview_dashboard)) + .route("/dashboard/users", get(admin_users_dashboard)) } -/// Display admin dashboard -async fn admin_dashboard( +/// Display admin overview dashboard +async fn admin_overview_dashboard(State(state): State) -> AppResult { + let q = ListUserQuery { page: 0, page_size: 25 }; + + let users_count = User::list(&state.db, &q).await?.len(); + + Ok(views::admin::AdminDashboardOverview { users_count }) +} + +// +async fn admin_users_dashboard( State(state): State, Query(query): Query, ) -> AppResult { let users = User::list(&state.db, &query).await?; - Ok(views::admin::AdminDashboard { users }) + Ok(views::admin::AdminDashboardUsersView { users }) } diff --git a/src/views/admin.rs b/src/views/admin.rs index bfa0ef6..4eb70fd 100644 --- a/src/views/admin.rs +++ b/src/views/admin.rs @@ -3,7 +3,13 @@ use askama::Template; use askama_web::WebTemplate; #[derive(Template, WebTemplate)] -#[template(path = "admin/dashboard.html")] -pub struct AdminDashboard { +#[template(path = "admin/dashboard/overview.html")] +pub struct AdminDashboardOverview { + pub users_count: usize, +} + +#[derive(Template, WebTemplate)] +#[template(path = "admin/dashboard/users.html")] +pub struct AdminDashboardUsersView { pub users: Vec, } From b34c138f8ff05def5aca9b3dc6fcf512ff5f0a58 Mon Sep 17 00:00:00 2001 From: RileyChampion Date: Fri, 25 Apr 2025 00:58:32 -0700 Subject: [PATCH 06/12] Added middleware layer to have only admin users access dashboard, change the styling a little --- frontend/styles/admin/dashboard.css | 6 +++--- frontend/styles/admin/dashboard/overview.css | 13 ++++++++++--- frontend/templates/admin/dashboard.html | 2 +- frontend/templates/admin/dashboard/overview.html | 10 ++++++---- src/app/admin.rs | 7 ++++++- src/app/auth.rs | 16 ++++++++++++++++ src/app/mod.rs | 2 +- 7 files changed, 43 insertions(+), 13 deletions(-) diff --git a/frontend/styles/admin/dashboard.css b/frontend/styles/admin/dashboard.css index b1e13bb..2bdc2b4 100644 --- a/frontend/styles/admin/dashboard.css +++ b/frontend/styles/admin/dashboard.css @@ -1,5 +1,5 @@ #admin\/ { - @apply flex max-h-[calc(100vh-58px)] min-h-[calc(100vh-58px)] flex-row; + @apply flex h-[calc(100vh-58px)] max-h-[calc(100vh-58px)] min-h-[calc(100vh-58px)] flex-row; } #sidebar { @apply relative z-30 flex min-w-64 flex-col border-r-1 border-neutral-800 p-2; @@ -25,7 +25,7 @@ } } #admin\/dashboard { - @apply w-full p-4; + @apply flex flex-1 flex-col overflow-auto p-4; } .widget-container { @@ -41,7 +41,7 @@ @apply text-5xl; } h2 { - @apply text-xl; + @apply text-xl text-neutral-400; } } } diff --git a/frontend/styles/admin/dashboard/overview.css b/frontend/styles/admin/dashboard/overview.css index 3cfe097..6e83b68 100644 --- a/frontend/styles/admin/dashboard/overview.css +++ b/frontend/styles/admin/dashboard/overview.css @@ -1,8 +1,15 @@ #admin\/dashboard\/overview { - @apply grid h-[94%] w-full grid-cols-3 gap-2; - grid-template-rows: 0.5fr 1fr 1fr; + @apply grid h-full w-full grid-cols-3 gap-2; + grid-template-rows: 0.25fr minmax(0, 1fr); } #widget-attendees { - @apply col-start-1 col-end-4 row-start-2 row-end-4; + @apply col-start-1 col-end-4 row-start-2 row-end-3 flex h-full w-full flex-col; + .widget-content { + @apply flex-1 overflow-auto; + .widget-line-graph { + @apply h-full min-w-screen object-contain; + display: block; + } + } } diff --git a/frontend/templates/admin/dashboard.html b/frontend/templates/admin/dashboard.html index ed09a2f..1113f41 100644 --- a/frontend/templates/admin/dashboard.html +++ b/frontend/templates/admin/dashboard.html @@ -94,7 +94,7 @@ clip-rule="evenodd" /> - Newsletters + Posts
diff --git a/frontend/templates/admin/dashboard/overview.html b/frontend/templates/admin/dashboard/overview.html index ea0ab35..e8bf19a 100644 --- a/frontend/templates/admin/dashboard/overview.html +++ b/frontend/templates/admin/dashboard/overview.html @@ -27,7 +27,7 @@

Overview

{{ users_count }}

-

# of Monthly Users

+

New Users

@@ -54,7 +54,7 @@

# of Monthly Users

7000

-

# of Newsletters Opened

+

Newsletters Opened

@@ -81,7 +81,7 @@

# of Newsletters Opened

15

-

# of Events

+

Events

@@ -104,7 +104,9 @@

# of Events

/> -
+
+ +
{% endblock panel %} diff --git a/src/app/admin.rs b/src/app/admin.rs index 2f690a2..eb4a7ce 100644 --- a/src/app/admin.rs +++ b/src/app/admin.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use axum::{ extract::{Query, State}, response::IntoResponse, @@ -13,11 +15,14 @@ use crate::{ views, }; +use super::{auth, AppState}; + /// Add all `admins` routes to the router. -pub fn routes() -> AppRouter { +pub fn routes(state: &Arc) -> AppRouter { AppRouter::new() .route("/dashboard/overview", get(admin_overview_dashboard)) .route("/dashboard/users", get(admin_users_dashboard)) + .layer(axum::middleware::from_fn_with_state(Arc::clone(state), auth::require_admin)) } /// Display admin overview dashboard diff --git a/src/app/auth.rs b/src/app/auth.rs index 542652f..460ca0a 100644 --- a/src/app/auth.rs +++ b/src/app/auth.rs @@ -70,6 +70,22 @@ pub async fn middleware( Ok((cookies, response)) } +// Middleware to be used to require admin role for certain routes (ie Admin dashboard) +pub async fn require_admin( + State(state): State, + request: Request, + next: Next, +) -> AppResult { + let user = request.extensions().get::().ok_or(AppError::NotAuthorized)?; + + // Check if user has admin role + if !user.has_role(&state.db, "admin").await? { + return Err(AppError::NotAuthorized); + } + + Ok(next.run(request).await) +} + /// Enable extracting an `Option` in a handler. impl OptionalFromRequestParts for User { type Rejection = Infallible; diff --git a/src/app/mod.rs b/src/app/mod.rs index 5c18963..4c188c2 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -39,7 +39,7 @@ pub async fn build(config: Config) -> anyhow::Result { .nest("/events", events::routes()) .nest("/lists", lists::routes()) .nest("/emails", emails::routes()) - .nest("/admin", admin::routes()) + .nest("/admin", admin::routes(&state)) .nest_service("/static", ServeDir::new("frontend/static")) // For non-HTML pages without a , this is where the browser looks .route("/favicon.ico", get(|| async { Redirect::to("/static/favicon.ico") })) From 01f10fb4bf9f84efb9e2a81de3e65db36be6fb36 Mon Sep 17 00:00:00 2001 From: RileyChampion Date: Fri, 25 Apr 2025 01:17:15 -0700 Subject: [PATCH 07/12] Fix css --- frontend/styles/admin/dashboard.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/styles/admin/dashboard.css b/frontend/styles/admin/dashboard.css index 2bdc2b4..d15db65 100644 --- a/frontend/styles/admin/dashboard.css +++ b/frontend/styles/admin/dashboard.css @@ -1,5 +1,5 @@ #admin\/ { - @apply flex h-[calc(100vh-58px)] max-h-[calc(100vh-58px)] min-h-[calc(100vh-58px)] flex-row; + @apply flex h-[calc(100vh-59px)] max-h-[calc(100vh-59px)] min-h-[calc(100vh-59px)] flex-row; } #sidebar { @apply relative z-30 flex min-w-64 flex-col border-r-1 border-neutral-800 p-2; From 701f2da9a0cd22b65116f3295047d40955c57832 Mon Sep 17 00:00:00 2001 From: RileyChampion Date: Wed, 30 Apr 2025 16:26:28 -0700 Subject: [PATCH 08/12] Fixes to the graph and styling and have the links on the side link to post and event pages for now --- frontend/styles/admin/dashboard.css | 2 +- frontend/styles/admin/dashboard/overview.css | 39 +++- frontend/templates/admin/dashboard.html | 6 +- .../templates/admin/dashboard/overview.html | 170 +++++++++++++++++- 4 files changed, 210 insertions(+), 7 deletions(-) diff --git a/frontend/styles/admin/dashboard.css b/frontend/styles/admin/dashboard.css index d15db65..e9291e2 100644 --- a/frontend/styles/admin/dashboard.css +++ b/frontend/styles/admin/dashboard.css @@ -25,7 +25,7 @@ } } #admin\/dashboard { - @apply flex flex-1 flex-col overflow-auto p-4; + @apply flex flex-1 flex-col overflow-auto p-3; } .widget-container { diff --git a/frontend/styles/admin/dashboard/overview.css b/frontend/styles/admin/dashboard/overview.css index 6e83b68..eb9bb57 100644 --- a/frontend/styles/admin/dashboard/overview.css +++ b/frontend/styles/admin/dashboard/overview.css @@ -7,9 +7,42 @@ @apply col-start-1 col-end-4 row-start-2 row-end-3 flex h-full w-full flex-col; .widget-content { @apply flex-1 overflow-auto; - .widget-line-graph { - @apply h-full min-w-screen object-contain; - display: block; + #chart1 { + .ct-series-a .ct-line { + @apply stroke-green-600 stroke-2; + } + .ct-series-a .ct-point { + @apply stroke-green-600; + } + .ct-series-a .ct-area { + @apply fill-green-600; + /* fill-opacity: .1; */ + } + .ct-label { + @apply text-white fill-white; + } + + .ct-horizontal:first-child { + @apply stroke-neutral-500; + } + + .ct-vertical:nth-child(6) { + @apply stroke-neutral-500; + } + + /* Change specific axes */ + .ct-axis-title, + .ct-axis-title text { + @apply fill-white; + } + + /* Basic CSS for the tooltip (add to your stylesheet) */ + .chartist-tooltip { + @apply absolute rounded-sm hidden min-w-5 py-2 px-2.5 border-neutral-500 border-1 bg-neutral-800 text-center cursor-none z-100 transition-opacity delay-200 ease-linear; + } + .chartist-tooltip.show { + @apply block; + } } } } diff --git a/frontend/templates/admin/dashboard.html b/frontend/templates/admin/dashboard.html index 1113f41..358029b 100644 --- a/frontend/templates/admin/dashboard.html +++ b/frontend/templates/admin/dashboard.html @@ -2,6 +2,7 @@ {% block title %}light and sound - admin{% endblock title %} {% block styles %} {% call super() %} + {% endblock styles %} {% block content %}
@@ -60,7 +61,7 @@ Users - + Events - +
+ + + {% endblock panel %} +{% block scripts %} +{% endblock scripts %} \ No newline at end of file From 9b176ab49912a3e7edab0a410aeebc13084acc02 Mon Sep 17 00:00:00 2001 From: RileyChampion Date: Wed, 30 Apr 2025 22:49:39 -0700 Subject: [PATCH 09/12] Inital styling for the users table --- frontend/styles/admin/dashboard.css | 8 +- frontend/styles/admin/dashboard/overview.css | 66 ++--- frontend/styles/admin/dashboard/users.css | 35 +++ frontend/templates/admin/dashboard.html | 6 +- .../templates/admin/dashboard/overview.html | 231 +++++++++--------- frontend/templates/admin/dashboard/users.html | 112 +++++++-- 6 files changed, 292 insertions(+), 166 deletions(-) create mode 100644 frontend/styles/admin/dashboard/users.css diff --git a/frontend/styles/admin/dashboard.css b/frontend/styles/admin/dashboard.css index e9291e2..4dbe570 100644 --- a/frontend/styles/admin/dashboard.css +++ b/frontend/styles/admin/dashboard.css @@ -27,13 +27,10 @@ #admin\/dashboard { @apply flex flex-1 flex-col overflow-auto p-3; } - .widget-container { @apply rounded-sm border border-neutral-800 bg-neutral-950 p-4; - a { - svg { - @apply ml-auto h-7 w-7 cursor-pointer rounded-sm p-1 hover:bg-neutral-800; - } + a > .widget-nav { + @apply ml-auto h-7 w-7 cursor-pointer rounded-sm p-1 hover:bg-neutral-800; } .widget-content-stat { @apply flex flex-col items-center; @@ -47,3 +44,4 @@ } @import "./dashboard/overview.css"; +@import "./dashboard/users.css"; diff --git a/frontend/styles/admin/dashboard/overview.css b/frontend/styles/admin/dashboard/overview.css index eb9bb57..3bfe354 100644 --- a/frontend/styles/admin/dashboard/overview.css +++ b/frontend/styles/admin/dashboard/overview.css @@ -8,41 +8,41 @@ .widget-content { @apply flex-1 overflow-auto; #chart1 { - .ct-series-a .ct-line { - @apply stroke-green-600 stroke-2; - } - .ct-series-a .ct-point { - @apply stroke-green-600; - } - .ct-series-a .ct-area { - @apply fill-green-600; - /* fill-opacity: .1; */ - } - .ct-label { - @apply text-white fill-white; - } - - .ct-horizontal:first-child { - @apply stroke-neutral-500; - } + .ct-series-a .ct-line { + @apply stroke-green-600 stroke-2; + } + .ct-series-a .ct-point { + @apply stroke-green-600; + } + .ct-series-a .ct-area { + @apply fill-green-600; + /* fill-opacity: .1; */ + } + .ct-label { + @apply fill-white text-white; + } - .ct-vertical:nth-child(6) { - @apply stroke-neutral-500; - } - - /* Change specific axes */ - .ct-axis-title, - .ct-axis-title text { - @apply fill-white; - } + .ct-horizontal:first-child { + @apply stroke-neutral-500; + } - /* Basic CSS for the tooltip (add to your stylesheet) */ - .chartist-tooltip { - @apply absolute rounded-sm hidden min-w-5 py-2 px-2.5 border-neutral-500 border-1 bg-neutral-800 text-center cursor-none z-100 transition-opacity delay-200 ease-linear; - } - .chartist-tooltip.show { - @apply block; - } + .ct-vertical:nth-child(6) { + @apply stroke-neutral-500; + } + + /* Change specific axes */ + .ct-axis-title, + .ct-axis-title text { + @apply fill-white; + } + + /* Basic CSS for the tooltip (add to your stylesheet) */ + .chartist-tooltip { + @apply absolute z-100 hidden min-w-5 cursor-none rounded-sm border-1 border-neutral-500 bg-neutral-800 px-2.5 py-2 text-center transition-opacity delay-200 ease-linear; + } + .chartist-tooltip.show { + @apply block; + } } } } diff --git a/frontend/styles/admin/dashboard/users.css b/frontend/styles/admin/dashboard/users.css new file mode 100644 index 0000000..bcdc7b6 --- /dev/null +++ b/frontend/styles/admin/dashboard/users.css @@ -0,0 +1,35 @@ +#admin\/dashboard\/users { + @apply grid h-full w-full grid-cols-3 gap-2; + grid-template-rows: 0.1fr minmax(0, 1fr); +} + +#widget-table { + @apply col-start-1 col-end-4 row-start-2 row-end-3 flex w-full flex-col; + .widget-item-table { + @apply h-full w-full flex-1 overflow-auto text-left; + } + table { + @apply w-full text-left; + thead { + @apply sticky border-b border-neutral-700; + } + td, + th { + @apply py-1.5 text-sm overflow-ellipsis; + } + th { + @apply sticky bg-black; + } + tr { + @apply odd:bg-neutral-900; + } + input { + @apply h-3.5 w-3.5 rounded-sm border-gray-300 bg-gray-100 text-blue-600 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600; + } + } + .table-footer { + a { + @apply inline-flex w-12 cursor-pointer justify-center rounded-sm border-1 border-neutral-700 py-1.5 first-of-type:mr-3; + } + } +} diff --git a/frontend/templates/admin/dashboard.html b/frontend/templates/admin/dashboard.html index 358029b..004fbda 100644 --- a/frontend/templates/admin/dashboard.html +++ b/frontend/templates/admin/dashboard.html @@ -2,7 +2,6 @@ {% block title %}light and sound - admin{% endblock title %} {% block styles %} {% call super() %} - {% endblock styles %} {% block content %}
@@ -103,7 +102,10 @@ {% endblock panel %}
- + {% endblock panel %} {% block scripts %} -{% endblock scripts %} \ No newline at end of file +{% endblock scripts %} diff --git a/frontend/templates/admin/dashboard/users.html b/frontend/templates/admin/dashboard/users.html index 0a9af7c..bba936d 100644 --- a/frontend/templates/admin/dashboard/users.html +++ b/frontend/templates/admin/dashboard/users.html @@ -1,21 +1,99 @@ {% extends "admin/dashboard.html" %} {% block title %}{% call super() %} overview{% endblock title %} {% block panel %} - - - - - - - - {% for user in users %} - - - - - - {% endfor %} - -
-
First NameLast NameEmail
{{ user.first_name }}{{ user.last_name }}{{ user.email }}
+
+ {% endblock panel %} From 4cc4fb66dc008540da8ad5e31ce40d6e8e01e011 Mon Sep 17 00:00:00 2001 From: RileyChampion Date: Fri, 2 May 2025 02:30:43 -0700 Subject: [PATCH 10/12] Restyling user table and buttons --- frontend/styles/admin/dashboard/overview.css | 3 --- frontend/styles/admin/dashboard/users.css | 11 +++++++---- frontend/templates/admin/dashboard.html | 16 ---------------- frontend/templates/admin/dashboard/users.html | 2 +- src/app/admin.rs | 2 +- 5 files changed, 9 insertions(+), 25 deletions(-) diff --git a/frontend/styles/admin/dashboard/overview.css b/frontend/styles/admin/dashboard/overview.css index 3bfe354..bcc8757 100644 --- a/frontend/styles/admin/dashboard/overview.css +++ b/frontend/styles/admin/dashboard/overview.css @@ -16,7 +16,6 @@ } .ct-series-a .ct-area { @apply fill-green-600; - /* fill-opacity: .1; */ } .ct-label { @apply fill-white text-white; @@ -30,13 +29,11 @@ @apply stroke-neutral-500; } - /* Change specific axes */ .ct-axis-title, .ct-axis-title text { @apply fill-white; } - /* Basic CSS for the tooltip (add to your stylesheet) */ .chartist-tooltip { @apply absolute z-100 hidden min-w-5 cursor-none rounded-sm border-1 border-neutral-500 bg-neutral-800 px-2.5 py-2 text-center transition-opacity delay-200 ease-linear; } diff --git a/frontend/styles/admin/dashboard/users.css b/frontend/styles/admin/dashboard/users.css index bcdc7b6..3e766ab 100644 --- a/frontend/styles/admin/dashboard/users.css +++ b/frontend/styles/admin/dashboard/users.css @@ -9,9 +9,11 @@ @apply h-full w-full flex-1 overflow-auto text-left; } table { - @apply w-full text-left; + @apply w-full border-separate border-spacing-0 text-left; thead { - @apply sticky border-b border-neutral-700; + th { + @apply sticky top-0 border-b border-neutral-700; + } } td, th { @@ -20,14 +22,15 @@ th { @apply sticky bg-black; } - tr { - @apply odd:bg-neutral-900; + td { + @apply border-b border-neutral-800; } input { @apply h-3.5 w-3.5 rounded-sm border-gray-300 bg-gray-100 text-blue-600 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600; } } .table-footer { + @apply py-1.5; a { @apply inline-flex w-12 cursor-pointer justify-center rounded-sm border-1 border-neutral-700 py-1.5 first-of-type:mr-3; } diff --git a/frontend/templates/admin/dashboard.html b/frontend/templates/admin/dashboard.html index 004fbda..86db7c0 100644 --- a/frontend/templates/admin/dashboard.html +++ b/frontend/templates/admin/dashboard.html @@ -26,22 +26,6 @@ Overview - New Users - + {% endfor %} diff --git a/src/app/admin.rs b/src/app/admin.rs index 3ad420c..459a980 100644 --- a/src/app/admin.rs +++ b/src/app/admin.rs @@ -25,7 +25,7 @@ mod view { Ok(Html { users_count }) } - // + // Display table of users dashboard pub async fn users( State(state): State, Query(query): Query, From 3cb93ee2fe4dec9b31c5de17560cf506a8c441db Mon Sep 17 00:00:00 2001 From: RileyChampion Date: Sun, 1 Jun 2025 22:49:11 -0700 Subject: [PATCH 11/12] Add functionality to give users roles, delete users and pagination changes --- frontend/styles/admin/dashboard/users.css | 2 +- frontend/styles/extentions/form.css | 3 + frontend/templates/admin/dashboard/users.html | 73 ++++++++++++++-- src/app/admin.rs | 65 +++++++++++++- src/db/user.rs | 84 +++++++++++++++++-- 5 files changed, 209 insertions(+), 18 deletions(-) diff --git a/frontend/styles/admin/dashboard/users.css b/frontend/styles/admin/dashboard/users.css index 3e766ab..f39cf2b 100644 --- a/frontend/styles/admin/dashboard/users.css +++ b/frontend/styles/admin/dashboard/users.css @@ -32,7 +32,7 @@ .table-footer { @apply py-1.5; a { - @apply inline-flex w-12 cursor-pointer justify-center rounded-sm border-1 border-neutral-700 py-1.5 first-of-type:mr-3; + @apply inline-flex w-12 justify-center rounded-sm border-1 border-neutral-700 py-1.5 first-of-type:mr-3; } } } diff --git a/frontend/styles/extentions/form.css b/frontend/styles/extentions/form.css index 5189d2a..5d3c88d 100644 --- a/frontend/styles/extentions/form.css +++ b/frontend/styles/extentions/form.css @@ -54,4 +54,7 @@ &.\:red { @apply border-lsd-red/30 bg-lsd-red/20 hover:bg-lsd-red/30; } + &.\:disabled { + @apply border-lsd-white/10 bg-lsd-white/5 hover:bg-lsd-white/5 cursor-default; + } } diff --git a/frontend/templates/admin/dashboard/users.html b/frontend/templates/admin/dashboard/users.html index caa2759..26a5fbc 100644 --- a/frontend/templates/admin/dashboard/users.html +++ b/frontend/templates/admin/dashboard/users.html @@ -44,17 +44,55 @@

New Users

{{ user.last_name }} {{ user.email }} - + + + + + + + - - {% endfor %}
- + {% endblock panel %} diff --git a/src/app/admin.rs b/src/app/admin.rs index 459a980..e5284cb 100644 --- a/src/app/admin.rs +++ b/src/app/admin.rs @@ -6,16 +6,74 @@ pub fn add_routes(router: AppRouter) -> AppRouter { router.restricted_routes(User::ADMIN, |r| { r.route("/admin/dashboard/overview", get(view::overview)) .route("/admin/dashboard/users", get(view::users)) + .route("/admin/action/users/{url}/changeRole", post(action::change_user_role)) + .route("/admin/action/users/{url}/remove", delete(action::remove_user)) }) } +mod action { + + use super::*; + + // Change user role + #[derive(serde::Deserialize)] + pub struct ActionQuery { + action: String, + role: String, + } + + // Change user's add/remove user role + pub async fn change_user_role( + State(state): State, + Path(url): Path, + Query(query): Query, + user: User, + ) -> AppResult { + if user.has_role(&state.db, "admin").await? { + let Some(_) = User::lookup_by_id(&state.db, url).await? else { + return Err(AppError::NotFound); + }; + + match query.action.as_str() { + "remove" => { + User::remove_role(&state.db, url, &query.role).await?; + Ok(()) + } + "add" => { + User::add_role(&state.db, url, &query.role).await?; + Ok(()) + } + _ => Err(AppError::NotAuthorized), + } + } else { + Err(AppError::NotAuthorized) + } + } + + pub async fn remove_user( + State(state): State, + Path(url): Path, + user: User, + ) -> AppResult { + if user.has_role(&state.db, "admin").await? { + let Some(_) = User::lookup_by_id(&state.db, url).await? else { + return Err(AppError::NotFound); + }; + User::remove(&state.db, url).await?; + Ok(()) + } else { + Err(AppError::NotAuthorized) + } + } +} + mod view { use super::*; /// Display admin overview dashboard pub async fn overview(State(state): State) -> AppResult { let q = ListUserQuery { page: 0, page_size: 25 }; - let users_count = User::list(&state.db, &q).await?.len(); + let users_count = User::list(&state.db, &q).await?.users.len(); #[derive(Template, WebTemplate)] #[template(path = "admin/dashboard/overview.html")] @@ -30,13 +88,14 @@ mod view { State(state): State, Query(query): Query, ) -> AppResult { - let users = User::list(&state.db, &query).await?; + let query_result = User::list(&state.db, &query).await?; #[derive(Template, WebTemplate)] #[template(path = "admin/dashboard/users.html")] pub struct Html { pub users: Vec, + pub has_next_page: bool, } - Ok(Html { users }) + Ok(Html { users: query_result.users, has_next_page: query_result.has_next_page }) } } diff --git a/src/db/user.rs b/src/db/user.rs index c795e71..9a34920 100644 --- a/src/db/user.rs +++ b/src/db/user.rs @@ -29,19 +29,29 @@ pub struct UpdateUser { #[derive(Debug, serde::Deserialize)] pub struct UserView { + pub id: i64, pub first_name: String, pub last_name: String, pub email: String, + pub is_admin: bool, + pub is_writer: bool, } impl From for UserView { fn from(user: User) -> Self { Self { + id: user.id, first_name: user.first_name.unwrap_or_default(), last_name: user.last_name.unwrap_or_default(), email: user.email, + is_writer: false, + is_admin: false, } } } +pub struct UserViewList { + pub users: Vec, + pub has_next_page: bool, +} #[derive(Debug, serde::Deserialize)] pub struct ListUserQuery { @@ -70,6 +80,13 @@ impl User { Ok(row.last_insert_rowid()) } + /// Delete a user + pub async fn remove(db: &Db, user_id: i64) -> AppResult<()> { + sqlx::query!(r#"DELETE FROM users WHERE id = ?"#, user_id).execute(db).await?; + Ok(()) + } + + /// Add role to a user pub async fn add_role(db: &Db, user_id: i64, role: &str) -> AppResult<()> { sqlx::query!(r#"INSERT INTO user_roles (user_id, role) VALUES (?, ?)"#, user_id, role) .execute(db) @@ -77,6 +94,22 @@ impl User { Ok(()) } + /// Remove role to a user + pub async fn remove_role(db: &Db, user_id: i64, role: &str) -> AppResult<()> { + sqlx::query!(r#"DELETE FROM user_roles WHERE user_id=? AND role=?"#, user_id, role) + .execute(db) + .await?; + Ok(()) + } + + /// Look up user by user_id, if one exists. + pub async fn lookup_by_id(db: &Db, user_id: i64) -> AppResult> { + let row = sqlx::query_as!(Self, "SELECT * FROM users WHERE id = ?", user_id) + .fetch_optional(db) + .await?; + Ok(row) + } + /// Lookup a user by email address, if one exists. pub async fn lookup_by_email(db: &Db, email: &str) -> AppResult> { let row = sqlx::query_as!(Self, "SELECT * FROM users WHERE email = ?", email) @@ -130,23 +163,58 @@ impl User { } //Query users based on params - pub async fn list(db: &Db, query: &ListUserQuery) -> AppResult> { + pub async fn list(db: &Db, query: &ListUserQuery) -> AppResult { let current_page = (query.page - 1) * query.page_size; - let users = sqlx::query_as!( + let next_page_check = query.page_size + 1; + let mut users = sqlx::query_as!( User, r#"SELECT u.* FROM users u LIMIT ? OFFSET ?"#, - query.page_size, + next_page_check, current_page, ) .fetch_all(db) - .await? - .into_iter() - .map(UserView::from) - .collect(); + .await?; + + let has_next_page = users.len() > query.page_size as usize; + users = if has_next_page { users[0..query.page_size as usize].to_vec() } else { users }; + + let user_ids: Vec = users.iter().map(|u| u.id).collect(); + + let user_roles: Vec<(i64, String)> = if !user_ids.is_empty() { + let placeholders = user_ids.iter().map(|_| "?").collect::>().join(","); + let query_str = + format!("SELECT user_id, role FROM user_roles WHERE user_id IN ({})", placeholders); + let mut query = sqlx::query_as(&query_str); + for user_id in &user_ids { + query = query.bind(user_id); + } + query.fetch_all(db).await? + } else { + Vec::new() + }; + + let user_view_list: Vec = users + .into_iter() + .map(|user| { + let user_roles_for_this_user: Vec = user_roles + .iter() + .filter(|(user_id, _)| *user_id == user.id) + .map(|(_, role)| role.clone()) + .collect(); + UserView { + id: user.id, + first_name: user.first_name.unwrap_or_default(), + last_name: user.last_name.unwrap_or_default(), + email: user.email, + is_writer: user_roles_for_this_user.contains(&"writer".to_string()), + is_admin: user_roles_for_this_user.contains(&"admin".to_string()), + } + }) + .collect(); - Ok(users) + Ok(UserViewList { users: user_view_list, has_next_page }) } } From 75f84640c97308aa8c4abb9d8d1a95c33ad7070e Mon Sep 17 00:00:00 2001 From: RileyChampion Date: Thu, 5 Jun 2025 22:54:09 -0700 Subject: [PATCH 12/12] prepared additional sql queries --- ...c0eba835e11cd8e1b07ea44046ae795008704.json | 44 +++++++++++++++++++ ...634a6970eeb4e1d3395f045ded747f0ce9d2a.json | 12 +++++ ...6322b98a16fa81e362e138d7ce99c4a020b4f.json | 4 +- ...31c5aac9eded9d0381e42b39d06e79547c5a6.json | 12 +++++ 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 .sqlx/query-6f540be5517aaffe1774bebe9a2c0eba835e11cd8e1b07ea44046ae795008704.json create mode 100644 .sqlx/query-73ffdf5be39aa5c4c160c2f77d6634a6970eeb4e1d3395f045ded747f0ce9d2a.json create mode 100644 .sqlx/query-e3f19de7c663b21e992056bdce331c5aac9eded9d0381e42b39d06e79547c5a6.json diff --git a/.sqlx/query-6f540be5517aaffe1774bebe9a2c0eba835e11cd8e1b07ea44046ae795008704.json b/.sqlx/query-6f540be5517aaffe1774bebe9a2c0eba835e11cd8e1b07ea44046ae795008704.json new file mode 100644 index 0000000..6a0f189 --- /dev/null +++ b/.sqlx/query-6f540be5517aaffe1774bebe9a2c0eba835e11cd8e1b07ea44046ae795008704.json @@ -0,0 +1,44 @@ +{ + "db_name": "SQLite", + "query": "SELECT * FROM users WHERE id = ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "first_name", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "last_name", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "email", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 4, + "type_info": "Datetime" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + true, + true, + false, + false + ] + }, + "hash": "6f540be5517aaffe1774bebe9a2c0eba835e11cd8e1b07ea44046ae795008704" +} diff --git a/.sqlx/query-73ffdf5be39aa5c4c160c2f77d6634a6970eeb4e1d3395f045ded747f0ce9d2a.json b/.sqlx/query-73ffdf5be39aa5c4c160c2f77d6634a6970eeb4e1d3395f045ded747f0ce9d2a.json new file mode 100644 index 0000000..3427c65 --- /dev/null +++ b/.sqlx/query-73ffdf5be39aa5c4c160c2f77d6634a6970eeb4e1d3395f045ded747f0ce9d2a.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM users WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "73ffdf5be39aa5c4c160c2f77d6634a6970eeb4e1d3395f045ded747f0ce9d2a" +} diff --git a/.sqlx/query-d23b45d169c59a7dd2c4756b49e6322b98a16fa81e362e138d7ce99c4a020b4f.json b/.sqlx/query-d23b45d169c59a7dd2c4756b49e6322b98a16fa81e362e138d7ce99c4a020b4f.json index ebd67c8..e8bbded 100644 --- a/.sqlx/query-d23b45d169c59a7dd2c4756b49e6322b98a16fa81e362e138d7ce99c4a020b4f.json +++ b/.sqlx/query-d23b45d169c59a7dd2c4756b49e6322b98a16fa81e362e138d7ce99c4a020b4f.json @@ -34,8 +34,8 @@ }, "nullable": [ false, - false, - false, + true, + true, false, false ] diff --git a/.sqlx/query-e3f19de7c663b21e992056bdce331c5aac9eded9d0381e42b39d06e79547c5a6.json b/.sqlx/query-e3f19de7c663b21e992056bdce331c5aac9eded9d0381e42b39d06e79547c5a6.json new file mode 100644 index 0000000..c0cf8d1 --- /dev/null +++ b/.sqlx/query-e3f19de7c663b21e992056bdce331c5aac9eded9d0381e42b39d06e79547c5a6.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM user_roles WHERE user_id=? AND role=?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "e3f19de7c663b21e992056bdce331c5aac9eded9d0381e42b39d06e79547c5a6" +}