Cleanup code for postgres conversion

This commit is contained in:
Chris Jean-Marie 2024-12-11 20:35:17 +00:00
parent d31c47853e
commit 4ff14e6fa1
30 changed files with 606 additions and 441 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@ backend/target
backend/db
backend/id_rsa
backend/id_rsa.pub
backend/sqlite3.env
backend/.env

8
backend/Cargo.lock generated
View File

@ -2603,6 +2603,7 @@ dependencies = [
"atoi",
"byteorder",
"bytes",
"chrono",
"crc",
"crossbeam-queue",
"either",
@ -2631,6 +2632,7 @@ dependencies = [
"tokio-stream",
"tracing",
"url",
"uuid",
]
[[package]]
@ -2683,6 +2685,7 @@ dependencies = [
"bitflags 2.6.0",
"byteorder",
"bytes",
"chrono",
"crc",
"digest",
"dotenvy",
@ -2711,6 +2714,7 @@ dependencies = [
"stringprep",
"thiserror",
"tracing",
"uuid",
"whoami",
]
@ -2724,6 +2728,7 @@ dependencies = [
"base64 0.22.1",
"bitflags 2.6.0",
"byteorder",
"chrono",
"crc",
"dotenvy",
"etcetera",
@ -2749,6 +2754,7 @@ dependencies = [
"stringprep",
"thiserror",
"tracing",
"uuid",
"whoami",
]
@ -2759,6 +2765,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680"
dependencies = [
"atoi",
"chrono",
"flume",
"futures-channel",
"futures-core",
@ -2773,6 +2780,7 @@ dependencies = [
"sqlx-core",
"tracing",
"url",
"uuid",
]
[[package]]

View File

@ -23,7 +23,7 @@ oauth2 = "4.4"
http = "1.1"
tower-http = { version = "0.6.1", features = ["full"] }
chrono = { version = "0.4.38", features = ["serde"] }
sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio", "macros"] }
sqlx = { version = "0.8", features = ["postgres", "runtime-tokio", "macros", "chrono", "uuid"] }
uuid = { version = "1.10", features = ["v4"] }
dotenvy = "0.15"
constant_time_eq = "0.3"

View File

@ -1 +0,0 @@
-- Add down migration script here

View File

@ -1,58 +0,0 @@
-- Add up migration script here
CREATE TABLE "oauth2_state_storage" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"csrf_state" text NOT NULL,
"pkce_code_verifier" text NOT NULL,
"return_url" text NOT NULL
);
CREATE TABLE "user_sessions" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"user_id" integer NOT NULL,
"session_token_p1" text NOT NULL,
"session_token_p2" text NOT NULL,
"created_at" integer NOT NULL,
"expires_at" integer NOT NULL
);
CREATE TABLE "users" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"created_at" integer NOT NULL,
"created_by" integer NOT NULL,
"updated_at" integer NOT NULL,
"updated_by" integer NOT NULL,
"email" text NOT NULL UNIQUE,
"name" text NOT NULL,
"family_name" text NOT NULL,
"given_name" text NOT NULL
);
CREATE TABLE IF NOT EXISTS roles (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"created_at" integer NOT NULL,
"created_by" integer NOT NULL,
"updated_at" integer NOT NULL,
"updated_by" integer NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT
);
CREATE TABLE IF NOT EXISTS user_roles (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"created_at" integer NOT NULL,
"created_by" integer NOT NULL,
"updated_at" integer NOT NULL,
"updated_by" integer NOT NULL,
"user_id" integer NOT NULL,
"role_id" integer NOT NULL
);
create TABLE IF NOT EXISTS role_permissions (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"created_at" integer NOT NULL,
"created_by" integer NOT NULL,
"updated_at" integer NOT NULL,
"updated_by" integer NOT NULL,
"role_id" integer NOT NULL,
"item" text NOT NULL
);

View File

@ -1 +0,0 @@
-- Add down migration script here

View File

@ -1,6 +0,0 @@
-- Add up migration script here
INSERT INTO "main"."roles" ("id", "created_at", "created_by", "updated_at", "updated_by", "name", "description") VALUES ('1', '0', '0', '0', '0', 'public', 'Users with only anonymous access');
INSERT INTO "main"."roles" ("id", "created_at", "created_by", "updated_at", "updated_by", "name", "description") VALUES ('2', '0', '0', '0', '0', 'normal', 'Users with no elevated privileges');
INSERT INTO "main"."roles" ("id", "created_at", "created_by", "updated_at", "updated_by", "name", "description") VALUES ('3', '0', '0', '0', '0', 'editor', 'Users with basic elevated privileges');
INSERT INTO "main"."roles" ("id", "created_at", "created_by", "updated_at", "updated_by", "name", "description") VALUES ('4', '0', '0', '0', '0', 'admin', 'Users with full administrative privileges');

View File

@ -1 +0,0 @@
-- Add down migration script here

View File

@ -1,12 +0,0 @@
-- Add up migration script here
-- Role permissions
INSERT INTO "main"."role_permissions" ("id", "created_at", "created_by", "updated_at", "updated_by", "role_id", "item") VALUES ('1', '0', '0', '0', '0', '1', '/');
INSERT INTO "main"."role_permissions" ("id", "created_at", "created_by", "updated_at", "updated_by", "role_id", "item") VALUES ('2', '0', '0', '0', '0', '1', '/login');
INSERT INTO "main"."role_permissions" ("id", "created_at", "created_by", "updated_at", "updated_by", "role_id", "item") VALUES ('3', '0', '0', '0', '0', '1', '/logout');
INSERT INTO "main"."role_permissions" ("id", "created_at", "created_by", "updated_at", "updated_by", "role_id", "item") VALUES ('4', '0', '0', '0', '0', '2', '/dashboard');
INSERT INTO "main"."role_permissions" ("id", "created_at", "created_by", "updated_at", "updated_by", "role_id", "item") VALUES ('5', '0', '0', '0', '0', '2', '/profile');
INSERT INTO "main"."role_permissions" ("id", "created_at", "created_by", "updated_at", "updated_by", "role_id", "item") VALUES ('6', '0', '0', '0', '0', '4', '/useradmin');
INSERT INTO "main"."role_permissions" ("id", "created_at", "created_by", "updated_at", "updated_by", "role_id", "item") VALUES ('7', '0', '0', '0', '0', '4', '/users');
-- First user is an admin
INSERT INTO "main"."user_roles" ("id", "created_at", "created_by", "updated_at", "updated_by", "user_id", "role_id") VALUES ('2', '0', '0', '0', '0', '1', '4');

View File

@ -1,9 +0,0 @@
-- Add down migration script here
-- Delete role records
DELETE FROM "main"."roles" WHERE "id" = '5';
-- Delete permission records
DELETE FROM "main"."role_permissions" WHERE "id" = '8';
-- Delete user role records
DELETE FROM "main"."user_roles" WHERE "role_id" = '5';

View File

@ -1,9 +0,0 @@
-- Add up migration script here
-- Add roles for calendar
INSERT INTO "main"."roles" ("id", "created_at", "created_by", "updated_at", "updated_by", "name", "description") VALUES ('5', '0', '0', '0', '0', 'calendar', 'Users with access to the calendar');
-- Add permissions for calendar
INSERT INTO "main"."role_permissions" ("id", "created_at", "created_by", "updated_at", "updated_by", "role_id", "item") VALUES ('8', '0', '0', '0', '0', '5', '/cottagecalendar');
-- Add user roles for calendar
INSERT INTO "main"."user_roles" ("id", "created_at", "created_by", "updated_at", "updated_by", "user_id", "role_id") VALUES ('1', '0', '0', '0', '0', '1', '5');

View File

@ -1,4 +0,0 @@
-- Add down migration script here
drop table if exists `wishlist_items`;
delete from `role_permissions` where id = 9;

View File

@ -1,16 +0,0 @@
-- Add up migration script here
CREATE TABLE
`wishlist_items` (
`id` integer not null primary key autoincrement,
`created_at` INTEGER not null default CURRENT_TIMESTAMP,
`created_by` ineger null,
`updated_at` INTEGER null default CURRENT_TIMESTAMP,
`updated_by` integer null,
`user_id` INTEGER null,
`item` varchar(255) null,
`item_url` varchar(255) null,
`purchased_by` INTEGER null,
unique (`id`)
);
insert into `role_permissions` (`created_at`, `created_by`, `id`, `item`, `role_id`, `updated_at`, `updated_by`) values ('0', '0', '9', '/wishlist', '2', '0', '0')

View File

@ -1,2 +0,0 @@
-- Add down migration script here
alter table wishlist_items drop column received_at;

View File

@ -1,2 +0,0 @@
-- Add up migration script here
alter table wishlist_items add column received_at integer null;

View File

@ -1,5 +0,0 @@
-- Add down migration script here
drop table gift_exchange;
drop table gift_exchange_participants;
delete from role_permissions where item = '/giftexchange';

View File

@ -1,28 +0,0 @@
-- Add up migration script here
CREATE TABLE
`gift_exchange` (
`id` integer not null primary key autoincrement,
`created_at` INTEGER not null default CURRENT_TIMESTAMP,
`created_by` integer not null default 0,
`updated_at` INTEGER not null default CURRENT_TIMESTAMP,
`updated_by` integer not null default 0,
`name` varchar(255) not null,
`exchange_date` INTEGER not null,
`status` INTEGER not null default 0,
unique (`id`)
);
CREATE TABLE
`gift_exchange_participants` (
`id` integer not null primary key autoincrement,
`created_at` INTEGER not null default CURRENT_TIMESTAMP,
`created_by` integer not null default 0,
`updated_at` INTEGER not null default CURRENT_TIMESTAMP,
`updated_by` integer not null default 0,
`exchange_id` INTEGER not null,
`participant_id` INTEGER not null,
`gifter_id` INTEGER not null,
unique (`id`)
);
insert into `role_permissions` (`created_at`, `created_by`, `id`, `item`, `role_id`, `updated_at`, `updated_by`) values ('0', '0', '10', '/giftexchange', '2', '0', '0')

View File

@ -1,5 +0,0 @@
-- Add down migration script here
drop table if exists `calendar_events`;
drop table if exists `calendar_event_types`;
drop table if exists `calendar`;

View File

@ -1,42 +0,0 @@
-- Add up migration script here
-- Calendars
-- 1 - Cottage
-- 2 - Family tree
create table calendar (
id integer not null primary key autoincrement,
created_at integer not null default CURRENT_TIMESTAMP,
created_by integer not null default 0,
updated_at integer null default CURRENT_TIMESTAMP,
updated_by integer not null default 0,
name varchar(255) not null
);
-- Event types
-- 1 - Rental
-- 2 - Life event
create table calendar_event_types (
id integer not null primary key autoincrement,
created_at integer not null default CURRENT_TIMESTAMP,
created_by integer not null default 0,
updated_at integer null default CURRENT_TIMESTAMP,
updated_by integer not null default 0,
name varchar(255) not null
);
create table calendar_events (
id integer not null primary key autoincrement,
created_at integer not null default CURRENT_TIMESTAMP,
created_by integer not null default 0,
updated_at integer null default CURRENT_TIMESTAMP,
updated_by integer not null default 0,
calendar_id integer not null,
event_type_id integer not null,
title varchar(255) not null,
description varchar(255) null,
start_time integer null,
end_time integer null,
repeat_type integer not null default 0, -- 0 - None, 1 - Daily, 2 - Weekly, 3 - Monthly, 4 - Yearly, 5 - Day of week, 6 - Day of month
repeat_interval integer not null default 0,
celebrate boolean not null default 0
);

View File

@ -0,0 +1,13 @@
-- Drop Postgres tables
drop table oauth2_state_storage;
drop table user_sessions;
drop table users;
drop table roles;
drop table user_roles;
drop table role_permissions;
drop table wishlist_items;
drop table gift_exchange;
drop table gift_exchange_participants;
drop table calendar;
drop table calendar_event_types;
drop table calendar_events;

View File

@ -0,0 +1,384 @@
-- Create Postgres tables
create table if not exists oauth2_state_storage (
id uuid PRIMARY KEY default gen_random_uuid(),
csrf_state text NOT NULL,
pkce_code_verifier text NOT NULL,
return_url text NOT NULL
);
create table if not exists user_sessions (
id uuid PRIMARY KEY default gen_random_uuid(),
user_id uuid NOT NULL,
session_token_p1 text NOT NULL,
session_token_p2 text NOT NULL,
created_at timestamp NOT NULL,
expires_at timestamp NOT NULL
);
create table if not exists users (
id uuid PRIMARY KEY default gen_random_uuid(),
created_at timestamp NOT NULL default now(),
created_by uuid NOT NULL,
updated_at timestamp NOT NULL default now(),
updated_by uuid NOT NULL,
email text NOT NULL UNIQUE,
"name" text NOT NULL,
family_name text NOT NULL,
given_name text NOT NULL
);
create table IF NOT EXISTS roles (
id uuid PRIMARY KEY default gen_random_uuid(),
created_at timestamp NOT NULL default now(),
created_by uuid NOT NULL,
updated_at timestamp NOT NULL default now(),
updated_by uuid NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT
);
create table IF NOT EXISTS user_roles (
id uuid PRIMARY KEY default gen_random_uuid(),
created_at timestamp NOT NULL default now(),
created_by uuid NOT NULL,
updated_at timestamp NOT NULL default now(),
updated_by uuid NOT NULL,
user_id uuid NOT NULL,
role_id uuid NOT NULL
);
create table IF NOT EXISTS role_permissions (
id uuid PRIMARY KEY default gen_random_uuid(),
created_at timestamp NOT NULL default now(),
created_by uuid NOT NULL,
updated_at timestamp NOT NULL default now(),
updated_by uuid NOT NULL,
role_id uuid NOT NULL,
item text NOT NULL
);
create table if not exists wishlist_items (
id uuid PRIMARY KEY default gen_random_uuid(),
created_at timestamp not null default now(),
created_by uuid null,
updated_at timestamp null default now(),
updated_by uuid null,
user_id uuid null,
item varchar(255) null,
item_url varchar(255) null,
purchased_by uuid null,
received_at timestamp null
);
create table if not exists gift_exchange (
id uuid PRIMARY KEY default gen_random_uuid(),
created_at timestamp not null default now(),
created_by uuid not null,
updated_at timestamp not null default now(),
updated_by uuid not null,
"name" varchar(255) not null,
exchange_date timestamp not null,
"status" INTEGER not null default 0
);
create table if not exists gift_exchange_participants (
id uuid PRIMARY KEY default gen_random_uuid(),
created_at timestamp not null default now(),
created_by uuid not null,
updated_at timestamp not null default now(),
updated_by uuid not null,
exchange_id uuid not null,
participant_id uuid not null,
gifter_id uuid not null
);
-- Calendars
-- 1 - Cottage
-- 2 - Family tree
create table if not exists calendar (
id uuid PRIMARY KEY default gen_random_uuid(),
created_at timestamp not null default now(),
created_by uuid not null,
updated_at timestamp null default now(),
updated_by uuid not null,
"name" varchar(255) not null
);
-- Event types
-- 1 - Rental
-- 2 - Life event
create table if not exists calendar_event_types (
id uuid PRIMARY KEY default gen_random_uuid(),
created_at timestamp not null default now(),
created_by uuid not null,
updated_at timestamp null default now(),
updated_by uuid not null,
"name" varchar(255) not null
);
create table if not exists calendar_events (
id uuid PRIMARY KEY default gen_random_uuid(),
created_at timestamp not null default now(),
created_by uuid not null,
updated_at timestamp null default now(),
updated_by uuid not null,
calendar_id uuid not null,
event_type_id uuid not null,
title varchar(255) not null,
"description" varchar(255) null,
start_time timestamp null,
end_time timestamp null,
repeat_type integer not null default 0,
-- 0 - None, 1 - Daily, 2 - Weekly, 3 - Monthly, 4 - Yearly, 5 - Day of week, 6 - Day of month
repeat_interval integer not null default 0,
celebrate boolean not null default true
);
do $$
declare user_uuid uuid := gen_random_uuid();
begin -- Initial user
insert into users (
id,
"name",
created_by,
updated_by,
email,
family_name,
given_name
)
values
(
user_uuid,
'admin',
user_uuid,
user_uuid,
'admin@jean-marie.ca',
'',
'admin'
);
-- Initial roles
INSERT INTO
roles (created_by, updated_by, "name", "description")
VALUES
(
user_uuid,
user_uuid,
'public',
'Users with only anonymous access'
);
INSERT INTO
roles (created_by, updated_by, "name", "description")
VALUES
(
user_uuid,
user_uuid,
'normal',
'Users with no elevated privileges'
);
INSERT INTO
roles (created_by, updated_by, "name", "description")
VALUES
(
user_uuid,
user_uuid,
'editor',
'Users with basic elevated privileges'
);
INSERT INTO
roles (created_by, updated_by, "name", "description")
VALUES
(
user_uuid,
user_uuid,
'admin',
'Users with full administrative privileges'
);
INSERT INTO
roles (created_by, updated_by, "name", "description")
VALUES
(
user_uuid,
user_uuid,
'calendar',
'Users with access to the calendar'
);
-- Initial permissions
INSERT INTO
role_permissions (created_by, updated_by, role_id, item)
VALUES
(
user_uuid,
user_uuid,
(
SELECT
id
FROM
roles
WHERE
"name" = 'public'
),
'/'
);
INSERT INTO
role_permissions (created_by, updated_by, role_id, item)
VALUES
(
user_uuid,
user_uuid,
(
SELECT
id
FROM
roles
WHERE
"name" = 'public'
),
'/login'
);
INSERT INTO
role_permissions (created_by, updated_by, role_id, item)
VALUES
(
user_uuid,
user_uuid,
(
SELECT
id
FROM
roles
WHERE
"name" = 'public'
),
'/logout'
);
INSERT INTO
role_permissions (created_by, updated_by, role_id, item)
VALUES
(
user_uuid,
user_uuid,
(
SELECT
id
FROM
roles
WHERE
"name" = 'normal'
),
'/dashboard'
);
INSERT INTO
role_permissions (created_by, updated_by, role_id, item)
VALUES
(
user_uuid,
user_uuid,
(
SELECT
id
FROM
roles
WHERE
"name" = 'normal'
),
'/profile'
);
INSERT INTO
role_permissions (created_by, updated_by, role_id, item)
VALUES
(
user_uuid,
user_uuid,
(
SELECT
id
FROM
roles
WHERE
"name" = 'admin'
),
'/useradmin'
);
INSERT INTO
role_permissions (created_by, updated_by, role_id, item)
VALUES
(
user_uuid,
user_uuid,
(
SELECT
id
FROM
roles
WHERE
"name" = 'admin'
),
'/users'
);
INSERT INTO
role_permissions (created_by, updated_by, role_id, item)
VALUES
(
user_uuid,
user_uuid,
(
SELECT
id
FROM
roles
WHERE
"name" = 'calendar'
),
'/calendar'
);
INSERT INTO
role_permissions (created_by, updated_by, role_id, item)
VALUES
(
user_uuid,
user_uuid,
(
SELECT
id
FROM
roles
WHERE
"name" = 'normal'
),
'/wishlist'
);
INSERT INTO
role_permissions (created_by, updated_by, role_id, item)
VALUES
(
user_uuid,
user_uuid,
(
SELECT
id
FROM
roles
WHERE
"name" = 'normal'
),
'/giftexchange'
);
end $$;

View File

@ -2,11 +2,11 @@ use askama::Template;
use askama_axum::{IntoResponse, Response};
use axum::{
extract::{Path, State},
response::{Html, Json, Redirect},
response::{Html, Redirect},
Extension,
};
use http::StatusCode;
use sqlx::SqlitePool;
use sqlx::PgPool;
use crate::{
middlewares::is_authorized,
@ -47,7 +47,7 @@ struct CalendarTemplate {
pub async fn calendar(
Extension(user_data): Extension<Option<UserData>>,
State(db_pool): State<SqlitePool>,
State(db_pool): State<PgPool>,
) -> impl IntoResponse {
// Is the user logged in?
let logged_in = user_data.is_some();
@ -89,7 +89,7 @@ pub async fn calendar(
pub async fn get_events(
Path(calendar): Path<String>,
State(db_pool): State<SqlitePool>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> String {
println!("Calendar: {}", calendar);
@ -101,8 +101,8 @@ pub async fn get_events(
if logged_in {
// Extract the user data.
let user = user_data.as_ref().unwrap().clone();
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
let _user = user_data.as_ref().unwrap().clone();
let _userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
if is_authorized("/calendar", user_data, db_pool.clone()).await {
// Get requested calendar events from database

View File

@ -17,8 +17,7 @@ use oauth2::{
TokenResponse, TokenUrl,
};
use chrono::Utc;
use sqlx::SqlitePool;
use sqlx::PgPool;
use std::collections::HashMap;
use uuid::Uuid;
@ -61,7 +60,7 @@ fn get_client(hostname: String) -> Result<BasicClient, AppError> {
pub async fn login(
Extension(user_data): Extension<Option<UserData>>,
Query(mut params): Query<HashMap<String, String>>,
State(db_pool): State<SqlitePool>,
State(db_pool): State<PgPool>,
Host(hostname): Host,
) -> Result<Redirect, AppError> {
if user_data.is_some() {
@ -89,21 +88,22 @@ pub async fn login(
.set_pkce_challenge(pkce_code_challenge)
.url();
sqlx::query(
"INSERT INTO oauth2_state_storage (csrf_state, pkce_code_verifier, return_url) VALUES (?, ?, ?);",
sqlx::query!(
"INSERT INTO oauth2_state_storage (csrf_state, pkce_code_verifier, return_url) VALUES ($1, $2, $3);",csrf_state.secret(), pkce_code_verifier.secret(), return_url
)
.bind(csrf_state.secret())
.bind(pkce_code_verifier.secret())
.bind(return_url)
.execute(&db_pool)
.await?;
.await
.map_err(|e| {
eprintln!("Error inserting into oauth2_state_storage: {}", e);
AppError::new("Error inserting into oauth2_state_storage")
})?;
Ok(Redirect::to(authorize_url.as_str()))
}
pub async fn google_auth_return(
Query(mut params): Query<HashMap<String, String>>,
State(db_pool): State<SqlitePool>,
State(db_pool): State<PgPool>,
cookie: Option<TypedHeader<Cookie>>,
Host(hostname): Host,
) -> Result<impl IntoResponse, AppError> {
@ -120,7 +120,7 @@ pub async fn google_auth_return(
);
let query: (String, String) = sqlx::query_as(
r#"DELETE FROM oauth2_state_storage WHERE csrf_state = ? RETURNING pkce_code_verifier,return_url"#,
r#"DELETE FROM oauth2_state_storage WHERE csrf_state = $1 RETURNING pkce_code_verifier,return_url"#,
)
.bind(state.secret())
.fetch_one(&db_pool)
@ -130,6 +130,7 @@ pub async fn google_auth_return(
let _return_url = query.1;
let pkce_code_verifier = PkceCodeVerifier::new(pkce_code_verifier);
// Exchange the code with a token.
let client = get_client(hostname)?;
let token_response = tokio::task::spawn_blocking(move || {
@ -185,21 +186,15 @@ pub async fn google_auth_return(
// Check if user exists in database
// If not, create a new user
let query: Result<(i64,), _> = sqlx::query_as(r#"SELECT id FROM users WHERE email=?"#)
let query: Result<(uuid::Uuid,), _> = sqlx::query_as(r#"SELECT id FROM users WHERE email=$1"#)
.bind(email.as_str())
.fetch_one(&db_pool)
.await;
let user_id = if let Ok(query) = query {
query.0
} else {
let now = Utc::now().timestamp();
// Add user
let query: (i64,) = sqlx::query_as("INSERT INTO users (created_at, created_by, updated_at, updated_by, email, name, family_name, given_name) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING id")
.bind(now)
.bind(0 as i64)// Created by system
.bind(now)
.bind(0 as i64) // Updated by system
let query: (uuid::Uuid,) = sqlx::query_as(r#"INSERT INTO users (created_by, updated_by, email, name, family_name, given_name) VALUES ((SELECT id FROM users WHERE "name" = 'admin'), (SELECT id FROM users WHERE "name" = 'admin'), $1, $2, $3, $4) RETURNING id"#)
.bind(email.clone())
.bind(name.clone())
.bind(family_name.clone())
@ -208,13 +203,8 @@ pub async fn google_auth_return(
.await?;
// Add public role
sqlx::query("INSERT INTO user_roles (created_at, created_by, updated_at, updated_by, user_id, role_id) VALUES (?, ?, ?, ?, ?, ?)")
.bind(now)
.bind(0 as i64)// Created by system
.bind(now)
.bind(0 as i64) // Updated by system
sqlx::query(r#"INSERT INTO user_roles (created_by, updated_by, user_id, role_id) VALUES ((SELECT id FROM users WHERE "name" = 'admin'), (SELECT id FROM users WHERE "name" = 'admin'), $1, (SELECT id FROM roles WHERE "name" = 'public'))"#)
.bind(query.0)
.bind("1")
.execute(&db_pool)
.await?;
@ -242,18 +232,15 @@ pub async fn google_auth_return(
session_token
)
).map_err(|_| AppError::new("Failed to create session token header"))?;
let now = Utc::now().timestamp();
sqlx::query(
"INSERT INTO user_sessions
(session_token_p1, session_token_p2, user_id, created_at, expires_at)
VALUES (?, ?, ?, ?, ?);",
VALUES ($1, $2, $3, now(), now() + interval '1 day');",
)
.bind(session_token_p1)
.bind(session_token_p2)
.bind(user_id) // Set user to anonymous
.bind(now)
.bind(now + 60 * 60 * 24)
.execute(&db_pool)
.await?;
}
@ -263,12 +250,12 @@ pub async fn google_auth_return(
pub async fn logout(
cookie: Option<TypedHeader<Cookie>>,
State(db_pool): State<SqlitePool>,
State(db_pool): State<PgPool>,
) -> Result<impl IntoResponse, AppError> {
if let Some(cookie) = cookie {
if let Some(session_token) = cookie.get("session_token") {
let session_token: Vec<&str> = session_token.split('_').collect();
let _ = sqlx::query("DELETE FROM user_sessions WHERE session_token_1 = ?")
let _ = sqlx::query("DELETE FROM user_sessions WHERE session_token_1 = $1")
.bind(session_token[0])
.execute(&db_pool)
.await;

View File

@ -5,9 +5,11 @@ use axum::{
};
use secret_gift_exchange::{giftexchange, giftexchange_save, giftexchanges};
use sqlx::migrate::Migrator;
use sqlx::{sqlite::SqlitePoolOptions, SqlitePool};
use sqlx::{PgPool, postgres::PgPoolOptions};
use std::net::SocketAddr;
use tower_http::services::ServeDir;
use dotenvy::var;
mod calendar;
mod email;
@ -30,7 +32,7 @@ use wishlist::{user_wishlist, user_wishlist_add, user_wishlist_add_item, user_wi
#[derive(Clone)]
pub struct AppState {
pub db_pool: SqlitePool,
pub db_pool: PgPool,
}
#[tokio::main]
@ -38,13 +40,12 @@ async fn main() {
// initialize tracing
tracing_subscriber::fmt::init();
let db_pool = SqlitePoolOptions::new()
.max_connections(5)
.connect("sqlite://db/db.sqlite3")
.await;
// Get the server settings from the env file
let database_url = var("DATABASE_URL").expect("DATABASE_URL not set");
let db_pool = PgPoolOptions::new().connect(&database_url).await.unwrap();
let app_state = AppState {
db_pool: db_pool.expect("Failed to get db_pool"),
db_pool: db_pool,
};
static MIGRATOR: Migrator = sqlx::migrate!();
@ -72,7 +73,7 @@ async fn main() {
.route("/getevents/:calendar", get(get_events))
// Wishlist
.route("/wishlists", get(wishlists))
.route("/wishlists", get(wishlists))
.route("/userwishlist/:user_id", get(user_wishlist))
.route("/userwishlist/add/:user_id", get(user_wishlist_add).post(user_wishlist_add_item))
.route("/userwishlist/edit/:item_id", get(user_wishlist_edit_item).post(user_wishlist_save_item))

View File

@ -1,20 +1,14 @@
use std::path::Path;
use super::{AppError, UserData};
use axum::{
body::Body,
extract::State,
http::Request,
middleware::Next,
response::IntoResponse,
};
use axum::{body::Body, extract::State, http::Request, middleware::Next, response::IntoResponse};
use axum_extra::TypedHeader;
use chrono::Utc;
use headers::Cookie;
use sqlx::SqlitePool;
use sqlx::PgPool;
pub async fn inject_user_data(
State(db_pool): State<SqlitePool>,
State(db_pool): State<PgPool>,
cookie: Option<TypedHeader<Cookie>>,
mut request: Request<Body>,
next: Next,
@ -22,8 +16,8 @@ pub async fn inject_user_data(
if let Some(cookie) = cookie {
if let Some(session_token) = cookie.get("session_token") {
let session_token: Vec<&str> = session_token.split('_').collect();
let query: Result<(i64, i64, String), _> = sqlx::query_as(
r#"SELECT user_id,expires_at,session_token_p2 FROM user_sessions WHERE session_token_p1=?"#,
let query: Result<(uuid::Uuid, chrono::NaiveDateTime, String), _> = sqlx::query_as(
r#"SELECT user_id,expires_at,session_token_p2 FROM user_sessions WHERE session_token_p1=$1"#,
)
.bind(session_token[0])
.fetch_one(&db_pool)
@ -44,14 +38,14 @@ pub async fn inject_user_data(
) {
let id = query.0;
let expires_at = query.1;
if expires_at > Utc::now().timestamp() {
let row = sqlx::query_as!(
UserData,
"SELECT * FROM users WHERE id = ?",
id
)
if expires_at > Utc::now().naive_local() {
let row: UserData = sqlx::query_as(
r#"SELECT id, created_at, created_by, updated_at, updated_by, email, name, family_name, given_name FROM users WHERE id = $1"#,
)
.bind(id)
.fetch_one(&db_pool)
.await?;
.await
.unwrap();
request.extensions_mut().insert(Some(UserData {
id: row.id,
@ -62,7 +56,7 @@ pub async fn inject_user_data(
email: row.email,
name: row.name,
family_name: row.family_name,
given_name: row.given_name
given_name: row.given_name,
}));
}
}
@ -75,9 +69,9 @@ pub async fn inject_user_data(
Ok(next.run(request).await)
}
pub async fn is_authorized(path: &str, user_data: Option<UserData>, db_pool: SqlitePool) -> bool {
pub async fn is_authorized(path: &str, user_data: Option<UserData>, db_pool: PgPool) -> bool {
if let Some(user_data) = user_data {
let query: Result<(i64,), _> = match path {
let query: Result<(uuid::Uuid,), _> = match path {
"/profile" => {
return true;
}
@ -85,10 +79,11 @@ pub async fn is_authorized(path: &str, user_data: Option<UserData>, db_pool: Sql
// loop through path to find a permission
let mut remaining_path = Path::new(path);
loop {
let query: Result<(String,), _> = sqlx::query_as(r#"select r.item from role_permissions r where item = ?"#)
.bind(remaining_path.to_str().unwrap())
.fetch_one(&db_pool)
.await;
let query: Result<(String,), _> =
sqlx::query_as(r#"select r.item from role_permissions r where item = $1"#)
.bind(remaining_path.to_str().unwrap())
.fetch_one(&db_pool)
.await;
if let Ok(query) = query {
if query.0 != "" {
break;
@ -99,7 +94,7 @@ pub async fn is_authorized(path: &str, user_data: Option<UserData>, db_pool: Sql
}
remaining_path = remaining_path.parent().unwrap();
}
sqlx::query_as(r#"select u.id from role_permissions r join user_roles ur on ur.role_id = r.role_id join users u on u.id = ur.user_id where item = ? and email = ?"#)
sqlx::query_as(r#"select u.id from role_permissions r join user_roles ur on ur.role_id = r.role_id join users u on u.id = ur.user_id where item = $1 and email = $2"#)
.bind(remaining_path.to_str().unwrap())
.bind(user_data.email.as_str())
.fetch_one(&db_pool)
@ -114,4 +109,4 @@ pub async fn is_authorized(path: &str, user_data: Option<UserData>, db_pool: Sql
}
}
return false;
}
}

View File

@ -5,7 +5,7 @@ use axum::{
Extension,
};
use http::StatusCode;
use sqlx::SqlitePool;
use sqlx::PgPool;
use crate::{
middlewares::is_authorized,
@ -67,7 +67,7 @@ struct DashboardTemplate {
}
pub async fn index(
State(db_pool): State<SqlitePool>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> impl IntoResponse {
// Is the user logged in?
@ -93,7 +93,7 @@ pub async fn index(
}
pub async fn dashboard(
State(db_pool): State<SqlitePool>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> impl IntoResponse {
// Is the user logged in?
@ -127,7 +127,7 @@ pub async fn dashboard(
/// Handles the profile page.
pub async fn profile(
State(db_pool): State<SqlitePool>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> impl IntoResponse {
// Is the user logged in?
@ -158,8 +158,8 @@ pub async fn profile(
}
pub async fn user_profile(
Path(user_id): Path<i64>,
State(db_pool): State<SqlitePool>,
Path(user_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> impl IntoResponse {
// Is the user logged in?
@ -171,7 +171,8 @@ pub async fn user_profile(
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
// Extract the user data.
let profile = sqlx::query_as!(UserData, "SELECT * FROM users WHERE id = ?", user_id)
let profile = sqlx::query_as( "SELECT * FROM users WHERE id = $1")
.bind(user_id)
.fetch_one(&db_pool)
.await
.unwrap();
@ -215,7 +216,7 @@ struct UserAdminTemplate {
pub async fn useradmin(
Extension(user_data): Extension<Option<UserData>>,
State(db_pool): State<SqlitePool>,
State(db_pool): State<PgPool>,
) -> impl IntoResponse {
// Is the user logged in?
let logged_in = user_data.is_some();
@ -261,17 +262,7 @@ pub async fn about(Extension(user_data): Extension<Option<UserData>>) -> impl In
let logged_in = user_data.is_some();
// Set empty user
let mut user = UserData {
id: 0,
email: "".to_string(),
created_at: 0,
created_by: 0,
updated_at: 0,
updated_by: 0,
name: "".to_string(),
family_name: "".to_string(),
given_name: "".to_string(),
};
let mut user = UserData::default();
if logged_in {
// Extract the user data.
@ -294,17 +285,7 @@ pub async fn contact(Extension(user_data): Extension<Option<UserData>>) -> impl
let logged_in = user_data.is_some();
// Set empty user
let mut user = UserData {
id: 0,
email: "".to_string(),
created_at: 0,
created_by: 0,
updated_at: 0,
updated_by: 0,
name: "".to_string(),
family_name: "".to_string(),
given_name: "".to_string(),
};
let mut user = UserData::default();
if logged_in {
// Extract the user data.

View File

@ -1,5 +1,3 @@
use std::collections::HashMap;
use askama::Template;
use askama_axum::{IntoResponse, Response};
use axum::{
@ -9,10 +7,9 @@ use axum::{
Extension, Form, Json, RequestExt,
};
use axum_extra::response::Html;
use chrono::Utc;
use http::{header::CONTENT_TYPE, StatusCode};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, SqlitePool};
use sqlx::{FromRow, PgPool};
use crate::{
middlewares::is_authorized,
@ -99,7 +96,7 @@ struct GiftExchangesTemplate {
pub async fn giftexchanges(
Extension(user_data): Extension<Option<UserData>>,
State(db_pool): State<SqlitePool>,
State(db_pool): State<PgPool>,
) -> impl IntoResponse {
// Is the user logged in?
let logged_in = user_data.is_some();
@ -159,7 +156,7 @@ struct GiftExchangeTemplate {
pub async fn giftexchange(
Path(exchange_id): Path<i64>,
Extension(user_data): Extension<Option<UserData>>,
State(db_pool): State<SqlitePool>,
State(db_pool): State<PgPool>,
) -> impl IntoResponse {
// Is the user logged in?
let logged_in = user_data.is_some();
@ -174,11 +171,9 @@ pub async fn giftexchange(
let user_roles = get_user_roles_display(userid, &db_pool.clone()).await;
// Get gift exchange
let giftexchange = match sqlx::query_as!(
GiftExchange,
"SELECT * FROM gift_exchange WHERE id = ?",
exchange_id
)
let giftexchange = match sqlx::query_as(
"SELECT * FROM gift_exchange WHERE id = ?")
.bind(exchange_id)
.fetch_one(&db_pool)
.await
{
@ -188,7 +183,7 @@ pub async fn giftexchange(
// Get participants
let participants = sqlx::query_as::<_, UserData>(
"select * from users where users.id in (select participant_id from gift_exchange_participants where exchange_id = ?)",
"select * from users where users.id in (select participant_id from gift_exchange_participants where exchange_id = $1)",
)
.bind(exchange_id)
.fetch_all(&db_pool)
@ -197,7 +192,7 @@ pub async fn giftexchange(
// Get non participants
let non_participants = sqlx::query_as::<_, UserData>(
"select * from users where users.id not in (select participant_id from gift_exchange_participants where exchange_id = ?)",
"select * from users where users.id not in (select participant_id from gift_exchange_participants where exchange_id = $1)",
)
.bind(exchange_id)
.fetch_all(&db_pool)
@ -225,15 +220,15 @@ pub async fn giftexchange(
pub struct ExchangeForm {
name: String,
exchange_date: String,
non_participants: Vec<i64>,
non_participants: Vec<uuid::Uuid>,
}
pub async fn giftexchange_save(
State(db_pool): State<SqlitePool>,
State(_db_pool): State<PgPool>,
request: Request<Body>,
) -> impl IntoResponse {
let content_type_header = request.headers().get(CONTENT_TYPE);
let content_type = content_type_header.and_then(|value| value.to_str().ok());
let _content_type = content_type_header.and_then(|value| value.to_str().ok());
/* if let Some(content_type) = content_type {
if content_type.starts_with("application/json") {

View File

@ -4,21 +4,20 @@ use axum::{
response::Redirect,
Extension,
};
use chrono::Utc;
///User related structs and functions
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, SqlitePool};
use sqlx::{FromRow, PgPool};
use crate::middlewares::is_authorized;
#[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)]
pub struct UserData {
pub id: i64,
pub created_at: i64,
pub created_by: i64,
pub updated_at: i64,
pub updated_by: i64,
pub id: uuid::Uuid,
pub created_at: chrono::NaiveDateTime,
pub created_by: uuid::Uuid,
pub updated_at: chrono::NaiveDateTime,
pub updated_by: uuid::Uuid,
pub email: String,
pub name: String,
pub family_name: String,
@ -27,58 +26,58 @@ pub struct UserData {
#[derive(Serialize, Deserialize)]
pub struct RoleData {
pub id: i64,
pub created_at: i64,
pub created_by: i64,
pub updated_at: i64,
pub updated_by: i64,
pub id: uuid::Uuid,
pub created_at: chrono::NaiveDateTime,
pub created_by: uuid::Uuid,
pub updated_at: chrono::NaiveDateTime,
pub updated_by: uuid::Uuid,
pub name: String,
pub description: String,
}
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct UserRoles {
pub id: i64,
pub created_at: i64,
pub created_by: i64,
pub updated_at: i64,
pub updated_by: i64,
pub user_id: i64,
pub role_id: i64,
pub id: uuid::Uuid,
pub created_at: chrono::NaiveDateTime,
pub created_by: uuid::Uuid,
pub updated_at: chrono::NaiveDateTime,
pub updated_by: uuid::Uuid,
pub user_id: uuid::Uuid,
pub role_id: uuid::Uuid,
}
#[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)]
pub struct UserRolesDisplay {
pub id: i64,
pub created_at: i64,
pub created_by: i64,
pub updated_at: i64,
pub updated_by: i64,
pub user_id: i64,
pub id: uuid::Uuid,
pub created_at: chrono::NaiveDateTime,
pub created_by: uuid::Uuid,
pub updated_at: chrono::NaiveDateTime,
pub updated_by: uuid::Uuid,
pub user_id: uuid::Uuid,
pub user_name: String,
pub role_id: i64,
pub role_id: uuid::Uuid,
pub role_name: String,
}
#[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)]
pub struct UserWishlistItem {
pub id: i64,
pub created_at: i64,
pub created_by: i64,
pub updated_at: i64,
pub updated_by: i64,
pub user_id: i64,
pub id: uuid::Uuid,
pub created_at: chrono::NaiveDateTime,
pub created_by: uuid::Uuid,
pub updated_at: chrono::NaiveDateTime,
pub updated_by: uuid::Uuid,
pub user_id: uuid::Uuid,
pub item: String,
pub item_url: String,
pub purchased_by: i64,
pub received_at: i64,
pub purchased_by: Option<uuid::Uuid>,
pub received_at: Option<chrono::NaiveDateTime>,
}
/*
pub async fn get_user_roles(user_id: i64, db_pool: &SqlitePool) -> Vec<UserRoles> {
pub async fn get_user_roles(user_id: i64, db_pool: &PgPool) -> Vec<UserRoles> {
// Get user roles
let user_roles = sqlx::query_as(
r#"SELECT id, created_at, created_by, updated_at, updated_by, user_id, role_id FROM user_roles WHERE user_id = ?"#
r#"SELECT id, created_at, created_by, updated_at, updated_by, user_id, role_id FROM user_roles WHERE user_id = $1"#
)
.bind(user_id)
.fetch_all(db_pool)
@ -88,10 +87,10 @@ pub async fn get_user_roles(user_id: i64, db_pool: &SqlitePool) -> Vec<UserRoles
user_roles
} */
pub async fn get_user_roles_display(user_id: i64, db_pool: &SqlitePool) -> Vec<UserRolesDisplay> {
pub async fn get_user_roles_display(user_id: uuid::Uuid, db_pool: &PgPool) -> Vec<UserRolesDisplay> {
// Get user roles
let user_roles = sqlx::query_as(
r#"select ur.id, u.id as user_id, u.name as user_name, r.id as role_id, r.name as role_name, r.created_at, r.created_by, r.updated_at, r.updated_by from roles r join user_roles ur on ur.role_id = r.id join users u on u.id = ur.user_id WHERE ur.user_id = ?"#
r#"select ur.id, u.id as user_id, u.name as user_name, r.id as role_id, r.name as role_name, r.created_at, r.created_by, r.updated_at, r.updated_by from roles r join user_roles ur on ur.role_id = r.id join users u on u.id = ur.user_id WHERE ur.user_id = $1"#
)
.bind(user_id)
.fetch_all(db_pool)
@ -101,10 +100,10 @@ pub async fn get_user_roles_display(user_id: i64, db_pool: &SqlitePool) -> Vec<U
user_roles
}
pub async fn get_other_roles_display(user_id: i64, db_pool: &SqlitePool) -> Vec<UserRolesDisplay> {
pub async fn get_other_roles_display(user_id: uuid::Uuid, db_pool: &PgPool) -> Vec<UserRolesDisplay> {
// Get roles user does not have
let user_roles = sqlx::query_as(
r#"select 0 as id, r.created_at, r.created_by, r.updated_at, r.updated_by, ? as user_id, '' as user_name, r.id as role_id, r.name as role_name from roles r where r.id not in (select ur.role_id from user_roles ur where ur.user_id = ?)"#
r#"select r.id as id, r.created_at, r.created_by, r.updated_at, r.updated_by, $1 as user_id, '' as user_name, r.id as role_id, r.name as role_name from roles r where r.id not in (select ur.role_id from user_roles ur where ur.user_id = $2)"#
)
.bind(user_id.clone())
.bind(user_id)
@ -116,17 +115,13 @@ pub async fn get_other_roles_display(user_id: i64, db_pool: &SqlitePool) -> Vec<
}
pub async fn add_user_role(
Path((user_id, role_id)): Path<(i64, i64)>,
State(db_pool): State<SqlitePool>,
Path((user_id, role_id)): Path<(uuid::Uuid, uuid::Uuid)>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> impl IntoResponse {
if is_authorized("/roles", user_data.clone(), db_pool.clone()).await {
let now = Utc::now().timestamp();
sqlx::query("INSERT INTO user_roles (created_at, created_by, updated_at, updated_by, user_id, role_id) VALUES (?, ?, ?, ?, ?, ?)")
.bind(now)// Created now
sqlx::query("INSERT INTO user_roles (created_by, updated_by, user_id, role_id) VALUES ($1, $2, $3, $4)")
.bind(user_data.as_ref().unwrap().id)// Created by current user
.bind(now) // Updated now
.bind(user_data.as_ref().unwrap().id) // Updated by current user
.bind(user_id)
.bind(role_id)
@ -144,12 +139,12 @@ pub async fn add_user_role(
}
pub async fn delete_user_role(
Path((user_id, user_role_id)): Path<(i64, i64)>,
State(db_pool): State<SqlitePool>,
Path((user_id, user_role_id)): Path<(uuid::Uuid, uuid::Uuid)>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> impl IntoResponse {
if is_authorized("/roles", user_data, db_pool.clone()).await {
sqlx::query("DELETE FROM user_roles WHERE id = ?")
sqlx::query("DELETE FROM user_roles WHERE id = $1")
.bind(user_role_id)
.execute(&db_pool)
.await
@ -162,11 +157,11 @@ pub async fn delete_user_role(
}
}
pub async fn get_user_wishlist_item_by_id(item_id: i64, db_pool: &SqlitePool) -> UserWishlistItem {
pub async fn get_user_wishlist_item_by_id(item_id: uuid::Uuid, db_pool: &PgPool) -> UserWishlistItem {
// Get wish list items for the user
let user_wishlist_item = sqlx::query_as(
r#"select id, created_at, created_by, updated_at, updated_by, user_id, item, item_url, purchased_by, received_at
from wishlist_items where id = ?"#
from wishlist_items where id = $1"#
)
.bind(item_id)
.fetch_one(db_pool)
@ -176,11 +171,11 @@ pub async fn get_user_wishlist_item_by_id(item_id: i64, db_pool: &SqlitePool) ->
user_wishlist_item
}
pub async fn get_user_wishlist_items(user_id: i64, db_pool: &SqlitePool) -> Vec<UserWishlistItem> {
pub async fn get_user_wishlist_items(user_id: uuid::Uuid, db_pool: &PgPool) -> Vec<UserWishlistItem> {
// Get wish list items for the user
let user_wishlist_items = sqlx::query_as(
r#"select id, created_at, created_by, updated_at, updated_by, user_id, item, item_url, purchased_by, received_at
from wishlist_items where user_id = ?"#
from wishlist_items where user_id = $1"#
)
.bind(user_id)
.fetch_all(db_pool)
@ -190,8 +185,8 @@ pub async fn get_user_wishlist_items(user_id: i64, db_pool: &SqlitePool) -> Vec<
user_wishlist_items
}
pub async fn get_useremails_by_role(role_name: String, db_pool: &SqlitePool) -> String {
let useremails: String = sqlx::query_scalar(r#"select group_concat(u.email) as email from user_roles ur, roles r, users u where u.id = ur.user_id and r.id = ur.role_id and r.name = ?"#)
pub async fn get_useremails_by_role(role_name: String, db_pool: &PgPool) -> String {
let useremails: String = sqlx::query_scalar(r#"select string_agg(u.email, ',') as email from user_roles ur, roles r, users u where u.id = ur.user_id and r.id = ur.role_id and r.name = $1"#)
.bind(role_name)
.fetch_one(db_pool)
.await

View File

@ -8,7 +8,8 @@ use axum_extra::response::Html;
use chrono::Utc;
use http::StatusCode;
use serde::Deserialize;
use sqlx::{Row, SqlitePool};
use sqlx::{Row, PgPool};
use uuid::Uuid;
use crate::{
middlewares::is_authorized,
@ -47,7 +48,7 @@ struct WishListsTemplate {
pub async fn wishlists(
Extension(user_data): Extension<Option<UserData>>,
State(db_pool): State<SqlitePool>,
State(db_pool): State<PgPool>,
) -> impl IntoResponse {
// Is the user logged in?
let logged_in = user_data.is_some();
@ -93,8 +94,8 @@ struct UserWishListTemplate {
}
pub async fn user_wishlist(
Path(user_id): Path<i64>,
State(db_pool): State<SqlitePool>,
Path(user_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> impl IntoResponse {
// Is the user logged in?
@ -106,14 +107,15 @@ pub async fn user_wishlist(
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
// Extract the user data.
let person = sqlx::query_as!(UserData, "SELECT * FROM users WHERE id = ?", user_id)
let person = sqlx::query_as("SELECT * FROM users WHERE id = $1")
.bind(user_id)
.fetch_one(&db_pool)
.await
.unwrap();
if is_authorized("/wishlist", user_data, db_pool.clone()).await {
// Get user roles
let user_roles = get_user_roles_display(userid, &db_pool.clone()).await;
if is_authorized("/wishlist", user_data, db_pool.clone()).await {
// Get user roles
let user_roles = get_user_roles_display(userid, &db_pool.clone()).await;
// Get user wishlist
let person_wishlist_items = get_user_wishlist_items(user_id, &db_pool.clone()).await;
@ -150,8 +152,8 @@ struct UserWishListAddTemplate {
}
pub async fn user_wishlist_add(
Path(user_id): Path<i64>,
State(db_pool): State<SqlitePool>,
Path(user_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> impl IntoResponse {
// Is the user logged in?
@ -163,7 +165,8 @@ pub async fn user_wishlist_add(
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
// Extract the user data.
let person = sqlx::query_as!(UserData, "SELECT * FROM users WHERE id = ?", user_id)
let person = sqlx::query_as("SELECT * FROM users WHERE id = $1")
.bind(user_id)
.fetch_one(&db_pool)
.await
.unwrap();
@ -199,19 +202,15 @@ pub struct ItemForm {
}
pub async fn user_wishlist_add_item(
Path(user_id): Path<i64>,
State(db_pool): State<SqlitePool>,
Path(user_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
Form(item_form): Form<ItemForm>,
) -> impl IntoResponse {
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
// Insert new item to database
let now = Utc::now().timestamp();
sqlx::query("insert into wishlist_items (created_at, created_by, updated_at, updated_by, user_id, item, item_url) values (?, ?, ?, ?, ?, ?, ?)")
.bind(now)// Created now
sqlx::query("insert into wishlist_items (created_by, updated_by, user_id, item, item_url) values ($1, $2, $3, $4, $5)")
.bind(user_data.as_ref().unwrap().id)// Created by current user
.bind(now) // Updated now
.bind(user_data.as_ref().unwrap().id) // Updated by current user
.bind(user_id)
.bind(item_form.item)
@ -237,8 +236,8 @@ struct UserWishListEditTemplate {
}
pub async fn user_wishlist_edit_item(
Path(item_id): Path<i64>,
State(db_pool): State<SqlitePool>,
Path(item_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> impl IntoResponse {
// Is the user logged in?
@ -274,16 +273,16 @@ pub async fn user_wishlist_edit_item(
}
pub async fn user_wishlist_save_item(
Path(item_id): Path<i64>,
State(db_pool): State<SqlitePool>,
Path(item_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
Form(item_form): Form<ItemForm>,
) -> impl IntoResponse {
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
// Insert new item to database
let now = Utc::now().timestamp();
let now = Utc::now().naive_local();
sqlx::query("update wishlist_items set updated_at = ?, updated_by = ?, item = ?, item_url = ? where id = ?")
sqlx::query("update wishlist_items set updated_at = $1, updated_by = $2, item = $3, item_url = $4 where id = $5")
.bind(now) // Updated now
.bind(user_data.as_ref().unwrap().id) // Updated by current user
.bind(item_form.item)
@ -302,28 +301,28 @@ pub async fn user_wishlist_save_item(
}
pub async fn user_wishlist_bought_item(
Path(user_id): Path<i64>,
State(db_pool): State<SqlitePool>,
Path(item_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> impl IntoResponse {
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
// Update item to purchased
sqlx::query("update wishlist_items set purchased_by = ? where id = ?")
sqlx::query("update wishlist_items set purchased_by = $1 where id = $2")
.bind(user_data.as_ref().unwrap().id) // Created by current user
.bind(user_id)
.bind(item_id)
.execute(&db_pool)
.await
.unwrap();
// Redirect to user wishlist
// Extract the user data.
let row = sqlx::query("SELECT user_id FROM wishlist_items WHERE id = ?")
.bind(user_id)
let row = sqlx::query("SELECT user_id FROM wishlist_items WHERE id = $1")
.bind(item_id)
.fetch_one(&db_pool)
.await
.unwrap();
let userid = row.get::<i64, _>("user_id");
let userid = row.get::<uuid::Uuid, _>("user_id");
let redirect_string = format!("/userwishlist/{userid}");
Redirect::to(&redirect_string).into_response()
} else {
@ -332,15 +331,15 @@ pub async fn user_wishlist_bought_item(
}
pub async fn user_wishlist_received_item(
Path(user_id): Path<i64>,
State(db_pool): State<SqlitePool>,
Path(user_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> impl IntoResponse {
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
// Update item received time
let now = Utc::now().timestamp();
let now = Utc::now().naive_local();
sqlx::query("update wishlist_items set received_at = ? where id = ?")
sqlx::query("update wishlist_items set received_at = $1 where id = $2")
.bind(now) // Received now
.bind(user_id)
.execute(&db_pool)
@ -357,12 +356,12 @@ pub async fn user_wishlist_received_item(
}
pub async fn user_wishlist_delete_item(
Path(item_id): Path<i64>,
State(db_pool): State<SqlitePool>,
Path(item_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> impl IntoResponse {
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
sqlx::query("delete from wishlist_items where id = ?")
sqlx::query("delete from wishlist_items where id = $1")
.bind(item_id)
.execute(&db_pool)
.await
@ -378,12 +377,12 @@ pub async fn user_wishlist_delete_item(
}
pub async fn user_wishlist_returned_item(
Path(item_id): Path<i64>,
State(db_pool): State<SqlitePool>,
Path(item_id): Path<uuid::Uuid>,
State(db_pool): State<PgPool>,
Extension(user_data): Extension<Option<UserData>>,
) -> impl IntoResponse {
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
sqlx::query("update wishlist_items set purchased_by = 0 where id = ?")
sqlx::query("update wishlist_items set purchased_by = null where id = $1")
.bind(item_id)
.execute(&db_pool)
.await
@ -391,13 +390,13 @@ pub async fn user_wishlist_returned_item(
// Redirect to user wishlist
// Extract the user data.
let row = sqlx::query("SELECT user_id FROM wishlist_items WHERE id = ?")
let row = sqlx::query("SELECT user_id FROM wishlist_items WHERE id = $1")
.bind(item_id)
.fetch_one(&db_pool)
.await
.unwrap();
let profileid = row.get::<i64, _>("user_id");
let profileid = row.get::<Uuid, _>("user_id");
let redirect_string = format!("/userwishlist/{profileid}");
Redirect::to(&redirect_string).into_response()
} else {

View File

@ -25,38 +25,44 @@
{% for person_wishlist_item in person_wishlist_items %}
<tr>
{% if my_wishlist %}
<td><a href="/userwishlist/edit/{{ person_wishlist_item.id }}">{{ person_wishlist_item.item }}</a></td>
<td><a href="/userwishlist/edit/{{ person_wishlist_item.id }}">{{ person_wishlist_item.item }}</a></td>
{% else %}
<td>{{ person_wishlist_item.item }}</td>
<td>{{ person_wishlist_item.item }}</td>
{% endif %}
{% if person_wishlist_item.item_url.len() > 0 %}
<td><a href="{{ person_wishlist_item.item_url }}">URL</a></td>
<td><a href="{{ person_wishlist_item.item_url }}">URL</a></td>
{% else %}
<td></td>
<td></td>
{% endif %}
{% if person_wishlist_item.received_at > 0 %}
<td>Got it!</td>
{% else %}
<td>Not yet!</td>
{% endif %}
{% match person_wishlist_item.received_at %}
{% when None %}
<td>Not yet!</td>
{% when Some with (received_at) %}
<td>Got it!</td>
{% endmatch %}
{% if my_wishlist %}
{% if person_wishlist_item.received_at > 0 %}
<td><a href="/userwishlist/delete/{{ person_wishlist_item.id }}">Delete</a></td>
{% else %}
<td><a href="/userwishlist/received/{{ person_wishlist_item.id }}">Received</a></td>
{% endif %}
{% match person_wishlist_item.received_at %}
{% when None %}
<td><a href="/userwishlist/received/{{ person_wishlist_item.id }}">Received</a></td>
{% when Some with (received_at) %}
<td><a href="/userwishlist/delete/{{ person_wishlist_item.id }}">Delete</a></td>
{% endmatch %}
{% else %}
{% if person_wishlist_item.purchased_by == user.id %}
<td><a href="/userwishlist/returned/{{ person_wishlist_item.id }}">Return</a></td>
{% else if person_wishlist_item.purchased_by > 0 %}
<td>Purchased</td>
{% else %}
<td><a href="/userwishlist/bought/{{ person_wishlist_item.id }}">Bought</a></td>
{% endif %}
{% match person_wishlist_item.purchased_by %}
{% when Some with (purchased_by) %}
{% if purchased_by.clone() == user.id %}
<td><a href="/userwishlist/returned/{{ person_wishlist_item.id }}">Return</a></td>
{% else %}
<td>Purchased</td>
{% endif %}
{% when None %}
<td><a href="/userwishlist/bought/{{ person_wishlist_item.id }}">Bought</a></td>
{% endmatch %}
{% endif %}
</tr>
{% endfor %}
</tbody>