Merge pull request #16 from BradNut/two-factor

Two factor
This commit is contained in:
Bradley Shellnut 2024-04-11 17:27:10 -07:00 committed by GitHub
commit f9123eea53
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
130 changed files with 11120 additions and 13479 deletions

View file

@ -2,7 +2,8 @@
"useTabs": true,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "none",
"trailingComma": "all",
"bracketSpacing": true,
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],

View file

@ -11,10 +11,10 @@ export default defineConfig({
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE || 'boredgame',
ssl: true
ssl: process.env.DATABASE_HOST !== 'localhost'
},
// Print all statements
verbose: true,
// Always as for confirmation
strict: true
});
});

View file

@ -1,233 +0,0 @@
CREATE TABLE IF NOT EXISTS "artists" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"name" varchar(255),
"slug" varchar(255),
"external_id" integer,
"created_at" timestamp with time zone DEFAULT (now(6)),
"updated_at" timestamp with time zone DEFAULT (now(6))
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "artists_to_games" (
"artist_id" varchar(255),
"game_id" varchar(255)
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "categories" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"name" varchar(255),
"slug" varchar(255),
"external_id" integer,
"created_at" timestamp with time zone DEFAULT (now(6)),
"updated_at" timestamp with time zone DEFAULT (now(6))
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "categories_to_games" (
"category_id" varchar(255),
"game_id" varchar(255)
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "collection_items" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"collection_id" varchar(255) NOT NULL,
"game_id" varchar(255) NOT NULL,
"created_at" timestamp with time zone DEFAULT (now(6)),
"updated_at" timestamp with time zone DEFAULT (now(6))
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "collections" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"created_at" timestamp with time zone DEFAULT (now(6)),
"updated_at" timestamp with time zone DEFAULT (now(6))
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "designers" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"name" varchar(255),
"slug" varchar(255),
"external_id" integer,
"created_at" timestamp with time zone DEFAULT (now(6)),
"updated_at" timestamp with time zone DEFAULT (now(6))
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "designers_to_games" (
"designer_id" varchar(255),
"game_id" varchar(255)
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "expansions" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"base_game_id" varchar(255) NOT NULL,
"game_id" varchar(255) NOT NULL,
"created_at" timestamp with time zone DEFAULT (now(6)),
"updated_at" timestamp with time zone DEFAULT (now(6))
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "games" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"name" varchar(255),
"slug" varchar(255),
"description" text,
"year_published" integer,
"min_players" integer,
"max_players" integer,
"playtime" integer,
"min_playtime" integer,
"max_playtime" integer,
"min_age" integer,
"image_url" varchar(255),
"thumb_url" varchar(255),
"url" varchar(255),
"external_id" integer,
"last_sync_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT (now(6)),
"updated_at" timestamp with time zone DEFAULT (now(6)),
CONSTRAINT "games_external_id_unique" UNIQUE("external_id")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "mechanics" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"name" varchar(255),
"slug" varchar(255),
"external_id" integer,
"created_at" timestamp with time zone DEFAULT (now(6)),
"updated_at" timestamp with time zone DEFAULT (now(6))
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "mechanics_to_games" (
"mechanic_id" varchar(255),
"game_id" varchar(255)
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "publishers" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"name" varchar(255),
"slug" varchar(255),
"external_id" integer,
"created_at" timestamp with time zone DEFAULT (now(6)),
"updated_at" timestamp with time zone DEFAULT (now(6))
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "publishers_to_games" (
"publisher_id" varchar(255),
"game_id" varchar(255)
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "roles" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"name" varchar(255),
CONSTRAINT "roles_name_unique" UNIQUE("name")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "sessions" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"ip_country" varchar(255),
"ip_address" varchar(255)
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "user_roles" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"role_id" varchar(255) NOT NULL,
"created_at" timestamp with time zone DEFAULT (now(6)),
"updated_at" timestamp with time zone DEFAULT (now(6))
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "users" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"username" varchar(255),
"hashed_password" varchar(255),
"email" varchar(255),
"first_name" varchar(255),
"last_name" varchar(255),
"verified" boolean DEFAULT false,
"receive_email" boolean DEFAULT false,
"theme" varchar(255) DEFAULT 'system',
"created_at" timestamp DEFAULT (now(6)),
"updated_at" timestamp DEFAULT (now(6)),
CONSTRAINT "users_username_unique" UNIQUE("username"),
CONSTRAINT "users_email_unique" UNIQUE("email")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "wishlist_items" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"wishlist_id" varchar(255) NOT NULL,
"game_id" varchar(255) NOT NULL,
"created_at" timestamp with time zone DEFAULT (now(6)),
"updated_at" timestamp with time zone DEFAULT (now(6))
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "wishlists" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"created_at" timestamp with time zone DEFAULT (now(6)),
"updated_at" timestamp with time zone DEFAULT (now(6))
);
--> 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;
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;
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;
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 cascade ON UPDATE no action;
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 cascade 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;
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;
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;
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;
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;
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 $$;

View file

@ -0,0 +1,365 @@
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,
"cuid" text,
"name" text,
"slug" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "categories_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "categories_to_external_ids" (
"category_id" uuid NOT NULL,
"external_id" uuid NOT NULL,
CONSTRAINT "categories_to_external_ids_category_id_external_id_pk" PRIMARY KEY("category_id","external_id")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "categories_to_games" (
"category_id" uuid NOT NULL,
"game_id" uuid NOT NULL,
CONSTRAINT "categories_to_games_category_id_game_id_pk" PRIMARY KEY("category_id","game_id")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "collection_items" (
"id" uuid PRIMARY KEY NOT NULL,
"cuid" text,
"collection_id" text NOT NULL,
"game_id" text NOT NULL,
"times_played" integer DEFAULT 0,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "collection_items_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "collections" (
"id" uuid PRIMARY KEY NOT NULL,
"cuid" text,
"user_id" text 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,
"cuid" text,
"type" "external_id_type" NOT NULL,
"external_id" text NOT NULL,
CONSTRAINT "external_ids_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "games" (
"id" uuid PRIMARY KEY NOT NULL,
"cuid" text,
"name" text,
"slug" text,
"description" text,
"year_published" integer,
"min_players" integer,
"max_players" integer,
"playtime" integer,
"min_playtime" integer,
"max_playtime" integer,
"min_age" integer,
"image_url" text,
"thumb_url" text,
"url" text,
"text_searchable_index" "tsvector",
"last_sync_at" timestamp (6) with time zone,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "games_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "games_to_external_ids" (
"game_id" uuid NOT NULL,
"external_id" uuid NOT NULL,
CONSTRAINT "games_to_external_ids_game_id_external_id_pk" PRIMARY KEY("game_id","external_id")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "mechanics" (
"id" uuid PRIMARY KEY NOT NULL,
"cuid" text,
"name" text,
"slug" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "mechanics_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "mechanics_to_external_ids" (
"mechanic_id" uuid NOT NULL,
"external_id" uuid NOT NULL,
CONSTRAINT "mechanics_to_external_ids_mechanic_id_external_id_pk" PRIMARY KEY("mechanic_id","external_id")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "mechanics_to_games" (
"mechanic_id" uuid NOT NULL,
"game_id" uuid NOT NULL,
CONSTRAINT "mechanics_to_games_mechanic_id_game_id_pk" PRIMARY KEY("mechanic_id","game_id")
);
--> 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,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "publishers" (
"id" uuid PRIMARY KEY NOT NULL,
"cuid" text,
"name" text,
"slug" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "publishers_cuid_unique" UNIQUE("cuid")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "publishers_to_external_ids" (
"publisher_id" uuid NOT NULL,
"external_id" uuid NOT NULL,
CONSTRAINT "publishers_to_external_ids_publisher_id_external_id_pk" PRIMARY KEY("publisher_id","external_id")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "publishers_to_games" (
"publisher_id" uuid NOT NULL,
"game_id" uuid NOT NULL,
CONSTRAINT "publishers_to_games_publisher_id_game_id_pk" PRIMARY KEY("publisher_id","game_id")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "roles" (
"id" uuid PRIMARY KEY NOT NULL,
"cuid" text,
"name" text,
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,
"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,
"cuid" text,
"user_id" text NOT NULL,
"role_id" text NOT NULL,
"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,
"cuid" text,
"username" text,
"hashed_password" text,
"email" text,
"first_name" text,
"last_name" text,
"verified" boolean DEFAULT false,
"receive_email" boolean DEFAULT false,
"theme" text DEFAULT 'system',
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "users_cuid_unique" UNIQUE("cuid"),
CONSTRAINT "users_username_unique" UNIQUE("username"),
CONSTRAINT "users_email_unique" UNIQUE("email")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "wishlist_items" (
"id" uuid PRIMARY KEY NOT NULL,
"cuid" text,
"wishlist_id" text NOT NULL,
"game_id" text 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,
"cuid" text,
"user_id" text 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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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 $$;

View file

@ -1,51 +0,0 @@
ALTER TABLE "artists" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "artists" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "artists" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "artists" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "categories" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "categories" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "categories" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "categories" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "collection_items" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "collection_items" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "collection_items" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "collection_items" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "collections" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "collections" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "collections" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "collections" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "designers" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "designers" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "designers" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "designers" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "expansions" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "expansions" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "expansions" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "expansions" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "games" ALTER COLUMN "last_sync_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "games" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "games" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "games" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "games" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "mechanics" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "mechanics" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "mechanics" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "mechanics" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "publishers" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "publishers" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "publishers" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "publishers" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "user_roles" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "user_roles" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "user_roles" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "user_roles" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "users" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "users" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "wishlist_items" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "wishlist_items" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "wishlist_items" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "wishlist_items" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "wishlists" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "wishlists" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
ALTER TABLE "wishlists" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "wishlists" ALTER COLUMN "updated_at" SET DEFAULT (now());

View file

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

View file

@ -1,27 +0,0 @@
ALTER TABLE "artists" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "artists" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "categories" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "categories" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "collection_items" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "collection_items" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "collections" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "collections" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "designers" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "designers" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "expansions" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "expansions" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "games" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "games" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "mechanics" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "mechanics" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "publishers" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "publishers" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "user_roles" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "user_roles" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "users" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "users" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "wishlist_items" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "wishlist_items" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "wishlists" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "wishlists" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "games" ADD COLUMN "text_searchable_index" "tsvector";

View file

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

View file

@ -0,0 +1 @@
ALTER TABLE "user_roles" ALTER COLUMN "role_id" SET DATA TYPE uuid;

View file

@ -1 +0,0 @@
CREATE INDEX IF NOT EXISTS "text_searchable_idx" ON "games" ("text_searchable_index");

View file

@ -1,30 +0,0 @@
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 "external_ids" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"type" varchar(255),
"external_id" varchar(255)
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "game_external_ids" (
"game_id" varchar(255) NOT NULL,
"external_id" varchar(255) NOT NULL
);
--> statement-breakpoint
ALTER TABLE "games" DROP CONSTRAINT "games_external_id_unique";--> statement-breakpoint
ALTER TABLE "games" DROP COLUMN IF EXISTS "external_id";--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "game_external_ids" ADD CONSTRAINT "game_external_ids_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "game_external_ids" ADD CONSTRAINT "game_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "external_ids"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View file

@ -0,0 +1,13 @@
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();

View file

@ -0,0 +1,2 @@
ALTER TABLE "roles" ALTER COLUMN "cuid" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "roles" ALTER COLUMN "name" SET NOT NULL;

View file

@ -1,16 +0,0 @@
ALTER TABLE "game_external_ids" RENAME TO "games_to_external_ids";--> statement-breakpoint
ALTER TABLE "games_to_external_ids" DROP CONSTRAINT "game_external_ids_game_id_games_id_fk";
--> statement-breakpoint
ALTER TABLE "games_to_external_ids" DROP CONSTRAINT "game_external_ids_external_id_external_ids_id_fk";
--> 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 cascade ON UPDATE no action;
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 cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View file

@ -1,2 +0,0 @@
ALTER TABLE "external_ids" ALTER COLUMN "type" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "external_ids" ALTER COLUMN "external_id" SET NOT NULL;

View file

@ -0,0 +1 @@
ALTER TABLE "user_roles" ADD COLUMN "default" boolean DEFAULT false;

View file

@ -0,0 +1 @@
ALTER TABLE "user_roles" RENAME COLUMN "default" TO "primary";

View file

@ -1,71 +0,0 @@
CREATE TABLE IF NOT EXISTS "categories_to_external_ids" (
"category_id" varchar(255) NOT NULL,
"external_id" varchar(255) NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "expansions_to_external_ids" (
"expansion_id" varchar(255) NOT NULL,
"external_id" varchar(255) NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "mechanics_to_external_ids" (
"mechanic_id" varchar(255) NOT NULL,
"external_id" varchar(255) NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "publishers_to_external_ids" (
"publisher_id" varchar(255) NOT NULL,
"external_id" varchar(255) NOT NULL
);
--> statement-breakpoint
DROP TABLE "artists";--> statement-breakpoint
DROP TABLE "artists_to_games";--> statement-breakpoint
DROP TABLE "designers";--> statement-breakpoint
DROP TABLE "designers_to_games";--> 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 cascade ON UPDATE no action;
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 cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "expansions_to_external_ids" ADD CONSTRAINT "expansions_to_external_ids_expansion_id_expansions_id_fk" FOREIGN KEY ("expansion_id") REFERENCES "expansions"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "expansions_to_external_ids" ADD CONSTRAINT "expansions_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "external_ids"("id") ON DELETE cascade ON UPDATE no action;
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 cascade ON UPDATE no action;
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 cascade ON UPDATE no action;
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 cascade ON UPDATE no action;
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 cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View file

@ -0,0 +1 @@
ALTER TABLE "users" ADD COLUMN "two_factor_secret" text;

View file

@ -1,7 +0,0 @@
DO $$ BEGIN
CREATE TYPE "type" AS ENUM('game', 'category', 'mechanic', 'publisher', 'designer', 'artist');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
ALTER TABLE "external_ids" ALTER COLUMN "type" SET DATA TYPE type;

View file

@ -1,7 +0,0 @@
DO $$ BEGIN
CREATE TYPE "external_id_type" AS ENUM('game', 'category', 'mechanic', 'publisher', 'designer', 'artist');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
ALTER TABLE "external_ids" ALTER COLUMN "type" SET DATA TYPE external_id_type;

View file

@ -0,0 +1,2 @@
ALTER TABLE "users" ALTER COLUMN "two_factor_secret" SET DEFAULT '';--> statement-breakpoint
ALTER TABLE "users" ADD COLUMN "two_factor_enabled" boolean DEFAULT false;

View file

@ -1 +0,0 @@
ALTER TABLE "collection_items" ADD COLUMN "times_played" integer DEFAULT 0;

View file

@ -0,0 +1,14 @@
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 $$;

View file

@ -1,97 +0,0 @@
ALTER TABLE "categories_to_external_ids" DROP CONSTRAINT "categories_to_external_ids_category_id_categories_id_fk";
--> statement-breakpoint
ALTER TABLE "expansions" DROP CONSTRAINT "expansions_base_game_id_games_id_fk";
--> statement-breakpoint
ALTER TABLE "expansions_to_external_ids" DROP CONSTRAINT "expansions_to_external_ids_expansion_id_expansions_id_fk";
--> statement-breakpoint
ALTER TABLE "games_to_external_ids" DROP CONSTRAINT "games_to_external_ids_game_id_games_id_fk";
--> statement-breakpoint
ALTER TABLE "mechanics_to_external_ids" DROP CONSTRAINT "mechanics_to_external_ids_mechanic_id_mechanics_id_fk";
--> statement-breakpoint
ALTER TABLE "publishers_to_external_ids" DROP CONSTRAINT "publishers_to_external_ids_publisher_id_publishers_id_fk";
--> statement-breakpoint
ALTER TABLE "categories_to_games" ALTER COLUMN "category_id" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "categories_to_games" ALTER COLUMN "game_id" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "mechanics_to_games" ALTER COLUMN "mechanic_id" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "mechanics_to_games" ALTER COLUMN "game_id" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "publishers_to_games" ALTER COLUMN "publisher_id" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "publishers_to_games" ALTER COLUMN "game_id" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "categories_to_external_ids" ADD CONSTRAINT "categories_to_external_ids_category_id_external_id_pk" PRIMARY KEY("category_id","external_id");--> statement-breakpoint
ALTER TABLE "categories_to_games" ADD CONSTRAINT "categories_to_games_category_id_game_id_pk" PRIMARY KEY("category_id","game_id");--> statement-breakpoint
ALTER TABLE "expansions_to_external_ids" ADD CONSTRAINT "expansions_to_external_ids_expansion_id_external_id_pk" PRIMARY KEY("expansion_id","external_id");--> statement-breakpoint
ALTER TABLE "games_to_external_ids" ADD CONSTRAINT "games_to_external_ids_game_id_external_id_pk" PRIMARY KEY("game_id","external_id");--> statement-breakpoint
ALTER TABLE "mechanics_to_external_ids" ADD CONSTRAINT "mechanics_to_external_ids_mechanic_id_external_id_pk" PRIMARY KEY("mechanic_id","external_id");--> statement-breakpoint
ALTER TABLE "mechanics_to_games" ADD CONSTRAINT "mechanics_to_games_mechanic_id_game_id_pk" PRIMARY KEY("mechanic_id","game_id");--> statement-breakpoint
ALTER TABLE "publishers_to_external_ids" ADD CONSTRAINT "publishers_to_external_ids_publisher_id_external_id_pk" PRIMARY KEY("publisher_id","external_id");--> statement-breakpoint
ALTER TABLE "publishers_to_games" ADD CONSTRAINT "publishers_to_games_publisher_id_game_id_pk" PRIMARY KEY("publisher_id","game_id");--> 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;
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;
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;
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;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "expansions_to_external_ids" ADD CONSTRAINT "expansions_to_external_ids_expansion_id_expansions_id_fk" FOREIGN KEY ("expansion_id") REFERENCES "expansions"("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;
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;
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;
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;
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;
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;
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;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View file

@ -1,2 +0,0 @@
ALTER TABLE "users" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
ALTER TABLE "users" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;

View file

@ -1,3 +0,0 @@
ALTER TABLE "categories" DROP COLUMN IF EXISTS "external_id";--> statement-breakpoint
ALTER TABLE "mechanics" DROP COLUMN IF EXISTS "external_id";--> statement-breakpoint
ALTER TABLE "publishers" DROP COLUMN IF EXISTS "external_id";

View file

@ -1 +0,0 @@
DROP TABLE "expansions_to_external_ids";

View file

@ -1,12 +0,0 @@
CREATE TABLE IF NOT EXISTS "password_reset_tokens" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"expires_at" timestamp (6) with time zone,
"created_at" timestamp (6) with time zone DEFAULT now()
);
--> 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;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

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

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

View file

@ -5,113 +5,78 @@
{
"idx": 0,
"version": "5",
"when": 1707437865821,
"tag": "0000_oval_wolverine",
"when": 1710268038944,
"tag": "0000_tricky_hitman",
"breakpoints": true
},
{
"idx": 1,
"version": "5",
"when": 1707438055782,
"tag": "0001_giant_tomorrow_man",
"when": 1710268191378,
"tag": "0001_numerous_dragon_man",
"breakpoints": true
},
{
"idx": 2,
"version": "5",
"when": 1707524139123,
"tag": "0002_sour_silverclaw",
"when": 1710268300740,
"tag": "0002_thick_lyja",
"breakpoints": true
},
{
"idx": 3,
"version": "5",
"when": 1707526808124,
"tag": "0003_thick_tinkerer",
"when": 1710268371021,
"tag": "0003_mushy_madame_masque",
"breakpoints": true
},
{
"idx": 4,
"version": "5",
"when": 1707932397672,
"tag": "0004_fancy_umar",
"when": 1710277583673,
"tag": "0004_glossy_enchantress",
"breakpoints": true
},
{
"idx": 5,
"version": "5",
"when": 1707932466413,
"tag": "0005_uneven_lifeguard",
"when": 1710366724519,
"tag": "0005_light_captain_marvel",
"breakpoints": true
},
{
"idx": 6,
"version": "5",
"when": 1707932522909,
"tag": "0006_light_corsair",
"when": 1710905422967,
"tag": "0006_wild_stone_men",
"breakpoints": true
},
{
"idx": 7,
"version": "5",
"when": 1707951501716,
"tag": "0007_same_valeria_richards",
"when": 1710905572670,
"tag": "0007_large_miss_america",
"breakpoints": true
},
{
"idx": 8,
"version": "5",
"when": 1708105454143,
"tag": "0008_complete_manta",
"when": 1711757183163,
"tag": "0008_amusing_franklin_richards",
"breakpoints": true
},
{
"idx": 9,
"version": "5",
"when": 1708105890146,
"tag": "0009_equal_christian_walker",
"when": 1711868447607,
"tag": "0009_gray_carlie_cooper",
"breakpoints": true
},
{
"idx": 10,
"version": "5",
"when": 1708243232524,
"tag": "0010_flat_mister_sinister",
"breakpoints": true
},
{
"idx": 11,
"version": "5",
"when": 1708330668971,
"tag": "0011_gigantic_mister_sinister",
"breakpoints": true
},
{
"idx": 12,
"version": "5",
"when": 1708330799655,
"tag": "0012_dizzy_lethal_legion",
"breakpoints": true
},
{
"idx": 13,
"version": "5",
"when": 1708453431550,
"tag": "0013_clever_monster_badoon",
"breakpoints": true
},
{
"idx": 14,
"version": "5",
"when": 1708479971410,
"tag": "0014_organic_morlocks",
"breakpoints": true
},
{
"idx": 15,
"version": "5",
"when": 1709344835732,
"tag": "0015_awesome_gabe_jones",
"when": 1712271520175,
"tag": "0010_wakeful_metal_master",
"breakpoints": true
}
]

View file

@ -4,12 +4,11 @@
"private": "true",
"scripts": {
"dev": "NODE_OPTIONS=\"--inspect\" vite dev --host",
"build": "prisma generate && vite build",
"build": "vite build",
"package": "svelte-kit package",
"preview": "vite preview",
"test": "playwright test",
"test:ui": "svelte-kit sync && playwright test --ui",
"postinstall": "prisma generate",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test:unit": "vitest",
@ -21,56 +20,53 @@
"seed": "tsx ./src/seed.ts",
"push": "drizzle-kit push:pg"
},
"prisma": {
"seed": "node --loader ts-node/esm prisma/seed.ts"
},
"devDependencies": {
"@melt-ui/pp": "^0.3.0",
"@melt-ui/svelte": "^0.75.2",
"@playwright/test": "^1.42.0",
"@resvg/resvg-js": "^2.6.0",
"@sveltejs/adapter-auto": "^3.1.1",
"@sveltejs/enhanced-img": "^0.1.8",
"@sveltejs/kit": "^2.5.2",
"@melt-ui/svelte": "^0.76.3",
"@playwright/test": "^1.43.0",
"@resvg/resvg-js": "^2.6.2",
"@sveltejs/adapter-auto": "^3.2.0",
"@sveltejs/enhanced-img": "^0.1.9",
"@sveltejs/kit": "^2.5.5",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@types/cookie": "^0.6.0",
"@types/node": "^20.11.24",
"@types/pg": "^8.11.2",
"@types/node": "^20.12.6",
"@types/pg": "^8.11.5",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"autoprefixer": "^10.4.18",
"autoprefixer": "^10.4.19",
"dotenv": "^16.4.5",
"drizzle-kit": "^0.20.14",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1",
"eslint-plugin-svelte": "^2.36.0",
"just-clone": "^6.2.0",
"just-debounce-it": "^3.2.0",
"postcss": "^8.4.35",
"postcss-import": "^16.0.1",
"postcss": "^8.4.38",
"postcss-import": "^16.1.0",
"postcss-load-config": "^5.0.3",
"postcss-preset-env": "^9.4.0",
"postcss-preset-env": "^9.5.4",
"prettier": "^3.2.5",
"prettier-plugin-svelte": "^3.2.2",
"prisma": "^5.9.1",
"sass": "^1.71.1",
"sass": "^1.74.1",
"satori": "^0.10.13",
"satori-html": "^0.3.2",
"svelte": "^4.2.12",
"svelte-check": "^3.6.6",
"svelte-meta-tags": "^3.1.1",
"svelte-check": "^3.6.9",
"svelte-headless-table": "^0.18.2",
"svelte-meta-tags": "^3.1.2",
"svelte-preprocess": "^5.1.3",
"svelte-sequential-preprocessor": "^2.0.1",
"sveltekit-flash-message": "^2.4.2",
"sveltekit-flash-message": "^2.4.4",
"sveltekit-rate-limiter": "^0.4.3",
"sveltekit-superforms": "^2.7.0",
"tailwindcss": "^3.4.1",
"sveltekit-superforms": "^2.12.4",
"tailwindcss": "^3.4.3",
"ts-node": "^10.9.2",
"tslib": "^2.6.1",
"tsx": "^4.7.1",
"typescript": "^5.3.3",
"vite": "^5.1.5",
"vitest": "^1.3.1",
"tsx": "^4.7.2",
"typescript": "^5.4.4",
"vite": "^5.2.8",
"vitest": "^1.4.0",
"zod": "^3.22.4"
},
"type": "module",
@ -82,43 +78,41 @@
"@fontsource/fira-mono": "^5.0.12",
"@iconify-icons/line-md": "^1.2.26",
"@iconify-icons/mdi": "^1.2.47",
"@lucia-auth/adapter-drizzle": "^1.0.2",
"@lucia-auth/adapter-prisma": "4.0.0",
"@lucia-auth/adapter-drizzle": "^1.0.7",
"@lukeed/uuid": "^2.0.1",
"@neondatabase/serverless": "^0.9.0",
"@paralleldrive/cuid2": "^2.2.2",
"@planetscale/database": "^1.16.0",
"@prisma/client": "^5.9.1",
"@sentry/sveltekit": "^7.100.1",
"@sveltejs/adapter-vercel": "^5.1.0",
"@sveltejs/adapter-vercel": "^5.2.0",
"@types/feather-icons": "^4.29.4",
"@vercel/og": "^0.5.20",
"bits-ui": "^0.19.3",
"bits-ui": "^0.19.7",
"boardgamegeekclient": "^1.9.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cookie": "^0.6.0",
"drizzle-orm": "^0.29.4",
"drizzle-orm": "^0.30.7",
"feather-icons": "^4.29.1",
"formsnap": "^0.5.1",
"html-entities": "^2.5.2",
"iconify-icon": "^2.0.0",
"just-capitalize": "^3.2.0",
"just-kebab-case": "^4.2.0",
"loader": "^2.1.1",
"lucia": "3.0.1",
"lucide-svelte": "^0.344.0",
"mysql2": "^3.9.2",
"nanoid": "^5.0.6",
"open-props": "^1.6.20",
"oslo": "^1.1.3",
"pg": "^8.11.3",
"postgres": "^3.4.3",
"lucia": "3.1.1",
"lucide-svelte": "^0.358.0",
"open-props": "^1.7.2",
"oslo": "^1.2.0",
"pg": "^8.11.5",
"postgres": "^3.4.4",
"qrcode": "^1.5.3",
"radix-svelte": "^0.9.0",
"svelte-french-toast": "^1.2.0",
"svelte-lazy-loader": "^1.0.0",
"tailwind-merge": "^2.2.1",
"tailwind-variants": "^0.2.0",
"tailwind-merge": "^2.2.2",
"tailwind-variants": "^0.2.1",
"tailwindcss-animate": "^1.0.6",
"zod-to-json-schema": "^3.22.4"
"zod-to-json-schema": "^3.22.5"
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,256 +0,0 @@
{
"categories": [
{
"name": "Abstract Strategy"
},
{
"name": "Action / Dexterity"
},
{
"name": "Adventure"
},
{
"name": "Age of Reason"
},
{
"name": "American Civil War"
},
{
"name": "American Indian Wars"
},
{
"name": "American Revolutionary War"
},
{
"name": "American West"
},
{
"name": "Ancient"
},
{
"name": "Animals"
},
{
"name": "Arabian"
},
{
"name": "Aviation / Flight"
},
{
"name": "Bluffing"
},
{
"name": "Book"
},
{
"name": "Card Game"
},
{
"name": "Children's Game"
},
{
"name": "City Building"
},
{
"name": "Civil War"
},
{
"name": "Civilization"
},
{
"name": "Collectible Components"
},
{
"name": "Comic Book / Strip"
},
{
"name": "Deduction"
},
{
"name": "Dice"
},
{
"name": "Economic"
},
{
"name": "Educational"
},
{
"name": "Electronic"
},
{
"name": "Environmental"
},
{
"name": "Expansion for Base-game"
},
{
"name": "Exploration"
},
{
"name": "Fan Expansion"
},
{
"name": "Fantasy"
},
{
"name": "Farming"
},
{
"name": "Fighting"
},
{
"name": "Game System"
},
{
"name": "Horror"
},
{
"name": "Humor"
},
{
"name": "Industry / Manufacturing"
},
{
"name": "Korean War"
},
{
"name": "Mafia"
},
{
"name": "Math"
},
{
"name": "Mature / Adult"
},
{
"name": "Maze"
},
{
"name": "Medical"
},
{
"name": "Medieval"
},
{
"name": "Memory"
},
{
"name": "Miniatures"
},
{
"name": "Modern Warefare"
},
{
"name": "Movies / TV / Radio Theme"
},
{
"name": "Murder/Mystery"
},
{
"name": "Music"
},
{
"name": "Mythology"
},
{
"name": "Napoleonic"
},
{
"name": "Nautical"
},
{
"name": "Negotiation"
},
{
"name": "Novel-based"
},
{
"name": "Number"
},
{
"name": "Party Game"
},
{
"name": "Pike and Shot"
},
{
"name": "Pirates"
},
{
"name": "Political"
},
{
"name": "Post-Napoleonic"
},
{
"name": "Prehistoric"
},
{
"name": "Print & Play"
},
{
"name": "Puzzle"
},
{
"name": "Racing"
},
{
"name": "Real-time"
},
{
"name": "Religious"
},
{
"name": "Renaissance"
},
{
"name": "Science Fiction"
},
{
"name": "Space Exploration"
},
{
"name": "Spies/Secret Agents"
},
{
"name": "Sports"
},
{
"name": "Territory Building"
},
{
"name": "Trains"
},
{
"name": "Transportation"
},
{
"name": "Travel"
},
{
"name": "Trivia"
},
{
"name": "Video Game Theme"
},
{
"name": "Vietnam War"
},
{
"name": "Wargame"
},
{
"name": "Word Game"
},
{
"name": "World War I"
},
{
"name": "World War II"
},
{
"name": "Zombies"
}
]
}

View file

@ -1,577 +0,0 @@
{
"mechanics": [
{
"name": "Acting"
},
{
"name": "Action Drafting"
},
{
"name": "Action Points"
},
{
"name": "Action Queue"
},
{
"name": "Action Retrieval"
},
{
"name": "Action Timer"
},
{
"name": "Action/Event"
},
{
"name": "Advantage Token"
},
{
"name": "Alliances"
},
{
"name": "Area Majority / Influence"
},
{
"name": "Area Movement"
},
{
"name": "Area-Impulse"
},
{
"name": "Auction Compensation"
},
{
"name": "Auction: Dexterity"
},
{
"name": "Auction: Dutch"
},
{
"name": "Auction: Dutch Priority"
},
{
"name": "Auction: English"
},
{
"name": "Auction: Fixed Placement"
},
{
"name": "Auction: Multiple Lot"
},
{
"name": "Auction: Once Around"
},
{
"name": "Auction: Sealed Bid"
},
{
"name": "Auction: Turn Order Until Pass"
},
{
"name": "Auction/Bidding"
},
{
"name": "Automatic Resource Growth"
},
{
"name": "Betting and Bluffing"
},
{
"name": "Bias"
},
{
"name": "Bids As Wagers"
},
{
"name": "Bingo"
},
{
"name": "Bribery"
},
{
"name": "Campaign / Battle Card Driven"
},
{
"name": "Card Play Conflict Resolution"
},
{
"name": "Catch the Leader"
},
{
"name": "Chaining"
},
{
"name": "Chit-Pull System"
},
{
"name": "Closed Drafting"
},
{
"name": "Closed Economy Auction"
},
{
"name": "Command Cards"
},
{
"name": "Commodity Speculation"
},
{
"name": "Communication Limits"
},
{
"name": "Connections"
},
{
"name": "Constrained Bidding"
},
{
"name": "Contracts"
},
{
"name": "Cooperative Game"
},
{
"name": "Crayon Rail System"
},
{
"name": "Critical Hits and Failures"
},
{
"name": "Cube Tower"
},
{
"name": "Deck Construction"
},
{
"name": "Deck, Bag, and Pool Building"
},
{
"name": "Deduction"
},
{
"name": "Delayed Purchase"
},
{
"name": "Dice Rolling"
},
{
"name": "Die Icon Resolution"
},
{
"name": "Different Dice Movement"
},
{
"name": "Drawing"
},
{
"name": "Elapsed Real Time Ending"
},
{
"name": "Enclosure"
},
{
"name": "End Game Bonuses"
},
{
"name": "Events"
},
{
"name": "Finale Ending"
},
{
"name": "Flicking"
},
{
"name": "Follow"
},
{
"name": "Force Commitment"
},
{
"name": "Grid Coverage"
},
{
"name": "Grid Movement"
},
{
"name": "Hand Management"
},
{
"name": "Hexagon Grid"
},
{
"name": "Hidden Movement"
},
{
"name": "Hidden Roles"
},
{
"name": "Hidden Victory Points"
},
{
"name": "Highest-Lowest Scoring"
},
{
"name": "Hot Potato"
},
{
"name": "I Cut, You Choose"
},
{
"name": "Impulse Movement"
},
{
"name": "Income"
},
{
"name": "Increase Value of Unchosen Resources"
},
{
"name": "Induction"
},
{
"name": "Interrupts"
},
{
"name": "Investment"
},
{
"name": "Kill Steal"
},
{
"name": "King of the Hill"
},
{
"name": "Ladder Climbing"
},
{
"name": "Layering"
},
{
"name": "Legacy Game"
},
{
"name": "Line Drawing"
},
{
"name": "Line of Sight"
},
{
"name": "Loans"
},
{
"name": "Lose a Turn"
},
{
"name": "Mancala"
},
{
"name": "Map Addition"
},
{
"name": "Map Deformation"
},
{
"name": "Map Reduction"
},
{
"name": "Market"
},
{
"name": "Matching"
},
{
"name": "Measurement Movement"
},
{
"name": "Melding and Splaying"
},
{
"name": "Memory"
},
{
"name": "Minimap Resolution"
},
{
"name": "Modular Board"
},
{
"name": "Move Through Deck"
},
{
"name": "Movement Points"
},
{
"name": "Movement Template"
},
{
"name": "Moving Multiple Units"
},
{
"name": "Multi-Use Cards"
},
{
"name": "Multiple Maps"
},
{
"name": "Narrative Choice / Paragraph"
},
{
"name": "Negotiation"
},
{
"name": "Neighbor Scope"
},
{
"name": "Network and Route Building"
},
{
"name": "Once-Per-Game Abilities"
},
{
"name": "Open Drafting"
},
{
"name": "Order Counters"
},
{
"name": "Ordering"
},
{
"name": "Ownership"
},
{
"name": "Paper-and-Pencil"
},
{
"name": "Passed Action Token"
},
{
"name": "Pattern Building"
},
{
"name": "Pattern Movement"
},
{
"name": "Pattern Recognition"
},
{
"name": "Physical Removal"
},
{
"name": "Pick-up and Deliver"
},
{
"name": "Pieces as Map"
},
{
"name": "Player Elimination"
},
{
"name": "Player Judge"
},
{
"name": "Point to Point Movement"
},
{
"name": "Predictive Bid"
},
{
"name": "Prisoner's Dilemma"
},
{
"name": "Programmed Movement"
},
{
"name": "Push Your Luck"
},
{
"name": "Questions and Answers"
},
{
"name": "Race"
},
{
"name": "Random Production"
},
{
"name": "Ratio / Combat Results Table"
},
{
"name": "Re-rolling and Locking"
},
{
"name": "Real-Time"
},
{
"name": "Relative Movement"
},
{
"name": "Resource Queue"
},
{
"name": "Resource to Move"
},
{
"name": "Rock-Paper-Scissors"
},
{
"name": "Role Playing"
},
{
"name": "Roles with Asymmetric Information"
},
{
"name": "Roll / Spin and Move"
},
{
"name": "Rondel"
},
{
"name": "Scenario / Mission / Campaign Game"
},
{
"name": "Score-and-Reset Game"
},
{
"name": "Secret Unit Deployment"
},
{
"name": "Selection Order Bid"
},
{
"name": "Semi-Cooperative Game"
},
{
"name": "Set Collection"
},
{
"name": "Simulation"
},
{
"name": "Simultaneous Action Selection"
},
{
"name": "Singing"
},
{
"name": "Single Loser Game"
},
{
"name": "Slide/Push"
},
{
"name": "Solo / Solitaire Game"
},
{
"name": "Speed Matching"
},
{
"name": "Square Grid"
},
{
"name": "Stacking and Balancing"
},
{
"name": "Stat Check Resolution"
},
{
"name": "Static Capture"
},
{
"name": "Stock Holding"
},
{
"name": "Storytelling"
},
{
"name": "Sudden Death Ending"
},
{
"name": "Tags"
},
{
"name": "Take That"
},
{
"name": "Targeted Clues"
},
{
"name": "Team-Based Game"
},
{
"name": "Tech Trees / Tech Tracks"
},
{
"name": "Three Dimensional Movement"
},
{
"name": "Tile Placement"
},
{
"name": "Track Movement"
},
{
"name": "Trading"
},
{
"name": "Traitor Game"
},
{
"name": "Trick-taking"
},
{
"name": "Tug of War"
},
{
"name": "Turn Order: Auction"
},
{
"name": "Turn Order: Claim Action"
},
{
"name": "Turn Order: Pass Order"
},
{
"name": "Turn Order: Progressive"
},
{
"name": "Turn Order: Random"
},
{
"name": "Turn Order: Role Order"
},
{
"name": "Turn Order: Stat-Based"
},
{
"name": "Turn Order: Time Track"
},
{
"name": "Variable Phase Order"
},
{
"name": "Variable Player Powers"
},
{
"name": "Variable Set-up"
},
{
"name": "Victory Points as a Resource"
},
{
"name": "Voting"
},
{
"name": "Worker Placement"
},
{
"name": "Worker Placement with Dice Workers"
},
{
"name": "Worker Placement, Different Worker Types"
},
{
"name": "Zone of Control"
}
]
}

View file

@ -1,272 +0,0 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
previewFeatures = ["fullTextSearch", "fullTextIndex", "relationJoins"]
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
relationMode = "prisma"
}
model Role {
id String @id @default(cuid())
name String @unique
userRoles UserRole[]
@@map("roles")
}
model UserRole {
id String @id @default(cuid())
user User @relation(fields: [user_id], references: [id])
user_id String
role Role @relation(fields: [role_id], references: [id])
role_id String
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
@@unique([user_id, role_id])
@@index([user_id])
@@index([role_id])
@@map("user_roles")
}
model User {
id String @id @default(cuid())
username String @unique
hashed_password String?
email String? @unique
firstName String?
lastName String?
roles UserRole[]
verified Boolean @default(false)
receiveEmail Boolean @default(false)
collection Collection?
wishlist Wishlist?
list List[]
theme String @default("system")
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
sessions Session[]
@@map("users")
}
model Session {
id String @id @unique
userId String
ip_country String
ip_address String
expiresAt DateTime
user User @relation(references: [id], fields: [userId], onDelete: Cascade)
@@index([userId])
@@map("sessions")
}
model Collection {
id String @id @default(cuid())
user_id String @unique
user User @relation(references: [id], fields: [user_id])
items CollectionItem[]
@@index([user_id])
@@map("collections")
}
model CollectionItem {
id String @id @default(cuid())
collection_id String
collection Collection @relation(references: [id], fields: [collection_id], onDelete: Cascade)
game_id String @unique
game Game @relation(references: [id], fields: [game_id])
times_played Int
@@index([game_id, collection_id])
@@index([game_id])
@@index([collection_id])
@@map("collection_items")
}
model Wishlist {
id String @id @default(cuid())
user_id String @unique
user User @relation(references: [id], fields: [user_id])
items WishlistItem[]
@@index([user_id])
@@map("wishlists")
}
model WishlistItem {
id String @id @default(cuid())
wishlist_id String
wishlist Wishlist @relation(references: [id], fields: [wishlist_id], onDelete: Cascade)
game_id String @unique
game Game @relation(references: [id], fields: [game_id])
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
@@index([game_id, wishlist_id])
@@index([game_id])
@@index([wishlist_id])
@@map("wishlist_items")
}
model List {
id String @id @default(cuid())
name String
user_id String @unique
user User @relation(references: [id], fields: [user_id])
items ListItem[]
@@index([user_id])
@@map("lists")
}
model ListItem {
id String @id @default(cuid())
list_id String
list List @relation(references: [id], fields: [list_id], onDelete: Cascade)
game_id String @unique
game Game @relation(references: [id], fields: [game_id])
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
@@index([game_id, list_id])
@@index([game_id])
@@index([list_id])
@@map("list_items")
}
model Game {
id String @id @default(cuid())
name String
slug String
description String? @db.LongText
year_published Int? @db.Year
min_players Int?
max_players Int?
playtime Int?
min_playtime Int?
max_playtime Int?
min_age Int?
image_url String?
thumb_url String?
url String?
categories Category[]
mechanics Mechanic[]
designers Designer[]
publishers Publisher[]
artists Artist[]
names GameName[]
expansions Expansion[] @relation("BaseToExpansion")
expansion_of Expansion[] @relation("ExpansionToBase")
collection_items CollectionItem[]
wishlist_items WishlistItem[]
list_items ListItem[]
external_id Int @unique
last_sync_at DateTime? @db.Timestamp(6)
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
@@fulltext([name])
@@fulltext([slug])
@@map("games")
}
model GameName {
id String @id @default(cuid())
name String
slug String
game_id String
game Game @relation(references: [id], fields: [game_id])
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
@@index([game_id])
@@map("game_names")
}
model Publisher {
id String @id @default(cuid())
name String
slug String
external_id Int @unique
games Game[]
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
@@fulltext([name])
@@map("publishers")
}
model Category {
id String @id @default(cuid())
name String
slug String
games Game[]
external_id Int @unique
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
@@fulltext([name])
@@map("categories")
}
model Mechanic {
id String @id @default(cuid())
name String
slug String
games Game[]
external_id Int @unique
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
@@fulltext([name])
@@map("mechanics")
}
model Designer {
id String @id @default(cuid())
name String
slug String
external_id Int @unique
games Game[]
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
@@fulltext([name])
@@map("designers")
}
model Artist {
id String @id @default(cuid())
name String
slug String @unique
external_id Int @unique
games Game[]
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
@@fulltext([name])
@@map("artists")
}
model Expansion {
id String @id @default(cuid())
base_game Game @relation(name: "BaseToExpansion", fields: [base_game_id], references: [id])
base_game_id String
game Game @relation(name: "ExpansionToBase", fields: [game_id], references: [id])
game_id String
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
@@index([base_game_id])
@@index([game_id])
@@map("expansions")
}

View file

@ -1,84 +0,0 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
console.log(`Start seeding ...`);
console.log('Creating roles ...');
const existingRoles = await prisma.role.findMany();
if (existingRoles.length === 0) {
await prisma.role.createMany({
data: [{ name: 'admin' }, { name: 'user' }]
});
console.log('Roles created.');
} else {
console.log('Roles already exist. No action taken.');
}
if (!await prisma.publisher.findFirst({
where: {
external_id: 9999
}
})) {
console.log('Publisher does not exist. Creating...');
await prisma.publisher.create({
data: {
name: 'Unknown',
slug: 'unknown',
external_id: 9999
}
});
}
if (!await prisma.designer.findFirst({
where: {
external_id: 9999
}
})) {
console.log('Designer does not exist. Creating...');
await prisma.designer.create({
data: {
name: 'Unknown',
slug: 'unknown',
external_id: 9999
}
});
}
if (!await prisma.artist.findFirst({
where: {
external_id: 9999
}
})) {
console.log('Artist does not exist. Creating...');
await prisma.artist.create({
data: {
name: 'Unknown',
slug: 'unknown',
external_id: 9999
}
});
}
// for (const p of userData) {
// const user = await prisma.user.create({
// data: {
// firstName: p.user.firstName,
// lastName: p.user.lastName,
// email: p.user.email,
// username: p.user.username
// }
// });
// console.log(`Created user with id: ${user.id}`);
// }
console.log(`Seeding finished.`);
}
main()
.catch(async (e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

11
src/app.d.ts vendored
View file

@ -2,21 +2,20 @@
// for information about these interfaces
// and what to do when importing types
import type { PrismaClient, User } from '@prisma/client';
type User = Omit<User, 'created_at' | 'updated_at'>;
// src/app.d.ts
declare global {
namespace App {
interface PageData {
flash?: { type: 'success' | 'error' | 'info'; message: string };
flash?: {
type: 'success' | 'error' | 'info';
message: string;
data?: Record<string, unknown>;
};
}
interface Locals {
auth: import('lucia').AuthRequest;
user: import('lucia').User | null;
session: import('lucia').Session | null;
prisma: PrismaClient;
startTimer: number;
ip: string;
country: string;

View file

@ -39,11 +39,12 @@ export const authentication: Handle = async function ({ event, resolve }) {
...sessionCookie.attributes
});
}
console.log('session from hooks', JSON.stringify(session, null, 2));
if (!session) {
const sessionCookie = lucia.createBlankSessionCookie();
console.log('blank sessionCookie', JSON.stringify(sessionCookie, null, 2));
event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: ".",
path: '.',
...sessionCookie.attributes
});
}
@ -57,4 +58,4 @@ export const handle: Handle = sequence(
// Sentry.sentryHandle(),
authentication
);
// export const handleError = Sentry.handleErrorWithSentry();
// export const handleError = Sentry.handleErrorWithSentry();

View file

@ -1,14 +1,12 @@
<script lang="ts">
import { enhance } from "$app/forms";
import { fly } from "svelte/transition";
import { createSelect, melt } from "@melt-ui/svelte";
import { Check, ChevronDown, MinusCircle, PlusCircle } from "lucide-svelte";
import { MinusCircle, PlusCircle } from "lucide-svelte";
import { Button } from '$components/ui/button';
import type { Collection, Wishlist } from "@prisma/client";
import type { CollectionItems, Wishlists } from "../../schema";
export let game_id: string;
export let collection: Collection;
export let wishlist: Wishlist;
export let collection: CollectionItems;
export let wishlist: Wishlists;
export let in_wishlist = false;
export let in_collection = false;
export let lists = [];

View file

@ -1,25 +1,28 @@
<script>
import { PUBLIC_SITE_URL } from "$env/static/public";
import { PUBLIC_SITE_URL } from '$env/static/public';
</script>
<footer>
<p>Bored Game &copy; {new Date().getFullYear()} | Built by <a target="__blank" href="https://bradleyshellnut.com">Bradley Shellnut</a> | {PUBLIC_SITE_URL}</p>
<p>Bored Game &copy; {new Date().getFullYear()}</p>
<p>Built by <a target="__blank" href="https://bradleyshellnut.com">Bradley Shellnut</a>
</p>
<p>{PUBLIC_SITE_URL}</p>
</footer>
<style lang="postcss">
footer {
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
gap: 0.25rem;
}
footer a {
font-weight: bold;
}
@media (min-width: 480px) {
@media (width < 640px) {
footer {
padding: 40px 0;
}

View file

@ -1,9 +1,9 @@
<script lang="ts">
import type { GameType, SavedGameType } from '$lib/types';
import * as Card from "$lib/components/ui/card";
import type { CollectionItem } from '@prisma/client';
import type { CollectionItems } from '../../schema';
export let game: GameType | CollectionItem;
export let game: GameType | CollectionItems;
export let detailed: boolean = false;
export let variant: 'default' | 'compact' = 'default';
@ -18,10 +18,10 @@
<Card.Header>
<Card.Title class="game-card-header">
<span style:--transition-name="game-name-{game.slug}">
{game.name}
<!-- {game.name}
{#if game?.year_published}
({game?.year_published})
{/if}
{/if} -->
</span>
</Card.Title>
</Card.Header>
@ -32,7 +32,7 @@
title={`View ${game.name}`}
data-sveltekit-preload-data
>
<img src={game.thumb_url} alt={`Image of ${game.name}`} loading="lazy" decoding="async" />
<!-- <img src={game.thumb_url} alt={`Image of ${game.name}`} loading="lazy" decoding="async" />
<div class="game-details">
{#if game?.players}
<p>Players: {game.players}</p>
@ -44,7 +44,7 @@
<div class="description">{@html game.description}</div>
{/if}
{/if}
</div>
</div> -->
</a>
</Card.Content>
</Card.Root>

View file

@ -6,10 +6,17 @@
import * as Avatar from "$components/ui/avatar";
import { invalidateAll } from '$app/navigation';
import Logo from '$components/logo.svelte';
import type { Users } from "../../schema";
export let user: User | null;
export let user: Users | null = null;
let avatar = user?.username.slice(0, 1).toUpperCase() || '?';
console.log('header user', user);
let avatar: string;
$: if (user) {
avatar = user.username?.slice(0, 1).toUpperCase() || ':)';
}
</script>
<header>
@ -18,14 +25,12 @@
<div class="logo-image">
<Logo />
</div>
Bored Game
<span class="logo-text">Bored Game</span>
</a>
</div>
<!-- <TextSearch /> -->
<nav>
{#if user}
<a href="/collection" title="Go to your collection" data-sveltekit-preload-data>Collection</a>
<a href="/wishlist" title="Go to your wishlist" data-sveltekit-preload-data>Wishlist</a>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Avatar.Root asChild>
@ -88,8 +93,7 @@
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
{/if}
{#if !user}
{:else}
<a href="/login">
<span class="flex-auto">Login</span></a
>
@ -131,6 +135,17 @@
.logo-image {
width: 2rem;
height: 2rem;
@media (width < 640px) {
width: 3rem;
height: 3rem;
}
}
.logo-text {
@media (width < 640px) {
display: none;
}
}
nav {

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { Button as ButtonPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
import { buttonVariants, type Props, type Events } from ".";
import { cn } from "$lib/utils.js";
import { buttonVariants, type Props, type Events } from "./index.js";
type $$Props = Props;
type $$Events = Events;

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { cn } from "$lib/utils";
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLUListElement>;

View file

@ -1,6 +1,6 @@
<script lang="ts">
import MoreHorizontal from "lucide-svelte/icons/more-horizontal";
import { cn } from "$lib/utils";
import Ellipsis from "lucide-svelte/icons/ellipsis";
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLSpanElement>;
@ -14,6 +14,6 @@
class={cn("flex h-9 w-9 items-center justify-center", className)}
{...$$restProps}
>
<MoreHorizontal class="h-4 w-4" />
<Ellipsis class="h-4 w-4" />
<span class="sr-only">More pages</span>
</span>

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { cn } from "$lib/utils";
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLLIElement>;

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
import { buttonVariants, type Props } from "$lib/components/ui/button";
import { cn } from "$lib/utils.js";
import { buttonVariants, type Props } from "$lib/components/ui/button/index.js";
type $$Props = PaginationPrimitive.PageProps &
Props & {

View file

@ -1,8 +1,8 @@
<script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui";
import ChevronRight from "lucide-svelte/icons/chevron-right";
import { Button } from "$lib/components/ui/button";
import { cn } from "$lib/utils";
import { Button } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
type $$Props = PaginationPrimitive.NextButtonProps;
type $$Events = PaginationPrimitive.NextButtonEvents;

View file

@ -1,8 +1,8 @@
<script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui";
import ChevronLeft from "lucide-svelte/icons/chevron-left";
import { Button } from "$lib/components/ui/button";
import { cn } from "$lib/utils";
import { Button } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
type $$Props = PaginationPrimitive.PrevButtonProps;
type $$Events = PaginationPrimitive.PrevButtonEvents;

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
import { cn } from "$lib/utils.js";
type $$Props = PaginationPrimitive.Props;
type $$Events = PaginationPrimitive.Events;

View file

@ -0,0 +1,28 @@
import Root from "./table.svelte";
import Body from "./table-body.svelte";
import Caption from "./table-caption.svelte";
import Cell from "./table-cell.svelte";
import Footer from "./table-footer.svelte";
import Head from "./table-head.svelte";
import Header from "./table-header.svelte";
import Row from "./table-row.svelte";
export {
Root,
Body,
Caption,
Cell,
Footer,
Head,
Header,
Row,
//
Root as Table,
Body as TableBody,
Caption as TableCaption,
Cell as TableCell,
Footer as TableFooter,
Head as TableHead,
Header as TableHeader,
Row as TableRow,
};

View file

@ -0,0 +1,13 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLTableSectionElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<tbody class={cn("[&_tr:last-child]:border-0", className)} {...$$restProps}>
<slot />
</tbody>

View file

@ -0,0 +1,13 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLTableCaptionElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<caption class={cn("mt-4 text-sm text-muted-foreground", className)} {...$$restProps}>
<slot />
</caption>

View file

@ -0,0 +1,18 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLTdAttributes } from "svelte/elements";
type $$Props = HTMLTdAttributes;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<td
class={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...$$restProps}
on:click
on:keydown
>
<slot />
</td>

View file

@ -0,0 +1,13 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLTableSectionElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<tfoot class={cn("bg-primary font-medium text-primary-foreground", className)} {...$$restProps}>
<slot />
</tfoot>

View file

@ -0,0 +1,19 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLThAttributes } from "svelte/elements";
type $$Props = HTMLThAttributes;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<th
class={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className
)}
{...$$restProps}
>
<slot />
</th>

View file

@ -0,0 +1,14 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLTableSectionElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<thead class={cn("[&_tr]:border-b", className)} {...$$restProps} on:click on:keydown>
<slot />
</thead>

View file

@ -0,0 +1,23 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLTableRowElement> & {
"data-state"?: unknown;
};
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<tr
class={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...$$restProps}
on:click
on:keydown
>
<slot />
</tr>

View file

@ -0,0 +1,15 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLTableAttributes } from "svelte/elements";
type $$Props = HTMLTableAttributes;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class="w-full overflow-auto">
<table class={cn("w-full caption-bottom text-sm", className)} {...$$restProps}>
<slot />
</table>
</div>

View file

@ -5,7 +5,7 @@ import {
DATABASE_PASSWORD,
DATABASE_HOST,
DATABASE_DB,
DATABASE_PORT,
DATABASE_PORT
} from '$env/static/private';
import * as schema from '../schema';
@ -16,7 +16,7 @@ const pool = new pg.Pool({
host: DATABASE_HOST,
port: Number(DATABASE_PORT).valueOf(),
database: DATABASE_DB,
ssl: true,
ssl: DATABASE_HOST !== 'localhost'
});
// user: DATABASE_USER,

View file

@ -1 +1,5 @@
export const notSignedInMessage = { type: 'error', message: 'You are not signed in' } as const;
export const notSignedInMessage = { type: 'error', message: 'You are not signed in' } as const;
export const forbiddenMessage = {
type: 'error',
message: 'You are not allowed to access this'
} as const;

View file

@ -1,4 +0,0 @@
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export default prisma

View file

@ -1,8 +1,8 @@
import db from "$lib/drizzle";
import { eq } from "drizzle-orm";
import { password_reset_tokens } from "../../schema";
import { generateId } from "lucia";
import { TimeSpan, createDate } from "oslo";
import { password_reset_tokens } from "../../schema";
export async function createPasswordResetToken(userId: string): Promise<string> {
// optionally invalidate all existing tokens

View file

@ -1,12 +1,20 @@
// lib/server/lucia.ts
import { Lucia, TimeSpan } from 'lucia';
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
import { dev } from '$app/environment';
import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle';
import db from '$lib/drizzle';
import { sessions, users } from '../../schema';
const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users);
let domain;
if (process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'production') {
domain = 'boredgame.vercel.app';
} else if (process.env.VERCEL_ENV === 'preview' || process.env.VERCEL_ENV === 'development') {
domain = process.env.VERCEL_BRANCH_URL;
} else {
domain = 'localhost';
}
export const lucia = new Lucia(adapter, {
getSessionAttributes: (attributes) => {
return {
@ -23,23 +31,24 @@ export const lucia = new Lucia(adapter, {
theme: attributes.theme
};
},
sessionExpiresIn: new TimeSpan(30, "d"), // 30 days
sessionExpiresIn: new TimeSpan(30, 'd'), // 30 days
sessionCookie: {
name: 'session',
expires: false, // session cookies have very long lifespan (2 years)
attributes: {
// set to `true` when using HTTPS
secure: !dev,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
domain: dev ? 'localhost' : 'boredgame.vercel.app',
domain
}
},
}
});
declare module "lucia" {
declare module 'lucia' {
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: DatabaseUserAttributes;
DatabaseSessionAttributes: DatabaseSessionAttributes;
}
interface DatabaseSessionAttributes {
ip_country: string;
@ -51,5 +60,6 @@ declare module "lucia" {
firstName: string;
lastName: string;
theme: string;
two_factor_secret: string | null;
}
}

View file

@ -1,9 +1,9 @@
import { error } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
import kebabCase from 'just-kebab-case';
import { PUBLIC_SITE_URL } from '$env/static/public';
import db from '$lib/drizzle';
import { externalIds, type Mechanics, type Categories, categories, categoriesToExternalIds } from '../../../schema';
import { eq } from 'drizzle-orm';
import { error } from '@sveltejs/kit';
import { PUBLIC_SITE_URL } from '$env/static/public';
export async function createCategory(locals: App.Locals, category: Categories, externalId: string) {
if (!category || !externalId || externalId === '') {
@ -44,7 +44,7 @@ export async function createCategory(locals: App.Locals, category: Categories, e
.insert(categories)
.values({
name: category.name,
slug: kebabCase(category.name || category.slug || '')
slug: kebabCase(category.name ?? category.slug ?? '')
})
.returning();
const dbExternalIds = await transaction

View file

@ -1,5 +1,4 @@
import type { GameType, SavedGameType } from '$lib/types';
import type { Game } from '@prisma/client';
import kebabCase from 'just-kebab-case';
import type { Games } from '../../schema';

View file

@ -4,7 +4,7 @@ import { userSchema } from './zod-schemas';
export const profileSchema = userSchema.pick({
firstName: true,
lastName: true,
username: true
username: true,
});
export const changeEmailSchema = userSchema.pick({
@ -15,7 +15,7 @@ export const changeUserPasswordSchema = z
.object({
current_password: z.string({ required_error: 'Current Password is required' }),
password: z.string({ required_error: 'Password is required' }).trim(),
confirm_password: z.string({ required_error: 'Confirm Password is required' }).trim()
confirm_password: z.string({ required_error: 'Confirm Password is required' }).trim(),
})
.superRefine(({ confirm_password, password }, ctx) => {
refinePasswords(confirm_password, password, ctx);
@ -23,6 +23,19 @@ export const changeUserPasswordSchema = z
export type ChangeUserPasswordSchema = typeof changeUserPasswordSchema;
export const addTwoFactorSchema = z.object({
current_password: z.string({ required_error: 'Current Password is required' }),
two_factor_code: z.string({ required_error: 'Two Factor Code is required' }).trim(),
});
export type AddTwoFactorSchema = typeof addTwoFactorSchema;
export const removeTwoFactorSchema = addTwoFactorSchema.pick({
current_password: true,
});
export type RemoveTwoFactorSchema = typeof removeTwoFactorSchema;
export const updateUserPasswordSchema = userSchema
.pick({ password: true, confirm_password: true })
.superRefine(({ confirm_password, password }, ctx) => {
@ -32,7 +45,7 @@ export const updateUserPasswordSchema = userSchema
export const refinePasswords = async function (
confirm_password: string,
password: string,
ctx: z.RefinementCtx
ctx: z.RefinementCtx,
) {
comparePasswords(confirm_password, password, ctx);
checkPasswordStrength(password, ctx);
@ -41,13 +54,13 @@ export const refinePasswords = async function (
const comparePasswords = async function (
confirm_password: string,
password: string,
ctx: z.RefinementCtx
ctx: z.RefinementCtx,
) {
if (confirm_password !== password) {
ctx.addIssue({
code: 'custom',
message: 'Password and Confirm Password must match',
path: ['confirm_password']
path: ['confirm_password'],
});
}
};
@ -100,7 +113,15 @@ const checkPasswordStrength = async function (password: string, ctx: z.Refinemen
ctx.addIssue({
code: 'custom',
message: errorMessage,
path: ['password']
path: ['password'],
});
}
};
};
export const addRoleSchema = z.object({
roles: z.array(z.string()).refine((value) => value.some((item) => item), {
message: 'You have to select at least one item.',
}),
});
export type AddRoleSchema = typeof addRoleSchema;

View file

@ -1,5 +1,6 @@
import { refinePasswords } from "./account";
import { userSchema } from "./zod-schemas";
import { refinePasswords } from './account';
import { userSchema } from './zod-schemas';
import { z } from 'zod';
export const signUpSchema = userSchema
.pick({
@ -9,13 +10,18 @@ export const signUpSchema = userSchema
username: true,
password: true,
confirm_password: true,
terms: true
terms: true,
})
.superRefine(({ confirm_password, password }, ctx) => {
refinePasswords(confirm_password, password, ctx);
});
export const signInSchema = userSchema.pick({
username: true,
password: true
});
export const signInSchema = z.object({
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(),
totpToken: z.string().trim().min(6).max(10).optional(),
});

View file

@ -5,11 +5,11 @@ import { migrate } from 'drizzle-orm/postgres-js/migrator';
const connection = postgres({
host: process.env.DATABASE_HOST || 'localhost',
port: 5432,
port: process.env.DATABASE_PORT,
user: process.env.DATABASE_USER || 'root',
password: process.env.DATABASE_PASSWORD || '',
database: process.env.DATABASE_DB || 'boredgame',
ssl: 'require',
ssl: process.env.NODE_ENV === 'development' ? false : 'require',
max: 1
});
const db = drizzle(connection);
@ -21,5 +21,5 @@ try {
console.error(e);
}
await connection.end();
// await connection.end();
process.exit();

View file

@ -1,12 +1,32 @@
import { redirect } from 'sveltekit-flash-message/server'
import type { PageServerLoad } from './$types';
import { notSignedInMessage } from '$lib/flashMessages';
import { redirect, loadFlash } from 'sveltekit-flash-message/server';
import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages';
import { eq } from 'drizzle-orm';
import db from '$lib/drizzle';
import { user_roles } from '../../../../schema';
export async function load(event) {
export const load = loadFlash(async (event) => {
const { locals } = event;
if (!locals?.user?.role?.includes('admin')) {
if (!locals?.user) {
redirect(302, '/login', notSignedInMessage, event);
}
return {}
};
const { user } = locals;
const userRoles = await db.query.user_roles.findMany({
where: eq(user_roles.user_id, user.id),
with: {
role: {
columns: {
name: true
}
}
}
});
const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin');
if (!userRoles?.length || !containsAdminRole) {
console.log('Not an admin');
redirect(302, '/', forbiddenMessage, event);
}
return {};
});

View file

@ -1 +1,50 @@
<slot />
<script lang="ts">
import { getFlash } from 'sveltekit-flash-message';
import { page } from '$app/stores';
import { onMount } from 'svelte';
import { theme } from '$state/theme';
import toast, { Toaster } from 'svelte-french-toast';
export let data;
$: ({ user } = data);
const flash = getFlash(page, {
clearOnNavigate: true,
clearAfterMs: 3000,
clearArray: true
});
onMount(() => {
// set the theme to the user's active theme
$theme = user?.theme || 'system';
document.querySelector('html')?.setAttribute('data-theme', $theme);
});
$: if ($flash) {
if ($flash.type === 'success') {
toast.success($flash.message);
} else {
toast.error($flash.message, {
duration: 5000
});
}
// Clearing the flash message could sometimes
// be required here to avoid double-toasting.
flash.set(undefined);
}
</script>
<h1>Do the admin stuff</h1>
<slot />
<Toaster />
<style lang="postcss">
:global(main) {
margin: 0;
max-width: 100vw;
}
</style>

View file

@ -0,0 +1,7 @@
<script lang="ts">
import { Button } from '$components/ui/button';
</script>
<h1>At the admin page yo!</h1>
<Button href="/admin/users">Search for users</Button>

View file

@ -0,0 +1,23 @@
import { redirect } from "sveltekit-flash-message/server";
import type { PageServerLoad } from "./$types";
import { notSignedInMessage } from "$lib/flashMessages";
import db from "$lib/drizzle";
export const load: PageServerLoad = async (event) => {
// TODO: Ensure admin user
if (!event.locals.user) {
redirect(302, '/login', notSignedInMessage, event);
}
const users = await db.query
.users
.findMany({
limit: 10,
offset: 0
});
return {
users
};
};

View file

@ -0,0 +1,10 @@
<script lang="ts">
import DataTable from './user-table.svelte';
export let data;
</script>
<h1>Users</h1>
<div class="container mx-auto py-10">
<DataTable users={data?.users ?? []}/>
</div>

View file

@ -0,0 +1,143 @@
import { and, eq, inArray, not } from 'drizzle-orm';
import { redirect } from 'sveltekit-flash-message/server';
import type { PageServerLoad } from './$types';
import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages';
import db from '$lib/drizzle';
import { roles, user_roles, users } from '../../../../../../schema';
export const load: PageServerLoad = async (event) => {
const { params } = event;
const { id } = params;
// TODO: Ensure admin user
if (!event.locals.user) {
redirect(302, '/login', notSignedInMessage, event);
}
const foundUser = await db.query.users.findFirst({
where: eq(users.cuid, id),
with: {
user_roles: {
with: {
role: {
columns: {
name: true,
cuid: true
}
}
}
}
}
});
const containsAdminRole = foundUser?.user_roles?.some(
(user_role) => user_role?.role?.name === 'admin'
);
if (!containsAdminRole) {
console.log('Not an admin');
redirect(302, '/login', notSignedInMessage, event);
}
const currentRoleIds = foundUser?.user_roles?.map((user_role) => user_role?.role.cuid) || [];
let availableRoles: { name: string; cuid: string }[] = [];
if (currentRoleIds?.length > 0) {
availableRoles = await db.query.roles.findMany({
where: not(inArray(roles.cuid, currentRoleIds)),
columns: {
name: true,
cuid: true
}
});
}
return {
user: foundUser,
availableRoles
};
};
export const actions = {
addRole: async (event) => {
const { request, locals } = event;
const { user } = locals;
if (!user) {
redirect(302, '/login', notSignedInMessage, event);
}
const userRoles = await db.query.user_roles.findMany({
where: eq(user_roles.user_id, user.id),
with: {
role: {
columns: {
name: true,
cuid: true
}
}
}
});
console.log('userRoles', userRoles);
const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin');
console.log('containsAdminRole', containsAdminRole);
if (!containsAdminRole) {
redirect(302, '/', forbiddenMessage, event);
}
const data = await request.formData();
const role = data.get('role');
const dbRole = await db.query.roles.findFirst({
where: eq(roles.cuid, role?.toString() ?? '')
});
console.log('dbRole', dbRole);
if (dbRole) {
await db.insert(user_roles).values({
user_id: user.id,
role_id: dbRole.id
});
redirect({ type: 'success', message: `Successfully added role ${dbRole.name}!` }, event);
} else {
redirect({ type: 'error', message: `Failed to add role ${dbRole.name}!` }, event);
}
},
removeRole: async (event) => {
const { request, locals } = event;
const { user } = locals;
if (!user) {
redirect(302, '/login', notSignedInMessage, event);
}
const userRoles = await db.query.user_roles.findMany({
where: eq(user_roles.user_id, user.id),
with: {
role: {
columns: {
name: true,
cuid: true
}
}
}
});
const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin');
if (!containsAdminRole) {
redirect(302, '/', forbiddenMessage, event);
}
const data = await request.formData();
const role = data.get('role');
const dbRole = await db.query.roles.findFirst({
where: eq(roles.cuid, role?.toString() ?? '')
});
console.log('dbRole', dbRole);
if (dbRole) {
await db
.delete(user_roles)
.where(and(eq(user_roles.user_id, user.id), eq(user_roles.role_id, dbRole.id)));
redirect({ type: 'success', message: `Successfully removed role ${dbRole.name}!` }, event);
} else {
redirect({ type: 'error', message: `Failed to remove role ${dbRole.name} !` }, event);
}
}
};

View file

@ -0,0 +1,44 @@
<script lang="ts">
import { enhance } from '$app/forms';
import capitalize from 'just-capitalize';
import { Button } from '$lib/components/ui/button';
// import AddRolesForm from './add-roles-form.svelte';
export let data;
const { user, availableRoles } = data;
const { user_roles }: { user_roles: { role: { name: string, cuid: string } }[] } = user;
</script>
<h1>User Details</h1>
<p>Username {user?.username}</p>
<p>Email Address: {user?.email || 'N/A'}</p>
<p>First Name: {user?.first_name || 'N/A'}</p>
<p>Last Name: {user?.last_name || 'N/A'}</p>
<h2>User Roles</h2>
{#each user_roles as user_role}
{#if user_role?.role?.name !== 'user'}
<form action="?/removeRole" method="POST" use:enhance data-sveltekit-replacestate>
<div class="flex flex-row space-x-3 place-items-center mt-2">
<input id="role" type="hidden" name="role" value={user_role?.role?.cuid} />
<Button type="submit">Remove</Button>
<p>{capitalize(user_role?.role?.name)}</p>
</div>
</form>
{:else}
<p>{capitalize(user_role?.role?.name)}</p>
{/if}
{/each}
<h2>Roles Available to Assign</h2>
<!--<AddRolesForm {availableRoles} />-->
{#each availableRoles as role}
<form action="?/addRole" method="POST" use:enhance data-sveltekit-replacestate>
<div class="flex flex-row space-x-3 place-items-center mt-2">
<input id="role" type="hidden" name="role" value={role?.cuid} />
<Button type="submit">Add</Button>
<p>{capitalize(role?.name)}</p>
</div>
</form>
{/each}

View file

@ -0,0 +1,82 @@
<script lang="ts">
import * as Form from '$lib/components/ui/form';
import { Input } from '$lib/components/ui/input';
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
import { addRoleSchema, type AddRoleSchema } from '$lib/validations/account';
import {
type SuperValidated,
type Infer,
superForm
} from 'sveltekit-superforms';
import { zodClient } from 'sveltekit-superforms/adapters';
export let availableRoles: { name: string; cuid: string }[] = [];
const data: SuperValidated<Infer<AddRoleSchema>> = availableRoles;
const form = superForm(data, {
validators: zodClient(addRoleSchema)
// onUpdated: ({ form: f }) => {
// if (f.valid) {
// toast.success("You submitted" + JSON.stringify(f.data, null, 2));
// } else {
// toast.error("Please fix the errors in the form.");
// }
// }
});
const { form: formData, enhance } = form;
function addRole(id: string) {
$formData.roles = [...$formData.roles, id];
}
function removeRole(id: string) {
$formData.roles = $formData.roles.filter((i) => i !== id);
}
</script>
<form action="/?/addMultipleRoles" method="POST" use:enhance>
<Form.Fieldset {form} name="roles" class="space-y-0">
<div class="mb-4">
<Form.Legend class="text-base">Roles</Form.Legend>
<Form.Description>
Select the roles you want to add to the user.
</Form.Description>
</div>
<div class="space-y-2">
{#each roles as item}
{@const checked = $formData.roles.includes(item.cuid)}
<div class="flex flex-row items-start space-x-3">
<Form.Control let:attrs>
<Checkbox
{...attrs}
{checked}
onCheckedChange={(v) => {
if (v) {
addItem(item.id);
} else {
removeItem(item.id);
}
}}
/>
<Form.Label class="text-sm font-normal">
{item.label}
</Form.Label>
<input
hidden
type="checkbox"
name={attrs.name}
value={item.id}
{checked}
/>
</Form.Control>
</div>
{/each}
<Form.FieldErrors />
</div>
</Form.Fieldset>
<Form.Button>Update display</Form.Button>
{#if browser}
<SuperDebug data={$formData} />
{/if}
</form>

View file

@ -0,0 +1,37 @@
<script lang="ts">
import Ellipsis from 'lucide-svelte/icons/ellipsis';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { Button } from '$lib/components/ui/button';
import { User } from 'lucide-svelte';
export let cuid: string;
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button
variant="ghost"
builders={[builder]}
size="icon"
class="relative h-8 w-8 p-0"
>
<span class="sr-only">Open menu</span>
<Ellipsis class="h-4 w-4" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Group>
<DropdownMenu.Label>Actions</DropdownMenu.Label>
<DropdownMenu.Item on:click={() => navigator.clipboard.writeText(cuid)}>
Copy User ID
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<a href={`/admin/users/${cuid}`}>
<DropdownMenu.Item>
<User class="mr-2 h-4 w-4" />
<span>View user</span>
</DropdownMenu.Item>
</a>
</DropdownMenu.Content>
</DropdownMenu.Root>

View file

@ -0,0 +1,8 @@
<script lang="ts">
import { Checkbox } from '$lib/components/ui/checkbox';
import type { Writable } from 'svelte/store';
export let checked: Writable<boolean>;
</script>
<Checkbox bind:checked={$checked} />

View file

@ -0,0 +1,232 @@
<script lang="ts">
import {
createTable,
Render,
Subscribe,
createRender,
} from "svelte-headless-table";
import {
addPagination,
addSortBy,
addTableFilter,
addHiddenColumns,
addSelectedRows,
} from "svelte-headless-table/plugins";
import { readable } from "svelte/store";
import ArrowUpDown from "lucide-svelte/icons/arrow-up-down";
import ChevronDown from "lucide-svelte/icons/chevron-down";
import * as Table from "$lib/components/ui/table";
import DataTableActions from "./user-table-actions.svelte";
import { Button } from "$lib/components/ui/button";
import { Input } from "$lib/components/ui/input";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import DataTableCheckbox from "./user-table-checkbox.svelte";
import type { Users } from '../../../../../schema';
export let users: Users[] = [];
const table = createTable(readable(users), {
page: addPagination(),
sort: addSortBy({ disableMultiSort: true }),
filter: addTableFilter({
fn: ({ filterValue, value }) => value.includes(filterValue),
}),
hide: addHiddenColumns(),
select: addSelectedRows(),
});
const columns = table.createColumns([
table.column({
accessor: "cuid",
header: (_, { pluginStates }) => {
const { allPageRowsSelected } = pluginStates.select;
return createRender(DataTableCheckbox, {
checked: allPageRowsSelected,
});
},
cell: ({ row }, { pluginStates }) => {
const { getRowState } = pluginStates.select;
const { isSelected } = getRowState(row);
return createRender(DataTableCheckbox, {
checked: isSelected,
});
},
plugins: {
filter: {
exclude: true,
},
},
}),
table.column({
accessor: "username",
header: "Username",
}),
table.column({
accessor: "email",
header: "Email",
cell: ({ value }) => {
return value ?? "N/A";
}
}),
table.column({
accessor: "first_name",
header: "First Name",
cell: ({ value }) => {
return value && value.length > 0 ? value : "N/A";
},
plugins: {
filter: {
exclude: true,
},
},
}),
table.column({
accessor: "last_name",
header: "Last Name",
cell: ({ value }) => {
return value && value.length > 0 ? value : "N/A";
},
plugins: {
filter: {
exclude: true,
},
},
}),
table.column({
accessor: ({ cuid }) => cuid,
header: "",
cell: ({ value }) => {
return createRender(DataTableActions, { cuid: value });
},
plugins: {
sort: {
disable: true,
},
},
}),
]);
const {
headerRows,
pageRows,
tableAttrs,
tableBodyAttrs,
pluginStates,
flatColumns,
rows,
} = table.createViewModel(columns);
const { pageIndex, hasNextPage, hasPreviousPage } = pluginStates.page;
const { filterValue } = pluginStates.filter;
const { hiddenColumnIds } = pluginStates.hide;
const { selectedDataIds } = pluginStates.select;
const ids = flatColumns.map((col) => col.id);
let hideForId = Object.fromEntries(ids.map((id) => [id, true]));
$: $hiddenColumnIds = Object.entries(hideForId)
.filter(([, hide]) => !hide)
.map(([id]) => id);
const columnsToHide: string[] = [];
</script>
<div>
<div class="flex items-center py-4">
<Input
class="max-w-sm"
placeholder="Filter emails..."
type="text"
bind:value={$filterValue}
/>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button variant="outline" class="ml-auto" builders={[builder]}>
Columns <ChevronDown class="ml-2 h-4 w-4" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{#each flatColumns as col}
{#if columnsToHide.includes(col.id)}
<DropdownMenu.CheckboxItem bind:checked={hideForId[col.id]}>
{col.header}
</DropdownMenu.CheckboxItem>
{/if}
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
<div class="rounded-md border">
<Table.Root {...$tableAttrs}>
<Table.Header>
{#each $headerRows as headerRow}
<Subscribe rowAttrs={headerRow.attrs()}>
<Table.Row>
{#each headerRow.cells as cell (cell.id)}
<Subscribe
attrs={cell.attrs()}
let:attrs
props={cell.props()}
let:props
>
<Table.Head {...attrs} class="[&:has([role=checkbox])]:pl-3">
{#if cell.id === "email"}
<Button variant="ghost" on:click={props.sort.toggle}>
<Render of={cell.render()} />
<ArrowUpDown class={"ml-2 h-4 w-4"} />
</Button>
{:else if cell.id === "username"}
<Button variant="ghost" on:click={props.sort.toggle}>
<Render of={cell.render()} />
<ArrowUpDown class={"ml-2 h-4 w-4"} />
</Button>
{:else}
<Render of={cell.render()} />
{/if}
</Table.Head>
</Subscribe>
{/each}
</Table.Row>
</Subscribe>
{/each}
</Table.Header>
<Table.Body {...$tableBodyAttrs}>
{#each $pageRows as row (row.id)}
<Subscribe rowAttrs={row.attrs()} let:rowAttrs>
<Table.Row
{...rowAttrs}
data-state={$selectedDataIds[row.id] && "selected"}
>
{#each row.cells as cell (cell.id)}
<Subscribe attrs={cell.attrs()} let:attrs>
<Table.Cell {...attrs} class="[&:has([role=checkbox])]:pl-3">
<Render of={cell.render()} />
</Table.Cell>
</Subscribe>
{/each}
</Table.Row>
</Subscribe>
{/each}
</Table.Body>
</Table.Root>
</div>
<div class="flex items-center justify-end space-x-4 py-4">
<div class="flex-1 text-sm text-muted-foreground">
{Object.keys($selectedDataIds).length} of{" "}
{$rows.length} row(s) selected.
</div>
<Button
variant="outline"
size="sm"
on:click={() => ($pageIndex = $pageIndex - 1)}
disabled={!$hasPreviousPage}>Previous</Button
>
<Button
variant="outline"
size="sm"
disabled={!$hasNextPage}
on:click={() => ($pageIndex = $pageIndex + 1)}>Next</Button
>
</div>
</div>

View file

@ -1,5 +1,7 @@
import { fail, redirect } from '@sveltejs/kit';
import prisma from '$lib/prisma';
import { fail } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
import db from '$lib/drizzle.js';
import { wishlists } from '../../../../schema.js';
export async function load({ locals }) {
if (!locals.user) {
@ -7,23 +9,12 @@ export async function load({ locals }) {
}
try {
let wishlists = await prisma.wishlist.findMany({
where: {
user_id: session.userId
}
const userWishlists = await db.query.wishlists.findMany({
where: eq(wishlists.user_id, locals.user.id)
});
if (wishlists.length === 0) {
const wishlist = await prisma.wishlist.create({
data: {
user_id: session.userId
}
});
wishlists.push(wishlist);
}
return {
wishlists
wishlsits: userWishlists
};
} catch (e) {
console.error(e);

View file

@ -5,20 +5,18 @@
const wishlists = data.wishlists || [];
</script>
<aside class="wishlists">
<h2>Your Wishlists</h2>
<div class="separator"></div>
{#each wishlists as wishlist}
<h2 class="wishlist"><a href={`/wishlist/${wishlist.id}`}>{wishlist.name}</a></h2>
{/each}
</aside>
<div class="content">
<Transition url={data.url} transition={{ type: 'page' }}>
<slot />
</Transition>
</div>
<aside class="wishlists">
<h2>Your Wishlists</h2>
<div class="separator"></div>
{#each wishlists as wishlist}
<h2 class="wishlist"><a href={`/wishlist/${wishlist.id}`}>{wishlist.name}</a></h2>
{/each}
</aside>
<div class="content">
<Transition url={data.url} transition={{ type: 'page' }}>
<slot />
</Transition>
</div>
<style lang="postcss">
.wishlists {

View file

@ -1,6 +1,10 @@
import { type Actions, fail, redirect } from "@sveltejs/kit";
import { eq } from "drizzle-orm";
import { zod } from "sveltekit-superforms/adapters";
import { superValidate } from 'sveltekit-superforms/server';
import prisma from '$lib/prisma';
import db from "$lib/drizzle.js";
import { modifyListGameSchema } from "$lib/validations/zod-schemas";
import { games, wishlist_items, wishlists } from "../../../../../schema.js";
export async function load({ params, locals }) {
const user = locals.user;
@ -9,28 +13,18 @@ export async function load({ params, locals }) {
}
try {
let wishlist = await prisma.wishlist.findUnique({
where: {
id: params.id,
AND: {
user_id: user.id
}
const wishlist = await db.select({
wishlistId: wishlists.id,
wishlistItems: {
id: wishlist_items.id,
gameId: wishlist_items.game_id,
gameName: games.name,
gameThumbUrl: games.thumb_url
},
include: {
items: {
include: {
game: {
select: {
id: true,
name: true,
thumb_url: true
}
}
}
}
}
});
}).from(wishlists)
.leftJoin(wishlist_items, eq(wishlists.id, wishlist_items.wishlist_id))
.leftJoin(games, eq(games.id, wishlist_items.game_id))
.where(eq(wishlists.id, params.id));
return {
wishlist
};
@ -43,35 +37,31 @@ export async function load({ params, locals }) {
export const actions: Actions = {
// Add game to a wishlist
add: async (event) => {
const { params, locals, request } = event;
const { params, locals } = event;
const form = await superValidate(event, zod(modifyListGameSchema));
if (!locals.user) {
throw fail(401);
}
if (!params?.id) {
throw fail(400, {
message: 'Invalid Request'
});
}
let game = await prisma.game.findUnique({
where: {
id: form.id
}
const game = await db.query.games.findFirst({
where: eq(games.id, form.id)
});
if (!game) {
// game = await prisma.game.create({
// data: {
// name: form.name
// }
// });
return fail(400, {
message: 'Game not found'
});
}
const wishlist = await prisma.wishlist.findUnique({
where: {
id: params.id
}
const wishlist = await db.query.wishlists.findFirst({
where: eq(wishlists.id, params.id)
});
if (wishlist?.user_id !== locals.user.id) {
@ -84,11 +74,9 @@ export const actions: Actions = {
redirect(302, '/404');
}
const wishlistItem = await prisma.wishlistItem.create({
data: {
game_id: game.id,
wishlist_id: wishlist.id
}
const wishlistItem = await db.insert(wishlist_items).values({
game_id: game.id,
wishlist_id: wishlist.id
});
if (!wishlistItem) {
@ -102,19 +90,19 @@ export const actions: Actions = {
};
},
// Create new wishlist
create: async ({ params, locals, request }) => {
create: async ({ locals }) => {
if (!locals.user) {
throw fail(401);
}
},
// Delete a wishlist
delete: async ({ params, locals, request }) => {
delete: async ({ locals}) => {
if (!locals.user) {
throw fail(401);
}
},
// Remove game from a wishlist
remove: async ({ params, locals, request }) => {
remove: async ({ locals }) => {
if (!locals.user) {
throw fail(401);
}

View file

@ -17,37 +17,37 @@ export const load: PageServerLoad = async (event) => {
const { user } = event.locals;
const dbUser = await db.query
.users
.findFirst({
where: eq(users.id, user.id)
});
const dbUser = await db.query.users.findFirst({
where: eq(users.id, user.id),
});
const profileForm = await superValidate(zod(profileSchema), {
defaults: {
firstName: dbUser?.first_name || '',
lastName: dbUser?.last_name || '',
username: dbUser?.username || '',
}
firstName: dbUser?.first_name ?? '',
lastName: dbUser?.last_name ?? '',
username: dbUser?.username ?? '',
},
});
const emailForm = await superValidate(zod(changeEmailSchema), {
defaults: {
email: dbUser?.email || '',
}
email: dbUser?.email ?? '',
},
});
return {
profileForm,
emailForm,
hasSetupTwoFactor: !!dbUser?.two_factor_enabled,
};
};
const changeEmailIfNotEmpty = z.object({
email: z.string()
email: z
.string()
.trim()
.max(64, { message: 'Email must be less than 64 characters' })
.email({ message: 'Please enter a valid email' })
});
.email({ message: 'Please enter a valid email' }),
});
export const actions: Actions = {
profileUpdate: async (event) => {
@ -55,7 +55,7 @@ export const actions: Actions = {
if (!form.valid) {
return fail(400, {
form
form,
});
}
if (!event.locals.user) {
@ -66,14 +66,10 @@ export const actions: Actions = {
console.log('updating profile');
const user = event.locals.user;
const newUsername = form.data.username;
const existingUser = await db.query
.users
.findFirst({
where: eq(users.username, newUsername)
}
);
const existingUser = await db.query.users.findFirst({
where: eq(users.username, newUsername),
});
if (existingUser && existingUser.id !== user.id) {
return setError(form, 'username', 'That username is already taken');
@ -84,7 +80,7 @@ export const actions: Actions = {
.set({
first_name: form.data.firstName,
last_name: form.data.lastName,
username: form.data.username
username: form.data.username,
})
.where(eq(users.id, user.id));
} catch (e) {
@ -102,29 +98,30 @@ export const actions: Actions = {
const form = await superValidate(event, zod(changeEmailSchema));
const newEmail = form.data?.email;
if (!form.valid || !newEmail || (newEmail !== '' && changeEmailIfNotEmpty.safeParse(form.data).success === false)) {
if (
!form.valid ||
!newEmail ||
(newEmail !== '' && !changeEmailIfNotEmpty.safeParse(form.data).success)
) {
return fail(400, {
form
form,
});
}
if (!event.locals.user) {
redirect(302, '/login', notSignedInMessage, event);
redirect(302, '/login', notSignedInMessage, event);
}
const user = event.locals.user;
const existingUser = await db.query.users.findFirst({
where: eq(users.email, newEmail)
where: eq(users.email, newEmail),
});
if (existingUser && existingUser.id !== user.id) {
return setError(form, 'email', 'That email is already taken');
}
await db
.update(users)
.set({ email: form.data.email })
.where(eq(users.id, user.id));
await db.update(users).set({ email: form.data.email }).where(eq(users.id, user.id));
if (user.email !== form.data.email) {
// Send email to confirm new email?
@ -143,5 +140,5 @@ export const actions: Actions = {
}
return message(form, { type: 'success', message: 'Email updated successfully!' });
}
},
};

Some files were not shown because too many files have changed in this diff Show more