Compare commits

..

No commits in common. "939f87d8209d3a40ada690cfb98d3e488a2ab87e" and "e3e7e4442b50aba5b1d0b844ae6fcf4e6d037ac3" have entirely different histories.

31 changed files with 468 additions and 830 deletions

2
.gitignore vendored
View File

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

7
backend/.env Normal file
View File

@ -0,0 +1,7 @@
DATABASE_URL=sqlite://db/db.sqlite3
GOOGLE_CLIENT_ID=735264084619-clsmvgdqdmum4rvrcj0kuk28k9agir1c.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=L6uI7FQGoMJd-ay1HO_iGJ6M
SMTP_SERVER_NAME=mailout.easymail.ca
SMTP_SERVER_PORT=587
EMAIL_USERNAME=admin@jean-marie.ca
EMAIL_PASSWORD=Cj6wX8^JivPD

8
backend/Cargo.lock generated
View File

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

View File

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

View File

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

View File

@ -0,0 +1,58 @@
-- 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

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

View File

@ -0,0 +1,6 @@
-- 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

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

View File

@ -0,0 +1,12 @@
-- 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

@ -0,0 +1,9 @@
-- 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

@ -0,0 +1,9 @@
-- 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

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

View File

@ -0,0 +1,16 @@
-- 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

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

View File

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

View File

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

View File

@ -0,0 +1,28 @@
-- 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

@ -0,0 +1,5 @@
-- 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

@ -0,0 +1,42 @@
-- 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

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

View File

@ -1,386 +0,0 @@
-- 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 unique index if not exists unique_user_role on user_roles(user_id, role_id);
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(512) null,
item_url varchar(1024) 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 askama_axum::{IntoResponse, Response};
use axum::{ use axum::{
extract::{Path, State}, extract::{Path, State},
response::{Html, Redirect}, response::{Html, Json, Redirect},
Extension, Extension,
}; };
use http::StatusCode; use http::StatusCode;
use sqlx::PgPool; use sqlx::SqlitePool;
use crate::{ use crate::{
middlewares::is_authorized, middlewares::is_authorized,
@ -47,7 +47,7 @@ struct CalendarTemplate {
pub async fn calendar( pub async fn calendar(
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<UserData>>,
State(db_pool): State<PgPool>, State(db_pool): State<SqlitePool>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Is the user logged in? // Is the user logged in?
let logged_in = user_data.is_some(); let logged_in = user_data.is_some();
@ -89,7 +89,7 @@ pub async fn calendar(
pub async fn get_events( pub async fn get_events(
Path(calendar): Path<String>, Path(calendar): Path<String>,
State(db_pool): State<PgPool>, State(db_pool): State<SqlitePool>,
Extension(user_data): Extension<Option<UserData>>, Extension(user_data): Extension<Option<UserData>>,
) -> String { ) -> String {
println!("Calendar: {}", calendar); println!("Calendar: {}", calendar);
@ -101,8 +101,8 @@ pub async fn get_events(
if logged_in { if logged_in {
// Extract the user data. // Extract the user data.
let _user = user_data.as_ref().unwrap().clone(); let user = user_data.as_ref().unwrap().clone();
let _userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default(); let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
if is_authorized("/calendar", user_data, db_pool.clone()).await { if is_authorized("/calendar", user_data, db_pool.clone()).await {
// Get requested calendar events from database // Get requested calendar events from database

View File

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

View File

@ -3,10 +3,9 @@ use axum::{
routing::{get, get_service}, routing::{get, get_service},
Extension, Router, Extension, Router,
}; };
use dotenvy::var;
use secret_gift_exchange::{giftexchange, giftexchange_save, giftexchanges}; use secret_gift_exchange::{giftexchange, giftexchange_save, giftexchanges};
use sqlx::{migrate::Migrator, sqlite::SqlitePoolOptions, sqlite::SqliteRow, Row, SqlitePool}; use sqlx::migrate::Migrator;
use sqlx::{postgres::PgPoolOptions, PgPool}; use sqlx::{sqlite::SqlitePoolOptions, SqlitePool};
use std::net::SocketAddr; use std::net::SocketAddr;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
@ -16,26 +15,22 @@ mod error_handling;
mod google_oauth; mod google_oauth;
mod middlewares; mod middlewares;
mod routes; mod routes;
mod secret_gift_exchange;
mod user; mod user;
mod wishlist; mod wishlist;
mod secret_gift_exchange;
use calendar::{calendar, get_events};
use error_handling::AppError; use error_handling::AppError;
use google_oauth::{google_auth_return, login, logout}; use google_oauth::{google_auth_return, login, logout};
use middlewares::inject_user_data; use middlewares::inject_user_data;
use routes::{about, contact, dashboard, index, profile, user_profile, useradmin}; use routes::{about, contact, dashboard, index, profile, user_profile, useradmin};
use user::{add_user_role, delete_user_role, UserData}; use user::{add_user_role, delete_user_role, UserData};
use wishlist::{ use calendar::{calendar, get_events};
user_wishlist, user_wishlist_add, user_wishlist_add_item, user_wishlist_bought_item, use wishlist::{user_wishlist, user_wishlist_add, user_wishlist_add_item, user_wishlist_bought_item, user_wishlist_delete_item, user_wishlist_edit_item, user_wishlist_received_item, user_wishlist_returned_item, user_wishlist_save_item, wishlists};
user_wishlist_delete_item, user_wishlist_edit_item, user_wishlist_received_item,
user_wishlist_returned_item, user_wishlist_save_item, wishlists,
};
//use email::send_emails; //use email::send_emails;
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {
pub db_pool: PgPool, pub db_pool: SqlitePool,
} }
#[tokio::main] #[tokio::main]
@ -43,11 +38,14 @@ async fn main() {
// initialize tracing // initialize tracing
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
// Get the server settings from the env file let db_pool = SqlitePoolOptions::new()
let database_url = var("DATABASE_URL").expect("DATABASE_URL not set"); .max_connections(5)
let db_pool = PgPoolOptions::new().connect(&database_url).await.unwrap(); .connect("sqlite://db/db.sqlite3")
.await;
let app_state = AppState { db_pool: db_pool }; let app_state = AppState {
db_pool: db_pool.expect("Failed to get db_pool"),
};
static MIGRATOR: Migrator = sqlx::migrate!(); static MIGRATOR: Migrator = sqlx::migrate!();
@ -56,74 +54,39 @@ async fn main() {
.await .await
.expect("Failed to run migrations"); .expect("Failed to run migrations");
// Copy from old sqlite database if it exists
if let Ok(source_db_url) = var("SOURCE_DB_URL") {
let sdb_pool = SqlitePoolOptions::new()
.max_connections(5)
.connect(&source_db_url)
.await
.unwrap();
copy_database(&sdb_pool, &app_state.db_pool).await;
} else {
println!("SOURCE_DB_URL not set");
}
let user_data: Option<UserData> = None; let user_data: Option<UserData> = None;
// build our application with some routes // build our application with some routes
let app = Router::new() let app = Router::new()
.route("/dashboard", get(dashboard)) .route("/dashboard", get(dashboard))
// User // User
.route("/profile", get(profile)) .route("/profile", get(profile))
.route("/useradmin", get(useradmin)) .route("/useradmin", get(useradmin))
.route("/users/:user_id", get(user_profile)) .route("/users/:user_id", get(user_profile))
.route("/roles/:user_id/:role_id/add", get(add_user_role)) .route("/roles/:user_id/:role_id/add", get(add_user_role))
.route( .route("/roles/:user_id/:user_role_id/delete", get(delete_user_role))
"/roles/:user_id/:user_role_id/delete",
get(delete_user_role),
)
// Calendar // Calendar
.route("/calendar", get(calendar)) .route("/calendar", get(calendar))
.route("/getevents/:calendar", get(get_events)) .route("/getevents/:calendar", get(get_events))
// Wishlist // Wishlist
.route("/wishlists", get(wishlists)) .route("/wishlists", get(wishlists))
.route("/userwishlist/:user_id", get(user_wishlist)) .route("/userwishlist/:user_id", get(user_wishlist))
.route( .route("/userwishlist/add/:user_id", get(user_wishlist_add).post(user_wishlist_add_item))
"/userwishlist/add/:user_id", .route("/userwishlist/edit/:item_id", get(user_wishlist_edit_item).post(user_wishlist_save_item))
get(user_wishlist_add).post(user_wishlist_add_item), .route("/userwishlist/bought/:user_id", get(user_wishlist_bought_item))
) .route("/userwishlist/received/:user_id", get(user_wishlist_received_item))
.route( .route("/userwishlist/delete/:item_id", get(user_wishlist_delete_item))
"/userwishlist/edit/:item_id", .route("/userwishlist/returned/:item_id", get(user_wishlist_returned_item))
get(user_wishlist_edit_item).post(user_wishlist_save_item),
)
.route(
"/userwishlist/bought/:user_id",
get(user_wishlist_bought_item),
)
.route(
"/userwishlist/received/:user_id",
get(user_wishlist_received_item),
)
.route(
"/userwishlist/delete/:item_id",
get(user_wishlist_delete_item),
)
.route(
"/userwishlist/returned/:item_id",
get(user_wishlist_returned_item),
)
// Secret Gift Exchange - Not ready for public use yet // Secret Gift Exchange - Not ready for public use yet
.route("/giftexchanges", get(giftexchanges)) .route("/giftexchanges", get(giftexchanges))
.route( .route("/giftexchange/:giftexchange_id", get(giftexchange).post(giftexchange_save))
"/giftexchange/:giftexchange_id",
get(giftexchange).post(giftexchange_save), .nest_service("/assets", ServeDir::new("templates/assets")
) .fallback(get_service(ServeDir::new("templates/assets"))))
.nest_service(
"/assets",
ServeDir::new("templates/assets")
.fallback(get_service(ServeDir::new("templates/assets"))),
)
.route("/", get(index)) .route("/", get(index))
.route("/about", get(about)) .route("/about", get(about))
.route("/contactus", get(contact)) .route("/contactus", get(contact))
@ -149,169 +112,3 @@ async fn main() {
.await .await
.unwrap(); .unwrap();
} }
async fn copy_database(sdb_pool: &SqlitePool, db_pool: &PgPool) {
// Copy users
let users = sqlx::query(
r#"select
datetime(u.created_at, 'unixepoch'),
coalesce(cb.email, 'admin@jean-marie.ca') as created_by_email,
datetime(u.updated_at, 'unixepoch'),
coalesce(ub.email, 'admin@jean-marie.ca') as updated_by_email,
u.email,
u.name,
u.family_name,
u.given_name
from users u
left join users cb on cb.id = u.created_by
left join users ub on ub.id = u.updated_by;"#,
)
.fetch_all(sdb_pool)
.await
.expect("Failed to copy users from SQLite to Postgres");
println!("\nCopying {} users", users.len());
for user in users {
if let (
Ok(created_at),
Ok(created_by),
Ok(updated_at),
Ok(updated_by),
Ok(email),
Ok(name),
Ok(family_name),
Ok(given_name),
) = (
user.try_get::<chrono::NaiveDateTime, _>(0),
user.try_get::<String, _>(1),
user.try_get::<chrono::NaiveDateTime, _>(2),
user.try_get::<String, _>(3),
user.try_get::<String, _>(4),
user.try_get::<String, _>(5),
user.try_get::<String, _>(6),
user.try_get::<String, _>(7),
) {
let result = sqlx::query(
r#"insert into users (created_at, created_by, updated_at, updated_by, email, name, family_name, given_name)
values ($1, (select id from users where email =$2), $3, (select id from users where email =$4), $5, $6, $7, $8)"#
)
.bind(created_at)
.bind(created_by)
.bind(updated_at)
.bind(updated_by)
.bind(email)
.bind(name)
.bind(family_name)
.bind(given_name)
.execute(db_pool)
.await;
if let Err(e) = result {
println!("Error: {}", e);
}
}
}
// Copy user roles
let user_roles = sqlx::query(
r#"select
datetime(ur.created_at, 'unixepoch'),
coalesce(cb.email, 'admin@jean-marie.ca') as created_by_email,
datetime(ur.updated_at, 'unixepoch'),
coalesce(ub.email, 'admin@jean-marie.ca') as updated_by_email,
u.email as user_email,
r.name as role_name
from user_roles ur
left join users cb on cb.id = ur.created_by
left join users ub on ub.id = ur.updated_by
join users u on u.id = ur.user_id
join roles r on r.id = ur.role_id;"#,
)
.fetch_all(sdb_pool)
.await
.expect("Failed to copy user roles from SQLite to Postgres");
println!("\nCopying {} user roles", user_roles.len());
for user_role in user_roles {
if let (
Ok(created_at),
Ok(created_by),
Ok(updated_at),
Ok(updated_by),
Ok(user_email),
Ok(role_name),
) = (
user_role.try_get::<chrono::NaiveDateTime, _>(0),
user_role.try_get::<String, _>(1),
user_role.try_get::<chrono::NaiveDateTime, _>(2),
user_role.try_get::<String, _>(3),
user_role.try_get::<String, _>(4),
user_role.try_get::<String, _>(5),
) {
let result = sqlx::query(
r#"insert into user_roles (created_at, created_by, updated_at, updated_by, user_id, role_id)
values ($1, (select id from users where email=$2), $3, (select id from users where email=$4), (select id from users where email=$5), (select id from roles where name=$6))"#
)
.bind(created_at)
.bind(created_by)
.bind(updated_at)
.bind(updated_by)
.bind(user_email)
.bind(role_name)
.execute(db_pool)
.await;
if let Err(e) = result {
println!("Error: {}", e);
}
}
}
// Copy wishlistitems
let wishlistitems = sqlx::query(
r#"select
datetime(wi.created_at, 'unixepoch'),
coalesce(cb.email, 'admin@jean-marie.ca') as created_by_email,
datetime(wi.updated_at, 'unixepoch'),
coalesce(ub.email, 'admin@jean-marie.ca') as updated_by_email,
u.email as user_email,
wi.item,
wi.item_url,
pb.email,
datetime(wi.received_at, 'unixepoch')
from wishlist_items wi
left join users cb on cb.id = wi.created_by
left join users ub on ub.id = wi.updated_by
left join users pb on pb.id = wi.purchased_by
join users u on u.id = wi.user_id;"#,
)
.fetch_all(sdb_pool)
.await
.expect("Failed to copy wishlistitems from SQLite to Postgres");
println!("\nCopying {} wishlistitems", wishlistitems.len());
for wishlistitem in wishlistitems {
let result = sqlx::query(
r#"insert into wishlist_items (created_at, created_by, updated_at, updated_by, user_id, item, item_url, purchased_by, received_at)
values ($1, (select id from users where email=$2), $3, (select id from users where email=$4), (select id from users where email=$5), $6, $7, (select id from users where email=$8), $9)"#
)
.bind(wishlistitem.try_get::<chrono::NaiveDateTime,_>(0).unwrap())
.bind(wishlistitem.try_get::<String,_>(1).unwrap())
.bind(wishlistitem.try_get::<chrono::NaiveDateTime,_>(2).unwrap())
.bind(wishlistitem.try_get::<String,_>(3).unwrap())
.bind(wishlistitem.try_get::<String,_>(4).unwrap())
.bind(wishlistitem.try_get::<String,_>(5).unwrap())
.bind(wishlistitem.try_get::<String,_>(6).unwrap())
.bind(wishlistitem.try_get::<String,_>(7).unwrap())
.bind(wishlistitem.try_get::<chrono::NaiveDateTime,_>(8).unwrap_or_default())
.execute(db_pool)
.await;
if let Err(e) = result {
println!("Error: {}", e);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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