Using Svelte 5, refactor to use a separate table for user two factor details, and update the whole application to use the new table.

This commit is contained in:
Bradley Shellnut 2024-07-11 15:53:56 -07:00
parent d83eaadc0b
commit 42292c15b2
37 changed files with 1095 additions and 3881 deletions

View file

@ -35,7 +35,7 @@
"@typescript-eslint/eslint-plugin": "^7.13.0",
"@typescript-eslint/parser": "^7.13.0",
"autoprefixer": "^10.4.19",
"drizzle-kit": "^0.22.8",
"drizzle-kit": "^0.23.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.41.0",
@ -47,18 +47,18 @@
"postcss-preset-env": "^9.6.0",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.5",
"sass": "^1.77.6",
"sass": "^1.77.8",
"satori": "^0.10.13",
"satori-html": "^0.3.2",
"svelte": "5.0.0-next.175",
"svelte-check": "^3.8.4",
"svelte-headless-table": "^0.18.2",
"svelte-meta-tags": "^3.1.2",
"svelte-preprocess": "^6.0.1",
"svelte-preprocess": "^6.0.2",
"svelte-sequential-preprocessor": "^2.0.1",
"sveltekit-flash-message": "^2.4.4",
"sveltekit-rate-limiter": "^0.5.1",
"sveltekit-superforms": "^2.15.2",
"sveltekit-superforms": "^2.16.0",
"tailwindcss": "^3.4.4",
"ts-node": "^10.9.2",
"tslib": "^2.6.3",
@ -91,7 +91,7 @@
"cookie": "^0.6.0",
"dotenv": "^16.4.5",
"dotenv-expand": "^11.0.6",
"drizzle-orm": "^0.31.2",
"drizzle-orm": "^0.32.0",
"feather-icons": "^4.29.2",
"formsnap": "^1.0.1",
"html-entities": "^2.5.2",
@ -100,7 +100,7 @@
"just-kebab-case": "^4.2.0",
"loader": "^2.1.1",
"lucia": "3.2.0",
"lucide-svelte": "^0.390.0",
"lucide-svelte": "^0.407.0",
"open-props": "^1.7.5",
"oslo": "^1.2.1",
"pg": "^8.12.0",
@ -109,7 +109,7 @@
"radix-svelte": "^0.9.0",
"svelte-french-toast": "^1.2.0",
"svelte-lazy-loader": "^1.0.0",
"tailwind-merge": "^2.3.0",
"tailwind-merge": "^2.4.0",
"tailwind-variants": "^0.2.1",
"tailwindcss-animate": "^1.0.7",
"zod-to-json-schema": "^3.23.1"

File diff suppressed because it is too large Load diff

View file

@ -23,5 +23,4 @@ try {
console.error(e);
}
// await connection.end();
process.exit();

View file

@ -1,10 +1,16 @@
DO $$ BEGIN
CREATE TYPE "public"."external_id_type" AS ENUM('game', 'category', 'mechanic', 'publisher', 'designer', 'artist');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "categories" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"cuid" text,
"name" text,
"slug" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "categories_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
@ -26,8 +32,8 @@ CREATE TABLE IF NOT EXISTS "collection_items" (
"collection_id" uuid NOT NULL,
"game_id" uuid NOT NULL,
"times_played" integer DEFAULT 0,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "collection_items_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
@ -36,8 +42,8 @@ CREATE TABLE IF NOT EXISTS "collections" (
"cuid" text,
"user_id" uuid NOT NULL,
"name" text DEFAULT 'My Collection' NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "collections_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
@ -46,15 +52,15 @@ CREATE TABLE IF NOT EXISTS "expansions" (
"cuid" text,
"base_game_id" uuid NOT NULL,
"game_id" uuid NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "expansions_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "external_ids" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"cuid" text,
"type" "external_id_type" NOT NULL,
"type" "external_id_type",
"external_id" text NOT NULL,
CONSTRAINT "external_ids_cuid_unique" UNIQUE("cuid")
);
@ -76,8 +82,8 @@ CREATE TABLE IF NOT EXISTS "games" (
"thumb_url" text,
"url" text,
"last_sync_at" timestamp,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "games_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
@ -92,8 +98,8 @@ CREATE TABLE IF NOT EXISTS "mechanics" (
"cuid" text,
"name" text,
"slug" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "mechanics_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
@ -113,7 +119,8 @@ CREATE TABLE IF NOT EXISTS "password_reset_tokens" (
"id" text PRIMARY KEY NOT NULL,
"user_id" uuid NOT NULL,
"expires_at" timestamp,
"created_at" timestamp DEFAULT now() NOT NULL
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "publishers" (
@ -121,8 +128,8 @@ CREATE TABLE IF NOT EXISTS "publishers" (
"cuid" text,
"name" text,
"slug" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "publishers_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
@ -143,16 +150,16 @@ CREATE TABLE IF NOT EXISTS "recovery_codes" (
"user_id" uuid NOT NULL,
"code" text NOT NULL,
"used" boolean DEFAULT false,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "roles" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"cuid" text NOT NULL,
"name" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "roles_cuid_unique" UNIQUE("cuid"),
CONSTRAINT "roles_name_unique" UNIQUE("name")
);
@ -167,14 +174,27 @@ CREATE TABLE IF NOT EXISTS "sessions" (
"is_two_factor_authenticated" boolean DEFAULT false
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "two_factor" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"cuid" text,
"two_factor_secret" text NOT NULL,
"two_factor_enabled" boolean DEFAULT false NOT NULL,
"initiated_time" timestamp with time zone NOT NULL,
"user_id" uuid NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "two_factor_cuid_unique" UNIQUE("cuid"),
CONSTRAINT "two_factor_user_id_unique" UNIQUE("user_id")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "user_roles" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"cuid" text,
"user_id" uuid NOT NULL,
"role_id" uuid NOT NULL,
"primary" boolean DEFAULT false,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "user_roles_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
@ -189,10 +209,8 @@ CREATE TABLE IF NOT EXISTS "users" (
"verified" boolean DEFAULT false,
"receive_email" boolean DEFAULT false,
"theme" text DEFAULT 'system',
"two_factor_secret" text DEFAULT '',
"two_factor_enabled" boolean DEFAULT false,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "users_cuid_unique" UNIQUE("cuid"),
CONSTRAINT "users_username_unique" UNIQUE("username"),
CONSTRAINT "users_email_unique" UNIQUE("email")
@ -203,8 +221,8 @@ CREATE TABLE IF NOT EXISTS "wishlist_items" (
"cuid" text,
"wishlist_id" uuid NOT NULL,
"game_id" uuid NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "wishlist_items_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
@ -213,8 +231,8 @@ CREATE TABLE IF NOT EXISTS "wishlists" (
"cuid" text,
"user_id" uuid NOT NULL,
"name" text DEFAULT 'My Wishlist' NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "wishlists_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
@ -350,6 +368,12 @@ EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "two_factor" ADD CONSTRAINT "two_factor_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION

View file

@ -0,0 +1,2 @@
ALTER TABLE "two_factor" RENAME COLUMN "two_factor_secret" TO "secret";--> statement-breakpoint
ALTER TABLE "two_factor" RENAME COLUMN "two_factor_enabled" TO "enabled";

View file

@ -1 +0,0 @@
ALTER TABLE "external_ids" ALTER COLUMN "type" DROP NOT NULL;

View file

@ -0,0 +1 @@
ALTER TABLE "two_factor" ALTER COLUMN "initiated_time" DROP NOT NULL;

View file

@ -1,5 +0,0 @@
DO $$ BEGIN
CREATE TYPE "public"."external_id_type" AS ENUM('game', 'category', 'mechanic', 'publisher', 'designer', 'artist');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View file

@ -1 +0,0 @@
ALTER TABLE "users" ADD COLUMN "initiated_time" timestamp;

View file

@ -1,24 +0,0 @@
CREATE TABLE IF NOT EXISTS "two_factor" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"cuid" text,
"two_factor_secret" text NOT NULL,
"two_factor_enabled" boolean DEFAULT false NOT NULL,
"initiated_time" timestamp with time zone NOT NULL,
"user_id" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "two_factor_cuid_unique" UNIQUE("cuid"),
CONSTRAINT "two_factor_user_id_unique" UNIQUE("user_id")
);
--> statement-breakpoint
ALTER TABLE "users" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
ALTER TABLE "users" ALTER COLUMN "updated_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "two_factor" ADD CONSTRAINT "two_factor_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
ALTER TABLE "users" DROP COLUMN IF EXISTS "two_factor_secret";--> statement-breakpoint
ALTER TABLE "users" DROP COLUMN IF EXISTS "two_factor_enabled";--> statement-breakpoint
ALTER TABLE "users" DROP COLUMN IF EXISTS "initiated_time";

View file

@ -1,5 +1,5 @@
{
"id": "7871a9e4-6916-4122-b200-42a9f21d4c8d",
"id": "e120d11a-bf28-4c96-9f2f-96e23e23c7e2",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
@ -35,14 +35,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -213,14 +213,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -298,14 +298,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -369,14 +369,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -444,7 +444,7 @@
"type": "external_id_type",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
"notNull": false
},
"external_id": {
"name": "external_id",
@ -569,14 +569,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -699,14 +699,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -863,7 +863,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -919,14 +926,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1091,14 +1098,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1148,14 +1155,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1249,6 +1256,97 @@
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.two_factor": {
"name": "two_factor",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"cuid": {
"name": "cuid",
"type": "text",
"primaryKey": false,
"notNull": false
},
"two_factor_secret": {
"name": "two_factor_secret",
"type": "text",
"primaryKey": false,
"notNull": true
},
"two_factor_enabled": {
"name": "two_factor_enabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"initiated_time": {
"name": "initiated_time",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"two_factor_user_id_users_id_fk": {
"name": "two_factor_user_id_users_id_fk",
"tableFrom": "two_factor",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"two_factor_cuid_unique": {
"name": "two_factor_cuid_unique",
"nullsNotDistinct": false,
"columns": [
"cuid"
]
},
"two_factor_user_id_unique": {
"name": "two_factor_user_id_unique",
"nullsNotDistinct": false,
"columns": [
"user_id"
]
}
}
},
"public.user_roles": {
"name": "user_roles",
"schema": "",
@ -1287,14 +1385,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1408,30 +1506,16 @@
"notNull": false,
"default": "'system'"
},
"two_factor_secret": {
"name": "two_factor_secret",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"two_factor_enabled": {
"name": "two_factor_enabled",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1495,14 +1579,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1580,14 +1664,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1621,7 +1705,20 @@
}
}
},
"enums": {},
"enums": {
"public.external_id_type": {
"name": "external_id_type",
"schema": "public",
"values": [
"game",
"category",
"mechanic",
"publisher",
"designer",
"artist"
]
}
},
"schemas": {},
"_meta": {
"columns": {},

View file

@ -1,6 +1,6 @@
{
"id": "c4b2e9ac-5e60-4de1-b860-eb11e4ca6bd6",
"prevId": "7871a9e4-6916-4122-b200-42a9f21d4c8d",
"id": "52e7c416-89cb-4c6a-9118-68a03cfc2920",
"prevId": "e120d11a-bf28-4c96-9f2f-96e23e23c7e2",
"version": "7",
"dialect": "postgresql",
"tables": {
@ -35,14 +35,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -213,14 +213,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -298,14 +298,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -369,14 +369,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -569,14 +569,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -699,14 +699,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -863,7 +863,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -919,14 +926,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1091,14 +1098,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1148,14 +1155,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1249,6 +1256,97 @@
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.two_factor": {
"name": "two_factor",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"cuid": {
"name": "cuid",
"type": "text",
"primaryKey": false,
"notNull": false
},
"secret": {
"name": "secret",
"type": "text",
"primaryKey": false,
"notNull": true
},
"enabled": {
"name": "enabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"initiated_time": {
"name": "initiated_time",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"two_factor_user_id_users_id_fk": {
"name": "two_factor_user_id_users_id_fk",
"tableFrom": "two_factor",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"two_factor_cuid_unique": {
"name": "two_factor_cuid_unique",
"nullsNotDistinct": false,
"columns": [
"cuid"
]
},
"two_factor_user_id_unique": {
"name": "two_factor_user_id_unique",
"nullsNotDistinct": false,
"columns": [
"user_id"
]
}
}
},
"public.user_roles": {
"name": "user_roles",
"schema": "",
@ -1287,14 +1385,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1408,30 +1506,16 @@
"notNull": false,
"default": "'system'"
},
"two_factor_secret": {
"name": "two_factor_secret",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"two_factor_enabled": {
"name": "two_factor_enabled",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1495,14 +1579,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1580,14 +1664,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1621,7 +1705,20 @@
}
}
},
"enums": {},
"enums": {
"public.external_id_type": {
"name": "external_id_type",
"schema": "public",
"values": [
"game",
"category",
"mechanic",
"publisher",
"designer",
"artist"
]
}
},
"schemas": {},
"_meta": {
"columns": {},

View file

@ -1,6 +1,6 @@
{
"id": "43322acf-8c50-4c6d-9575-df44978be5a0",
"prevId": "c4b2e9ac-5e60-4de1-b860-eb11e4ca6bd6",
"id": "79adee85-e57c-4a9f-87df-835457b68129",
"prevId": "52e7c416-89cb-4c6a-9118-68a03cfc2920",
"version": "7",
"dialect": "postgresql",
"tables": {
@ -35,14 +35,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -213,14 +213,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -298,14 +298,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -369,14 +369,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -569,14 +569,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -699,14 +699,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -863,7 +863,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -919,14 +926,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1091,14 +1098,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1148,14 +1155,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1249,6 +1256,97 @@
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.two_factor": {
"name": "two_factor",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"cuid": {
"name": "cuid",
"type": "text",
"primaryKey": false,
"notNull": false
},
"secret": {
"name": "secret",
"type": "text",
"primaryKey": false,
"notNull": true
},
"enabled": {
"name": "enabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"initiated_time": {
"name": "initiated_time",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"two_factor_user_id_users_id_fk": {
"name": "two_factor_user_id_users_id_fk",
"tableFrom": "two_factor",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"two_factor_cuid_unique": {
"name": "two_factor_cuid_unique",
"nullsNotDistinct": false,
"columns": [
"cuid"
]
},
"two_factor_user_id_unique": {
"name": "two_factor_user_id_unique",
"nullsNotDistinct": false,
"columns": [
"user_id"
]
}
}
},
"public.user_roles": {
"name": "user_roles",
"schema": "",
@ -1287,14 +1385,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1408,30 +1506,16 @@
"notNull": false,
"default": "'system'"
},
"two_factor_secret": {
"name": "two_factor_secret",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"two_factor_enabled": {
"name": "two_factor_enabled",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1495,14 +1579,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
@ -1580,14 +1664,14 @@
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -5,36 +5,22 @@
{
"idx": 0,
"version": "7",
"when": 1718402690897,
"tag": "0000_premium_pepper_potts",
"when": 1720625651245,
"tag": "0000_dazzling_stick",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1718404319259,
"tag": "0001_spicy_legion",
"when": 1720625948784,
"tag": "0001_noisy_sally_floyd",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1718405257084,
"tag": "0002_third_black_tom",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1720415770693,
"tag": "0003_premium_ravenous",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1720454952583,
"tag": "0004_glossy_gideon",
"when": 1720626020902,
"tag": "0002_fancy_valkyrie",
"breakpoints": true
}
]

View file

@ -1,8 +1,9 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import categoriesToExternalIds from './categoriesToExternalIds';
import categories_to_games from './categoriesToGames';
import { timestamps } from '../utils';
const categories = pgTable('categories', {
id: uuid('id').primaryKey().defaultRandom(),
@ -11,8 +12,7 @@ const categories = pgTable('categories', {
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
...timestamps,
});
export type Categories = InferSelectModel<typeof categories>;

View file

@ -1,8 +1,9 @@
import { integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import collections from './collections';
import games from './games';
import { timestamps } from '../utils';
const collection_items = pgTable('collection_items', {
id: uuid('id').primaryKey().defaultRandom(),
@ -16,8 +17,7 @@ const collection_items = pgTable('collection_items', {
.notNull()
.references(() => games.id, { onDelete: 'cascade' }),
times_played: integer('times_played').default(0),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
...timestamps,
});
export type CollectionItems = InferSelectModel<typeof collection_items>;

View file

@ -2,6 +2,7 @@ import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import users from './users';
import { timestamps } from '../utils';
const collections = pgTable('collections', {
id: uuid('id').primaryKey().defaultRandom(),
@ -12,8 +13,7 @@ const collections = pgTable('collections', {
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
name: text('name').notNull().default('My Collection'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
...timestamps,
});
export const collection_relations = relations(collections, ({ one }) => ({

View file

@ -2,6 +2,7 @@ import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import games from './games';
import { timestamps } from '../utils';
export const expansions = pgTable('expansions', {
id: uuid('id').primaryKey().defaultRandom(),
@ -14,8 +15,7 @@ export const expansions = pgTable('expansions', {
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
...timestamps,
});
export type Expansions = InferSelectModel<typeof expansions>;

View file

@ -5,6 +5,7 @@ import categoriesToGames from './categoriesToGames';
import gamesToExternalIds from './gamesToExternalIds';
import mechanicsToGames from './mechanicsToGames';
import publishersToGames from './publishersToGames';
import { timestamps } from '../utils';
const games = pgTable(
'games',
@ -27,8 +28,7 @@ const games = pgTable(
thumb_url: text('thumb_url'),
url: text('url'),
last_sync_at: timestamp('last_sync_at'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
...timestamps,
},
(table) => ({
searchIndex: index('search_index').using(

View file

@ -1,8 +1,9 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import mechanicsToGames from './mechanicsToGames';
import mechanicsToExternalIds from './mechanicsToExternalIds';
import { timestamps } from '../utils';
const mechanics = pgTable('mechanics', {
id: uuid('id').primaryKey().defaultRandom(),
@ -11,8 +12,7 @@ const mechanics = pgTable('mechanics', {
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
...timestamps,
});
export type Mechanics = InferSelectModel<typeof mechanics>;

View file

@ -2,6 +2,7 @@ import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import users from './users';
import { timestamps } from '../utils';
const password_reset_tokens = pgTable('password_reset_tokens', {
id: text('id')
@ -11,7 +12,7 @@ const password_reset_tokens = pgTable('password_reset_tokens', {
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
expires_at: timestamp('expires_at'),
created_at: timestamp('created_at').notNull().defaultNow(),
...timestamps,
});
export type PasswordResetTokens = InferSelectModel<typeof password_reset_tokens>;

View file

@ -1,8 +1,9 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import publishers_to_games from './publishersToGames';
import publishersToExternalIds from './publishersToExternalIds';
import { timestamps } from '../utils';
const publishers = pgTable('publishers', {
id: uuid('id').primaryKey().defaultRandom(),
@ -11,8 +12,7 @@ const publishers = pgTable('publishers', {
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
...timestamps,
});
export type Publishers = InferSelectModel<typeof publishers>;

View file

@ -1,6 +1,7 @@
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
import type { InferSelectModel } from 'drizzle-orm';
import users from './users';
import { timestamps } from '../utils';
const recovery_codes = pgTable('recovery_codes', {
id: uuid('id').primaryKey().defaultRandom(),
@ -9,8 +10,7 @@ const recovery_codes = pgTable('recovery_codes', {
.references(() => users.id),
code: text('code').notNull(),
used: boolean('used').default(false),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
...timestamps,
});
export type RecoveryCodes = InferSelectModel<typeof recovery_codes>;

View file

@ -1,7 +1,8 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import user_roles from './userRoles';
import { timestamps } from '../utils';
const roles = pgTable('roles', {
id: uuid('id').primaryKey().defaultRandom(),
@ -10,8 +11,7 @@ const roles = pgTable('roles', {
.$defaultFn(() => cuid2())
.notNull(),
name: text('name').unique().notNull(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
...timestamps,
});
export type Roles = InferSelectModel<typeof roles>;

View file

@ -1,22 +1,21 @@
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { relations } from 'drizzle-orm';
import { type InferSelectModel, relations } from 'drizzle-orm';
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { timestamps } from '../utils';
import users from './users';
const twoFactorTable = pgTable('two_factor', {
id: uuid('id')
.primaryKey().defaultRandom(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
two_factor_secret: text('two_factor_secret').notNull(),
two_factor_enabled: boolean('two_factor_enabled').notNull().default(false),
initiated_time: timestamp('initiated_time', {
secret: text('secret').notNull(),
enabled: boolean('enabled').notNull().default(false),
initiatedTime: timestamp('initiated_time', {
mode: 'date',
withTimezone: true,
}).notNull(),
userId: text('user_id')
}),
userId: uuid('user_id')
.notNull()
.references(() => users.id)
.unique(),

View file

@ -1,8 +1,9 @@
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import users from './users';
import roles from './roles';
import { timestamps } from '../utils';
const user_roles = pgTable('user_roles', {
id: uuid('id').primaryKey().defaultRandom(),
@ -16,8 +17,7 @@ const user_roles = pgTable('user_roles', {
.notNull()
.references(() => roles.id, { onDelete: 'cascade' }),
primary: boolean('primary').default(false),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
...timestamps,
});
export const user_role_relations = relations(user_roles, ({ one }) => ({

View file

@ -1,8 +1,9 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import wishlists from './wishlists';
import games from './games';
import { timestamps } from '../utils';
const wishlist_items = pgTable('wishlist_items', {
id: uuid('id').primaryKey().defaultRandom(),
@ -15,8 +16,7 @@ const wishlist_items = pgTable('wishlist_items', {
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'cascade' }),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
...timestamps,
});
export type WishlistItems = InferSelectModel<typeof wishlist_items>;

View file

@ -1,7 +1,8 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import users from './users';
import { timestamps } from '../utils';
const wishlists = pgTable('wishlists', {
id: uuid('id').primaryKey().defaultRandom(),
@ -12,8 +13,7 @@ const wishlists = pgTable('wishlists', {
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
name: text('name').notNull().default('My Wishlist'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
...timestamps,
});
export type Wishlists = InferSelectModel<typeof wishlists>;

View file

@ -17,6 +17,7 @@ const EnvSchema = z.object({
DATABASE_PORT: z.coerce.number(),
DATABASE_DB: z.string(),
DATABASE_URL: z.string(),
PUBLIC_SITE_NAME: z.string(),
PUBLIC_SITE_URL: z.string(),
PUBLIC_UMAMI_DO_NOT_TRACK: z.string(),
PUBLIC_UMAMI_ID: z.string(),

View file

@ -8,7 +8,7 @@ import { changeEmailSchema, profileSchema } from '$lib/validations/account';
import { notSignedInMessage } from '$lib/flashMessages';
import db from '../../../../db';
import type { PageServerLoad } from './$types';
import { users } from '$db/schema';
import { users, twoFactor } from '$db/schema';
import { userNotAuthenticated } from '$lib/server/auth-utils';
export const load: PageServerLoad = async (event) => {
@ -35,10 +35,14 @@ export const load: PageServerLoad = async (event) => {
},
});
const twoFactorDetails = await db.query.twoFactor.findFirst({
where: eq(twoFactor.userId, dbUser!.id!),
});
return {
profileForm,
emailForm,
hasSetupTwoFactor: !!dbUser?.two_factor_enabled,
hasSetupTwoFactor: !!twoFactorDetails?.enabled,
};
};

View file

@ -1,9 +1,10 @@
import { type Actions, fail } from '@sveltejs/kit';
import { type Actions, fail, error } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
import { encodeHex, decodeHex } from 'oslo/encoding';
import { Argon2id } from 'oslo/password';
import { createTOTPKeyURI, TOTPController } from 'oslo/otp';
import { HMAC } from 'oslo/crypto';
import kebabCase from 'just-kebab-case';
import QRCode from 'qrcode';
import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server';
@ -12,8 +13,9 @@ import type { PageServerLoad } from '../../$types';
import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account';
import { notSignedInMessage } from '$lib/flashMessages';
import db from '../../../../../../db';
import { recoveryCodes, users } from '$db/schema';
import { recoveryCodes, twoFactor, users } from '$db/schema';
import { userNotAuthenticated } from '$lib/server/auth-utils';
import env from '../../../../../../env';
export const load: PageServerLoad = async (event) => {
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema));
@ -29,7 +31,11 @@ export const load: PageServerLoad = async (event) => {
where: eq(users.id, user!.id!),
});
if (dbUser?.two_factor_enabled) {
const twoFactorDetails = await db.query.twoFactor.findFirst({
where: eq(twoFactor.userId, dbUser!.id!),
});
if (twoFactorDetails?.enabled) {
return {
addTwoFactorForm,
removeTwoFactorForm,
@ -39,17 +45,31 @@ export const load: PageServerLoad = async (event) => {
qrCode: '',
};
}
const twoFactorSecret = await new HMAC('SHA-1').generateKey();
await db
.update(users)
.set({
two_factor_secret: encodeHex(twoFactorSecret),
two_factor_enabled: false,
})
.where(eq(users.id, user!.id!));
const issuer = 'bored-game';
const accountName = user!.email! || user!.username!;
const twoFactorSecret = await new HMAC('SHA-1').generateKey();
try {
await db
.insert(twoFactor)
.values({
secret: encodeHex(twoFactorSecret),
enabled: false,
userId: dbUser!.id!,
})
.onConflictDoUpdate({
target: twoFactor.userId,
set: {
secret: encodeHex(twoFactorSecret),
enabled: false,
},
});
} catch (e) {
console.error(e);
error(500);
}
const issuer = kebabCase(env.PUBLIC_SITE_NAME);
const accountName = dbUser!.email! || dbUser!.username!;
// pass the website's name and the user identifier (e.g. email, username)
const totpUri = createTOTPKeyURI(issuer, accountName, twoFactorSecret);
@ -104,7 +124,20 @@ export const actions: Actions = {
);
}
if (dbUser?.two_factor_secret === '' || dbUser?.two_factor_secret === null) {
const twoFactorDetails = await db.query.twoFactor.findFirst({
where: eq(twoFactor.userId, dbUser?.id),
});
if (!twoFactorDetails) {
addTwoFactorForm.data.current_password = '';
addTwoFactorForm.data.two_factor_code = '';
return setError(
addTwoFactorForm,
'Error occurred. Please try again or contact support if you need further help.',
);
}
if (twoFactorDetails.secret === '' || twoFactorDetails.secret === null) {
addTwoFactorForm.data.current_password = '';
addTwoFactorForm.data.two_factor_code = '';
return setError(
@ -129,19 +162,20 @@ export const actions: Actions = {
const twoFactorCode = addTwoFactorForm.data.two_factor_code;
const validOTP = await new TOTPController().verify(
twoFactorCode,
decodeHex(dbUser.two_factor_secret),
decodeHex(twoFactorDetails.secret),
);
if (!validOTP) {
return setError(addTwoFactorForm, 'two_factor_code', 'Invalid code');
}
await db.update(users).set({ two_factor_enabled: true }).where(eq(users.id, user!.id!));
await db.update(twoFactor).set({ enabled: true }).where(eq(twoFactor.userId, user!.id!));
redirect(302, '/profile/security/two-factor/recovery-codes');
},
disableTwoFactor: async (event) => {
const { cookies } = event;
const { locals } = event;
const { user, session } = locals;
const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema));
if (!removeTwoFactorForm.valid) {
@ -150,16 +184,12 @@ export const actions: Actions = {
});
}
if (!event.locals.user) {
redirect(302, '/login', notSignedInMessage, event);
if (!user || !session) {
return fail(401, {
removeTwoFactorForm,
});
}
if (!event.locals.session) {
return fail(401);
}
const user = event.locals.user;
const dbUser = await db.query.users.findFirst({
where: eq(users.id, user.id),
});
@ -181,16 +211,28 @@ export const actions: Actions = {
return setError(removeTwoFactorForm, 'current_password', 'Your password is incorrect');
}
await db
.update(users)
.set({ two_factor_enabled: false, two_factor_secret: null })
.where(eq(users.id, user.id));
const twoFactorDetails = await db.query.twoFactor.findFirst({
where: eq(twoFactor.userId, dbUser.id),
});
if (!twoFactorDetails) {
return fail(500, {
removeTwoFactorForm,
});
}
await db.update(twoFactor).set({ enabled: false }).where(eq(twoFactor.userId, user.id));
await db.delete(recoveryCodes).where(eq(recoveryCodes.userId, user.id));
// setFlash({ type: 'success', message: 'Two-Factor Authentication has been disabled.' }, cookies);
redirect(302, '/profile/security/two-factor', {
redirect(
302,
'/profile/security/two-factor',
{
type: 'success',
message: 'Two-Factor Authentication has been disabled.',
}, event);
},
event,
);
},
};

View file

@ -5,7 +5,7 @@ import { alphabet, generateRandomString } from 'oslo/crypto';
import { redirect } from 'sveltekit-flash-message/server';
import { notSignedInMessage } from '$lib/flashMessages';
import type { PageServerLoad } from '../../../$types';
import { recoveryCodes, users } from '$db/schema';
import {recoveryCodes, twoFactor, users} from '$db/schema';
import { userNotAuthenticated } from '$lib/server/auth-utils';
export const load: PageServerLoad = async (event) => {
@ -19,7 +19,11 @@ export const load: PageServerLoad = async (event) => {
where: eq(users.id, user!.id),
});
if (dbUser?.two_factor_enabled) {
const twoFactorDetails = await db.query.twoFactor.findFirst({
where: eq(twoFactor.userId, dbUser!.id),
});
if (twoFactorDetails?.enabled) {
const dbRecoveryCodes = await db.query.recoveryCodes.findMany({
where: eq(recoveryCodes.userId, user!.id),
});
@ -46,6 +50,7 @@ export const load: PageServerLoad = async (event) => {
recoveryCodes: [],
};
} else {
console.error('2FA not enabled');
redirect(
302,
'/profile',

View file

@ -1,5 +1,5 @@
import { fail, error, type Actions } from '@sveltejs/kit';
import { and, eq } from 'drizzle-orm';
import { eq } from 'drizzle-orm';
import { Argon2id } from 'oslo/password';
import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server';
@ -8,7 +8,7 @@ import { RateLimiter } from 'sveltekit-rate-limiter/server';
import db from '../../../db';
import { lucia } from '$lib/server/auth';
import { signInSchema } from '$lib/validations/auth';
import { users, type Users } from '$db/schema';
import { twoFactor, users, type Users } from '$db/schema';
import type { PageServerLoad } from './$types';
import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils';
@ -66,6 +66,8 @@ export const actions: Actions = {
return setError(form, 'username', 'Your username or password is incorrect.');
}
let twoFactorDetails;
try {
const password = form.data.password;
console.log('user', JSON.stringify(user, null, 2));
@ -85,9 +87,13 @@ export const actions: Actions = {
console.log('ip', locals.ip);
console.log('country', locals.country);
await db
.update(users)
.set({
twoFactorDetails = await db.query.twoFactor.findFirst({
where: eq(twoFactor.userId, user?.id),
});
if (twoFactorDetails?.secret && twoFactorDetails?.enabled) {
await db.update(twoFactor).set({
initiated_time: new Date(),
});
@ -95,11 +101,19 @@ export const actions: Actions = {
ip_country: locals.country,
ip_address: locals.ip,
twoFactorAuthEnabled:
user?.two_factor_enabled &&
user?.two_factor_secret !== null &&
user?.two_factor_secret !== '',
twoFactorDetails?.enabled &&
twoFactorDetails?.secret !== null &&
twoFactorDetails?.secret !== '',
isTwoFactorAuthenticated: false,
});
} else {
session = await lucia.createSession(user.id, {
ip_country: locals.country,
ip_address: locals.ip,
twoFactorAuthEnabled: false,
isTwoFactorAuthenticated: false,
});
}
console.log('logging in session', session);
sessionCookie = lucia.createSessionCookie(session.id);
console.log('logging in session cookie', sessionCookie);
@ -120,9 +134,9 @@ export const actions: Actions = {
form.data.password = '';
if (
user?.two_factor_enabled &&
user?.two_factor_secret !== null &&
user?.two_factor_secret !== ''
twoFactorDetails?.enabled &&
twoFactorDetails?.secret !== null &&
twoFactorDetails?.secret !== ''
) {
console.log('redirecting to TOTP page');
const message = { type: 'success', message: 'Please enter your TOTP code.' } as const;

View file

@ -28,8 +28,8 @@
},
syncFlashMessage: false,
taintedMessage: null,
validators: zodClient(signInSchema),
validationMethod: 'oninput',
// validators: zodClient(signInSchema),
// validationMethod: 'oninput',
delayMs: 0,
});

View file

@ -7,13 +7,13 @@ import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server';
import { RateLimiter } from 'sveltekit-rate-limiter/server';
import { TWO_FACTOR_TIMEOUT } from '../env';
import db from '../../../db';
import { lucia } from '$lib/server/auth';
import { totpSchema } from '$lib/validations/auth';
import { users, recoveryCodes } from '$db/schema';
import { users, twoFactor, recoveryCodes } from '$db/schema';
import type { PageServerLoad } from './$types';
import { notSignedInMessage } from '$lib/flashMessages';
import { TWO_FACTOR_TIMEOUT } from '../../../env';
export const load: PageServerLoad = async (event) => {
const { user, session } = event.locals;
@ -27,8 +27,12 @@ export const load: PageServerLoad = async (event) => {
where: eq(users.username, user.username),
});
const twoFactorDetails = await db.query.twoFactor.findFirst({
where: eq(twoFactor.userId, dbUser!.id!),
});
// Check if two factor started less than TWO_FACTOR_TIMEOUT
if (Date.now() - dbUser?.initiated_time > TWO_FACTOR_TIMEOUT) {
if (Date.now() - twoFactorDetails?.initiatedTime > TWO_FACTOR_TIMEOUT) {
const message = { type: 'error', message: 'Two factor authentication has expired' } as const;
redirect(302, '/login', message, event);
}
@ -40,8 +44,8 @@ export const load: PageServerLoad = async (event) => {
if (
isTwoFactorAuthenticated &&
dbUser?.two_factor_enabled &&
dbUser?.two_factor_secret !== ''
twoFactorDetails?.enabled &&
twoFactorDetails?.secret !== ''
) {
const message = { type: 'success', message: 'You are already signed in' } as const;
throw redirect('/', message, event);
@ -83,11 +87,14 @@ export const actions: Actions = {
}
const isTwoFactorAuthenticated = session?.isTwoFactorAuthenticated;
const twoFactorDetails = await db.query.twoFactor.findFirst({
where: eq(twoFactor.userId, dbUser!.id!),
})
if (
isTwoFactorAuthenticated &&
dbUser?.two_factor_enabled &&
dbUser?.two_factor_secret !== ''
twoFactorDetails?.enabled &&
twoFactorDetails?.secret !== ''
) {
const message = { type: 'success', message: 'You are already signed in' } as const;
throw redirect('/', message, event);
@ -107,8 +114,8 @@ export const actions: Actions = {
const totpToken = form?.data?.totpToken;
const twoFactorSecretPopulated =
dbUser?.two_factor_secret !== '' && dbUser?.two_factor_secret !== null;
if (dbUser?.two_factor_enabled && !twoFactorSecretPopulated && !totpToken) {
twoFactorDetails?.secret !== '' && twoFactorDetails?.secret !== null;
if (twoFactorDetails.enabled && !twoFactorSecretPopulated && !totpToken) {
return fail(400, {
form,
});
@ -116,7 +123,7 @@ export const actions: Actions = {
console.log('totpToken', totpToken);
const validOTP = await new TOTPController().verify(
totpToken,
decodeHex(dbUser.two_factor_secret ?? ''),
decodeHex(twoFactorDetails.secret ?? ''),
);
console.log('validOTP', validOTP);