mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Adding totp enabled to session, restarting migrations, updating many Shadcn svelte components, and refactoring to move 2FA to its own page.
This commit is contained in:
parent
a8eed0d86d
commit
2609c28619
98 changed files with 5757 additions and 21633 deletions
2
.nvmrc
2
.nvmrc
|
|
@ -1 +1 @@
|
|||
v20
|
||||
20
|
||||
|
|
@ -2,9 +2,9 @@ import 'dotenv/config';
|
|||
import { defineConfig } from 'drizzle-kit';
|
||||
|
||||
export default defineConfig({
|
||||
dialect: 'postgresql',
|
||||
schema: './src/db/schema/index.ts',
|
||||
out: './src/db/migrations',
|
||||
driver: 'pg',
|
||||
dbCredentials: {
|
||||
host: process.env.DATABASE_HOST || 'localhost',
|
||||
port: Number(process.env.DATABASE_PORT) || 5432,
|
||||
|
|
|
|||
80
package.json
80
package.json
|
|
@ -15,59 +15,59 @@
|
|||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||
"format": "prettier --plugin-search-dir . --write .",
|
||||
"site:update": "pnpm update -i -L",
|
||||
"generate": "drizzle-kit generate:pg",
|
||||
"generate": "drizzle-kit generate",
|
||||
"migrate": "tsx src/db/migrate.ts",
|
||||
"seed": "tsx src/db/seed.ts",
|
||||
"push": "drizzle-kit push:pg"
|
||||
"push": "drizzle-kit push"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@melt-ui/pp": "^0.3.1",
|
||||
"@melt-ui/svelte": "^0.77.0",
|
||||
"@playwright/test": "^1.43.1",
|
||||
"@melt-ui/pp": "^0.3.2",
|
||||
"@melt-ui/svelte": "^0.81.0",
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"@sveltejs/adapter-auto": "^3.2.0",
|
||||
"@sveltejs/enhanced-img": "^0.2.0",
|
||||
"@sveltejs/kit": "^2.5.7",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.0",
|
||||
"@sveltejs/adapter-auto": "^3.2.1",
|
||||
"@sveltejs/enhanced-img": "^0.2.1",
|
||||
"@sveltejs/kit": "^2.5.10",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/node": "^20.12.6",
|
||||
"@types/pg": "^8.11.5",
|
||||
"@typescript-eslint/eslint-plugin": "^7.7.1",
|
||||
"@typescript-eslint/parser": "^7.7.1",
|
||||
"@types/node": "^20.14.2",
|
||||
"@types/pg": "^8.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"dotenv": "^16.4.5",
|
||||
"drizzle-kit": "^0.20.17",
|
||||
"drizzle-kit": "^0.22.4",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.38.0",
|
||||
"eslint-plugin-svelte": "^2.39.0",
|
||||
"just-clone": "^6.2.0",
|
||||
"just-debounce-it": "^3.2.0",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-import": "^16.1.0",
|
||||
"postcss-load-config": "^5.0.3",
|
||||
"postcss-preset-env": "^9.5.9",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-svelte": "^3.2.3",
|
||||
"sass": "^1.74.1",
|
||||
"postcss-load-config": "^5.1.0",
|
||||
"postcss-preset-env": "^9.5.14",
|
||||
"prettier": "^3.3.1",
|
||||
"prettier-plugin-svelte": "^3.2.4",
|
||||
"sass": "^1.77.4",
|
||||
"satori": "^0.10.13",
|
||||
"satori-html": "^0.3.2",
|
||||
"svelte": "^4.2.15",
|
||||
"svelte-check": "^3.7.0",
|
||||
"svelte": "^4.2.18",
|
||||
"svelte-check": "^3.8.0",
|
||||
"svelte-headless-table": "^0.18.2",
|
||||
"svelte-meta-tags": "^3.1.2",
|
||||
"svelte-preprocess": "^5.1.4",
|
||||
"svelte-sequential-preprocessor": "^2.0.1",
|
||||
"sveltekit-flash-message": "^2.4.4",
|
||||
"sveltekit-rate-limiter": "^0.5.1",
|
||||
"sveltekit-superforms": "^2.12.6",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"sveltekit-superforms": "^2.14.0",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.6.1",
|
||||
"tsx": "^4.7.3",
|
||||
"typescript": "^5.4.4",
|
||||
"vite": "^5.2.10",
|
||||
"vitest": "^1.5.2",
|
||||
"zod": "^3.23.4"
|
||||
"tslib": "^2.6.3",
|
||||
"tsx": "^4.12.0",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.12",
|
||||
"vitest": "^1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"type": "module",
|
||||
"engines": {
|
||||
|
|
@ -76,22 +76,22 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@fontsource/fira-mono": "^5.0.13",
|
||||
"@iconify-icons/line-md": "^1.2.26",
|
||||
"@iconify-icons/mdi": "^1.2.47",
|
||||
"@iconify-icons/line-md": "^1.2.30",
|
||||
"@iconify-icons/mdi": "^1.2.48",
|
||||
"@lucia-auth/adapter-drizzle": "^1.0.7",
|
||||
"@lukeed/uuid": "^2.0.1",
|
||||
"@neondatabase/serverless": "^0.9.1",
|
||||
"@neondatabase/serverless": "^0.9.3",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@sveltejs/adapter-vercel": "^5.3.0",
|
||||
"@sveltejs/adapter-vercel": "^5.3.1",
|
||||
"@types/feather-icons": "^4.29.4",
|
||||
"@vercel/og": "^0.5.20",
|
||||
"bits-ui": "^0.21.5",
|
||||
"bits-ui": "^0.21.10",
|
||||
"boardgamegeekclient": "^1.9.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cookie": "^0.6.0",
|
||||
"drizzle-orm": "^0.30.9",
|
||||
"feather-icons": "^4.29.1",
|
||||
"drizzle-orm": "^0.31.1",
|
||||
"feather-icons": "^4.29.2",
|
||||
"formsnap": "^1.0.0",
|
||||
"html-entities": "^2.5.2",
|
||||
"iconify-icon": "^2.1.0",
|
||||
|
|
@ -99,10 +99,10 @@
|
|||
"just-kebab-case": "^4.2.0",
|
||||
"loader": "^2.1.1",
|
||||
"lucia": "3.2.0",
|
||||
"lucide-svelte": "^0.373.0",
|
||||
"lucide-svelte": "^0.390.0",
|
||||
"open-props": "^1.7.4",
|
||||
"oslo": "^1.2.0",
|
||||
"pg": "^8.11.5",
|
||||
"pg": "^8.12.0",
|
||||
"postgres": "^3.4.4",
|
||||
"qrcode": "^1.5.3",
|
||||
"radix-svelte": "^0.9.0",
|
||||
|
|
@ -110,7 +110,7 @@
|
|||
"svelte-lazy-loader": "^1.0.0",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwind-variants": "^0.2.1",
|
||||
"tailwindcss-animate": "^1.0.6",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod-to-json-schema": "^3.23.0"
|
||||
}
|
||||
}
|
||||
8732
pnpm-lock.yaml
8732
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +1,5 @@
|
|||
DO $$ BEGIN
|
||||
CREATE TYPE "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 NOT NULL,
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"cuid" text,
|
||||
"name" text,
|
||||
"slug" text,
|
||||
|
|
@ -27,10 +21,10 @@ CREATE TABLE IF NOT EXISTS "categories_to_games" (
|
|||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "collection_items" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"cuid" text,
|
||||
"collection_id" text NOT NULL,
|
||||
"game_id" text NOT NULL,
|
||||
"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,
|
||||
|
|
@ -38,26 +32,17 @@ CREATE TABLE IF NOT EXISTS "collection_items" (
|
|||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "collections" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"cuid" text,
|
||||
"user_id" text NOT NULL,
|
||||
"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,
|
||||
CONSTRAINT "collections_cuid_unique" UNIQUE("cuid")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "expansions" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"cuid" text,
|
||||
"base_game_id" text NOT NULL,
|
||||
"game_id" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "expansions_cuid_unique" UNIQUE("cuid")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "external_ids" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"cuid" text,
|
||||
"type" "external_id_type" NOT NULL,
|
||||
"external_id" text NOT NULL,
|
||||
|
|
@ -65,7 +50,7 @@ CREATE TABLE IF NOT EXISTS "external_ids" (
|
|||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "games" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"cuid" text,
|
||||
"name" text,
|
||||
"slug" text,
|
||||
|
|
@ -80,8 +65,7 @@ CREATE TABLE IF NOT EXISTS "games" (
|
|||
"image_url" text,
|
||||
"thumb_url" text,
|
||||
"url" text,
|
||||
"text_searchable_index" "tsvector",
|
||||
"last_sync_at" timestamp (6) with time zone,
|
||||
"last_sync_at" timestamp,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "games_cuid_unique" UNIQUE("cuid")
|
||||
|
|
@ -94,7 +78,7 @@ CREATE TABLE IF NOT EXISTS "games_to_external_ids" (
|
|||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "mechanics" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"cuid" text,
|
||||
"name" text,
|
||||
"slug" text,
|
||||
|
|
@ -117,13 +101,13 @@ CREATE TABLE IF NOT EXISTS "mechanics_to_games" (
|
|||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "password_reset_tokens" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"expires_at" timestamp (6) with time zone,
|
||||
"user_id" uuid NOT NULL,
|
||||
"expires_at" timestamp,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "publishers" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"cuid" text,
|
||||
"name" text,
|
||||
"slug" text,
|
||||
|
|
@ -144,34 +128,46 @@ CREATE TABLE IF NOT EXISTS "publishers_to_games" (
|
|||
CONSTRAINT "publishers_to_games_publisher_id_game_id_pk" PRIMARY KEY("publisher_id","game_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "recovery_codes" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"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
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "roles" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"cuid" text,
|
||||
"name" text,
|
||||
"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,
|
||||
CONSTRAINT "roles_cuid_unique" UNIQUE("cuid"),
|
||||
CONSTRAINT "roles_name_unique" UNIQUE("name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "sessions" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL,
|
||||
"ip_country" text,
|
||||
"ip_address" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "user_roles" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"cuid" text,
|
||||
"user_id" text NOT NULL,
|
||||
"role_id" text NOT NULL,
|
||||
"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,
|
||||
CONSTRAINT "user_roles_cuid_unique" UNIQUE("cuid")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"cuid" text,
|
||||
"username" text,
|
||||
"hashed_password" text,
|
||||
|
|
@ -181,6 +177,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,
|
||||
CONSTRAINT "users_cuid_unique" UNIQUE("cuid"),
|
||||
|
|
@ -189,177 +187,176 @@ CREATE TABLE IF NOT EXISTS "users" (
|
|||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "wishlist_items" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"cuid" text,
|
||||
"wishlist_id" text NOT NULL,
|
||||
"game_id" text NOT NULL,
|
||||
"wishlist_id" uuid NOT NULL,
|
||||
"game_id" uuid NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "wishlist_items_cuid_unique" UNIQUE("cuid")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "wishlists" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"cuid" text,
|
||||
"user_id" text NOT NULL,
|
||||
"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,
|
||||
CONSTRAINT "wishlists_cuid_unique" UNIQUE("cuid")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "text_searchable_idx" ON "games" ("text_searchable_index");--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "categories_to_external_ids" ADD CONSTRAINT "categories_to_external_ids_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "categories"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "categories_to_external_ids" ADD CONSTRAINT "categories_to_external_ids_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "categories_to_external_ids" ADD CONSTRAINT "categories_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "external_ids"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "categories_to_external_ids" ADD CONSTRAINT "categories_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "public"."external_ids"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "categories_to_games" ADD CONSTRAINT "categories_to_games_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "categories"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "categories_to_games" ADD CONSTRAINT "categories_to_games_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "categories_to_games" ADD CONSTRAINT "categories_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "categories_to_games" ADD CONSTRAINT "categories_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "collection_items" ADD CONSTRAINT "collection_items_collection_id_collections_id_fk" FOREIGN KEY ("collection_id") REFERENCES "collections"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "collection_items" ADD CONSTRAINT "collection_items_collection_id_collections_id_fk" FOREIGN KEY ("collection_id") REFERENCES "public"."collections"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "collection_items" ADD CONSTRAINT "collection_items_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "collection_items" ADD CONSTRAINT "collection_items_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "collections" ADD CONSTRAINT "collections_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "collections" ADD CONSTRAINT "collections_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "expansions" ADD CONSTRAINT "expansions_base_game_id_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "games_to_external_ids" ADD CONSTRAINT "games_to_external_ids_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "expansions" ADD CONSTRAINT "expansions_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "games_to_external_ids" ADD CONSTRAINT "games_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "public"."external_ids"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "games_to_external_ids" ADD CONSTRAINT "games_to_external_ids_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "mechanics_to_external_ids" ADD CONSTRAINT "mechanics_to_external_ids_mechanic_id_mechanics_id_fk" FOREIGN KEY ("mechanic_id") REFERENCES "public"."mechanics"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "games_to_external_ids" ADD CONSTRAINT "games_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "external_ids"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "mechanics_to_external_ids" ADD CONSTRAINT "mechanics_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "public"."external_ids"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "mechanics_to_external_ids" ADD CONSTRAINT "mechanics_to_external_ids_mechanic_id_mechanics_id_fk" FOREIGN KEY ("mechanic_id") REFERENCES "mechanics"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "mechanics_to_games" ADD CONSTRAINT "mechanics_to_games_mechanic_id_mechanics_id_fk" FOREIGN KEY ("mechanic_id") REFERENCES "public"."mechanics"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "mechanics_to_external_ids" ADD CONSTRAINT "mechanics_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "external_ids"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "mechanics_to_games" ADD CONSTRAINT "mechanics_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "mechanics_to_games" ADD CONSTRAINT "mechanics_to_games_mechanic_id_mechanics_id_fk" FOREIGN KEY ("mechanic_id") REFERENCES "mechanics"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "password_reset_tokens" ADD CONSTRAINT "password_reset_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "mechanics_to_games" ADD CONSTRAINT "mechanics_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "publishers_to_external_ids" ADD CONSTRAINT "publishers_to_external_ids_publisher_id_publishers_id_fk" FOREIGN KEY ("publisher_id") REFERENCES "public"."publishers"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "password_reset_tokens" ADD CONSTRAINT "password_reset_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "publishers_to_external_ids" ADD CONSTRAINT "publishers_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "public"."external_ids"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "publishers_to_external_ids" ADD CONSTRAINT "publishers_to_external_ids_publisher_id_publishers_id_fk" FOREIGN KEY ("publisher_id") REFERENCES "publishers"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "publishers_to_games" ADD CONSTRAINT "publishers_to_games_publisher_id_publishers_id_fk" FOREIGN KEY ("publisher_id") REFERENCES "public"."publishers"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "publishers_to_external_ids" ADD CONSTRAINT "publishers_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "external_ids"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "publishers_to_games" ADD CONSTRAINT "publishers_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "publishers_to_games" ADD CONSTRAINT "publishers_to_games_publisher_id_publishers_id_fk" FOREIGN KEY ("publisher_id") REFERENCES "publishers"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "recovery_codes" ADD CONSTRAINT "recovery_codes_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 "publishers_to_games" ADD CONSTRAINT "publishers_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_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 "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
|
||||
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
|
||||
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 "users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "roles"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "wishlist_items" ADD CONSTRAINT "wishlist_items_wishlist_id_wishlists_id_fk" FOREIGN KEY ("wishlist_id") REFERENCES "public"."wishlists"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "wishlist_items" ADD CONSTRAINT "wishlist_items_wishlist_id_wishlists_id_fk" FOREIGN KEY ("wishlist_id") REFERENCES "wishlists"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "wishlist_items" ADD CONSTRAINT "wishlist_items_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "wishlist_items" ADD CONSTRAINT "wishlist_items_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "wishlists" ADD CONSTRAINT "wishlists_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "wishlists" ADD CONSTRAINT "wishlists_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
CREATE INDEX IF NOT EXISTS "search_index" ON "games" USING gin ((
|
||||
setweight(to_tsvector('english', "name"), 'A') ||
|
||||
setweight(to_tsvector('english', "slug"), 'B')
|
||||
));
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
ALTER TABLE "collection_items" ALTER COLUMN "collection_id" SET DATA TYPE uuid;--> statement-breakpoint
|
||||
ALTER TABLE "collection_items" ALTER COLUMN "game_id" SET DATA TYPE uuid;--> statement-breakpoint
|
||||
ALTER TABLE "expansions" ALTER COLUMN "base_game_id" SET DATA TYPE uuid;--> statement-breakpoint
|
||||
ALTER TABLE "expansions" ALTER COLUMN "game_id" SET DATA TYPE uuid;--> statement-breakpoint
|
||||
ALTER TABLE "wishlist_items" ALTER COLUMN "wishlist_id" SET DATA TYPE uuid;--> statement-breakpoint
|
||||
ALTER TABLE "wishlist_items" ALTER COLUMN "game_id" SET DATA TYPE uuid;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
ALTER TABLE "collections" ALTER COLUMN "user_id" SET DATA TYPE uuid;--> statement-breakpoint
|
||||
ALTER TABLE "password_reset_tokens" ALTER COLUMN "user_id" SET DATA TYPE uuid;--> statement-breakpoint
|
||||
ALTER TABLE "sessions" ALTER COLUMN "user_id" SET DATA TYPE uuid;--> statement-breakpoint
|
||||
ALTER TABLE "user_roles" ALTER COLUMN "user_id" SET DATA TYPE uuid;--> statement-breakpoint
|
||||
ALTER TABLE "wishlists" ALTER COLUMN "user_id" SET DATA TYPE uuid;
|
||||
|
|
@ -1 +0,0 @@
|
|||
ALTER TABLE "user_roles" ALTER COLUMN "role_id" SET DATA TYPE uuid;
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
ALTER TABLE "categories" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||
ALTER TABLE "collection_items" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||
ALTER TABLE "collections" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||
ALTER TABLE "expansions" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||
ALTER TABLE "external_ids" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||
ALTER TABLE "games" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||
ALTER TABLE "mechanics" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||
ALTER TABLE "publishers" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||
ALTER TABLE "roles" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||
ALTER TABLE "user_roles" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||
ALTER TABLE "users" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||
ALTER TABLE "wishlist_items" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||
ALTER TABLE "wishlists" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
ALTER TABLE "roles" ALTER COLUMN "cuid" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "roles" ALTER COLUMN "name" SET NOT NULL;
|
||||
|
|
@ -1 +0,0 @@
|
|||
ALTER TABLE "user_roles" ADD COLUMN "default" boolean DEFAULT false;
|
||||
|
|
@ -1 +0,0 @@
|
|||
ALTER TABLE "user_roles" RENAME COLUMN "default" TO "primary";
|
||||
|
|
@ -1 +0,0 @@
|
|||
ALTER TABLE "users" ADD COLUMN "two_factor_secret" text;
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
ALTER TABLE "users" ALTER COLUMN "two_factor_secret" SET DEFAULT '';--> statement-breakpoint
|
||||
ALTER TABLE "users" ADD COLUMN "two_factor_enabled" boolean DEFAULT false;
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS "recovery_codes" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"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
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "recovery_codes" ADD CONSTRAINT "recovery_codes_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
ALTER TABLE "collections" ADD COLUMN "name" text DEFAULT 'My Collection' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "wishlists" ADD COLUMN "name" text DEFAULT 'My Wishlist' NOT NULL;
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"id": "176e5b1f-d146-4604-ab17-b16052da13c0",
|
||||
"id": "9622fc3a-51a1-4f66-8535-fd1ceaf46fc2",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"categories": {
|
||||
"public.categories": {
|
||||
"name": "categories",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -12,7 +12,8 @@
|
|||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"cuid": {
|
||||
"name": "cuid",
|
||||
|
|
@ -60,7 +61,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"categories_to_external_ids": {
|
||||
"public.categories_to_external_ids": {
|
||||
"name": "categories_to_external_ids",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -117,7 +118,7 @@
|
|||
},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"categories_to_games": {
|
||||
"public.categories_to_games": {
|
||||
"name": "categories_to_games",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -174,7 +175,7 @@
|
|||
},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"collection_items": {
|
||||
"public.collection_items": {
|
||||
"name": "collection_items",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -182,7 +183,8 @@
|
|||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"cuid": {
|
||||
"name": "cuid",
|
||||
|
|
@ -192,13 +194,13 @@
|
|||
},
|
||||
"collection_id": {
|
||||
"name": "collection_id",
|
||||
"type": "text",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"game_id": {
|
||||
"name": "game_id",
|
||||
"type": "text",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
|
|
@ -264,7 +266,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"collections": {
|
||||
"public.collections": {
|
||||
"name": "collections",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -272,7 +274,8 @@
|
|||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"cuid": {
|
||||
"name": "cuid",
|
||||
|
|
@ -282,10 +285,17 @@
|
|||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'My Collection'"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
|
|
@ -328,90 +338,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"expansions": {
|
||||
"name": "expansions",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"cuid": {
|
||||
"name": "cuid",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"base_game_id": {
|
||||
"name": "base_game_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"game_id": {
|
||||
"name": "game_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"expansions_base_game_id_games_id_fk": {
|
||||
"name": "expansions_base_game_id_games_id_fk",
|
||||
"tableFrom": "expansions",
|
||||
"tableTo": "games",
|
||||
"columnsFrom": [
|
||||
"base_game_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "restrict",
|
||||
"onUpdate": "cascade"
|
||||
},
|
||||
"expansions_game_id_games_id_fk": {
|
||||
"name": "expansions_game_id_games_id_fk",
|
||||
"tableFrom": "expansions",
|
||||
"tableTo": "games",
|
||||
"columnsFrom": [
|
||||
"game_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "restrict",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"expansions_cuid_unique": {
|
||||
"name": "expansions_cuid_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"cuid"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"external_ids": {
|
||||
"public.external_ids": {
|
||||
"name": "external_ids",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -419,7 +346,8 @@
|
|||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"cuid": {
|
||||
"name": "cuid",
|
||||
|
|
@ -430,6 +358,7 @@
|
|||
"type": {
|
||||
"name": "type",
|
||||
"type": "external_id_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
|
|
@ -453,7 +382,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"games": {
|
||||
"public.games": {
|
||||
"name": "games",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -461,7 +390,8 @@
|
|||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"cuid": {
|
||||
"name": "cuid",
|
||||
|
|
@ -547,15 +477,9 @@
|
|||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"text_searchable_index": {
|
||||
"name": "text_searchable_index",
|
||||
"type": "tsvector",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_sync_at": {
|
||||
"name": "last_sync_at",
|
||||
"type": "timestamp (6) with time zone",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
|
|
@ -575,12 +499,20 @@
|
|||
}
|
||||
},
|
||||
"indexes": {
|
||||
"text_searchable_idx": {
|
||||
"name": "text_searchable_idx",
|
||||
"search_index": {
|
||||
"name": "search_index",
|
||||
"columns": [
|
||||
"text_searchable_index"
|
||||
{
|
||||
"expression": "(\n\t\t\t\t\t\tsetweight(to_tsvector('english', \"name\"), 'A') ||\n\t\t\t\t\t\tsetweight(to_tsvector('english', \"slug\"), 'B')\n\t\t\t\t\t)",
|
||||
"asc": true,
|
||||
"isExpression": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "gin",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
|
|
@ -595,7 +527,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"games_to_external_ids": {
|
||||
"public.games_to_external_ids": {
|
||||
"name": "games_to_external_ids",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -652,7 +584,7 @@
|
|||
},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"mechanics": {
|
||||
"public.mechanics": {
|
||||
"name": "mechanics",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -660,7 +592,8 @@
|
|||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"cuid": {
|
||||
"name": "cuid",
|
||||
|
|
@ -708,7 +641,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"mechanics_to_external_ids": {
|
||||
"public.mechanics_to_external_ids": {
|
||||
"name": "mechanics_to_external_ids",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -765,7 +698,7 @@
|
|||
},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"mechanics_to_games": {
|
||||
"public.mechanics_to_games": {
|
||||
"name": "mechanics_to_games",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -822,7 +755,7 @@
|
|||
},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"password_reset_tokens": {
|
||||
"public.password_reset_tokens": {
|
||||
"name": "password_reset_tokens",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -834,13 +767,13 @@
|
|||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp (6) with time zone",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
|
|
@ -871,7 +804,7 @@
|
|||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"publishers": {
|
||||
"public.publishers": {
|
||||
"name": "publishers",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -879,7 +812,8 @@
|
|||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"cuid": {
|
||||
"name": "cuid",
|
||||
|
|
@ -927,7 +861,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"publishers_to_external_ids": {
|
||||
"public.publishers_to_external_ids": {
|
||||
"name": "publishers_to_external_ids",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -984,7 +918,7 @@
|
|||
},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"publishers_to_games": {
|
||||
"public.publishers_to_games": {
|
||||
"name": "publishers_to_games",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -1041,7 +975,71 @@
|
|||
},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"roles": {
|
||||
"public.recovery_codes": {
|
||||
"name": "recovery_codes",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"code": {
|
||||
"name": "code",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"used": {
|
||||
"name": "used",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"recovery_codes_user_id_users_id_fk": {
|
||||
"name": "recovery_codes_user_id_users_id_fk",
|
||||
"tableFrom": "recovery_codes",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.roles": {
|
||||
"name": "roles",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -1049,19 +1047,34 @@
|
|||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"cuid": {
|
||||
"name": "cuid",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
|
|
@ -1084,7 +1097,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"sessions": {
|
||||
"public.sessions": {
|
||||
"name": "sessions",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -1096,7 +1109,7 @@
|
|||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
|
|
@ -1138,7 +1151,7 @@
|
|||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"user_roles": {
|
||||
"public.user_roles": {
|
||||
"name": "user_roles",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -1146,7 +1159,8 @@
|
|||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"cuid": {
|
||||
"name": "cuid",
|
||||
|
|
@ -1156,16 +1170,23 @@
|
|||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"role_id": {
|
||||
"name": "role_id",
|
||||
"type": "text",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"primary": {
|
||||
"name": "primary",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
|
|
@ -1221,7 +1242,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -1229,7 +1250,8 @@
|
|||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"cuid": {
|
||||
"name": "cuid",
|
||||
|
|
@ -1288,6 +1310,20 @@
|
|||
"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",
|
||||
|
|
@ -1330,7 +1366,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"wishlist_items": {
|
||||
"public.wishlist_items": {
|
||||
"name": "wishlist_items",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -1338,7 +1374,8 @@
|
|||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"cuid": {
|
||||
"name": "cuid",
|
||||
|
|
@ -1348,13 +1385,13 @@
|
|||
},
|
||||
"wishlist_id": {
|
||||
"name": "wishlist_id",
|
||||
"type": "text",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"game_id": {
|
||||
"name": "game_id",
|
||||
"type": "text",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
|
|
@ -1413,7 +1450,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"wishlists": {
|
||||
"public.wishlists": {
|
||||
"name": "wishlists",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
|
|
@ -1421,7 +1458,8 @@
|
|||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"cuid": {
|
||||
"name": "cuid",
|
||||
|
|
@ -1431,10 +1469,17 @@
|
|||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'My Wishlist'"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
|
|
@ -1478,19 +1523,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"external_id_type": {
|
||||
"name": "external_id_type",
|
||||
"values": {
|
||||
"game": "game",
|
||||
"category": "category",
|
||||
"mechanic": "mechanic",
|
||||
"publisher": "publisher",
|
||||
"designer": "designer",
|
||||
"artist": "artist"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,89 +1,12 @@
|
|||
{
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1710268038944,
|
||||
"tag": "0000_tricky_hitman",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "5",
|
||||
"when": 1710268191378,
|
||||
"tag": "0001_numerous_dragon_man",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "5",
|
||||
"when": 1710268300740,
|
||||
"tag": "0002_thick_lyja",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "5",
|
||||
"when": 1710268371021,
|
||||
"tag": "0003_mushy_madame_masque",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "5",
|
||||
"when": 1710277583673,
|
||||
"tag": "0004_glossy_enchantress",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "5",
|
||||
"when": 1710366724519,
|
||||
"tag": "0005_light_captain_marvel",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "5",
|
||||
"when": 1710905422967,
|
||||
"tag": "0006_wild_stone_men",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "5",
|
||||
"when": 1710905572670,
|
||||
"tag": "0007_large_miss_america",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "5",
|
||||
"when": 1711757183163,
|
||||
"tag": "0008_amusing_franklin_richards",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "5",
|
||||
"when": 1711868447607,
|
||||
"tag": "0009_gray_carlie_cooper",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "5",
|
||||
"when": 1712271520175,
|
||||
"tag": "0010_wakeful_metal_master",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "5",
|
||||
"when": 1713311328819,
|
||||
"tag": "0011_charming_bucky",
|
||||
"version": "7",
|
||||
"when": 1717548517806,
|
||||
"tag": "0000_spotty_changeling",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,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 categoriesToExternalIds from './categoriesToExternalIds';
|
||||
import categories_to_games from './categoriesToGames';
|
||||
|
||||
const categories = pgTable('categories', {
|
||||
|
|
@ -10,8 +11,8 @@ 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(),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export type Categories = InferSelectModel<typeof categories>;
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ 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(),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export type CollectionItems = InferSelectModel<typeof collection_items>;
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ 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(),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const collection_relations = relations(collections, ({ one }) => ({
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ 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(),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export type Expansions = InferSelectModel<typeof expansions>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { index, integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { tsvector } from '../../tsVector';
|
||||
import { type InferSelectModel, relations, sql } from 'drizzle-orm';
|
||||
import categoriesToGames from './categoriesToGames';
|
||||
import gamesToExternalIds from './gamesToExternalIds';
|
||||
|
|
@ -27,22 +26,19 @@ const games = pgTable(
|
|||
image_url: text('image_url'),
|
||||
thumb_url: text('thumb_url'),
|
||||
url: text('url'),
|
||||
text_searchable_index: tsvector('text_searchable_index'),
|
||||
last_sync_at: timestamp('last_sync_at', {
|
||||
withTimezone: true,
|
||||
mode: 'date',
|
||||
precision: 6,
|
||||
last_sync_at: timestamp('last_sync_at'),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
searchIndex: index('search_index').using(
|
||||
'gin',
|
||||
sql`(
|
||||
setweight(to_tsvector('english', ${table.name}), 'A') ||
|
||||
setweight(to_tsvector('english', ${table.slug}), 'B')
|
||||
)`,
|
||||
),
|
||||
}),
|
||||
created_at: timestamp('created_at').notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at').notNull().defaultNow(),
|
||||
},
|
||||
(table) => {
|
||||
return {
|
||||
text_searchable_idx: index('text_searchable_idx')
|
||||
.on(table.text_searchable_index)
|
||||
.using(sql`'gin'`),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export const gameRelations = relations(games, ({ many }) => ({
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ export { default as roles, role_relations, type Roles } from './roles';
|
|||
export { default as userRoles, user_role_relations, type UserRoles } from './userRoles';
|
||||
export { default as collections, collection_relations, type Collections } from './collections';
|
||||
export {
|
||||
default as collectionItems,
|
||||
default as collection_items,
|
||||
collection_item_relations,
|
||||
type CollectionItems,
|
||||
} from './collectionItems';
|
||||
export { default as wishlists, wishlists_relations, type Wishlists } from './wishlists';
|
||||
export {
|
||||
default as wishlistItems,
|
||||
default as wishlist_items,
|
||||
wishlist_item_relations,
|
||||
type WishlistItems,
|
||||
} from './wishlistItems';
|
||||
|
|
@ -24,11 +24,11 @@ export { default as externalIds, type ExternalIds, type ExternalIdType } from '.
|
|||
export { default as games, gameRelations, type Games } from './games';
|
||||
export { default as gamesToExternalIds } from './gamesToExternalIds';
|
||||
export { default as publishers, publishers_relations, type Publishers } from './publishers';
|
||||
export { default as publishersToGames, publishers_to_games_relations } from './publishersToGames';
|
||||
export { default as publishers_to_games, publishers_to_games_relations } from './publishersToGames';
|
||||
export { default as publishersToExternalIds } from './publishersToExternalIds';
|
||||
export { default as categories, categories_relations, type Categories } from './categories';
|
||||
export { default as categoriesToExternalIds } from './categoriesToExternalIds';
|
||||
export { default as categoriesToGames, categories_to_games_relations } from './categoriesToGames';
|
||||
export { default as categories_to_games, categories_to_games_relations } from './categoriesToGames';
|
||||
export { default as mechanics, mechanics_relations, type Mechanics } from './mechanics';
|
||||
export { default as mechanicsToExternalIds } from './mechanicsToExternalIds';
|
||||
export { default as mechanicsToGames, mechanics_to_games_relations } from './mechanicsToGames';
|
||||
export { default as mechanics_to_games, mechanics_to_games_relations } from './mechanicsToGames';
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ 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(),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export type Mechanics = InferSelectModel<typeof mechanics>;
|
||||
|
|
|
|||
|
|
@ -10,12 +10,8 @@ const password_reset_tokens = pgTable('password_reset_tokens', {
|
|||
user_id: uuid('user_id')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
expires_at: timestamp('expires_at', {
|
||||
withTimezone: true,
|
||||
mode: 'date',
|
||||
precision: 6,
|
||||
}),
|
||||
created_at: timestamp('created_at').notNull().defaultNow(),
|
||||
expires_at: timestamp('expires_at', { mode: 'string' }),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export type PasswordResetTokens = InferSelectModel<typeof password_reset_tokens>;
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ 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(),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export type Publishers = InferSelectModel<typeof publishers>;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ 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(),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export type RecoveryCodes = InferSelectModel<typeof recovery_codes>;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
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 user_roles from './userRoles';
|
||||
|
|
@ -10,6 +10,8 @@ const roles = pgTable('roles', {
|
|||
.$defaultFn(() => cuid2())
|
||||
.notNull(),
|
||||
name: text('name').unique().notNull(),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export type Roles = InferSelectModel<typeof roles>;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
||||
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
||||
import users from './users';
|
||||
|
||||
const sessions = pgTable('sessions', {
|
||||
|
|
@ -12,6 +12,7 @@ const sessions = pgTable('sessions', {
|
|||
}).notNull(),
|
||||
ipCountry: text('ip_country'),
|
||||
ipAddress: text('ip_address'),
|
||||
isTwoFactorAuthenticated: boolean('is_two_factor_authenticated').default(false),
|
||||
});
|
||||
|
||||
export default sessions;
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ 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(),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const user_role_relations = relations(user_roles, ({ one }) => ({
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ const users = pgTable('users', {
|
|||
theme: text('theme').default('system'),
|
||||
two_factor_secret: text('two_factor_secret').default(''),
|
||||
two_factor_enabled: boolean('two_factor_enabled').default(false),
|
||||
created_at: timestamp('created_at').notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at').notNull().defaultNow(),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const user_relations = relations(users, ({ many }) => ({
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ 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(),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export type WishlistItems = InferSelectModel<typeof wishlist_items>;
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ 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(),
|
||||
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export type Wishlists = InferSelectModel<typeof wishlists>;
|
||||
|
|
|
|||
|
|
@ -32,11 +32,10 @@ export const authentication: Handle = async function ({ event, resolve }) {
|
|||
if (session && session.fresh) {
|
||||
const sessionCookie = lucia.createSessionCookie(session.id);
|
||||
console.log('sessionCookie', JSON.stringify(sessionCookie, null, 2));
|
||||
// sveltekit types deviates from the de-facto standard
|
||||
// you can use 'as any' too
|
||||
// sveltekit types deviates from the de-facto standard, you can use 'as any' too
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: '.',
|
||||
...sessionCookie.attributes
|
||||
...sessionCookie.attributes,
|
||||
});
|
||||
}
|
||||
console.log('session from hooks', JSON.stringify(session, null, 2));
|
||||
|
|
@ -45,7 +44,7 @@ export const authentication: Handle = async function ({ event, resolve }) {
|
|||
console.log('blank sessionCookie', JSON.stringify(sessionCookie, null, 2));
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: '.',
|
||||
...sessionCookie.attributes
|
||||
...sessionCookie.attributes,
|
||||
});
|
||||
}
|
||||
event.locals.user = user;
|
||||
|
|
@ -56,6 +55,6 @@ export const authentication: Handle = async function ({ event, resolve }) {
|
|||
|
||||
export const handle: Handle = sequence(
|
||||
// Sentry.sentryHandle(),
|
||||
authentication
|
||||
authentication,
|
||||
);
|
||||
// export const handleError = Sentry.handleErrorWithSentry();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = AvatarPrimitive.FallbackProps;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = AvatarPrimitive.ImageProps;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = AvatarPrimitive.Props;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Button as ButtonPrimitive } from "bits-ui";
|
||||
import { type Events, type Props, buttonVariants } from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { buttonVariants, type Props, type Events } from "./index.js";
|
||||
|
||||
type $$Props = Props;
|
||||
type $$Events = Events;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import Root from "./button.svelte";
|
||||
import { tv, type VariantProps } from "tailwind-variants";
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
import type { Button as ButtonPrimitive } from "bits-ui";
|
||||
import Root from "./button.svelte";
|
||||
|
||||
const buttonVariants = tv({
|
||||
base: "inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLParagraphElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HeadingLevel } from ".";
|
||||
import type { HeadingLevel } from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
|
||||
tag?: HeadingLevel;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { Checkbox as CheckboxPrimitive } from "bits-ui";
|
||||
import { Check, Minus } from "lucide-svelte";
|
||||
import { cn } from "$lib/utils";
|
||||
import Check from "lucide-svelte/icons/check";
|
||||
import Minus from "lucide-svelte/icons/minus";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = CheckboxPrimitive.Props;
|
||||
type $$Events = CheckboxPrimitive.Events;
|
||||
|
|
@ -13,7 +14,7 @@
|
|||
|
||||
<CheckboxPrimitive.Root
|
||||
class={cn(
|
||||
"box-content peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
|
||||
"peer box-content h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[disabled=true]:opacity-50",
|
||||
className
|
||||
)}
|
||||
bind:checked
|
||||
|
|
@ -21,7 +22,7 @@
|
|||
on:click
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
class={cn("flex items-center justify-center text-current h-4 w-4")}
|
||||
class={cn("flex h-4 w-4 items-center justify-center text-current")}
|
||||
let:isChecked
|
||||
let:isIndeterminate
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ import Root from "./checkbox.svelte";
|
|||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Checkbox
|
||||
Root as Checkbox,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import Check from "lucide-svelte/icons/check";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.CheckboxItemProps;
|
||||
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils";
|
||||
import { cn, flyAndScale } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.ContentProps;
|
||||
type $$Events = DropdownMenuPrimitive.ContentEvents;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.ItemProps & {
|
||||
inset?: boolean;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.LabelProps & {
|
||||
inset?: boolean;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import Circle from "lucide-svelte/icons/circle";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.RadioItemProps;
|
||||
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.SeparatorProps;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils";
|
||||
import { cn, flyAndScale } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.SubContentProps;
|
||||
type $$Events = DropdownMenuPrimitive.SubContentEvents;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import ChevronRight from "lucide-svelte/icons/chevron-right";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.SubTriggerProps & {
|
||||
inset?: boolean;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<script lang="ts">
|
||||
import * as Button from "$lib/components/ui/button";
|
||||
import * as Button from "$lib/components/ui/button/index.js";
|
||||
|
||||
type $$Props = Button.Props;
|
||||
type $$Events = Button.Events;
|
||||
</script>
|
||||
|
||||
<Button.Root type="submit" {...$$restProps}>
|
||||
<Button.Root type="submit" on:click on:keydown {...$$restProps}>
|
||||
<slot />
|
||||
</Button.Root>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
let className: string | undefined | null = undefined;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
<script lang="ts" context="module">
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import type { FormPathLeaves, SuperForm } from "sveltekit-superforms";
|
||||
type T = Record<string, unknown>;
|
||||
type U = unknown;
|
||||
type U = FormPathLeaves<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = FormPrimitive.ElementFieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = FormPrimitive.FieldErrorsProps & {
|
||||
errorClasses?: string | undefined | null;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
<script lang="ts" context="module">
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import type { FormPath, SuperForm } from "sveltekit-superforms";
|
||||
type T = Record<string, unknown>;
|
||||
type U = unknown;
|
||||
type U = FormPath<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = FormPrimitive.FieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
<script lang="ts" context="module">
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import type { FormPath, SuperForm } from "sveltekit-superforms";
|
||||
type T = Record<string, unknown>;
|
||||
type U = unknown;
|
||||
type U = FormPath<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = FormPrimitive.FieldsetProps<T, U>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import type { Label as LabelPrimitive } from "bits-ui";
|
||||
import { getFormControl } from "formsnap";
|
||||
import { cn } from "$lib/utils";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Label } from "$lib/components/ui/label/index.js";
|
||||
|
||||
type $$Props = LabelPrimitive.Props;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = FormPrimitive.LegendProps;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Label as LabelPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = LabelPrimitive.Props;
|
||||
type $$Events = LabelPrimitive.Events;
|
||||
|
|
|
|||
|
|
@ -30,5 +30,5 @@ export {
|
|||
Value as SelectValue,
|
||||
Content as SelectContent,
|
||||
Trigger as SelectTrigger,
|
||||
Separator as SelectSeparator
|
||||
Separator as SelectSeparator,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils";
|
||||
import { scale } from "svelte/transition";
|
||||
import { cn, flyAndScale } from "$lib/utils.js";
|
||||
|
||||
type $$Props = SelectPrimitive.ContentProps;
|
||||
type $$Events = SelectPrimitive.ContentEvents;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
export let outTransitionConfig: $$Props["outTransitionConfig"] = {
|
||||
start: 0.95,
|
||||
opacity: 0,
|
||||
duration: 50
|
||||
duration: 50,
|
||||
};
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import Check from "lucide-svelte/icons/check";
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { Check } from "lucide-svelte";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = SelectPrimitive.ItemProps;
|
||||
type $$Events = SelectPrimitive.ItemEvents;
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
{disabled}
|
||||
{label}
|
||||
class={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
|
|
@ -34,5 +34,7 @@
|
|||
<Check class="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
<slot>
|
||||
{label || value}
|
||||
</slot>
|
||||
</SelectPrimitive.Item>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = SelectPrimitive.LabelProps;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = SelectPrimitive.SeparatorProps;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { ChevronDown } from "lucide-svelte";
|
||||
import { cn } from "$lib/utils";
|
||||
import ChevronDown from "lucide-svelte/icons/chevron-down";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = SelectPrimitive.TriggerProps;
|
||||
type $$Events = SelectPrimitive.TriggerEvents;
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<SelectPrimitive.Trigger
|
||||
class={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 line-clamp-1 truncate",
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ import Root from "./switch.svelte";
|
|||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Switch
|
||||
Root as Switch,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { Switch as SwitchPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = SwitchPrimitive.Props;
|
||||
type $$Events = SwitchPrimitive.Events;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let checked: $$Props["checked"] = undefined;
|
||||
|
|
@ -16,6 +17,8 @@
|
|||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
>
|
||||
<SwitchPrimitive.Thumb
|
||||
class={cn(
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
import Root from "./toggle.svelte";
|
||||
import { tv, type VariantProps } from "tailwind-variants";
|
||||
|
||||
export const toggleVariants = tv({
|
||||
base: "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors data-[state=on]:bg-accent data-[state=on]:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 ring-offset-background hover:bg-muted hover:text-muted-foreground",
|
||||
base: "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
"bg-transparent border border-input hover:bg-accent hover:text-accent-foreground"
|
||||
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-3",
|
||||
sm: "h-9 px-2.5",
|
||||
lg: "h-11 px-5"
|
||||
}
|
||||
lg: "h-11 px-5",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default"
|
||||
}
|
||||
size: "default",
|
||||
},
|
||||
});
|
||||
|
||||
export type Variant = VariantProps<typeof toggleVariants>["variant"];
|
||||
|
|
@ -27,5 +27,5 @@ export type Size = VariantProps<typeof toggleVariants>["size"];
|
|||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Toggle
|
||||
Root as Toggle,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Toggle as TogglePrimitive } from "bits-ui";
|
||||
import { toggleVariants, type Variant, type Size } from ".";
|
||||
import { cn } from "$lib/utils";
|
||||
import { type Size, type Variant, toggleVariants } from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type $$Props = TogglePrimitive.Props & {
|
||||
variant?: Variant;
|
||||
|
|
|
|||
15
src/lib/components/ui/tooltip/index.ts
Normal file
15
src/lib/components/ui/tooltip/index.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
||||
import Content from "./tooltip-content.svelte";
|
||||
|
||||
const Root = TooltipPrimitive.Root;
|
||||
const Trigger = TooltipPrimitive.Trigger;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Trigger,
|
||||
Content,
|
||||
//
|
||||
Root as Tooltip,
|
||||
Content as TooltipContent,
|
||||
Trigger as TooltipTrigger,
|
||||
};
|
||||
28
src/lib/components/ui/tooltip/tooltip-content.svelte
Normal file
28
src/lib/components/ui/tooltip/tooltip-content.svelte
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<script lang="ts">
|
||||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils.js";
|
||||
|
||||
type $$Props = TooltipPrimitive.ContentProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let sideOffset: $$Props["sideOffset"] = 4;
|
||||
export let transition: $$Props["transition"] = flyAndScale;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = {
|
||||
y: 8,
|
||||
duration: 150,
|
||||
};
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<TooltipPrimitive.Content
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</TooltipPrimitive.Content>
|
||||
|
|
@ -20,6 +20,7 @@ export const lucia = new Lucia(adapter, {
|
|||
return {
|
||||
ipCountry: attributes.ip_country,
|
||||
ipAddress: attributes.ip_address,
|
||||
isTwoFactorAuthenticated: attributes.isTwoFactorAuthenticated,
|
||||
};
|
||||
},
|
||||
getUserAttributes: (attributes) => {
|
||||
|
|
@ -53,6 +54,7 @@ declare module 'lucia' {
|
|||
interface DatabaseSessionAttributes {
|
||||
ip_country: string;
|
||||
ip_address: string;
|
||||
isTwoFactorAuthenticated: boolean;
|
||||
}
|
||||
interface DatabaseUserAttributes {
|
||||
username: string;
|
||||
|
|
|
|||
|
|
@ -23,5 +23,8 @@ export const signInSchema = z.object({
|
|||
.min(3, { message: 'Username must be at least 3 characters' })
|
||||
.max(50, { message: 'Username must be less than 50 characters' }),
|
||||
password: z.string({ required_error: 'Password is required' }).trim(),
|
||||
totpToken: z.string().trim().min(6).max(10).optional(),
|
||||
});
|
||||
|
||||
export const totpSchema = z.object({
|
||||
totpToken: z.string().trim().min(6).max(10),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { IntegerString } from '$lib/zodValidation';
|
||||
|
||||
export type ListGame = {
|
||||
id: string;
|
||||
|
|
@ -13,7 +14,7 @@ export type ListGame = {
|
|||
};
|
||||
|
||||
export const modifyListGameSchema = z.object({
|
||||
id: z.string()
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export type ModifyListGame = typeof modifyListGameSchema;
|
||||
|
|
@ -21,24 +22,62 @@ export type ModifyListGame = typeof modifyListGameSchema;
|
|||
export const userSchema = z.object({
|
||||
firstName: z.string().trim().optional(),
|
||||
lastName: z.string().trim().optional(),
|
||||
email: z.string()
|
||||
.trim()
|
||||
.max(64, { message: 'Email must be less than 64 characters' })
|
||||
.optional(),
|
||||
email: z.string().trim().max(64, { message: 'Email must be less than 64 characters' }).optional(),
|
||||
username: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, { message: 'Username must be at least 3 characters' })
|
||||
.max(50, { message: 'Username must be less than 50 characters' }),
|
||||
password: z
|
||||
.string({ required_error: 'Password is required' })
|
||||
.trim(),
|
||||
confirm_password: z
|
||||
.string({ required_error: 'Confirm Password is required' })
|
||||
.trim(),
|
||||
password: z.string({ required_error: 'Password is required' }).trim(),
|
||||
confirm_password: z.string({ required_error: 'Confirm Password is required' }).trim(),
|
||||
verified: z.boolean().default(false),
|
||||
token: z.string().optional(),
|
||||
receiveEmail: z.boolean().default(false),
|
||||
createdAt: z.date().optional(),
|
||||
updatedAt: z.date().optional()
|
||||
updatedAt: z.date().optional(),
|
||||
});
|
||||
|
||||
export const PaginationSchema = z.object({
|
||||
skip: z.number().min(0).default(0),
|
||||
limit: z.number().min(10).max(100).default(10),
|
||||
});
|
||||
|
||||
export type PaginationSchema = typeof PaginationSchema;
|
||||
|
||||
export const FilterSchema = z.object({
|
||||
status: z.string(),
|
||||
author: z.string(),
|
||||
});
|
||||
|
||||
export type FilterSchema = typeof FilterSchema;
|
||||
|
||||
export const SortSchema = z
|
||||
.object({
|
||||
sort: z.string(),
|
||||
order: z.enum(['asc', 'desc']).default('asc'),
|
||||
minAge: IntegerString(z.number().min(1).max(120).optional()),
|
||||
minPlayers: IntegerString(z.number().min(1).max(50).optional()),
|
||||
maxPlayers: IntegerString(z.number().min(1).max(50).optional()),
|
||||
})
|
||||
.superRefine(({ minPlayers, maxPlayers }, ctx) => {
|
||||
console.log({ minPlayers, maxPlayers });
|
||||
if (minPlayers && maxPlayers && minPlayers > maxPlayers) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Players must be smaller than Max Players',
|
||||
path: ['minPlayers'],
|
||||
});
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Players must be smaller than Max Players',
|
||||
path: ['maxPlayers'],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const SearchSchema = z.object({
|
||||
q: z.string(),
|
||||
exact: z.preprocess((a) => Boolean(a), z.boolean().default(true)),
|
||||
});
|
||||
|
||||
export type SearchSchema = typeof SearchSchema;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export const BoardGameSearch = z.object({
|
|||
minAge: z.number(),
|
||||
maxAge: z.number(),
|
||||
minPlayers: z.number(),
|
||||
maxPlayers: z.number()
|
||||
maxPlayers: z.number(),
|
||||
});
|
||||
|
||||
export const saved_game_schema = z.object({
|
||||
|
|
@ -13,18 +13,18 @@ export const saved_game_schema = z.object({
|
|||
name: z.string(),
|
||||
thumb_url: z.string(),
|
||||
players: z.string(),
|
||||
playtime: IntegerString(z.number())
|
||||
playtime: IntegerString(z.number()),
|
||||
});
|
||||
|
||||
export const list_game_request_schema = z.object({
|
||||
id: z.string(),
|
||||
externalId: z.string()
|
||||
externalId: z.string(),
|
||||
});
|
||||
|
||||
export type ListGameSchema = typeof list_game_request_schema;
|
||||
|
||||
// https://github.com/colinhacks/zod/discussions/330
|
||||
function IntegerString<schema extends ZodNumber | ZodOptional<ZodNumber>>(schema: schema) {
|
||||
export function IntegerString<schema extends ZodNumber | ZodOptional<ZodNumber>>(schema: schema) {
|
||||
return z.preprocess(
|
||||
(value) =>
|
||||
typeof value === 'string'
|
||||
|
|
@ -32,7 +32,7 @@ function IntegerString<schema extends ZodNumber | ZodOptional<ZodNumber>>(schema
|
|||
: typeof value === 'number'
|
||||
? value
|
||||
: undefined,
|
||||
schema
|
||||
schema,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ const Search = z.object({
|
|||
sort: z.enum(['asc', 'desc']).optional(),
|
||||
sortBy: z.enum(['name', 'min_players', 'max_players', 'min_age', 'times_played']).optional(),
|
||||
limit: z.number().min(10).max(100).default(10),
|
||||
skip: z.number().min(0).default(0)
|
||||
skip: z.number().min(0).default(0),
|
||||
});
|
||||
|
||||
// minAge: z
|
||||
|
|
@ -71,7 +71,7 @@ export const search_schema = z
|
|||
sort: z.enum(['asc', 'desc']).optional(),
|
||||
order: z.enum(['name', 'min_players', 'max_players', 'min_age', 'times_played']).optional(),
|
||||
limit: z.number().min(10).max(100).default(10),
|
||||
skip: z.number().min(0).default(0)
|
||||
skip: z.number().min(0).default(0),
|
||||
})
|
||||
.superRefine(
|
||||
({ minPlayers, maxPlayers, minAge, exactMinAge, exactMinPlayers, exactMaxPlayers }, ctx) => {
|
||||
|
|
@ -80,12 +80,12 @@ export const search_schema = z
|
|||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Players must be smaller than Max Players',
|
||||
path: ['minPlayers']
|
||||
path: ['minPlayers'],
|
||||
});
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Players must be smaller than Max Players',
|
||||
path: ['maxPlayers']
|
||||
path: ['maxPlayers'],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ export const search_schema = z
|
|||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Age required when searching for exact min age',
|
||||
path: ['minAge']
|
||||
path: ['minAge'],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ export const search_schema = z
|
|||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Players required when searching for exact min players',
|
||||
path: ['minPlayers']
|
||||
path: ['minPlayers'],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -109,20 +109,20 @@ export const search_schema = z
|
|||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Max Players required when searching for exact max players',
|
||||
path: ['maxPlayers']
|
||||
path: ['maxPlayers'],
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export type SearchSchema = typeof search_schema;
|
||||
|
||||
export const BggForm = z.object({
|
||||
link: z.string().trim().startsWith('https://boardgamegeek.com/boardgame/')
|
||||
})
|
||||
link: z.string().trim().startsWith('https://boardgamegeek.com/boardgame/'),
|
||||
});
|
||||
|
||||
export const collection_search_schema = Search.extend({
|
||||
collection_id: z.string()
|
||||
collection_id: z.string(),
|
||||
});
|
||||
|
||||
export type CollectionSearchSchema = typeof collection_search_schema;
|
||||
|
|
@ -175,7 +175,7 @@ export const search_result_schema = z.object({
|
|||
lt_reddit_count: z.number(),
|
||||
lt_reddit_week_count: z.number(),
|
||||
lt_reddit_day_count: z.number(),
|
||||
fields: z.string()
|
||||
fields: z.string(),
|
||||
});
|
||||
|
||||
export type SearchResultSchema = typeof search_result_schema;
|
||||
|
|
@ -199,18 +199,18 @@ export const game_schema = z.object({
|
|||
min_age: z.number(),
|
||||
description: z.string(),
|
||||
players: z.string(),
|
||||
playtime: z.string()
|
||||
playtime: z.string(),
|
||||
});
|
||||
|
||||
export const category_schema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
name: z.string(),
|
||||
});
|
||||
|
||||
export const mechanics_schema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().optional()
|
||||
description: z.string().optional(),
|
||||
});
|
||||
|
||||
const gameSchema = z.object({
|
||||
|
|
@ -234,24 +234,24 @@ const gameSchema = z.object({
|
|||
.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
publishers: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
artists: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
names: z.array(z.string()).optional(),
|
||||
|
|
@ -260,26 +260,26 @@ const gameSchema = z.object({
|
|||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
year_published: z.number().optional()
|
||||
})
|
||||
year_published: z.number().optional(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
primary_publisher: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
name: z.string(),
|
||||
})
|
||||
.optional()
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const searchResultSchema = z.object({
|
||||
games: z.array(gameSchema),
|
||||
count: z.number()
|
||||
count: z.number(),
|
||||
});
|
||||
|
||||
export const WishlistSchema = z.object({
|
||||
name: z.string(),
|
||||
id: z.string()
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
// export const game_raw_schema_json = zodToJsonSchema(game_schema, {
|
||||
|
|
|
|||
|
|
@ -59,15 +59,16 @@ export const actions: Actions = {
|
|||
console.log('user', JSON.stringify(user, null, 2));
|
||||
|
||||
if (!user?.hashed_password) {
|
||||
console.log('invalid username/password');
|
||||
form.data.password = '';
|
||||
return setError(form, '', 'Your username or password is incorrect.');
|
||||
return setError(form, 'password', 'Your username or password is incorrect.');
|
||||
}
|
||||
|
||||
const validPassword = await new Argon2id().verify(user.hashed_password, password);
|
||||
if (!validPassword) {
|
||||
console.log('invalid password');
|
||||
form.data.password = '';
|
||||
return setError(form, '', 'Your username or password is incorrect.');
|
||||
return setError(form, 'password', 'Your username or password is incorrect.');
|
||||
}
|
||||
|
||||
// await db
|
||||
|
|
@ -101,11 +102,13 @@ export const actions: Actions = {
|
|||
const usedRecoveryCode = await checkRecoveryCode(form?.data?.totpToken, user.id);
|
||||
if (!usedRecoveryCode) {
|
||||
console.log('invalid TOTP code');
|
||||
form.errors.totpToken = ['Invalid code'];
|
||||
return fail(400, {
|
||||
form,
|
||||
twoFactorRequired: true,
|
||||
});
|
||||
form.data.password = '';
|
||||
// form.errors.totpToken = ['Invalid code'];
|
||||
return setError(form, 'totpToken', 'Invalid code.');
|
||||
// return fail(400, {
|
||||
// form,
|
||||
// twoFactorRequired: true,
|
||||
// });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -114,6 +117,7 @@ export const actions: Actions = {
|
|||
session = await lucia.createSession(user.id, {
|
||||
ip_country: locals.country,
|
||||
ip_address: locals.ip,
|
||||
isTwoFactorAuthenticated: false,
|
||||
});
|
||||
console.log('logging in session', session);
|
||||
sessionCookie = lucia.createSessionCookie(session.id);
|
||||
|
|
|
|||
153
src/routes/(auth)/totp/+page.server.ts
Normal file
153
src/routes/(auth)/totp/+page.server.ts
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
import { fail, error, type Actions } from '@sveltejs/kit';
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
import { Argon2id } from 'oslo/password';
|
||||
import { decodeHex } from 'oslo/encoding';
|
||||
import { TOTPController } from 'oslo/otp';
|
||||
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 db from '../../../db';
|
||||
import { lucia } from '$lib/server/auth';
|
||||
import { totpSchema } from '$lib/validations/auth';
|
||||
import { users, recovery_codes } from '$db/schema';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
if (event.locals.user) {
|
||||
const user = event.locals.user;
|
||||
const dbUser = await db.query.users.findFirst({
|
||||
where: eq(users.username, user.username),
|
||||
});
|
||||
|
||||
const session = event.locals.session;
|
||||
const isTwoFactorAuthenticated = session?.isTwoFactorAuthenticated;
|
||||
|
||||
if (isTwoFactorAuthenticated && dbUser?.two_factor_enabled && dbUser?.two_factor_secret) {
|
||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||
throw redirect('/', message, event);
|
||||
}
|
||||
}
|
||||
|
||||
const form = await superValidate(event, zod(totpSchema));
|
||||
|
||||
return {
|
||||
form,
|
||||
};
|
||||
};
|
||||
|
||||
const limiter = new RateLimiter({
|
||||
// A rate is defined by [number, unit]
|
||||
IPUA: [5, 'm'],
|
||||
});
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
if (await limiter.isLimited(event)) {
|
||||
throw error(429);
|
||||
}
|
||||
|
||||
const { locals } = event;
|
||||
const session = locals.session;
|
||||
const user = locals.user;
|
||||
|
||||
if (!user || !session) {
|
||||
throw fail(401);
|
||||
}
|
||||
|
||||
const dbUser = await db.query.users.findFirst({
|
||||
where: eq(users.username, user.username),
|
||||
});
|
||||
|
||||
if (!dbUser) {
|
||||
throw fail(401);
|
||||
}
|
||||
|
||||
const isTwoFactorAuthenticated = session?.isTwoFactorAuthenticated;
|
||||
|
||||
if (isTwoFactorAuthenticated && dbUser?.two_factor_enabled && dbUser?.two_factor_secret) {
|
||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||
throw redirect('/', message, event);
|
||||
}
|
||||
|
||||
const form = await superValidate(event, zod(totpSchema));
|
||||
|
||||
if (!form.valid) {
|
||||
form.data.totpToken = '';
|
||||
return fail(400, {
|
||||
form,
|
||||
});
|
||||
}
|
||||
|
||||
let sessionCookie;
|
||||
try {
|
||||
const totpToken = form?.data?.totpToken;
|
||||
|
||||
if (dbUser?.two_factor_enabled && dbUser?.two_factor_secret && !totpToken) {
|
||||
return fail(400, {
|
||||
form,
|
||||
});
|
||||
} else if (dbUser?.two_factor_enabled && dbUser?.two_factor_secret && totpToken) {
|
||||
console.log('totpToken',totpToken);
|
||||
const validOTP = await new TOTPController().verify(
|
||||
form.data.totpToken,
|
||||
decodeHex(dbUser.two_factor_secret),
|
||||
);
|
||||
console.log('validOTP', validOTP);
|
||||
|
||||
if (!validOTP) {
|
||||
console.log('invalid TOTP code check for recovery codes');
|
||||
const usedRecoveryCode = await checkRecoveryCode(totpToken, dbUser.id);
|
||||
if (!usedRecoveryCode) {
|
||||
console.log('invalid TOTP code');
|
||||
return setError(form, 'totpToken', 'Invalid code.');
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('ip', locals.ip);
|
||||
console.log('country', locals.country);
|
||||
await lucia.invalidateSession(session.id);
|
||||
const newSession = await lucia.createSession(dbUser.id, {
|
||||
ip_country: locals.country,
|
||||
ip_address: locals.ip,
|
||||
isTwoFactorAuthenticated: true,
|
||||
});
|
||||
console.log('logging in session', newSession);
|
||||
sessionCookie = lucia.createSessionCookie(newSession.id);
|
||||
console.log('logging in session cookie', sessionCookie);
|
||||
} catch (e) {
|
||||
// TODO: need to return error message to the client
|
||||
console.error(e);
|
||||
return setError(form, 'totpToken', 'Error verifying TOTP code.');
|
||||
}
|
||||
|
||||
console.log('setting session cookie', sessionCookie);
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: '.',
|
||||
...sessionCookie.attributes,
|
||||
});
|
||||
|
||||
form.data.totpToken = '';
|
||||
const message = { type: 'success', message: 'Signed In!' } as const;
|
||||
redirect(302, '/', message, event);
|
||||
},
|
||||
};
|
||||
|
||||
async function checkRecoveryCode(recoveryCode: string, userId: string) {
|
||||
const userRecoveryCodes = await db.query.recovery_codes.findMany({
|
||||
where: and(eq(recovery_codes.used, false), eq(recovery_codes.userId, userId)),
|
||||
});
|
||||
for (const code of userRecoveryCodes) {
|
||||
const validRecoveryCode = await new Argon2id().verify(code.code, recoveryCode);
|
||||
if (validRecoveryCode) {
|
||||
await db
|
||||
.update(recovery_codes)
|
||||
.set({
|
||||
used: true,
|
||||
})
|
||||
.where(eq(recovery_codes.id, code.id));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
123
src/routes/(auth)/totp/+page.svelte
Normal file
123
src/routes/(auth)/totp/+page.svelte
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<script lang="ts">
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { superForm } from 'sveltekit-superforms/client';
|
||||
import * as flashModule from 'sveltekit-flash-message/client';
|
||||
import { AlertCircle } from "lucide-svelte";
|
||||
import { signInSchema } from '$lib/validations/auth';
|
||||
import * as Form from '$lib/components/ui/form';
|
||||
import { Label } from '$components/ui/label';
|
||||
import { Input } from '$components/ui/input';
|
||||
import { Button } from '$components/ui/button';
|
||||
import * as Alert from "$components/ui/alert";
|
||||
import { boredState } from '$lib/stores/boredState.js';
|
||||
|
||||
export let data;
|
||||
export let form;
|
||||
|
||||
const superLoginForm = superForm(data.form, {
|
||||
onSubmit: () => boredState.update((n) => ({ ...n, loading: true })),
|
||||
onResult: () => boredState.update((n) => ({ ...n, loading: false })),
|
||||
flashMessage: {
|
||||
module: flashModule,
|
||||
onError: ({ result, message }) => {
|
||||
// Error handling for the flash message:
|
||||
// - result is the ActionResult
|
||||
// - message is the flash store (not the status message store)
|
||||
const errorMessage = result.error.message
|
||||
message.set({ type: 'error', message: errorMessage });
|
||||
}
|
||||
},
|
||||
syncFlashMessage: false,
|
||||
taintedMessage: null,
|
||||
validators: zodClient(signInSchema),
|
||||
validationMethod: 'oninput',
|
||||
delayMs: 0,
|
||||
});
|
||||
|
||||
const { form: loginForm, errors, enhance } = superLoginForm;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Bored Game | Login</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="login">
|
||||
<form method="POST" use:enhance>
|
||||
<h2
|
||||
class="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0"
|
||||
>
|
||||
Please enter your two factor code
|
||||
</h2>
|
||||
<Form.Field form={superLoginForm} name="username">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label for="username">Username</Form.Label>
|
||||
<Input {...attrs} autocomplete="username" bind:value={$loginForm.username} />
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Field form={superLoginForm} name="password">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label for="password">Password</Form.Label>
|
||||
<Input {...attrs} autocomplete="current-password" type="password" bind:value={$loginForm.password} />
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
{#if form?.twoFactorRequired}
|
||||
<Form.Field form={superLoginForm} name="totpToken">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label for="totpToken">Two Factor Code or Recovery Code</Form.Label>
|
||||
<Input {...attrs} autocomplete="one-time-code" bind:value={$loginForm.totpToken} />
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
{/if}
|
||||
<Form.Button>Login</Form.Button>
|
||||
<p class="px-8 text-center text-sm text-muted-foreground">
|
||||
By clicking continue, you agree to our
|
||||
<a href="/terms" class="underline underline-offset-4 hover:text-primary">
|
||||
Terms of Use
|
||||
</a>
|
||||
and
|
||||
<a href="/privacy" class="underline underline-offset-4 hover:text-primary">
|
||||
Privacy Policy
|
||||
</a>.
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.loading {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 101;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
h3 {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
.login {
|
||||
display: flex;
|
||||
margin-top: 1.5rem;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
|
||||
@media (min-width: 640px) {
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
form {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
max-width: 24rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,10 +3,28 @@ import db from '../../../../db';
|
|||
import { asc, desc, eq, ilike, or } from 'drizzle-orm';
|
||||
import { games } from '$db/schema';
|
||||
import kebabCase from 'just-kebab-case';
|
||||
import {
|
||||
FilterSchema,
|
||||
PaginationSchema,
|
||||
SearchSchema,
|
||||
SortSchema,
|
||||
} from '$lib/validations/zod-schemas';
|
||||
|
||||
// Search a user's collection
|
||||
export const GET = async ({ url, locals }) => {
|
||||
const searchParams = Object.fromEntries(url.searchParams);
|
||||
|
||||
const searchGames = PaginationSchema.merge(FilterSchema)
|
||||
.merge(SortSchema)
|
||||
.merge(SearchSchema)
|
||||
.parse(searchParams);
|
||||
|
||||
if (searchGames.status !== 'success') {
|
||||
error(400, 'Invalid request');
|
||||
}
|
||||
|
||||
const { q, limit, skip, order, exact } = searchGames;
|
||||
|
||||
const q = searchParams?.q?.trim() || '';
|
||||
const limit = parseInt(searchParams?.limit) || 10;
|
||||
const skip = parseInt(searchParams?.skip) || 0;
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
import { customType } from 'drizzle-orm/pg-core';
|
||||
import {sql} from "drizzle-orm";
|
||||
|
||||
function genExpWithWeights(input: string[]) {
|
||||
const columnExpressions = input.map((column, index) => {
|
||||
const weight = String.fromCharCode(index + 65);
|
||||
return sql`setweight(to_tsvector('english', coalesce(${column}, '')), '${weight}')`;
|
||||
});
|
||||
|
||||
return sql`tsvector GENERATED ALWAYS AS (${columnExpressions.join(' || ')}) STORED`;
|
||||
}
|
||||
|
||||
export const tsvector = customType<{
|
||||
data: string;
|
||||
config: { sources: string[]; weighted: boolean };
|
||||
}>({
|
||||
dataType(config) {
|
||||
if (config) {
|
||||
const sources = config.sources.join(" || ' ' || ");
|
||||
return config.weighted
|
||||
? genExpWithWeights(config.sources)
|
||||
: sql`tsvector generated always as (to_tsvector('english', ${sources})) stored`;
|
||||
} else {
|
||||
return sql`tsvector`;
|
||||
}
|
||||
}
|
||||
});
|
||||
Loading…
Reference in a new issue