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, "useTabs": true,
"tabWidth": 2, "tabWidth": 2,
"singleQuote": true, "singleQuote": true,
"trailingComma": "none", "trailingComma": "all",
"bracketSpacing": true,
"printWidth": 100, "printWidth": 100,
"plugins": ["prettier-plugin-svelte"], "plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."], "pluginSearchDirs": ["."],

View file

@ -11,10 +11,10 @@ export default defineConfig({
user: process.env.DATABASE_USER, user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD, password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE || 'boredgame', database: process.env.DATABASE || 'boredgame',
ssl: true ssl: process.env.DATABASE_HOST !== 'localhost'
}, },
// Print all statements // Print all statements
verbose: true, verbose: true,
// Always as for confirmation // Always as for confirmation
strict: true 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, "idx": 0,
"version": "5", "version": "5",
"when": 1707437865821, "when": 1710268038944,
"tag": "0000_oval_wolverine", "tag": "0000_tricky_hitman",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 1, "idx": 1,
"version": "5", "version": "5",
"when": 1707438055782, "when": 1710268191378,
"tag": "0001_giant_tomorrow_man", "tag": "0001_numerous_dragon_man",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 2, "idx": 2,
"version": "5", "version": "5",
"when": 1707524139123, "when": 1710268300740,
"tag": "0002_sour_silverclaw", "tag": "0002_thick_lyja",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 3, "idx": 3,
"version": "5", "version": "5",
"when": 1707526808124, "when": 1710268371021,
"tag": "0003_thick_tinkerer", "tag": "0003_mushy_madame_masque",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 4, "idx": 4,
"version": "5", "version": "5",
"when": 1707932397672, "when": 1710277583673,
"tag": "0004_fancy_umar", "tag": "0004_glossy_enchantress",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 5, "idx": 5,
"version": "5", "version": "5",
"when": 1707932466413, "when": 1710366724519,
"tag": "0005_uneven_lifeguard", "tag": "0005_light_captain_marvel",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 6, "idx": 6,
"version": "5", "version": "5",
"when": 1707932522909, "when": 1710905422967,
"tag": "0006_light_corsair", "tag": "0006_wild_stone_men",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 7, "idx": 7,
"version": "5", "version": "5",
"when": 1707951501716, "when": 1710905572670,
"tag": "0007_same_valeria_richards", "tag": "0007_large_miss_america",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 8, "idx": 8,
"version": "5", "version": "5",
"when": 1708105454143, "when": 1711757183163,
"tag": "0008_complete_manta", "tag": "0008_amusing_franklin_richards",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 9, "idx": 9,
"version": "5", "version": "5",
"when": 1708105890146, "when": 1711868447607,
"tag": "0009_equal_christian_walker", "tag": "0009_gray_carlie_cooper",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 10, "idx": 10,
"version": "5", "version": "5",
"when": 1708243232524, "when": 1712271520175,
"tag": "0010_flat_mister_sinister", "tag": "0010_wakeful_metal_master",
"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",
"breakpoints": true "breakpoints": true
} }
] ]

View file

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

View file

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

View file

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

View file

@ -1,25 +1,28 @@
<script> <script>
import { PUBLIC_SITE_URL } from "$env/static/public"; import { PUBLIC_SITE_URL } from '$env/static/public';
</script> </script>
<footer> <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> </footer>
<style lang="postcss"> <style lang="postcss">
footer { footer {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 40px; gap: 0.25rem;
} }
footer a { footer a {
font-weight: bold; font-weight: bold;
} }
@media (min-width: 480px) { @media (width < 640px) {
footer { footer {
padding: 40px 0; padding: 40px 0;
} }

View file

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

View file

@ -6,10 +6,17 @@
import * as Avatar from "$components/ui/avatar"; import * as Avatar from "$components/ui/avatar";
import { invalidateAll } from '$app/navigation'; import { invalidateAll } from '$app/navigation';
import Logo from '$components/logo.svelte'; 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> </script>
<header> <header>
@ -18,14 +25,12 @@
<div class="logo-image"> <div class="logo-image">
<Logo /> <Logo />
</div> </div>
Bored Game <span class="logo-text">Bored Game</span>
</a> </a>
</div> </div>
<!-- <TextSearch /> --> <!-- <TextSearch /> -->
<nav> <nav>
{#if user} {#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.Root>
<DropdownMenu.Trigger> <DropdownMenu.Trigger>
<Avatar.Root asChild> <Avatar.Root asChild>
@ -88,8 +93,7 @@
</DropdownMenu.Group> </DropdownMenu.Group>
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
{/if} {:else}
{#if !user}
<a href="/login"> <a href="/login">
<span class="flex-auto">Login</span></a <span class="flex-auto">Login</span></a
> >
@ -131,6 +135,17 @@
.logo-image { .logo-image {
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
@media (width < 640px) {
width: 3rem;
height: 3rem;
}
}
.logo-text {
@media (width < 640px) {
display: none;
}
} }
nav { nav {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui"; import { Pagination as PaginationPrimitive } from "bits-ui";
import { cn } from "$lib/utils"; import { cn } from "$lib/utils.js";
type $$Props = PaginationPrimitive.Props; type $$Props = PaginationPrimitive.Props;
type $$Events = PaginationPrimitive.Events; 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_PASSWORD,
DATABASE_HOST, DATABASE_HOST,
DATABASE_DB, DATABASE_DB,
DATABASE_PORT, DATABASE_PORT
} from '$env/static/private'; } from '$env/static/private';
import * as schema from '../schema'; import * as schema from '../schema';
@ -16,7 +16,7 @@ const pool = new pg.Pool({
host: DATABASE_HOST, host: DATABASE_HOST,
port: Number(DATABASE_PORT).valueOf(), port: Number(DATABASE_PORT).valueOf(),
database: DATABASE_DB, database: DATABASE_DB,
ssl: true, ssl: DATABASE_HOST !== 'localhost'
}); });
// user: DATABASE_USER, // 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 db from "$lib/drizzle";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { password_reset_tokens } from "../../schema";
import { generateId } from "lucia"; import { generateId } from "lucia";
import { TimeSpan, createDate } from "oslo"; import { TimeSpan, createDate } from "oslo";
import { password_reset_tokens } from "../../schema";
export async function createPasswordResetToken(userId: string): Promise<string> { export async function createPasswordResetToken(userId: string): Promise<string> {
// optionally invalidate all existing tokens // optionally invalidate all existing tokens

View file

@ -1,12 +1,20 @@
// lib/server/lucia.ts // lib/server/lucia.ts
import { Lucia, TimeSpan } from 'lucia'; import { Lucia, TimeSpan } from 'lucia';
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle"; import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle';
import { dev } from '$app/environment';
import db from '$lib/drizzle'; import db from '$lib/drizzle';
import { sessions, users } from '../../schema'; import { sessions, users } from '../../schema';
const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users); 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, { export const lucia = new Lucia(adapter, {
getSessionAttributes: (attributes) => { getSessionAttributes: (attributes) => {
return { return {
@ -23,23 +31,24 @@ export const lucia = new Lucia(adapter, {
theme: attributes.theme theme: attributes.theme
}; };
}, },
sessionExpiresIn: new TimeSpan(30, "d"), // 30 days sessionExpiresIn: new TimeSpan(30, 'd'), // 30 days
sessionCookie: { sessionCookie: {
name: 'session', name: 'session',
expires: false, // session cookies have very long lifespan (2 years) expires: false, // session cookies have very long lifespan (2 years)
attributes: { attributes: {
// set to `true` when using HTTPS // set to `true` when using HTTPS
secure: !dev, secure: process.env.NODE_ENV === 'production',
sameSite: 'strict', sameSite: 'strict',
domain: dev ? 'localhost' : 'boredgame.vercel.app', domain
} }
}, }
}); });
declare module "lucia" { declare module 'lucia' {
interface Register { interface Register {
Lucia: typeof lucia; Lucia: typeof lucia;
DatabaseUserAttributes: DatabaseUserAttributes; DatabaseUserAttributes: DatabaseUserAttributes;
DatabaseSessionAttributes: DatabaseSessionAttributes;
} }
interface DatabaseSessionAttributes { interface DatabaseSessionAttributes {
ip_country: string; ip_country: string;
@ -51,5 +60,6 @@ declare module "lucia" {
firstName: string; firstName: string;
lastName: string; lastName: string;
theme: 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 kebabCase from 'just-kebab-case';
import { PUBLIC_SITE_URL } from '$env/static/public';
import db from '$lib/drizzle'; import db from '$lib/drizzle';
import { externalIds, type Mechanics, type Categories, categories, categoriesToExternalIds } from '../../../schema'; 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) { export async function createCategory(locals: App.Locals, category: Categories, externalId: string) {
if (!category || !externalId || externalId === '') { if (!category || !externalId || externalId === '') {
@ -44,7 +44,7 @@ export async function createCategory(locals: App.Locals, category: Categories, e
.insert(categories) .insert(categories)
.values({ .values({
name: category.name, name: category.name,
slug: kebabCase(category.name || category.slug || '') slug: kebabCase(category.name ?? category.slug ?? '')
}) })
.returning(); .returning();
const dbExternalIds = await transaction const dbExternalIds = await transaction

View file

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

View file

@ -4,7 +4,7 @@ import { userSchema } from './zod-schemas';
export const profileSchema = userSchema.pick({ export const profileSchema = userSchema.pick({
firstName: true, firstName: true,
lastName: true, lastName: true,
username: true username: true,
}); });
export const changeEmailSchema = userSchema.pick({ export const changeEmailSchema = userSchema.pick({
@ -15,7 +15,7 @@ export const changeUserPasswordSchema = z
.object({ .object({
current_password: z.string({ required_error: 'Current Password is required' }), current_password: z.string({ required_error: 'Current Password is required' }),
password: z.string({ required_error: 'Password is required' }).trim(), 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) => { .superRefine(({ confirm_password, password }, ctx) => {
refinePasswords(confirm_password, password, ctx); refinePasswords(confirm_password, password, ctx);
@ -23,6 +23,19 @@ export const changeUserPasswordSchema = z
export type ChangeUserPasswordSchema = typeof changeUserPasswordSchema; 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 export const updateUserPasswordSchema = userSchema
.pick({ password: true, confirm_password: true }) .pick({ password: true, confirm_password: true })
.superRefine(({ confirm_password, password }, ctx) => { .superRefine(({ confirm_password, password }, ctx) => {
@ -32,7 +45,7 @@ export const updateUserPasswordSchema = userSchema
export const refinePasswords = async function ( export const refinePasswords = async function (
confirm_password: string, confirm_password: string,
password: string, password: string,
ctx: z.RefinementCtx ctx: z.RefinementCtx,
) { ) {
comparePasswords(confirm_password, password, ctx); comparePasswords(confirm_password, password, ctx);
checkPasswordStrength(password, ctx); checkPasswordStrength(password, ctx);
@ -41,13 +54,13 @@ export const refinePasswords = async function (
const comparePasswords = async function ( const comparePasswords = async function (
confirm_password: string, confirm_password: string,
password: string, password: string,
ctx: z.RefinementCtx ctx: z.RefinementCtx,
) { ) {
if (confirm_password !== password) { if (confirm_password !== password) {
ctx.addIssue({ ctx.addIssue({
code: 'custom', code: 'custom',
message: 'Password and Confirm Password must match', 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({ ctx.addIssue({
code: 'custom', code: 'custom',
message: errorMessage, 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 { refinePasswords } from './account';
import { userSchema } from "./zod-schemas"; import { userSchema } from './zod-schemas';
import { z } from 'zod';
export const signUpSchema = userSchema export const signUpSchema = userSchema
.pick({ .pick({
@ -9,13 +10,18 @@ export const signUpSchema = userSchema
username: true, username: true,
password: true, password: true,
confirm_password: true, confirm_password: true,
terms: true terms: true,
}) })
.superRefine(({ confirm_password, password }, ctx) => { .superRefine(({ confirm_password, password }, ctx) => {
refinePasswords(confirm_password, password, ctx); refinePasswords(confirm_password, password, ctx);
}); });
export const signInSchema = userSchema.pick({ export const signInSchema = z.object({
username: true, username: z
password: true .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({ const connection = postgres({
host: process.env.DATABASE_HOST || 'localhost', host: process.env.DATABASE_HOST || 'localhost',
port: 5432, port: process.env.DATABASE_PORT,
user: process.env.DATABASE_USER || 'root', user: process.env.DATABASE_USER || 'root',
password: process.env.DATABASE_PASSWORD || '', password: process.env.DATABASE_PASSWORD || '',
database: process.env.DATABASE_DB || 'boredgame', database: process.env.DATABASE_DB || 'boredgame',
ssl: 'require', ssl: process.env.NODE_ENV === 'development' ? false : 'require',
max: 1 max: 1
}); });
const db = drizzle(connection); const db = drizzle(connection);
@ -21,5 +21,5 @@ try {
console.error(e); console.error(e);
} }
await connection.end(); // await connection.end();
process.exit(); process.exit();

View file

@ -1,12 +1,32 @@
import { redirect } from 'sveltekit-flash-message/server' import { redirect, loadFlash } from 'sveltekit-flash-message/server';
import type { PageServerLoad } from './$types'; import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages';
import { 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; const { locals } = event;
if (!locals?.user?.role?.includes('admin')) { if (!locals?.user) {
redirect(302, '/login', notSignedInMessage, event); 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 { fail } from '@sveltejs/kit';
import prisma from '$lib/prisma'; import { eq } from 'drizzle-orm';
import db from '$lib/drizzle.js';
import { wishlists } from '../../../../schema.js';
export async function load({ locals }) { export async function load({ locals }) {
if (!locals.user) { if (!locals.user) {
@ -7,23 +9,12 @@ export async function load({ locals }) {
} }
try { try {
let wishlists = await prisma.wishlist.findMany({ const userWishlists = await db.query.wishlists.findMany({
where: { where: eq(wishlists.user_id, locals.user.id)
user_id: session.userId
}
}); });
if (wishlists.length === 0) {
const wishlist = await prisma.wishlist.create({
data: {
user_id: session.userId
}
});
wishlists.push(wishlist);
}
return { return {
wishlists wishlsits: userWishlists
}; };
} catch (e) { } catch (e) {
console.error(e); console.error(e);

View file

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

View file

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

View file

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