Merge pull request 'postgres-conversion' (#3) from postgres-conversion into main
Reviewed-on: #3
This commit is contained in:
commit
939f87d820
|
|
@ -2,3 +2,5 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -2603,6 +2603,7 @@ dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
"crossbeam-queue",
|
"crossbeam-queue",
|
||||||
"either",
|
"either",
|
||||||
|
|
@ -2631,6 +2632,7 @@ dependencies = [
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2683,6 +2685,7 @@ dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
"digest",
|
"digest",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
|
@ -2711,6 +2714,7 @@ dependencies = [
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"uuid",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2724,6 +2728,7 @@ 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",
|
||||||
|
|
@ -2749,6 +2754,7 @@ dependencies = [
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"uuid",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2759,6 +2765,7 @@ 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",
|
||||||
|
|
@ -2773,6 +2780,7 @@ dependencies = [
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -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 = ["sqlite", "runtime-tokio", "macros"] }
|
sqlx = { version = "0.8", features = ["postgres", "sqlite","runtime-tokio", "macros", "chrono", "uuid"] }
|
||||||
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"
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
-- Add down migration script here
|
|
||||||
|
|
@ -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
|
|
||||||
);
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
-- Add down migration script here
|
|
||||||
|
|
@ -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');
|
|
||||||
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
-- Add down migration script here
|
|
||||||
|
|
@ -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');
|
|
||||||
|
|
@ -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';
|
|
||||||
|
|
@ -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');
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
-- Add down migration script here
|
|
||||||
drop table if exists `wishlist_items`;
|
|
||||||
|
|
||||||
delete from `role_permissions` where id = 9;
|
|
||||||
|
|
@ -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')
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- Add down migration script here
|
|
||||||
alter table wishlist_items drop column received_at;
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- Add up migration script here
|
|
||||||
alter table wishlist_items add column received_at integer null;
|
|
||||||
|
|
@ -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';
|
|
||||||
|
|
@ -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')
|
|
||||||
|
|
@ -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`;
|
|
||||||
|
|
@ -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
|
|
||||||
);
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
-- 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;
|
||||||
|
|
@ -0,0 +1,386 @@
|
||||||
|
-- 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 $$;
|
||||||
|
|
@ -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, Json, Redirect},
|
response::{Html, Redirect},
|
||||||
Extension,
|
Extension,
|
||||||
};
|
};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
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<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
) -> 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<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,7 @@ use oauth2::{
|
||||||
TokenResponse, TokenUrl,
|
TokenResponse, TokenUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::Utc;
|
use sqlx::PgPool;
|
||||||
use sqlx::SqlitePool;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
|
@ -61,7 +60,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<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
Host(hostname): Host,
|
Host(hostname): Host,
|
||||||
) -> Result<Redirect, AppError> {
|
) -> Result<Redirect, AppError> {
|
||||||
if user_data.is_some() {
|
if user_data.is_some() {
|
||||||
|
|
@ -89,21 +88,22 @@ 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 (?, ?, ?);",
|
"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)
|
.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<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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 = ? 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())
|
.bind(state.secret())
|
||||||
.fetch_one(&db_pool)
|
.fetch_one(&db_pool)
|
||||||
|
|
@ -130,6 +130,7 @@ 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 || {
|
||||||
|
|
@ -185,21 +186,15 @@ 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<(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())
|
.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: (i64,) = sqlx::query_as("INSERT INTO users (created_at, created_by, updated_at, updated_by, email, name, family_name, given_name) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING id")
|
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(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())
|
||||||
|
|
@ -208,13 +203,8 @@ pub async fn google_auth_return(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Add public role
|
// Add public role
|
||||||
sqlx::query("INSERT INTO user_roles (created_at, created_by, updated_at, updated_by, user_id, role_id) VALUES (?, ?, ?, ?, ?, ?)")
|
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(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?;
|
||||||
|
|
||||||
|
|
@ -242,18 +232,15 @@ 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 (?, ?, ?, ?, ?);",
|
VALUES ($1, $2, $3, now(), now() + interval '1 day');",
|
||||||
)
|
)
|
||||||
.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?;
|
||||||
}
|
}
|
||||||
|
|
@ -263,12 +250,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<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
) -> 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 = ?")
|
let _ = sqlx::query("DELETE FROM user_sessions WHERE session_token_1 = $1")
|
||||||
.bind(session_token[0])
|
.bind(session_token[0])
|
||||||
.execute(&db_pool)
|
.execute(&db_pool)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@ 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;
|
use sqlx::{migrate::Migrator, sqlite::SqlitePoolOptions, sqlite::SqliteRow, Row, SqlitePool};
|
||||||
use sqlx::{sqlite::SqlitePoolOptions, SqlitePool};
|
use sqlx::{postgres::PgPoolOptions, PgPool};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
|
|
||||||
|
|
@ -15,22 +16,26 @@ 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 calendar::{calendar, get_events};
|
use wishlist::{
|
||||||
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, 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,
|
||||||
|
};
|
||||||
//use email::send_emails;
|
//use email::send_emails;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub db_pool: SqlitePool,
|
pub db_pool: PgPool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
@ -38,14 +43,11 @@ async fn main() {
|
||||||
// initialize tracing
|
// initialize tracing
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let db_pool = SqlitePoolOptions::new()
|
// Get the server settings from the env file
|
||||||
.max_connections(5)
|
let database_url = var("DATABASE_URL").expect("DATABASE_URL not set");
|
||||||
.connect("sqlite://db/db.sqlite3")
|
let db_pool = PgPoolOptions::new().connect(&database_url).await.unwrap();
|
||||||
.await;
|
|
||||||
|
|
||||||
let app_state = AppState {
|
let app_state = AppState { db_pool: db_pool };
|
||||||
db_pool: db_pool.expect("Failed to get db_pool"),
|
|
||||||
};
|
|
||||||
|
|
||||||
static MIGRATOR: Migrator = sqlx::migrate!();
|
static MIGRATOR: Migrator = sqlx::migrate!();
|
||||||
|
|
||||||
|
|
@ -54,39 +56,74 @@ 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("/roles/:user_id/:user_role_id/delete", get(delete_user_role))
|
.route(
|
||||||
|
"/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("/userwishlist/add/:user_id", get(user_wishlist_add).post(user_wishlist_add_item))
|
.route(
|
||||||
.route("/userwishlist/edit/:item_id", get(user_wishlist_edit_item).post(user_wishlist_save_item))
|
"/userwishlist/add/:user_id",
|
||||||
.route("/userwishlist/bought/:user_id", get(user_wishlist_bought_item))
|
get(user_wishlist_add).post(user_wishlist_add_item),
|
||||||
.route("/userwishlist/received/:user_id", get(user_wishlist_received_item))
|
)
|
||||||
.route("/userwishlist/delete/:item_id", get(user_wishlist_delete_item))
|
.route(
|
||||||
.route("/userwishlist/returned/:item_id", get(user_wishlist_returned_item))
|
"/userwishlist/edit/:item_id",
|
||||||
|
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("/giftexchange/:giftexchange_id", get(giftexchange).post(giftexchange_save))
|
.route(
|
||||||
|
"/giftexchange/:giftexchange_id",
|
||||||
.nest_service("/assets", ServeDir::new("templates/assets")
|
get(giftexchange).post(giftexchange_save),
|
||||||
.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))
|
||||||
|
|
@ -112,3 +149,169 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,14 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use super::{AppError, UserData};
|
use super::{AppError, UserData};
|
||||||
use axum::{
|
use axum::{body::Body, extract::State, http::Request, middleware::Next, response::IntoResponse};
|
||||||
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::SqlitePool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
pub async fn inject_user_data(
|
pub async fn inject_user_data(
|
||||||
State(db_pool): State<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
cookie: Option<TypedHeader<Cookie>>,
|
cookie: Option<TypedHeader<Cookie>>,
|
||||||
mut request: Request<Body>,
|
mut request: Request<Body>,
|
||||||
next: Next,
|
next: Next,
|
||||||
|
|
@ -22,8 +16,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<(i64, i64, String), _> = sqlx::query_as(
|
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=?"#,
|
r#"SELECT user_id,expires_at,session_token_p2 FROM user_sessions WHERE session_token_p1=$1"#,
|
||||||
)
|
)
|
||||||
.bind(session_token[0])
|
.bind(session_token[0])
|
||||||
.fetch_one(&db_pool)
|
.fetch_one(&db_pool)
|
||||||
|
|
@ -44,14 +38,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().timestamp() {
|
if expires_at > Utc::now().naive_local() {
|
||||||
let row = sqlx::query_as!(
|
let row: UserData = sqlx::query_as(
|
||||||
UserData,
|
r#"SELECT id, created_at, created_by, updated_at, updated_by, email, name, family_name, given_name FROM users WHERE id = $1"#,
|
||||||
"SELECT * FROM users WHERE id = ?",
|
)
|
||||||
id
|
.bind(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,
|
||||||
|
|
@ -62,7 +56,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,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -75,9 +69,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: SqlitePool) -> bool {
|
pub async fn is_authorized(path: &str, user_data: Option<UserData>, db_pool: PgPool) -> bool {
|
||||||
if let Some(user_data) = user_data {
|
if let Some(user_data) = user_data {
|
||||||
let query: Result<(i64,), _> = match path {
|
let query: Result<(uuid::Uuid,), _> = match path {
|
||||||
"/profile" => {
|
"/profile" => {
|
||||||
return true;
|
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
|
// 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,), _> = sqlx::query_as(r#"select r.item from role_permissions r where item = ?"#)
|
let query: Result<(String,), _> =
|
||||||
.bind(remaining_path.to_str().unwrap())
|
sqlx::query_as(r#"select r.item from role_permissions r where item = $1"#)
|
||||||
.fetch_one(&db_pool)
|
.bind(remaining_path.to_str().unwrap())
|
||||||
.await;
|
.fetch_one(&db_pool)
|
||||||
|
.await;
|
||||||
if let Ok(query) = query {
|
if let Ok(query) = query {
|
||||||
if query.0 != "" {
|
if query.0 != "" {
|
||||||
break;
|
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();
|
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(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)
|
||||||
|
|
@ -114,4 +109,4 @@ pub async fn is_authorized(path: &str, user_data: Option<UserData>, db_pool: Sql
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use axum::{
|
||||||
Extension,
|
Extension,
|
||||||
};
|
};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
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<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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<i64>,
|
Path(user_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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,7 +171,8 @@ 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!(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)
|
.fetch_one(&db_pool)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -215,7 +216,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<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
) -> 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();
|
||||||
|
|
@ -261,17 +262,7 @@ 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 {
|
let mut user = UserData::default();
|
||||||
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.
|
||||||
|
|
@ -294,17 +285,7 @@ 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 {
|
let mut user = UserData::default();
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use askama_axum::{IntoResponse, Response};
|
use askama_axum::{IntoResponse, Response};
|
||||||
use axum::{
|
use axum::{
|
||||||
|
|
@ -9,10 +7,9 @@ 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, SqlitePool};
|
use sqlx::{FromRow, PgPool};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
middlewares::is_authorized,
|
middlewares::is_authorized,
|
||||||
|
|
@ -99,7 +96,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<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
) -> 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();
|
||||||
|
|
@ -159,7 +156,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<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
) -> 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();
|
||||||
|
|
@ -174,11 +171,9 @@ 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(
|
||||||
GiftExchange,
|
"SELECT * FROM gift_exchange WHERE id = ?")
|
||||||
"SELECT * FROM gift_exchange WHERE id = ?",
|
.bind(exchange_id)
|
||||||
exchange_id
|
|
||||||
)
|
|
||||||
.fetch_one(&db_pool)
|
.fetch_one(&db_pool)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|
@ -188,7 +183,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 = ?)",
|
"select * from users where users.id in (select participant_id from gift_exchange_participants where exchange_id = $1)",
|
||||||
)
|
)
|
||||||
.bind(exchange_id)
|
.bind(exchange_id)
|
||||||
.fetch_all(&db_pool)
|
.fetch_all(&db_pool)
|
||||||
|
|
@ -197,7 +192,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 = ?)",
|
"select * from users where users.id not in (select participant_id from gift_exchange_participants where exchange_id = $1)",
|
||||||
)
|
)
|
||||||
.bind(exchange_id)
|
.bind(exchange_id)
|
||||||
.fetch_all(&db_pool)
|
.fetch_all(&db_pool)
|
||||||
|
|
@ -225,15 +220,15 @@ pub async fn giftexchange(
|
||||||
pub struct ExchangeForm {
|
pub struct ExchangeForm {
|
||||||
name: String,
|
name: String,
|
||||||
exchange_date: String,
|
exchange_date: String,
|
||||||
non_participants: Vec<i64>,
|
non_participants: Vec<uuid::Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn giftexchange_save(
|
pub async fn giftexchange_save(
|
||||||
State(db_pool): State<SqlitePool>,
|
State(_db_pool): State<PgPool>,
|
||||||
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") {
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,20 @@ 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, SqlitePool};
|
use sqlx::{FromRow, PgPool};
|
||||||
|
|
||||||
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: i64,
|
pub id: uuid::Uuid,
|
||||||
pub created_at: i64,
|
pub created_at: chrono::NaiveDateTime,
|
||||||
pub created_by: i64,
|
pub created_by: uuid::Uuid,
|
||||||
pub updated_at: i64,
|
pub updated_at: chrono::NaiveDateTime,
|
||||||
pub updated_by: i64,
|
pub updated_by: uuid::Uuid,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub family_name: String,
|
pub family_name: String,
|
||||||
|
|
@ -27,58 +26,58 @@ pub struct UserData {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct RoleData {
|
pub struct RoleData {
|
||||||
pub id: i64,
|
pub id: uuid::Uuid,
|
||||||
pub created_at: i64,
|
pub created_at: chrono::NaiveDateTime,
|
||||||
pub created_by: i64,
|
pub created_by: uuid::Uuid,
|
||||||
pub updated_at: i64,
|
pub updated_at: chrono::NaiveDateTime,
|
||||||
pub updated_by: i64,
|
pub updated_by: uuid::Uuid,
|
||||||
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: i64,
|
pub id: uuid::Uuid,
|
||||||
pub created_at: i64,
|
pub created_at: chrono::NaiveDateTime,
|
||||||
pub created_by: i64,
|
pub created_by: uuid::Uuid,
|
||||||
pub updated_at: i64,
|
pub updated_at: chrono::NaiveDateTime,
|
||||||
pub updated_by: i64,
|
pub updated_by: uuid::Uuid,
|
||||||
pub user_id: i64,
|
pub user_id: uuid::Uuid,
|
||||||
pub role_id: i64,
|
pub role_id: uuid::Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)]
|
#[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)]
|
||||||
pub struct UserRolesDisplay {
|
pub struct UserRolesDisplay {
|
||||||
pub id: i64,
|
pub id: uuid::Uuid,
|
||||||
pub created_at: i64,
|
pub created_at: chrono::NaiveDateTime,
|
||||||
pub created_by: i64,
|
pub created_by: uuid::Uuid,
|
||||||
pub updated_at: i64,
|
pub updated_at: chrono::NaiveDateTime,
|
||||||
pub updated_by: i64,
|
pub updated_by: uuid::Uuid,
|
||||||
pub user_id: i64,
|
pub user_id: uuid::Uuid,
|
||||||
pub user_name: String,
|
pub user_name: String,
|
||||||
pub role_id: i64,
|
pub role_id: uuid::Uuid,
|
||||||
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: i64,
|
pub id: uuid::Uuid,
|
||||||
pub created_at: i64,
|
pub created_at: chrono::NaiveDateTime,
|
||||||
pub created_by: i64,
|
pub created_by: uuid::Uuid,
|
||||||
pub updated_at: i64,
|
pub updated_at: chrono::NaiveDateTime,
|
||||||
pub updated_by: i64,
|
pub updated_by: uuid::Uuid,
|
||||||
pub user_id: i64,
|
pub user_id: uuid::Uuid,
|
||||||
pub item: String,
|
pub item: String,
|
||||||
pub item_url: String,
|
pub item_url: String,
|
||||||
pub purchased_by: i64,
|
pub purchased_by: Option<uuid::Uuid>,
|
||||||
pub received_at: i64,
|
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
|
// 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 = ?"#
|
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)
|
.bind(user_id)
|
||||||
.fetch_all(db_pool)
|
.fetch_all(db_pool)
|
||||||
|
|
@ -88,10 +87,10 @@ pub async fn get_user_roles(user_id: i64, db_pool: &SqlitePool) -> Vec<UserRoles
|
||||||
user_roles
|
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
|
// 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 = ?"#
|
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)
|
.bind(user_id)
|
||||||
.fetch_all(db_pool)
|
.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
|
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
|
// Get roles user does not have
|
||||||
let user_roles = sqlx::query_as(
|
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.clone())
|
||||||
.bind(user_id)
|
.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(
|
pub async fn add_user_role(
|
||||||
Path((user_id, role_id)): Path<(i64, i64)>,
|
Path((user_id, role_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
||||||
State(db_pool): State<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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 {
|
||||||
let now = Utc::now().timestamp();
|
sqlx::query("INSERT INTO user_roles (created_by, updated_by, user_id, role_id) VALUES ($1, $2, $3, $4)")
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -144,12 +139,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<(i64, i64)>,
|
Path((user_id, user_role_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
||||||
State(db_pool): State<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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 = ?")
|
sqlx::query("DELETE FROM user_roles WHERE id = $1")
|
||||||
.bind(user_role_id)
|
.bind(user_role_id)
|
||||||
.execute(&db_pool)
|
.execute(&db_pool)
|
||||||
.await
|
.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
|
// 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 = ?"#
|
from wishlist_items where id = $1"#
|
||||||
)
|
)
|
||||||
.bind(item_id)
|
.bind(item_id)
|
||||||
.fetch_one(db_pool)
|
.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
|
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
|
// 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 = ?"#
|
from wishlist_items where user_id = $1"#
|
||||||
)
|
)
|
||||||
.bind(user_id)
|
.bind(user_id)
|
||||||
.fetch_all(db_pool)
|
.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
|
user_wishlist_items
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_useremails_by_role(role_name: String, db_pool: &SqlitePool) -> String {
|
pub async fn get_useremails_by_role(role_name: String, db_pool: &PgPool) -> 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 = ?"#)
|
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)
|
.bind(role_name)
|
||||||
.fetch_one(db_pool)
|
.fetch_one(db_pool)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ 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, SqlitePool};
|
use sqlx::{Row, PgPool};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
middlewares::is_authorized,
|
middlewares::is_authorized,
|
||||||
|
|
@ -47,7 +48,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<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
) -> 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();
|
||||||
|
|
@ -93,8 +94,8 @@ struct UserWishListTemplate {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn user_wishlist(
|
pub async fn user_wishlist(
|
||||||
Path(user_id): Path<i64>,
|
Path(user_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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?
|
||||||
|
|
@ -106,14 +107,15 @@ 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!(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)
|
.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;
|
||||||
|
|
@ -150,8 +152,8 @@ struct UserWishListAddTemplate {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn user_wishlist_add(
|
pub async fn user_wishlist_add(
|
||||||
Path(user_id): Path<i64>,
|
Path(user_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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?
|
||||||
|
|
@ -163,7 +165,8 @@ 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!(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)
|
.fetch_one(&db_pool)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -199,19 +202,15 @@ pub struct ItemForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn user_wishlist_add_item(
|
pub async fn user_wishlist_add_item(
|
||||||
Path(user_id): Path<i64>,
|
Path(user_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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().timestamp();
|
sqlx::query("insert into wishlist_items (created_by, updated_by, user_id, item, item_url) values ($1, $2, $3, $4, $5)")
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -237,8 +236,8 @@ struct UserWishListEditTemplate {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn user_wishlist_edit_item(
|
pub async fn user_wishlist_edit_item(
|
||||||
Path(item_id): Path<i64>,
|
Path(item_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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?
|
||||||
|
|
@ -274,16 +273,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<i64>,
|
Path(item_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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().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(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)
|
||||||
|
|
@ -302,28 +301,28 @@ pub async fn user_wishlist_save_item(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn user_wishlist_bought_item(
|
pub async fn user_wishlist_bought_item(
|
||||||
Path(user_id): Path<i64>,
|
Path(item_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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 = ? 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_data.as_ref().unwrap().id) // Created by current user
|
||||||
.bind(user_id)
|
.bind(item_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 = ?")
|
let row = sqlx::query("SELECT user_id FROM wishlist_items WHERE id = $1")
|
||||||
.bind(user_id)
|
.bind(item_id)
|
||||||
.fetch_one(&db_pool)
|
.fetch_one(&db_pool)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let userid = row.get::<i64, _>("user_id");
|
let userid = row.get::<uuid::Uuid, _>("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 {
|
||||||
|
|
@ -332,15 +331,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<i64>,
|
Path(user_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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().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(now) // Received now
|
||||||
.bind(user_id)
|
.bind(user_id)
|
||||||
.execute(&db_pool)
|
.execute(&db_pool)
|
||||||
|
|
@ -357,12 +356,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<i64>,
|
Path(item_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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 = ?")
|
sqlx::query("delete from wishlist_items where id = $1")
|
||||||
.bind(item_id)
|
.bind(item_id)
|
||||||
.execute(&db_pool)
|
.execute(&db_pool)
|
||||||
.await
|
.await
|
||||||
|
|
@ -378,12 +377,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<i64>,
|
Path(item_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<SqlitePool>,
|
State(db_pool): State<PgPool>,
|
||||||
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 = 0 where id = ?")
|
sqlx::query("update wishlist_items set purchased_by = null where id = $1")
|
||||||
.bind(item_id)
|
.bind(item_id)
|
||||||
.execute(&db_pool)
|
.execute(&db_pool)
|
||||||
.await
|
.await
|
||||||
|
|
@ -391,13 +390,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 = ?")
|
let row = sqlx::query("SELECT user_id FROM wishlist_items WHERE id = $1")
|
||||||
.bind(item_id)
|
.bind(item_id)
|
||||||
.fetch_one(&db_pool)
|
.fetch_one(&db_pool)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let profileid = row.get::<i64, _>("user_id");
|
let profileid = row.get::<Uuid, _>("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 {
|
||||||
|
|
|
||||||
|
|
@ -25,38 +25,44 @@
|
||||||
{% 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 %}
|
||||||
|
|
||||||
{% if person_wishlist_item.received_at > 0 %}
|
{% match person_wishlist_item.received_at %}
|
||||||
<td>Got it!</td>
|
{% when None %}
|
||||||
{% else %}
|
<td>Not yet!</td>
|
||||||
<td>Not yet!</td>
|
{% when Some with (received_at) %}
|
||||||
{% endif %}
|
<td>Got it!</td>
|
||||||
|
{% endmatch %}
|
||||||
|
|
||||||
{% if my_wishlist %}
|
{% if my_wishlist %}
|
||||||
{% if person_wishlist_item.received_at > 0 %}
|
{% match person_wishlist_item.received_at %}
|
||||||
<td><a href="/userwishlist/delete/{{ person_wishlist_item.id }}">Delete</a></td>
|
{% when None %}
|
||||||
{% else %}
|
<td><a href="/userwishlist/received/{{ person_wishlist_item.id }}">Received</a></td>
|
||||||
<td><a href="/userwishlist/received/{{ person_wishlist_item.id }}">Received</a></td>
|
{% when Some with (received_at) %}
|
||||||
{% endif %}
|
<td><a href="/userwishlist/delete/{{ person_wishlist_item.id }}">Delete</a></td>
|
||||||
|
{% endmatch %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if person_wishlist_item.purchased_by == user.id %}
|
{% match person_wishlist_item.purchased_by %}
|
||||||
<td><a href="/userwishlist/returned/{{ person_wishlist_item.id }}">Return</a></td>
|
{% when Some with (purchased_by) %}
|
||||||
{% else if person_wishlist_item.purchased_by > 0 %}
|
{% if purchased_by.clone() == user.id %}
|
||||||
<td>Purchased</td>
|
<td><a href="/userwishlist/returned/{{ person_wishlist_item.id }}">Return</a></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td><a href="/userwishlist/bought/{{ person_wishlist_item.id }}">Bought</a></td>
|
<td>Purchased</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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue