mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
commit
196dfd48f6
137 changed files with 25129 additions and 2501 deletions
20
drizzle.config.ts
Normal file
20
drizzle.config.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import 'dotenv/config';
|
||||||
|
import { defineConfig } from 'drizzle-kit';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: './src/schema.ts',
|
||||||
|
out: './drizzle',
|
||||||
|
driver: 'pg',
|
||||||
|
dbCredentials: {
|
||||||
|
host: process.env.DATABASE_HOST || 'localhost',
|
||||||
|
port: Number(process.env.DATABASE_PORT) || 5432,
|
||||||
|
user: process.env.DATABASE_USER,
|
||||||
|
password: process.env.DATABASE_PASSWORD,
|
||||||
|
database: process.env.DATABASE || 'boredgame',
|
||||||
|
ssl: true
|
||||||
|
},
|
||||||
|
// Print all statements
|
||||||
|
verbose: true,
|
||||||
|
// Always as for confirmation
|
||||||
|
strict: true
|
||||||
|
});
|
||||||
233
drizzle/0000_oval_wolverine.sql
Normal file
233
drizzle/0000_oval_wolverine.sql
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
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 $$;
|
||||||
51
drizzle/0001_giant_tomorrow_man.sql
Normal file
51
drizzle/0001_giant_tomorrow_man.sql
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
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());
|
||||||
27
drizzle/0002_sour_silverclaw.sql
Normal file
27
drizzle/0002_sour_silverclaw.sql
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
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";
|
||||||
1
drizzle/0003_thick_tinkerer.sql
Normal file
1
drizzle/0003_thick_tinkerer.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
CREATE INDEX IF NOT EXISTS "text_searchable_idx" ON "games" ("text_searchable_index");
|
||||||
30
drizzle/0004_fancy_umar.sql
Normal file
30
drizzle/0004_fancy_umar.sql
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
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 $$;
|
||||||
16
drizzle/0005_uneven_lifeguard.sql
Normal file
16
drizzle/0005_uneven_lifeguard.sql
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
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 $$;
|
||||||
2
drizzle/0006_light_corsair.sql
Normal file
2
drizzle/0006_light_corsair.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE "external_ids" ALTER COLUMN "type" SET NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "external_ids" ALTER COLUMN "external_id" SET NOT NULL;
|
||||||
71
drizzle/0007_same_valeria_richards.sql
Normal file
71
drizzle/0007_same_valeria_richards.sql
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
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 $$;
|
||||||
7
drizzle/0008_complete_manta.sql
Normal file
7
drizzle/0008_complete_manta.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
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;
|
||||||
7
drizzle/0009_equal_christian_walker.sql
Normal file
7
drizzle/0009_equal_christian_walker.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
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;
|
||||||
1
drizzle/0010_flat_mister_sinister.sql
Normal file
1
drizzle/0010_flat_mister_sinister.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE "collection_items" ADD COLUMN "times_played" integer DEFAULT 0;
|
||||||
97
drizzle/0011_gigantic_mister_sinister.sql
Normal file
97
drizzle/0011_gigantic_mister_sinister.sql
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
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 $$;
|
||||||
2
drizzle/0012_dizzy_lethal_legion.sql
Normal file
2
drizzle/0012_dizzy_lethal_legion.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
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;
|
||||||
3
drizzle/0013_clever_monster_badoon.sql
Normal file
3
drizzle/0013_clever_monster_badoon.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
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";
|
||||||
1
drizzle/0014_organic_morlocks.sql
Normal file
1
drizzle/0014_organic_morlocks.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE "expansions_to_external_ids";
|
||||||
12
drizzle/0015_awesome_gabe_jones.sql
Normal file
12
drizzle/0015_awesome_gabe_jones.sql
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
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 $$;
|
||||||
1049
drizzle/meta/0000_snapshot.json
Normal file
1049
drizzle/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1049
drizzle/meta/0001_snapshot.json
Normal file
1049
drizzle/meta/0001_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1055
drizzle/meta/0002_snapshot.json
Normal file
1055
drizzle/meta/0002_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1063
drizzle/meta/0003_snapshot.json
Normal file
1063
drizzle/meta/0003_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1138
drizzle/meta/0004_snapshot.json
Normal file
1138
drizzle/meta/0004_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1138
drizzle/meta/0005_snapshot.json
Normal file
1138
drizzle/meta/0005_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1138
drizzle/meta/0006_snapshot.json
Normal file
1138
drizzle/meta/0006_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1194
drizzle/meta/0007_snapshot.json
Normal file
1194
drizzle/meta/0007_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1194
drizzle/meta/0008_snapshot.json
Normal file
1194
drizzle/meta/0008_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1194
drizzle/meta/0009_snapshot.json
Normal file
1194
drizzle/meta/0009_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1201
drizzle/meta/0010_snapshot.json
Normal file
1201
drizzle/meta/0010_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1346
drizzle/meta/0011_snapshot.json
Normal file
1346
drizzle/meta/0011_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1346
drizzle/meta/0012_snapshot.json
Normal file
1346
drizzle/meta/0012_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1328
drizzle/meta/0013_snapshot.json
Normal file
1328
drizzle/meta/0013_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1271
drizzle/meta/0014_snapshot.json
Normal file
1271
drizzle/meta/0014_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1320
drizzle/meta/0015_snapshot.json
Normal file
1320
drizzle/meta/0015_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
118
drizzle/meta/_journal.json
Normal file
118
drizzle/meta/_journal.json
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1707437865821,
|
||||||
|
"tag": "0000_oval_wolverine",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1707438055782,
|
||||||
|
"tag": "0001_giant_tomorrow_man",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1707524139123,
|
||||||
|
"tag": "0002_sour_silverclaw",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1707526808124,
|
||||||
|
"tag": "0003_thick_tinkerer",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 4,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1707932397672,
|
||||||
|
"tag": "0004_fancy_umar",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 5,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1707932466413,
|
||||||
|
"tag": "0005_uneven_lifeguard",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 6,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1707932522909,
|
||||||
|
"tag": "0006_light_corsair",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 7,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1707951501716,
|
||||||
|
"tag": "0007_same_valeria_richards",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 8,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1708105454143,
|
||||||
|
"tag": "0008_complete_manta",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 9,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1708105890146,
|
||||||
|
"tag": "0009_equal_christian_walker",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 10,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1708243232524,
|
||||||
|
"tag": "0010_flat_mister_sinister",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 11,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1708330668971,
|
||||||
|
"tag": "0011_gigantic_mister_sinister",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 12,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1708330799655,
|
||||||
|
"tag": "0012_dizzy_lethal_legion",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 13,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1708453431550,
|
||||||
|
"tag": "0013_clever_monster_badoon",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 14,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1708479971410,
|
||||||
|
"tag": "0014_organic_morlocks",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 15,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1709344835732,
|
||||||
|
"tag": "0015_awesome_gabe_jones",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
74
package.json
74
package.json
|
|
@ -16,57 +16,61 @@
|
||||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||||
"format": "prettier --plugin-search-dir . --write .",
|
"format": "prettier --plugin-search-dir . --write .",
|
||||||
"site:update": "pnpm update -i -L",
|
"site:update": "pnpm update -i -L",
|
||||||
"db:studio": "prisma studio",
|
"generate": "drizzle-kit generate:pg",
|
||||||
"db:push": "prisma db push",
|
"migrate": "tsx ./src/migrate.ts",
|
||||||
"db:generate": "prisma generate",
|
"seed": "tsx ./src/seed.ts",
|
||||||
"db:seed": "prisma db seed",
|
"push": "drizzle-kit push:pg"
|
||||||
"i-changed-the-schema": "pnpm run db:push && pnpm run db:generate"
|
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "node --loader ts-node/esm prisma/seed.ts"
|
"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.73.0",
|
"@melt-ui/svelte": "^0.75.2",
|
||||||
"@playwright/test": "^1.41.2",
|
"@playwright/test": "^1.42.0",
|
||||||
"@resvg/resvg-js": "^2.6.0",
|
"@resvg/resvg-js": "^2.6.0",
|
||||||
"@sveltejs/adapter-auto": "^3.1.1",
|
"@sveltejs/adapter-auto": "^3.1.1",
|
||||||
"@sveltejs/enhanced-img": "^0.1.8",
|
"@sveltejs/enhanced-img": "^0.1.8",
|
||||||
"@sveltejs/kit": "^2.5.0",
|
"@sveltejs/kit": "^2.5.2",
|
||||||
"@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.17",
|
"@types/node": "^20.11.24",
|
||||||
|
"@types/pg": "^8.11.2",
|
||||||
"@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.17",
|
"autoprefixer": "^10.4.18",
|
||||||
"eslint": "^8.56.0",
|
"dotenv": "^16.4.5",
|
||||||
|
"drizzle-kit": "^0.20.14",
|
||||||
|
"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.35.1",
|
||||||
"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.35",
|
||||||
"postcss-import": "^16.0.0",
|
"postcss-import": "^16.0.1",
|
||||||
"postcss-load-config": "^5.0.3",
|
"postcss-load-config": "^5.0.3",
|
||||||
"postcss-preset-env": "^9.3.0",
|
"postcss-preset-env": "^9.4.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prettier-plugin-svelte": "^3.1.2",
|
"prettier-plugin-svelte": "^3.2.2",
|
||||||
"prisma": "^5.9.1",
|
"prisma": "^5.9.1",
|
||||||
"sass": "^1.70.0",
|
"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.10",
|
"svelte": "^4.2.12",
|
||||||
"svelte-check": "^3.6.4",
|
"svelte-check": "^3.6.6",
|
||||||
"svelte-meta-tags": "^3.1.0",
|
"svelte-meta-tags": "^3.1.1",
|
||||||
"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.1",
|
"sveltekit-flash-message": "^2.4.2",
|
||||||
"sveltekit-superforms": "^1.13.4",
|
"sveltekit-rate-limiter": "^0.4.3",
|
||||||
|
"sveltekit-superforms": "^2.7.0",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tslib": "^2.6.1",
|
"tslib": "^2.6.1",
|
||||||
|
"tsx": "^4.7.1",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.1.1",
|
"vite": "^5.1.5",
|
||||||
"vitest": "^1.2.2",
|
"vitest": "^1.3.1",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
@ -75,37 +79,45 @@
|
||||||
"pnpm": ">=8"
|
"pnpm": ">=8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/fira-mono": "^5.0.8",
|
"@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-prisma": "4.0.0",
|
"@lucia-auth/adapter-prisma": "4.0.0",
|
||||||
"@lukeed/uuid": "^2.0.1",
|
"@lukeed/uuid": "^2.0.1",
|
||||||
|
"@neondatabase/serverless": "^0.9.0",
|
||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
|
"@planetscale/database": "^1.16.0",
|
||||||
"@prisma/client": "^5.9.1",
|
"@prisma/client": "^5.9.1",
|
||||||
"@sentry/sveltekit": "^7.88.0",
|
"@sentry/sveltekit": "^7.100.1",
|
||||||
"@sveltejs/adapter-vercel": "^5.1.0",
|
"@sveltejs/adapter-vercel": "^5.1.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.17.0",
|
"bits-ui": "^0.19.3",
|
||||||
"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",
|
||||||
"feather-icons": "^4.29.1",
|
"feather-icons": "^4.29.1",
|
||||||
"formsnap": "^0.4.3",
|
"formsnap": "^0.5.1",
|
||||||
"html-entities": "^2.4.0",
|
"html-entities": "^2.5.2",
|
||||||
"iconify-icon": "^2.0.0",
|
"iconify-icon": "^2.0.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.0.1",
|
||||||
"lucide-svelte": "^0.323.0",
|
"lucide-svelte": "^0.344.0",
|
||||||
"open-props": "^1.6.18",
|
"mysql2": "^3.9.2",
|
||||||
"oslo": "^1.1.0",
|
"nanoid": "^5.0.6",
|
||||||
|
"open-props": "^1.6.20",
|
||||||
|
"oslo": "^1.1.3",
|
||||||
|
"pg": "^8.11.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.1",
|
||||||
"tailwind-variants": "^0.1.20",
|
"tailwind-variants": "^0.2.0",
|
||||||
"tailwindcss-animate": "^1.0.6",
|
"tailwindcss-animate": "^1.0.6",
|
||||||
"zod-to-json-schema": "^3.22.4"
|
"zod-to-json-schema": "^3.22.4"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3278
pnpm-lock.yaml
3278
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -158,7 +158,6 @@ model Game {
|
||||||
image_url String?
|
image_url String?
|
||||||
thumb_url String?
|
thumb_url String?
|
||||||
url String?
|
url String?
|
||||||
rules_url String?
|
|
||||||
categories Category[]
|
categories Category[]
|
||||||
mechanics Mechanic[]
|
mechanics Mechanic[]
|
||||||
designers Designer[]
|
designers Designer[]
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<meta name="robots" content="noindex, nofollow" />
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="description" content="Bored? Find a game! Bored Game!" />
|
<meta name="description" content="Bored? Find a game! Bored Game!" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon-bored.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon-bored-game.svg" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<script>
|
<script>
|
||||||
// const htmlElement = document.documentElement;
|
// const htmlElement = document.documentElement;
|
||||||
|
|
|
||||||
1
src/lib/assets/bored-game.svg
Normal file
1
src/lib/assets/bored-game.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 35 KiB |
|
|
@ -1,12 +1,9 @@
|
||||||
|
<script>
|
||||||
|
import { PUBLIC_SITE_URL } from "$env/static/public";
|
||||||
|
</script>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>Built by <a target="__blank" href="https://bradleyshellnut.com">Bradley Shellnut</a></p>
|
<p>Bored Game © {new Date().getFullYear()} | Built by <a target="__blank" href="https://bradleyshellnut.com">Bradley Shellnut</a> | {PUBLIC_SITE_URL}</p>
|
||||||
<p>
|
|
||||||
<a
|
|
||||||
target="__blank"
|
|
||||||
href="https://www.flaticon.com/free-icons/board-game"
|
|
||||||
title="board game icons">Board game icons created by Freepik - Flaticon</a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,24 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { applyAction, enhance } from '$app/forms';
|
import { applyAction, enhance } from '$app/forms';
|
||||||
|
import toast from 'svelte-french-toast';
|
||||||
import { ListChecks, ListTodo, LogOut, User } from 'lucide-svelte';
|
import { ListChecks, ListTodo, LogOut, User } from 'lucide-svelte';
|
||||||
import * as DropdownMenu from "$components/ui/dropdown-menu";
|
import * as DropdownMenu from "$components/ui/dropdown-menu";
|
||||||
import * as Avatar from "$components/ui/avatar";
|
import * as Avatar from "$components/ui/avatar";
|
||||||
import Logo from '$components/logo.svelte';
|
|
||||||
import { invalidateAll } from '$app/navigation';
|
import { invalidateAll } from '$app/navigation';
|
||||||
import toast from 'svelte-french-toast';
|
import Logo from '$components/logo.svelte';
|
||||||
|
|
||||||
export let user;
|
export let user: User | null;
|
||||||
|
|
||||||
let avatar = user?.username.slice(0, 1).toUpperCase() || '?';
|
let avatar = user?.username.slice(0, 1).toUpperCase() || '?';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div class="corner">
|
<div class="corner">
|
||||||
<a href="/" class="logo" title="Home">
|
<a href="/" title="Home">
|
||||||
<div class="logo-image">
|
<div class="logo-image">
|
||||||
<Logo />
|
<Logo />
|
||||||
</div>
|
</div>
|
||||||
|
Bored Game
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- <TextSearch /> -->
|
<!-- <TextSearch /> -->
|
||||||
|
|
@ -113,23 +114,23 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.corner {
|
.corner {
|
||||||
width: 3em;
|
|
||||||
height: 3em;
|
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.corner a {
|
.corner a {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
place-items: center;
|
||||||
justify-content: center;
|
gap: 0.5rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-image {
|
.logo-image {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
object-fit: contain;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
|
|
@ -154,8 +155,4 @@
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
|
||||||
@apply m-[5px] h-[1px] bg-black;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let kind = 'primary';
|
export let kind = 'primary';
|
||||||
export let size;
|
|
||||||
export let icon = false;
|
export let icon = false;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -27,18 +26,4 @@
|
||||||
min-width: 23.5rem;
|
min-width: 23.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.danger {
|
|
||||||
background-color: var(--warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
.danger:hover {
|
|
||||||
background-color: var(--warning-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-icon {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, auto);
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
<script lang="ts">
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-go-game" viewBox="0 0 24 24"
|
||||||
import logo from '$lib/assets/bored-game.png';
|
stroke-width="1" stroke="var(--fg)" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
</script>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M6 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||||
<img src={logo} alt="Bored Game Home" />
|
<path d="M12 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||||
|
<path d="M6 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||||
|
<path d="M18 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||||
|
<path d="M3 12h7m4 0h7" />
|
||||||
|
<path d="M3 6h1m4 0h13" />
|
||||||
|
<path d="M3 18h1m4 0h8m4 0h1" />
|
||||||
|
<path d="M6 3v1m0 4v8m0 4v1" />
|
||||||
|
<path d="M12 3v7m0 4v7" />
|
||||||
|
<path d="M18 3v13m0 4v1" />
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 118 B After Width: | Height: | Size: 671 B |
|
|
@ -1,38 +1,54 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SuperValidated } from 'sveltekit-superforms';
|
import { superForm, type Infer, type SuperValidated } from 'sveltekit-superforms';
|
||||||
import { search_schema, type SearchSchema } from '$lib/zodValidation';
|
import { search_schema, type SearchSchema } from '$lib/zodValidation';
|
||||||
import * as Form from "$lib/components/ui/form";
|
import * as Form from "$lib/components/ui/form";
|
||||||
|
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||||
|
import Input from '$components/ui/input/input.svelte';
|
||||||
|
import Checkbox from '$components/ui/checkbox/checkbox.svelte';
|
||||||
|
|
||||||
export let form: SuperValidated<SearchSchema>;
|
export let data: SuperValidated<Infer<SearchSchema>>;
|
||||||
|
|
||||||
|
const form = superForm(data, {
|
||||||
|
validators: zodClient(search_schema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { form: formData } = form;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<search>
|
<search>
|
||||||
<Form.Root id="search-form" action="/search" method="GET" data-sveltekit-reload {form} schema={search_schema} let:config>
|
<form id="search-form" action="/search" method="GET" data-sveltekit-reload>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<Form.Item>
|
<Form.Field {form} name="q">
|
||||||
<Form.Field {config} name="q">
|
<Form.Control let:attrs>
|
||||||
<Form.Label for="label">Search</Form.Label>
|
<Form.Label>Search</Form.Label>
|
||||||
<Form.Input />
|
<Input {...attrs} bind:value={$formData.q} />
|
||||||
<Form.Validation />
|
</Form.Control>
|
||||||
</Form.Field>
|
<Form.FieldErrors />
|
||||||
<Form.Field {config} name="skip">
|
</Form.Field>
|
||||||
<Form.Input type="hidden" />
|
<Form.Field {form} name="skip">
|
||||||
</Form.Field>
|
<Form.Control let:attrs>
|
||||||
<Form.Field {config} name="limit">
|
<Input type="hidden" />
|
||||||
<Form.Input type="hidden" />
|
</Form.Control>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
</Form.Item>
|
<Form.Field {form} name="limit">
|
||||||
|
<Form.Control let:attrs>
|
||||||
|
<Input type="hidden" />
|
||||||
|
</Form.Control>
|
||||||
|
</Form.Field>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<Form.Field {config} name="exact">
|
<Form.Field {form} name="exact">
|
||||||
<Form.Label>Exact Search</Form.Label>
|
<Form.Control let:attrs>
|
||||||
<Form.Checkbox class="mt-0" />
|
<Form.Label>Exact Search</Form.Label>
|
||||||
|
<Checkbox {...attrs} class="mt-0" bind:checked={$formData.exact} />
|
||||||
|
<input name={attrs.name} value={$formData.exact} hidden />
|
||||||
|
</Form.Control>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<Form.Button>Submit</Form.Button>
|
<Form.Button>Submit</Form.Button>
|
||||||
</Form.Root>
|
</form>
|
||||||
</search>
|
</search>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||||
import { ConicGradient } from '@skeletonlabs/skeleton';
|
import { ConicGradient } from '@skeletonlabs/skeleton';
|
||||||
import type { ConicStop } from '@skeletonlabs/skeleton';
|
import type { ConicStop } from '@skeletonlabs/skeleton';
|
||||||
|
import { i } from "@inlang/sdk-js";
|
||||||
import { superForm } from 'sveltekit-superforms/client';
|
import { superForm } from 'sveltekit-superforms/client';
|
||||||
//import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
|
//import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
|
||||||
import { userSchema } from '$lib/config/zod-schemas';
|
import { userSchema } from '$lib/validations/zod-schemas';
|
||||||
import { AlertTriangle } from 'lucide-svelte';
|
import { AlertTriangle } from 'lucide-svelte';
|
||||||
import { i } from "@inlang/sdk-js";
|
import { signInSchema } from '$lib/validations/auth';
|
||||||
export let data;
|
export let data;
|
||||||
const signInSchema = userSchema.pick({ email: true, password: true });
|
|
||||||
const { form, errors, enhance, delayed } = superForm(data.form, {
|
const { form, errors, enhance, delayed } = superForm(data.form, {
|
||||||
taintedMessage: null,
|
taintedMessage: null,
|
||||||
validators: signInSchema,
|
validators: zodClient(signInSchema),
|
||||||
delayMs: 0
|
delayMs: 0
|
||||||
});
|
});
|
||||||
const conicStops: ConicStop[] = [
|
const conicStops: ConicStop[] = [
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||||
import { superForm } from 'sveltekit-superforms/client';
|
import { superForm } from 'sveltekit-superforms/client';
|
||||||
import { userSchema } from '$lib/config/zod-schemas';
|
import { signUpSchema } from '$lib/validations/auth';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
const signUpSchema = userSchema.pick({
|
const { form, errors, enhance } = superForm(data.form, {
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
username: true,
|
|
||||||
email: true,
|
|
||||||
password: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const { form, errors, enhance, delayed } = superForm(data.form, {
|
|
||||||
taintedMessage: null,
|
taintedMessage: null,
|
||||||
validators: signUpSchema,
|
validators: zodClient(signUpSchema),
|
||||||
delayMs: 0
|
delayMs: 0
|
||||||
});
|
});
|
||||||
// $: termsValue = $form.terms as Writable<boolean>;
|
// $: termsValue = $form.terms as Writable<boolean>;
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AvatarPrimitive.Fallback
|
<AvatarPrimitive.Fallback
|
||||||
class={cn(
|
class={cn("flex h-full w-full items-center justify-center rounded-full bg-muted", className)}
|
||||||
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,7 @@
|
||||||
|
|
||||||
<AvatarPrimitive.Root
|
<AvatarPrimitive.Root
|
||||||
{delayMs}
|
{delayMs}
|
||||||
class={cn(
|
class={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
|
||||||
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,5 @@ export {
|
||||||
//
|
//
|
||||||
Root as Avatar,
|
Root as Avatar,
|
||||||
Image as AvatarImage,
|
Image as AvatarImage,
|
||||||
Fallback as AvatarFallback
|
Fallback as AvatarFallback,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,19 +12,19 @@ const buttonVariants = tv({
|
||||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
link: "text-primary underline-offset-4 hover:underline"
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-10 px-4 py-2",
|
default: "h-10 px-4 py-2",
|
||||||
sm: "h-9 rounded-md px-3",
|
sm: "h-9 rounded-md px-3",
|
||||||
lg: "h-11 rounded-md px-8",
|
lg: "h-11 rounded-md px-8",
|
||||||
icon: "h-10 w-10"
|
icon: "h-10 w-10",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default"
|
size: "default",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
type Variant = VariantProps<typeof buttonVariants>["variant"];
|
type Variant = VariantProps<typeof buttonVariants>["variant"];
|
||||||
|
|
@ -45,5 +45,5 @@ export {
|
||||||
Root as Button,
|
Root as Button,
|
||||||
type Props as ButtonProps,
|
type Props as ButtonProps,
|
||||||
type Events as ButtonEvents,
|
type Events as ButtonEvents,
|
||||||
buttonVariants
|
buttonVariants,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={cn(
|
class={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
|
||||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export {
|
||||||
Description as CardDescription,
|
Description as CardDescription,
|
||||||
Footer as CardFooter,
|
Footer as CardFooter,
|
||||||
Header as CardHeader,
|
Header as CardHeader,
|
||||||
Title as CardTitle
|
Title as CardTitle,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
export let transition: $$Props["transition"] = slide;
|
export let transition: $$Props["transition"] = slide;
|
||||||
export let transitionConfig: $$Props["transitionConfig"] = {
|
export let transitionConfig: $$Props["transitionConfig"] = {
|
||||||
duration: 150
|
duration: 150,
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,5 @@ export {
|
||||||
//
|
//
|
||||||
Root as Collapsible,
|
Root as Collapsible,
|
||||||
Content as CollapsibleContent,
|
Content as CollapsibleContent,
|
||||||
Trigger as CollapsibleTrigger
|
Trigger as CollapsibleTrigger,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
import Check from "lucide-svelte/icons/check";
|
||||||
import { cn } from "$lib/utils";
|
import { cn } from "$lib/utils";
|
||||||
import { Check } from "lucide-svelte";
|
|
||||||
|
|
||||||
type $$Props = DropdownMenuPrimitive.CheckboxItemProps;
|
type $$Props = DropdownMenuPrimitive.CheckboxItemProps;
|
||||||
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents;
|
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents;
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<DropdownMenuPrimitive.CheckboxItem
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
bind:checked
|
bind:checked
|
||||||
class={cn(
|
class={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
type $$Events = DropdownMenuPrimitive.ContentEvents;
|
type $$Events = DropdownMenuPrimitive.ContentEvents;
|
||||||
|
|
||||||
let className: $$Props["class"] = undefined;
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let sideOffset: $$Props["sideOffset"] = 4;
|
||||||
export let transition: $$Props["transition"] = flyAndScale;
|
export let transition: $$Props["transition"] = flyAndScale;
|
||||||
export let transitionConfig: $$Props["transitionConfig"] = undefined;
|
export let transitionConfig: $$Props["transitionConfig"] = undefined;
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
@ -14,6 +15,7 @@
|
||||||
<DropdownMenuPrimitive.Content
|
<DropdownMenuPrimitive.Content
|
||||||
{transition}
|
{transition}
|
||||||
{transitionConfig}
|
{transitionConfig}
|
||||||
|
{sideOffset}
|
||||||
class={cn(
|
class={cn(
|
||||||
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md focus:outline-none",
|
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md focus:outline-none",
|
||||||
className
|
className
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
<DropdownMenuPrimitive.Item
|
<DropdownMenuPrimitive.Item
|
||||||
class={cn(
|
class={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
|
||||||
inset && "pl-8",
|
inset && "pl-8",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
import Circle from "lucide-svelte/icons/circle";
|
||||||
import { cn } from "$lib/utils";
|
import { cn } from "$lib/utils";
|
||||||
import { Circle } from "lucide-svelte";
|
|
||||||
|
|
||||||
type $$Props = DropdownMenuPrimitive.RadioItemProps;
|
type $$Props = DropdownMenuPrimitive.RadioItemProps;
|
||||||
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
|
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
<DropdownMenuPrimitive.RadioItem
|
<DropdownMenuPrimitive.RadioItem
|
||||||
class={cn(
|
class={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{value}
|
{value}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,6 @@
|
||||||
export { className as class };
|
export { className as class };
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span
|
<span class={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...$$restProps}>
|
||||||
class={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
|
||||||
{...$$restProps}
|
|
||||||
>
|
|
||||||
<slot />
|
<slot />
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
export let transition: $$Props["transition"] = flyAndScale;
|
export let transition: $$Props["transition"] = flyAndScale;
|
||||||
export let transitionConfig: $$Props["transitionConfig"] = {
|
export let transitionConfig: $$Props["transitionConfig"] = {
|
||||||
x: -10,
|
x: -10,
|
||||||
y: 0
|
y: 0,
|
||||||
};
|
};
|
||||||
export { className as class };
|
export { className as class };
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
import ChevronRight from "lucide-svelte/icons/chevron-right";
|
||||||
import { cn } from "$lib/utils";
|
import { cn } from "$lib/utils";
|
||||||
import { ChevronRight } from "lucide-svelte";
|
|
||||||
|
|
||||||
type $$Props = DropdownMenuPrimitive.SubTriggerProps & {
|
type $$Props = DropdownMenuPrimitive.SubTriggerProps & {
|
||||||
inset?: boolean;
|
inset?: boolean;
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<DropdownMenuPrimitive.SubTrigger
|
<DropdownMenuPrimitive.SubTrigger
|
||||||
class={cn(
|
class={cn(
|
||||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[state=open]:bg-accent",
|
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground",
|
||||||
inset && "pl-8",
|
inset && "pl-8",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -44,5 +44,5 @@ export {
|
||||||
RadioGroup as DropdownMenuRadioGroup,
|
RadioGroup as DropdownMenuRadioGroup,
|
||||||
SubContent as DropdownMenuSubContent,
|
SubContent as DropdownMenuSubContent,
|
||||||
SubTrigger as DropdownMenuSubTrigger,
|
SubTrigger as DropdownMenuSubTrigger,
|
||||||
CheckboxItem as DropdownMenuCheckboxItem
|
CheckboxItem as DropdownMenuCheckboxItem,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Button from "$lib/components/ui/button";
|
import * as Button from "$lib/components/ui/button";
|
||||||
|
|
||||||
type $$Props = Button.Props;
|
type $$Props = Button.Props;
|
||||||
type $$Events = Button.Events;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button.Root type="submit" {...$$restProps} on:click on:keydown>
|
<Button.Root type="submit" {...$$restProps}>
|
||||||
<slot />
|
<slot />
|
||||||
</Button.Root>
|
</Button.Root>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Form as FormPrimitive } from "formsnap";
|
import * as FormPrimitive from "formsnap";
|
||||||
import { cn } from "$lib/utils";
|
import { cn } from "$lib/utils";
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
|
@ -8,6 +8,10 @@
|
||||||
export { className as class };
|
export { className as class };
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormPrimitive.Description class={cn("text-sm text-muted-foreground", className)} {...$$restProps}>
|
<FormPrimitive.Description
|
||||||
<slot />
|
class={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
let:descriptionAttrs
|
||||||
|
>
|
||||||
|
<slot {descriptionAttrs} />
|
||||||
</FormPrimitive.Description>
|
</FormPrimitive.Description>
|
||||||
|
|
|
||||||
26
src/lib/components/ui/form/form-element-field.svelte
Normal file
26
src/lib/components/ui/form/form-element-field.svelte
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script lang="ts" context="module">
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
import type { FormPathLeaves, SuperForm } from "sveltekit-superforms";
|
||||||
|
type T = Record<string, unknown>;
|
||||||
|
type U = unknown;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import * as FormPrimitive from "formsnap";
|
||||||
|
import { cn } from "$lib/utils";
|
||||||
|
|
||||||
|
type $$Props = FormPrimitive.ElementFieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||||
|
|
||||||
|
export let form: SuperForm<T>;
|
||||||
|
export let name: U;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormPrimitive.ElementField {form} {name} let:constraints let:errors let:tainted let:value>
|
||||||
|
<div class={cn("space-y-2", className)}>
|
||||||
|
<slot {constraints} {errors} {tainted} {value} />
|
||||||
|
</div>
|
||||||
|
</FormPrimitive.ElementField>
|
||||||
26
src/lib/components/ui/form/form-field-errors.svelte
Normal file
26
src/lib/components/ui/form/form-field-errors.svelte
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import * as FormPrimitive from "formsnap";
|
||||||
|
import { cn } from "$lib/utils";
|
||||||
|
|
||||||
|
type $$Props = FormPrimitive.FieldErrorsProps & {
|
||||||
|
errorClasses?: string | undefined | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
export let errorClasses: $$Props["class"] = undefined;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormPrimitive.FieldErrors
|
||||||
|
class={cn("text-sm font-medium text-destructive", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
let:errors
|
||||||
|
let:fieldErrorsAttrs
|
||||||
|
let:errorAttrs
|
||||||
|
>
|
||||||
|
<slot {errors} {fieldErrorsAttrs} {errorAttrs}>
|
||||||
|
{#each errors as error}
|
||||||
|
<div {...errorAttrs} class={cn(errorClasses)}>{error}</div>
|
||||||
|
{/each}
|
||||||
|
</slot>
|
||||||
|
</FormPrimitive.FieldErrors>
|
||||||
26
src/lib/components/ui/form/form-field.svelte
Normal file
26
src/lib/components/ui/form/form-field.svelte
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script lang="ts" context="module">
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
import type { FormPath, SuperForm } from "sveltekit-superforms";
|
||||||
|
type T = Record<string, unknown>;
|
||||||
|
type U = unknown;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import * as FormPrimitive from "formsnap";
|
||||||
|
import { cn } from "$lib/utils";
|
||||||
|
|
||||||
|
type $$Props = FormPrimitive.FieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||||
|
|
||||||
|
export let form: SuperForm<T>;
|
||||||
|
export let name: U;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormPrimitive.Field {form} {name} let:constraints let:errors let:tainted let:value>
|
||||||
|
<div class={cn("space-y-2", className)}>
|
||||||
|
<slot {constraints} {errors} {tainted} {value} />
|
||||||
|
</div>
|
||||||
|
</FormPrimitive.Field>
|
||||||
31
src/lib/components/ui/form/form-fieldset.svelte
Normal file
31
src/lib/components/ui/form/form-fieldset.svelte
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<script lang="ts" context="module">
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
import type { FormPath, SuperForm } from "sveltekit-superforms";
|
||||||
|
type T = Record<string, unknown>;
|
||||||
|
type U = unknown;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||||
|
import * as FormPrimitive from "formsnap";
|
||||||
|
import { cn } from "$lib/utils";
|
||||||
|
|
||||||
|
type $$Props = FormPrimitive.FieldsetProps<T, U>;
|
||||||
|
|
||||||
|
export let form: SuperForm<T>;
|
||||||
|
export let name: U;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormPrimitive.Fieldset
|
||||||
|
{form}
|
||||||
|
{name}
|
||||||
|
let:constraints
|
||||||
|
let:errors
|
||||||
|
let:tainted
|
||||||
|
let:value
|
||||||
|
class={cn("space-y-2", className)}
|
||||||
|
>
|
||||||
|
<slot {constraints} {errors} {tainted} {value} />
|
||||||
|
</FormPrimitive.Fieldset>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Label as LabelPrimitive } from "bits-ui";
|
import type { Label as LabelPrimitive } from "bits-ui";
|
||||||
import { getFormField } from "formsnap";
|
import { getFormControl } from "formsnap";
|
||||||
import { cn } from "$lib/utils";
|
import { cn } from "$lib/utils";
|
||||||
import { Label } from "$lib/components/ui/label";
|
import { Label } from "$lib/components/ui/label";
|
||||||
|
|
||||||
|
|
@ -9,9 +9,9 @@
|
||||||
let className: $$Props["class"] = undefined;
|
let className: $$Props["class"] = undefined;
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
||||||
const { errors, ids } = getFormField();
|
const { labelAttrs } = getFormControl();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Label for={$ids.input} class={cn($errors && "text-destructive", className)} {...$$restProps}>
|
<Label {...$labelAttrs} class={cn("data-[fs-error]:text-destructive", className)} {...$$restProps}>
|
||||||
<slot />
|
<slot {labelAttrs} />
|
||||||
</Label>
|
</Label>
|
||||||
|
|
|
||||||
17
src/lib/components/ui/form/form-legend.svelte
Normal file
17
src/lib/components/ui/form/form-legend.svelte
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import * as FormPrimitive from "formsnap";
|
||||||
|
import { cn } from "$lib/utils";
|
||||||
|
|
||||||
|
type $$Props = FormPrimitive.LegendProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormPrimitive.Legend
|
||||||
|
{...$$restProps}
|
||||||
|
class={cn("text-sm font-medium leading-none data-[fs-error]:text-destructive", className)}
|
||||||
|
let:legendAttrs
|
||||||
|
>
|
||||||
|
<slot {legendAttrs} />
|
||||||
|
</FormPrimitive.Legend>
|
||||||
|
|
@ -1,82 +1,33 @@
|
||||||
import { Form as FormPrimitive, getFormField } from "formsnap";
|
import * as FormPrimitive from "formsnap";
|
||||||
import * as RadioGroupComp from "$lib/components/ui/radio-group";
|
|
||||||
import * as SelectComp from "$lib/components/ui/select";
|
|
||||||
import type { Writable } from "svelte/store";
|
|
||||||
import Item from "./form-item.svelte";
|
|
||||||
import Input from "./form-input.svelte";
|
|
||||||
import Textarea from "./form-textarea.svelte";
|
|
||||||
import Description from "./form-description.svelte";
|
import Description from "./form-description.svelte";
|
||||||
import Label from "./form-label.svelte";
|
import Label from "./form-label.svelte";
|
||||||
import Validation from "./form-validation.svelte";
|
import FieldErrors from "./form-field-errors.svelte";
|
||||||
import Checkbox from "./form-checkbox.svelte";
|
import Field from "./form-field.svelte";
|
||||||
import Switch from "./form-switch.svelte";
|
import Fieldset from "./form-fieldset.svelte";
|
||||||
import NativeSelect from "./form-native-select.svelte";
|
import Legend from "./form-legend.svelte";
|
||||||
import RadioGroup from "./form-radio-group.svelte";
|
import ElementField from "./form-element-field.svelte";
|
||||||
import Select from "./form-select.svelte";
|
|
||||||
import SelectTrigger from "./form-select-trigger.svelte";
|
|
||||||
import Button from "./form-button.svelte";
|
import Button from "./form-button.svelte";
|
||||||
|
|
||||||
const Root = FormPrimitive.Root;
|
|
||||||
const Field = FormPrimitive.Field;
|
|
||||||
const Control = FormPrimitive.Control;
|
const Control = FormPrimitive.Control;
|
||||||
const RadioItem = RadioGroupComp.Item;
|
|
||||||
const NativeRadio = FormPrimitive.Radio;
|
|
||||||
const SelectContent = SelectComp.Content;
|
|
||||||
const SelectLabel = SelectComp.Label;
|
|
||||||
const SelectGroup = SelectComp.Group;
|
|
||||||
const SelectItem = SelectComp.Item;
|
|
||||||
const SelectSeparator = SelectComp.Separator;
|
|
||||||
|
|
||||||
export type TextareaGetFormField = Omit<ReturnType<typeof getFormField>, "value"> & {
|
|
||||||
value: Writable<string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Root,
|
|
||||||
Field,
|
Field,
|
||||||
Control,
|
Control,
|
||||||
Item,
|
|
||||||
Input,
|
|
||||||
Label,
|
Label,
|
||||||
Button,
|
Button,
|
||||||
Switch,
|
FieldErrors,
|
||||||
Select,
|
|
||||||
Checkbox,
|
|
||||||
Textarea,
|
|
||||||
Validation,
|
|
||||||
RadioGroup,
|
|
||||||
RadioItem,
|
|
||||||
Description,
|
Description,
|
||||||
SelectContent,
|
Fieldset,
|
||||||
SelectLabel,
|
Legend,
|
||||||
SelectGroup,
|
ElementField,
|
||||||
SelectItem,
|
|
||||||
SelectSeparator,
|
|
||||||
SelectTrigger,
|
|
||||||
NativeSelect,
|
|
||||||
NativeRadio,
|
|
||||||
//
|
//
|
||||||
Root as Form,
|
|
||||||
Field as FormField,
|
Field as FormField,
|
||||||
Control as FormControl,
|
Control as FormControl,
|
||||||
Item as FormItem,
|
|
||||||
Input as FormInput,
|
|
||||||
Textarea as FormTextarea,
|
|
||||||
Description as FormDescription,
|
Description as FormDescription,
|
||||||
Label as FormLabel,
|
Label as FormLabel,
|
||||||
Validation as FormValidation,
|
FieldErrors as FormFieldErrors,
|
||||||
NativeSelect as FormNativeSelect,
|
Fieldset as FormFieldset,
|
||||||
NativeRadio as FormNativeRadio,
|
Legend as FormLegend,
|
||||||
Checkbox as FormCheckbox,
|
ElementField as FormElementField,
|
||||||
Switch as FormSwitch,
|
Button as FormButton,
|
||||||
RadioGroup as FormRadioGroup,
|
|
||||||
RadioItem as FormRadioItem,
|
|
||||||
Select as FormSelect,
|
|
||||||
SelectContent as FormSelectContent,
|
|
||||||
SelectLabel as FormSelectLabel,
|
|
||||||
SelectGroup as FormSelectGroup,
|
|
||||||
SelectItem as FormSelectItem,
|
|
||||||
SelectSeparator as FormSelectSeparator,
|
|
||||||
SelectTrigger as FormSelectTrigger,
|
|
||||||
Button as FormButton
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,5 @@ import Root from "./label.svelte";
|
||||||
export {
|
export {
|
||||||
Root,
|
Root,
|
||||||
//
|
//
|
||||||
Root as Label
|
Root as Label,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,5 @@ export {
|
||||||
Link as PaginationLink,
|
Link as PaginationLink,
|
||||||
PrevButton as PaginationPrevButton,
|
PrevButton as PaginationPrevButton,
|
||||||
NextButton as PaginationNextButton,
|
NextButton as PaginationNextButton,
|
||||||
Ellipsis as PaginationEllipsis
|
Ellipsis as PaginationEllipsis,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import MoreHorizontal from "lucide-svelte/icons/more-horizontal";
|
||||||
import { cn } from "$lib/utils";
|
import { cn } from "$lib/utils";
|
||||||
import { MoreHorizontal } from "lucide-svelte";
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
class={cn(
|
class={cn(
|
||||||
buttonVariants({
|
buttonVariants({
|
||||||
variant: isActive ? "outline" : "ghost",
|
variant: isActive ? "outline" : "ghost",
|
||||||
size
|
size,
|
||||||
}),
|
}),
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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 { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
import { cn } from "$lib/utils";
|
import { cn } from "$lib/utils";
|
||||||
import { ChevronRight } from "lucide-svelte";
|
|
||||||
|
|
||||||
type $$Props = PaginationPrimitive.NextButtonProps;
|
type $$Props = PaginationPrimitive.NextButtonProps;
|
||||||
type $$Events = PaginationPrimitive.NextButtonEvents;
|
type $$Events = PaginationPrimitive.NextButtonEvents;
|
||||||
|
|
|
||||||
|
|
@ -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 { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
import { cn } from "$lib/utils";
|
import { cn } from "$lib/utils";
|
||||||
import { ChevronLeft } from "lucide-svelte";
|
|
||||||
|
|
||||||
type $$Props = PaginationPrimitive.PrevButtonProps;
|
type $$Props = PaginationPrimitive.PrevButtonProps;
|
||||||
type $$Events = PaginationPrimitive.PrevButtonEvents;
|
type $$Events = PaginationPrimitive.PrevButtonEvents;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
asChild
|
asChild
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
>
|
>
|
||||||
<nav {...builder} class={cn("mx-auto flex flex-col w-full items-center", className)}>
|
<nav {...builder} class={cn("mx-auto flex w-full flex-col items-center", className)}>
|
||||||
<slot {pages} {range} {currentPage} />
|
<slot {pages} {range} {currentPage} />
|
||||||
</nav>
|
</nav>
|
||||||
</PaginationPrimitive.Root>
|
</PaginationPrimitive.Root>
|
||||||
|
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
export type ListGame = {
|
|
||||||
id: string;
|
|
||||||
game_name: string;
|
|
||||||
game_id: string;
|
|
||||||
collection_id: string;
|
|
||||||
wishlist_id: string;
|
|
||||||
times_played: number;
|
|
||||||
thumb_url: string | null;
|
|
||||||
in_collection: boolean;
|
|
||||||
in_wishlist: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const modifyListGameSchema = z.object({
|
|
||||||
id: z.string()
|
|
||||||
});
|
|
||||||
|
|
||||||
export type ModifyListGame = typeof modifyListGameSchema;
|
|
||||||
|
|
||||||
export const userSchema = z.object({
|
|
||||||
firstName: z.string().trim().optional(),
|
|
||||||
lastName: z.string().trim().optional(),
|
|
||||||
email: z.string().email({ message: 'Please enter a valid email address' }).optional(),
|
|
||||||
username: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.min(3, { message: 'Username must be at least 3 characters' })
|
|
||||||
.max(50, { message: 'Username must be less than 50 characters' }),
|
|
||||||
password: z
|
|
||||||
.string({ required_error: 'Password is required' })
|
|
||||||
.trim()
|
|
||||||
.min(8, { message: 'Password must be at least 8 characters' })
|
|
||||||
.max(128, { message: 'Password must be less than 128 characters' }),
|
|
||||||
confirm_password: z
|
|
||||||
.string({ required_error: 'Confirm Password is required' })
|
|
||||||
.trim()
|
|
||||||
.min(8, { message: 'Confirm Password must be at least 8 characters' }),
|
|
||||||
role: z.enum(['USER', 'ADMIN'], { required_error: 'You must have a role' }).default('USER'),
|
|
||||||
verified: z.boolean().default(false),
|
|
||||||
token: z.string().optional(),
|
|
||||||
receiveEmail: z.boolean().default(false),
|
|
||||||
createdAt: z.date().optional(),
|
|
||||||
updatedAt: z.date().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
export const signUpSchema = userSchema
|
|
||||||
.pick({
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
email: true,
|
|
||||||
username: true,
|
|
||||||
password: true,
|
|
||||||
confirm_password: true,
|
|
||||||
terms: true
|
|
||||||
})
|
|
||||||
.superRefine(({ confirm_password, password }, ctx) => {
|
|
||||||
if (confirm_password !== password) {
|
|
||||||
// ctx.addIssue({
|
|
||||||
// code: 'custom',
|
|
||||||
// message: 'Password and Confirm Password must match',
|
|
||||||
// path: ['password']
|
|
||||||
// });
|
|
||||||
ctx.addIssue({
|
|
||||||
code: 'custom',
|
|
||||||
message: ' Password and Confirm Password must match',
|
|
||||||
path: ['confirm_password']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const signInSchema = userSchema.pick({
|
|
||||||
username: true,
|
|
||||||
password: true
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateUserPasswordSchema = userSchema
|
|
||||||
.pick({ password: true, confirm_password: true })
|
|
||||||
.superRefine(({ confirm_password, password }, ctx) => {
|
|
||||||
if (confirm_password !== password) {
|
|
||||||
ctx.addIssue({
|
|
||||||
code: 'custom',
|
|
||||||
message: 'Password and Confirm Password must match',
|
|
||||||
path: ['password']
|
|
||||||
});
|
|
||||||
ctx.addIssue({
|
|
||||||
code: 'custom',
|
|
||||||
message: 'Password and Confirm Password must match',
|
|
||||||
path: ['confirm_password']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const changeUserPasswordSchema = z
|
|
||||||
.object({
|
|
||||||
current_password: z.string({ required_error: 'Current Password is required' }),
|
|
||||||
password: z
|
|
||||||
.string({ required_error: 'Password is required' })
|
|
||||||
.trim()
|
|
||||||
.min(8, { message: 'Password must be at least 8 characters' })
|
|
||||||
.max(128, { message: 'Password must be less than 128 characters' }),
|
|
||||||
confirm_password: z
|
|
||||||
.string({ required_error: 'Confirm Password is required' })
|
|
||||||
.trim()
|
|
||||||
.min(8, { message: 'Confirm Password must be at least 8 characters' })
|
|
||||||
})
|
|
||||||
.superRefine(({ confirm_password, password }, ctx) => {
|
|
||||||
if (confirm_password !== password) {
|
|
||||||
ctx.addIssue({
|
|
||||||
code: 'custom',
|
|
||||||
message: 'Password and Confirm Password must match',
|
|
||||||
path: ['password']
|
|
||||||
});
|
|
||||||
ctx.addIssue({
|
|
||||||
code: 'custom',
|
|
||||||
message: 'Password and Confirm Password must match',
|
|
||||||
path: ['confirm_password']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
30
src/lib/drizzle.ts
Normal file
30
src/lib/drizzle.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
|
import pg from 'pg';
|
||||||
|
import {
|
||||||
|
DATABASE_USER,
|
||||||
|
DATABASE_PASSWORD,
|
||||||
|
DATABASE_HOST,
|
||||||
|
DATABASE_DB,
|
||||||
|
DATABASE_PORT,
|
||||||
|
} from '$env/static/private';
|
||||||
|
import * as schema from '../schema';
|
||||||
|
|
||||||
|
// create the connection
|
||||||
|
const pool = new pg.Pool({
|
||||||
|
user: DATABASE_USER,
|
||||||
|
password: DATABASE_PASSWORD,
|
||||||
|
host: DATABASE_HOST,
|
||||||
|
port: Number(DATABASE_PORT).valueOf(),
|
||||||
|
database: DATABASE_DB,
|
||||||
|
ssl: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// user: DATABASE_USER,
|
||||||
|
// password: DATABASE_PASSWORD,
|
||||||
|
// host: DATABASE_HOST,
|
||||||
|
// port: Number(DATABASE_PORT).valueOf(),
|
||||||
|
// database: DATABASE_DB
|
||||||
|
|
||||||
|
const db = drizzle(pool, { schema });
|
||||||
|
|
||||||
|
export default db;
|
||||||
1
src/lib/flashMessages.ts
Normal file
1
src/lib/flashMessages.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const notSignedInMessage = { type: 'error', message: 'You are not signed in' } as const;
|
||||||
|
|
@ -5,7 +5,7 @@ import { dev } from '$app/environment';
|
||||||
import { read } from '$app/server';
|
import { read } from '$app/server';
|
||||||
|
|
||||||
// we use a Vite plugin to turn this import into the result of fs.readFileSync during build
|
// we use a Vite plugin to turn this import into the result of fs.readFileSync during build
|
||||||
import firaSansSemiBold from '$lib/fonts/FiraSans-SemiBold.ttf';
|
import firaSansSemiBold from '$lib/fonts/FiraSans-Bold.ttf';
|
||||||
|
|
||||||
const fontData = read(firaSansSemiBold).arrayBuffer();
|
const fontData = read(firaSansSemiBold).arrayBuffer();
|
||||||
|
|
||||||
|
|
|
||||||
19
src/lib/server/auth-utils.ts
Normal file
19
src/lib/server/auth-utils.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import db from "$lib/drizzle";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { password_reset_tokens } from "../../schema";
|
||||||
|
import { generateId } from "lucia";
|
||||||
|
import { TimeSpan, createDate } from "oslo";
|
||||||
|
|
||||||
|
export async function createPasswordResetToken(userId: string): Promise<string> {
|
||||||
|
// optionally invalidate all existing tokens
|
||||||
|
await db.delete(password_reset_tokens).where(eq(password_reset_tokens.user_id, userId));
|
||||||
|
const tokenId = generateId(40);
|
||||||
|
await db
|
||||||
|
.insert(password_reset_tokens)
|
||||||
|
.values({
|
||||||
|
id: tokenId,
|
||||||
|
user_id: userId,
|
||||||
|
expires_at: createDate(new TimeSpan(2, "h"))
|
||||||
|
});
|
||||||
|
return tokenId;
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
// lib/server/lucia.ts
|
// lib/server/lucia.ts
|
||||||
import { Lucia, TimeSpan } from 'lucia';
|
import { Lucia, TimeSpan } from 'lucia';
|
||||||
import { PrismaAdapter } from '@lucia-auth/adapter-prisma';
|
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
|
||||||
import { dev } from '$app/environment';
|
import { dev } from '$app/environment';
|
||||||
import prisma_client from '$lib/prisma';
|
import db from '$lib/drizzle';
|
||||||
|
import { sessions, users } from '../../schema';
|
||||||
|
|
||||||
const adapter = new PrismaAdapter(prisma_client.session, prisma_client.user);
|
const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users);
|
||||||
|
|
||||||
export const lucia = new Lucia(adapter, {
|
export const lucia = new Lucia(adapter, {
|
||||||
getSessionAttributes: (attributes) => {
|
getSessionAttributes: (attributes) => {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
--input: 20 5.9% 90%;
|
--input: 20 5.9% 90%;
|
||||||
--ring: 20 14.3% 4.1%;
|
--ring: 20 14.3% 4.1%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
|
--fg: #2c3e50;
|
||||||
}
|
}
|
||||||
.dark {
|
.dark {
|
||||||
--background: 20 14.3% 4.1%;
|
--background: 20 14.3% 4.1%;
|
||||||
|
|
@ -47,6 +48,7 @@
|
||||||
--border: 12 6.5% 15.1%;
|
--border: 12 6.5% 15.1%;
|
||||||
--input: 12 6.5% 15.1%;
|
--input: 12 6.5% 15.1%;
|
||||||
--ring: 35.5 91.7% 32.9%;
|
--ring: 35.5 91.7% 32.9%;
|
||||||
|
--fg: #ffff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
3
src/lib/utils/authUtils.ts
Normal file
3
src/lib/utils/authUtils.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const normalizeEmail = (email: string): string => {
|
||||||
|
return decodeURIComponent(email).toLowerCase().trim();
|
||||||
|
};
|
||||||
77
src/lib/utils/db/categoryUtils.ts
Normal file
77
src/lib/utils/db/categoryUtils.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import kebabCase from 'just-kebab-case';
|
||||||
|
import db from '$lib/drizzle';
|
||||||
|
import { externalIds, type Mechanics, type Categories, categories, categoriesToExternalIds } from '../../../schema';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import { PUBLIC_SITE_URL } from '$env/static/public';
|
||||||
|
|
||||||
|
export async function createCategory(locals: App.Locals, category: Categories, externalId: string) {
|
||||||
|
if (!category || !externalId || externalId === '') {
|
||||||
|
error(400, 'Invalid Request');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dbExternalId = await db.query.externalIds.findFirst({
|
||||||
|
where: eq(externalIds.externalId, externalId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbExternalId) {
|
||||||
|
const foundCategory = await db
|
||||||
|
.select({
|
||||||
|
id: categories.id,
|
||||||
|
name: categories.name,
|
||||||
|
slug: categories.slug
|
||||||
|
})
|
||||||
|
.from(categories)
|
||||||
|
.leftJoin(categoriesToExternalIds, eq(categoriesToExternalIds.externalId, externalId));
|
||||||
|
console.log('Mechanic already exists', foundCategory);
|
||||||
|
if (foundCategory.length > 0) {
|
||||||
|
console.log('Mechanic name', foundCategory[0].name);
|
||||||
|
return new Response('Mechanic already exists', {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Location: `${PUBLIC_SITE_URL}/api/mechanic/${foundCategory[0].id}`
|
||||||
|
},
|
||||||
|
status: 409
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dbCategory: Mechanics[] = [];
|
||||||
|
console.log('Creating category', JSON.stringify(category, null, 2));
|
||||||
|
await db.transaction(async (transaction) => {
|
||||||
|
dbCategory = await transaction
|
||||||
|
.insert(categories)
|
||||||
|
.values({
|
||||||
|
name: category.name,
|
||||||
|
slug: kebabCase(category.name || category.slug || '')
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
const dbExternalIds = await transaction
|
||||||
|
.insert(externalIds)
|
||||||
|
.values({
|
||||||
|
externalId,
|
||||||
|
type: 'category'
|
||||||
|
})
|
||||||
|
.returning({ id: externalIds.id });
|
||||||
|
await transaction.insert(categoriesToExternalIds).values({
|
||||||
|
categoryId: dbCategory[0].id,
|
||||||
|
externalId: dbExternalIds[0].id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbCategory.length === 0) {
|
||||||
|
return new Response('Could not create category', {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Created category', JSON.stringify(dbCategory[0], null, 2));
|
||||||
|
return new Response(JSON.stringify(dbCategory[0]), {
|
||||||
|
status: 201,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
throw new Error('Something went wrong creating Category');
|
||||||
|
}
|
||||||
|
}
|
||||||
178
src/lib/utils/db/expansionUtils.ts
Normal file
178
src/lib/utils/db/expansionUtils.ts
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import { and, eq } from 'drizzle-orm';
|
||||||
|
import db from '$lib/drizzle';
|
||||||
|
import { type Expansions, expansions } from '../../../schema';
|
||||||
|
import { PUBLIC_SITE_URL } from '$env/static/public';
|
||||||
|
|
||||||
|
export async function createExpansion(locals: App.Locals, expansion: Expansions) {
|
||||||
|
if (!expansion || expansion?.base_game_id === '' || expansion?.game_id === '') {
|
||||||
|
error(400, 'Invalid Request');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const foundExpansion = await db.query.expansions
|
||||||
|
.findFirst({
|
||||||
|
where: and(eq(expansions.base_game_id, expansion.base_game_id), eq(expansions.game_id, expansion.game_id)),
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
game_id: true,
|
||||||
|
base_game_id: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('Expansion already exists', foundExpansion);
|
||||||
|
if (foundExpansion) {
|
||||||
|
console.log('Expansion Game ID', foundExpansion.game_id);
|
||||||
|
return new Response('Expansion already exists', {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Location: `${PUBLIC_SITE_URL}/api/game/${foundExpansion.game_id}`
|
||||||
|
},
|
||||||
|
status: 409
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Creating expansion', JSON.stringify(expansion, null, 2));
|
||||||
|
const dbExpansion = await db
|
||||||
|
.insert(expansions)
|
||||||
|
.values({
|
||||||
|
base_game_id: expansion.base_game_id,
|
||||||
|
game_id: expansion.game_id,
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (dbExpansion.length === 0) {
|
||||||
|
return new Response('Could not create expansion', {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Created expansion', JSON.stringify(dbExpansion[0], null, 2));
|
||||||
|
return new Response(JSON.stringify(dbExpansion[0]), {
|
||||||
|
status: 201,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
throw new Error('Something went wrong creating Expansion');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// export async function createExpansion(
|
||||||
|
// locals: App.Locals,
|
||||||
|
// game: Game,
|
||||||
|
// externalExpansion: BggLinkDto,
|
||||||
|
// gameIsExpansion: boolean,
|
||||||
|
// eventFetch: Function
|
||||||
|
// ) {
|
||||||
|
// try {
|
||||||
|
// let dbExpansionGame = await prisma.game.findUnique({
|
||||||
|
// where: {
|
||||||
|
// external_id: externalExpansion.id
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (!dbExpansionGame) {
|
||||||
|
// const externalGameResponse = await eventFetch(
|
||||||
|
// `/api/external/game/${externalExpansion.id}?simplified=true`
|
||||||
|
// );
|
||||||
|
// if (externalGameResponse.ok) {
|
||||||
|
// const externalGame = await externalGameResponse.json();
|
||||||
|
// console.log('externalGame', externalGame);
|
||||||
|
// const boredGame = mapAPIGameToBoredGame(externalGame);
|
||||||
|
// dbExpansionGame = await createOrUpdateGameMinimal(locals, boredGame);
|
||||||
|
// } else {
|
||||||
|
// throw new Error(
|
||||||
|
// `${gameIsExpansion ? 'Base game' : 'Expansion game'} not found and failed to create.`
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let dbExpansion;
|
||||||
|
// let baseGameId;
|
||||||
|
// let gameId;
|
||||||
|
// if (gameIsExpansion) {
|
||||||
|
// console.log(
|
||||||
|
// 'External expansion is expansion. Looking for base game',
|
||||||
|
// JSON.stringify(game, null, 2)
|
||||||
|
// );
|
||||||
|
// dbExpansion = await prisma.expansion.findFirst({
|
||||||
|
// where: {
|
||||||
|
// game_id: dbExpansionGame.id
|
||||||
|
// },
|
||||||
|
// select: {
|
||||||
|
// id: true,
|
||||||
|
// base_game_id: true,
|
||||||
|
// game_id: true
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// baseGameId = game.id;
|
||||||
|
// gameId = dbExpansionGame.id;
|
||||||
|
// } else {
|
||||||
|
// console.log(
|
||||||
|
// 'External Expansion is base game. Looking for expansion',
|
||||||
|
// JSON.stringify(game, null, 2)
|
||||||
|
// );
|
||||||
|
// dbExpansion = await prisma.expansion.findFirst({
|
||||||
|
// where: {
|
||||||
|
// base_game_id: dbExpansionGame.id
|
||||||
|
// },
|
||||||
|
// select: {
|
||||||
|
// id: true,
|
||||||
|
// base_game_id: true,
|
||||||
|
// game_id: true
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// baseGameId = dbExpansionGame.id;
|
||||||
|
// gameId = game.id;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (dbExpansion) {
|
||||||
|
// console.log('Expansion already exists', JSON.stringify(dbExpansion, null, 2));
|
||||||
|
// return dbExpansion;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// console.log(`Creating expansion. baseGameId: ${baseGameId}, gameId: ${gameId}`);
|
||||||
|
// const expansion = await prisma.expansion.create({
|
||||||
|
// data: {
|
||||||
|
// base_game_id: baseGameId,
|
||||||
|
// game_id: gameId
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// console.log('Created expansion', JSON.stringify(expansion, null, 2));
|
||||||
|
|
||||||
|
// if (gameIsExpansion) {
|
||||||
|
// console.log('Connecting current game to expansion');
|
||||||
|
// await prisma.game.update({
|
||||||
|
// where: {
|
||||||
|
// id: gameId
|
||||||
|
// },
|
||||||
|
// data: {
|
||||||
|
// expansions: {
|
||||||
|
// connect: {
|
||||||
|
// id: expansion.id
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// console.log('Connecting current game to base game');
|
||||||
|
// await prisma.game.update({
|
||||||
|
// where: {
|
||||||
|
// id: baseGameId
|
||||||
|
// },
|
||||||
|
// data: {
|
||||||
|
// expansions: {
|
||||||
|
// connect: {
|
||||||
|
// id: expansion.id
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return expansion;
|
||||||
|
// } catch (e) {
|
||||||
|
// console.error(e);
|
||||||
|
// throw new Error('Something went wrong creating Expansion');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
439
src/lib/utils/db/gameUtils.ts
Normal file
439
src/lib/utils/db/gameUtils.ts
Normal file
|
|
@ -0,0 +1,439 @@
|
||||||
|
import kebabCase from 'just-kebab-case';
|
||||||
|
import db from '$lib/drizzle';
|
||||||
|
import { externalIds, gamesToExternalIds, type Games, games } from '../../../schema';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import { PUBLIC_SITE_URL } from '$env/static/public';
|
||||||
|
|
||||||
|
export async function getGame(locals: App.Locals, id: string) {
|
||||||
|
if (!id || id === '') {
|
||||||
|
error(400, 'Invalid Request');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await db.query.games.findFirst({
|
||||||
|
where: eq(games.id, id)
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return new Response('Could not get games', {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createGame(locals: App.Locals, game: Games, externalId: string) {
|
||||||
|
if (!game || !externalId || externalId === '') {
|
||||||
|
error(400, 'Invalid Request');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dbExternalId = await db.query.externalIds.findFirst({
|
||||||
|
where: eq(externalIds.externalId, externalId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbExternalId) {
|
||||||
|
const foundGame = await db
|
||||||
|
.select({
|
||||||
|
id: games.id,
|
||||||
|
name: games.name,
|
||||||
|
slug: games.slug
|
||||||
|
})
|
||||||
|
.from(games)
|
||||||
|
.leftJoin(gamesToExternalIds, eq(gamesToExternalIds.externalId, externalId));
|
||||||
|
console.log('Game already exists', foundGame);
|
||||||
|
if (foundGame.length > 0) {
|
||||||
|
console.log('Game name', foundGame[0].name);
|
||||||
|
return new Response('Game already exists', {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Location: `${PUBLIC_SITE_URL}/api/game/${foundGame[0].id}`
|
||||||
|
},
|
||||||
|
status: 409
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dbGames: Games[] = [];
|
||||||
|
console.log('Creating game', JSON.stringify(game, null, 2));
|
||||||
|
await db.transaction(async (transaction) => {
|
||||||
|
dbGames = await transaction
|
||||||
|
.insert(games)
|
||||||
|
.values({
|
||||||
|
name: game.name,
|
||||||
|
slug: kebabCase(game.name || game.slug || ''),
|
||||||
|
description: game.description,
|
||||||
|
year_published: game.year_published,
|
||||||
|
url: game.url,
|
||||||
|
image_url: game.image_url,
|
||||||
|
thumb_url: game.thumb_url,
|
||||||
|
min_age: game.min_age,
|
||||||
|
min_players: game.min_players,
|
||||||
|
max_players: game.max_players,
|
||||||
|
min_playtime: game.min_playtime,
|
||||||
|
max_playtime: game.max_playtime
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
const dbExternalIds = await transaction
|
||||||
|
.insert(externalIds)
|
||||||
|
.values({
|
||||||
|
externalId,
|
||||||
|
type: 'game'
|
||||||
|
})
|
||||||
|
.returning({ id: externalIds.id });
|
||||||
|
await transaction.insert(gamesToExternalIds).values({
|
||||||
|
gameId: dbGames[0].id,
|
||||||
|
externalId: dbExternalIds[0].id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbGames.length === 0) {
|
||||||
|
return new Response('Could not create game', {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Created game', JSON.stringify(dbGames[0], null, 2));
|
||||||
|
return new Response(JSON.stringify(dbGames[0]), {
|
||||||
|
status: 201,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
throw new Error('Something went wrong creating Game');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createOrUpdateGameMinimal(locals: App.Locals, game: Games, externalId: string) {
|
||||||
|
if (!game || !externalId || externalId === '') {
|
||||||
|
error(400, 'Invalid Request');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Creating or updating minimal game data', JSON.stringify(game, null, 2));
|
||||||
|
const externalUrl = `https://boardgamegeek.com/boardgame/${externalId}`;
|
||||||
|
try {
|
||||||
|
let dbGames: Games[] = [];
|
||||||
|
console.log('Creating game', JSON.stringify(game, null, 2));
|
||||||
|
await db.transaction(async (transaction) => {
|
||||||
|
dbGames = await transaction
|
||||||
|
.insert(games)
|
||||||
|
.values({
|
||||||
|
name: game.name,
|
||||||
|
slug: kebabCase(game.name || game.slug || ''),
|
||||||
|
description: game.description,
|
||||||
|
year_published: game.year_published,
|
||||||
|
url: externalUrl,
|
||||||
|
image_url: game.image_url,
|
||||||
|
thumb_url: game.thumb_url,
|
||||||
|
min_age: game.min_age,
|
||||||
|
min_players: game.min_players,
|
||||||
|
max_players: game.max_players,
|
||||||
|
min_playtime: game.min_playtime,
|
||||||
|
max_playtime: game.max_playtime
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: games.id,
|
||||||
|
set: {
|
||||||
|
name: game.name,
|
||||||
|
slug: kebabCase(game.name || game.slug || ''),
|
||||||
|
description: game.description,
|
||||||
|
year_published: game.year_published,
|
||||||
|
url: externalUrl,
|
||||||
|
image_url: game.image_url,
|
||||||
|
thumb_url: game.thumb_url,
|
||||||
|
min_age: game.min_age,
|
||||||
|
min_players: game.min_players,
|
||||||
|
max_players: game.max_players,
|
||||||
|
min_playtime: game.min_playtime,
|
||||||
|
max_playtime: game.max_playtime
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
const dbExternalIds = await transaction
|
||||||
|
.insert(externalIds)
|
||||||
|
.values({
|
||||||
|
externalId,
|
||||||
|
type: 'game'
|
||||||
|
})
|
||||||
|
.onConflictDoNothing()
|
||||||
|
.returning({ id: externalIds.id });
|
||||||
|
await transaction.insert(gamesToExternalIds).values({
|
||||||
|
gameId: dbGames[0].id,
|
||||||
|
externalId: dbExternalIds[0].id
|
||||||
|
}).onConflictDoNothing();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbGames.length === 0) {
|
||||||
|
return new Response('Could not create game', {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Created game', JSON.stringify(dbGames[0], null, 2));
|
||||||
|
return new Response(JSON.stringify(dbGames[0]), {
|
||||||
|
status: 201,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
throw new Error('Something went wrong creating Game');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createOrUpdateGame(locals: App.Locals, game: Games, externalId: string) {
|
||||||
|
if (!game || !externalId || externalId === '') {
|
||||||
|
error(400, 'Invalid Request');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const externalUrl = `https://boardgamegeek.com/boardgame/${externalId}`;
|
||||||
|
const dbExternalId = await db.query.externalIds.findFirst({
|
||||||
|
where: eq(externalIds.externalId, externalId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbExternalId) {
|
||||||
|
const foundGame = await db
|
||||||
|
.select({
|
||||||
|
id: games.id,
|
||||||
|
name: games.name,
|
||||||
|
slug: games.slug
|
||||||
|
})
|
||||||
|
.from(games)
|
||||||
|
.leftJoin(gamesToExternalIds, eq(gamesToExternalIds.externalId, externalId));
|
||||||
|
console.log('Game already exists', foundGame);
|
||||||
|
if (foundGame.length > 0) {
|
||||||
|
console.log('Game name', foundGame[0].name);
|
||||||
|
return new Response('Game already exists', {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Location: `${PUBLIC_SITE_URL}/api/game/${foundGame[0].id}`
|
||||||
|
},
|
||||||
|
status: 409
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dbGames: Games[] = [];
|
||||||
|
console.log('Creating game', JSON.stringify(game, null, 2));
|
||||||
|
await db.transaction(async (transaction) => {
|
||||||
|
dbGames = await transaction
|
||||||
|
.insert(games)
|
||||||
|
.values({
|
||||||
|
name: game.name,
|
||||||
|
slug: kebabCase(game.name || game.slug || ''),
|
||||||
|
description: game.description,
|
||||||
|
year_published: game.year_published,
|
||||||
|
url: game.url,
|
||||||
|
image_url: game.image_url,
|
||||||
|
thumb_url: game.thumb_url,
|
||||||
|
min_age: game.min_age,
|
||||||
|
min_players: game.min_players,
|
||||||
|
max_players: game.max_players,
|
||||||
|
min_playtime: game.min_playtime,
|
||||||
|
max_playtime: game.max_playtime
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: games.id,
|
||||||
|
set: {
|
||||||
|
name: game.name,
|
||||||
|
slug: kebabCase(game.name || game.slug || ''),
|
||||||
|
description: game.description,
|
||||||
|
year_published: game.year_published,
|
||||||
|
url: externalUrl,
|
||||||
|
image_url: game.image_url,
|
||||||
|
thumb_url: game.thumb_url,
|
||||||
|
min_age: game.min_age,
|
||||||
|
min_players: game.min_players,
|
||||||
|
max_players: game.max_players,
|
||||||
|
min_playtime: game.min_playtime,
|
||||||
|
max_playtime: game.max_playtime
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
const dbExternalIds = await transaction
|
||||||
|
.insert(externalIds)
|
||||||
|
.values({
|
||||||
|
externalId,
|
||||||
|
type: 'game'
|
||||||
|
})
|
||||||
|
.onConflictDoNothing()
|
||||||
|
.returning({ id: externalIds.id });
|
||||||
|
await transaction.insert(gamesToExternalIds).values({
|
||||||
|
gameId: dbGames[0].id,
|
||||||
|
externalId: dbExternalIds[0].id
|
||||||
|
}).onConflictDoNothing();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbGames.length === 0) {
|
||||||
|
return new Response('Could not create game', {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Created game', JSON.stringify(dbGames[0], null, 2));
|
||||||
|
return new Response(JSON.stringify(dbGames[0]), {
|
||||||
|
status: 201,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
throw new Error('Something went wrong creating Game');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateGame(locals: App.Locals, game: Games, id: string) {
|
||||||
|
if (!game || !id || id === '') {
|
||||||
|
error(400, 'Invalid Request');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dbGame = await db
|
||||||
|
.update(games)
|
||||||
|
.set({
|
||||||
|
name: game.name,
|
||||||
|
slug: kebabCase(game.name || game.slug || ''),
|
||||||
|
description: game.description,
|
||||||
|
year_published: game.year_published,
|
||||||
|
url: game.url,
|
||||||
|
image_url: game.image_url,
|
||||||
|
thumb_url: game.thumb_url,
|
||||||
|
min_age: game.min_age,
|
||||||
|
min_players: game.min_players,
|
||||||
|
max_players: game.max_players,
|
||||||
|
min_playtime: game.min_playtime,
|
||||||
|
max_playtime: game.max_playtime
|
||||||
|
})
|
||||||
|
.where(eq(games.id, id))
|
||||||
|
.returning();
|
||||||
|
return new Response(JSON.stringify(dbGame[0]), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return new Response('Could not get publishers', {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('Creating or updating game', JSON.stringify(game, null, 2));
|
||||||
|
// const categoryIds = game.categories;
|
||||||
|
// const mechanicIds = game.mechanics;
|
||||||
|
// const publisherIds = game.publishers;
|
||||||
|
// const designerIds = game.designers;
|
||||||
|
// const artistIds = game.artists;
|
||||||
|
// // const expansionIds = game.expansions;
|
||||||
|
// const externalUrl = `https://boardgamegeek.com/boardgame/${game.external_id}`;
|
||||||
|
// console.log('categoryIds', categoryIds);
|
||||||
|
// console.log('mechanicIds', mechanicIds);
|
||||||
|
// await db.transaction(async (transaction) => {
|
||||||
|
// const dbGame = await db.transaction(async (transaction) => {
|
||||||
|
// transaction.insert(games).values({
|
||||||
|
// name: game.name,
|
||||||
|
// slug: kebabCase(game.name || ''),
|
||||||
|
// description: game.description,
|
||||||
|
// external_id: game.external_id,
|
||||||
|
// url: externalUrl,
|
||||||
|
// thumb_url: game.thumb_url,
|
||||||
|
// image_url: game.image_url,
|
||||||
|
// min_age: game.min_age || 0,
|
||||||
|
// min_players: game.min_players || 0,
|
||||||
|
// max_players: game.max_players || 0,
|
||||||
|
// min_playtime: game.min_playtime || 0,
|
||||||
|
// max_playtime: game.max_playtime || 0,
|
||||||
|
// year_published: game.year_published || 0,
|
||||||
|
// last_sync_at: new Date(),
|
||||||
|
// }).onConflictDoUpdate({
|
||||||
|
// target: games.id, set: {
|
||||||
|
// name: game.name,
|
||||||
|
// slug: kebabCase(game.name),
|
||||||
|
// description: game.description,
|
||||||
|
// external_id: game.external_id,
|
||||||
|
// url: externalUrl,
|
||||||
|
// thumb_url: game.thumb_url,
|
||||||
|
// image_url: game.image_url,
|
||||||
|
// min_age: game.min_age || 0,
|
||||||
|
// min_players: game.min_players || 0,
|
||||||
|
// max_players: game.max_players || 0,
|
||||||
|
// min_playtime: game.min_playtime || 0,
|
||||||
|
// max_playtime: game.max_playtime || 0,
|
||||||
|
// year_published: game.year_published || 0,
|
||||||
|
// last_sync_at: new Date(),
|
||||||
|
// }
|
||||||
|
// }).returning();
|
||||||
|
// });
|
||||||
|
// // TODO: Connect to everything else
|
||||||
|
// });
|
||||||
|
// await db.insert(games).values({
|
||||||
|
// include: {
|
||||||
|
// mechanics: true,
|
||||||
|
// publishers: true,
|
||||||
|
// designers: true,
|
||||||
|
// artists: true,
|
||||||
|
// expansions: true
|
||||||
|
// },
|
||||||
|
// where: {
|
||||||
|
// external_id: game.external_id
|
||||||
|
// },
|
||||||
|
// create: {
|
||||||
|
// name: game.name,
|
||||||
|
// slug: kebabCase(game.name),
|
||||||
|
// description: game.description,
|
||||||
|
// external_id: game.external_id,
|
||||||
|
// url: externalUrl,
|
||||||
|
// thumb_url: game.thumb_url,
|
||||||
|
// image_url: game.image_url,
|
||||||
|
// min_age: game.min_age || 0,
|
||||||
|
// min_players: game.min_players || 0,
|
||||||
|
// max_players: game.max_players || 0,
|
||||||
|
// min_playtime: game.min_playtime || 0,
|
||||||
|
// max_playtime: game.max_playtime || 0,
|
||||||
|
// year_published: game.year_published || 0,
|
||||||
|
// last_sync_at: new Date(),
|
||||||
|
// categories: {
|
||||||
|
// connect: categoryIds
|
||||||
|
// },
|
||||||
|
// mechanics: {
|
||||||
|
// connect: mechanicIds
|
||||||
|
// },
|
||||||
|
// publishers: {
|
||||||
|
// connect: publisherIds
|
||||||
|
// },
|
||||||
|
// designers: {
|
||||||
|
// connect: designerIds
|
||||||
|
// },
|
||||||
|
// artists: {
|
||||||
|
// connect: artistIds
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// update: {
|
||||||
|
// name: game.name,
|
||||||
|
// slug: kebabCase(game.name),
|
||||||
|
// description: game.description,
|
||||||
|
// external_id: game.external_id,
|
||||||
|
// url: externalUrl,
|
||||||
|
// thumb_url: game.thumb_url,
|
||||||
|
// image_url: game.image_url,
|
||||||
|
// min_age: game.min_age || 0,
|
||||||
|
// min_players: game.min_players || 0,
|
||||||
|
// max_players: game.max_players || 0,
|
||||||
|
// min_playtime: game.min_playtime || 0,
|
||||||
|
// max_playtime: game.max_playtime || 0,
|
||||||
|
// year_published: game.year_published || 0,
|
||||||
|
// last_sync_at: new Date(),
|
||||||
|
// categories: {
|
||||||
|
// connect: categoryIds
|
||||||
|
// },
|
||||||
|
// mechanics: {
|
||||||
|
// connect: mechanicIds
|
||||||
|
// },
|
||||||
|
// publishers: {
|
||||||
|
// connect: publisherIds
|
||||||
|
// },
|
||||||
|
// designers: {
|
||||||
|
// connect: designerIds
|
||||||
|
// },
|
||||||
|
// artists: {
|
||||||
|
// connect: artistIds
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
77
src/lib/utils/db/mechanicUtils.ts
Normal file
77
src/lib/utils/db/mechanicUtils.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import kebabCase from 'just-kebab-case';
|
||||||
|
import db from '$lib/drizzle';
|
||||||
|
import { externalIds, mechanics, mechanicsToExternalIds, type Mechanics } from '../../../schema';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import { PUBLIC_SITE_URL } from '$env/static/public';
|
||||||
|
|
||||||
|
export async function createMechanic(locals: App.Locals, mechanic: Mechanics, externalId: string) {
|
||||||
|
if (!mechanic || !externalId || externalId === '') {
|
||||||
|
error(400, 'Invalid Request');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dbExternalId = await db.query.externalIds.findFirst({
|
||||||
|
where: eq(externalIds.externalId, externalId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbExternalId) {
|
||||||
|
const foundMechanic = await db
|
||||||
|
.select({
|
||||||
|
id: mechanics.id,
|
||||||
|
name: mechanics.name,
|
||||||
|
slug: mechanics.slug
|
||||||
|
})
|
||||||
|
.from(mechanics)
|
||||||
|
.leftJoin(mechanicsToExternalIds, eq(mechanicsToExternalIds.externalId, externalId));
|
||||||
|
console.log('Mechanic already exists', foundMechanic);
|
||||||
|
if (foundMechanic.length > 0) {
|
||||||
|
console.log('Mechanic name', foundMechanic[0].name);
|
||||||
|
return new Response('Mechanic already exists', {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Location: `${PUBLIC_SITE_URL}/api/mechanic/${foundMechanic[0].id}`
|
||||||
|
},
|
||||||
|
status: 409
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dbMechanics: Mechanics[] = [];
|
||||||
|
console.log('Creating mechanic', JSON.stringify(mechanic, null, 2));
|
||||||
|
await db.transaction(async (transaction) => {
|
||||||
|
dbMechanics = await transaction
|
||||||
|
.insert(mechanics)
|
||||||
|
.values({
|
||||||
|
name: mechanic.name,
|
||||||
|
slug: kebabCase(mechanic.name || mechanic.slug || '')
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
const dbExternalIds = await transaction
|
||||||
|
.insert(externalIds)
|
||||||
|
.values({
|
||||||
|
externalId,
|
||||||
|
type: 'mechanic'
|
||||||
|
})
|
||||||
|
.returning({ id: externalIds.id });
|
||||||
|
await transaction.insert(mechanicsToExternalIds).values({
|
||||||
|
mechanicId: dbMechanics[0].id,
|
||||||
|
externalId: dbExternalIds[0].id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbMechanics.length === 0) {
|
||||||
|
return new Response('Could not create mechanic', {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Created mechanic', JSON.stringify(dbMechanics[0], null, 2));
|
||||||
|
return new Response(JSON.stringify(dbMechanics[0]), {
|
||||||
|
status: 201,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
throw new Error('Something went wrong creating Mechanic');
|
||||||
|
}
|
||||||
|
}
|
||||||
125
src/lib/utils/db/publisherUtils.ts
Normal file
125
src/lib/utils/db/publisherUtils.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import kebabCase from 'just-kebab-case';
|
||||||
|
import db from '$lib/drizzle';
|
||||||
|
import {
|
||||||
|
externalIds,
|
||||||
|
publishersToExternalIds,
|
||||||
|
type Publishers,
|
||||||
|
publishers,
|
||||||
|
} from '../../../schema';
|
||||||
|
import { PUBLIC_SITE_URL } from '$env/static/public';
|
||||||
|
|
||||||
|
export async function getPublisher(locals: App.Locals, id: string) {
|
||||||
|
const publisher = await db.select().from(publishers).where(eq(publishers.id, id));
|
||||||
|
if (publisher.length === 0) {
|
||||||
|
error(404, 'not found');
|
||||||
|
}
|
||||||
|
return new Response(JSON.stringify(publisher[0]), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updatePublisher(locals: App.Locals, publisher: Publishers, id: string) {
|
||||||
|
if (!publisher || publisher.name === '' || !id || id === '') {
|
||||||
|
error(400, 'Invalid Request');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dbPublisher = await db
|
||||||
|
.update(publishers)
|
||||||
|
.set({
|
||||||
|
name: publisher.name,
|
||||||
|
slug: kebabCase(publisher.name || '')
|
||||||
|
})
|
||||||
|
.where(eq(publishers.id, id))
|
||||||
|
.returning();
|
||||||
|
return new Response(JSON.stringify(dbPublisher[0]), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return new Response('Could not get publishers', {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createPublisher(
|
||||||
|
locals: App.Locals,
|
||||||
|
publisher: Publishers,
|
||||||
|
externalId: string
|
||||||
|
) {
|
||||||
|
if (!publisher || !externalId || externalId === '') {
|
||||||
|
error(400, 'Invalid Request');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dbExternalId = await db.query.externalIds.findFirst({
|
||||||
|
where: eq(externalIds.externalId, externalId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbExternalId) {
|
||||||
|
const foundPublisher = await db
|
||||||
|
.select({
|
||||||
|
id: publishers.id,
|
||||||
|
name: publishers.name,
|
||||||
|
slug: publishers.slug
|
||||||
|
})
|
||||||
|
.from(publishers)
|
||||||
|
.leftJoin(publishersToExternalIds, eq(publishersToExternalIds.externalId, externalId));
|
||||||
|
console.log('Publisher already exists', foundPublisher);
|
||||||
|
if (foundPublisher.length > 0) {
|
||||||
|
console.log('Publisher name', foundPublisher[0].name);
|
||||||
|
return new Response('Publisher already exists', {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Location: `${PUBLIC_SITE_URL}/api/publisher/${foundPublisher[0].id}`
|
||||||
|
},
|
||||||
|
status: 409
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dbPublishers: Publishers[] = [];
|
||||||
|
console.log('Creating publisher', JSON.stringify(publisher, null, 2));
|
||||||
|
await db.transaction(async (transaction) => {
|
||||||
|
dbPublishers = await transaction
|
||||||
|
.insert(publishers)
|
||||||
|
.values({
|
||||||
|
name: publisher.name,
|
||||||
|
slug: kebabCase(publisher.name || publisher.slug || '')
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
const dbExternalIds = await transaction
|
||||||
|
.insert(externalIds)
|
||||||
|
.values({
|
||||||
|
externalId,
|
||||||
|
type: 'publisher'
|
||||||
|
})
|
||||||
|
.returning({ id: externalIds.id });
|
||||||
|
await transaction.insert(publishersToExternalIds).values({
|
||||||
|
publisherId: dbPublishers[0].id,
|
||||||
|
externalId: dbExternalIds[0].id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbPublishers.length === 0) {
|
||||||
|
return new Response('Could not create publisher', {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Created publisher', JSON.stringify(dbPublishers[0], null, 2));
|
||||||
|
return new Response(JSON.stringify(dbPublishers[0]), {
|
||||||
|
status: 201,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
throw new Error('Something went wrong creating Publisher');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,456 +0,0 @@
|
||||||
import type { Game } from '@prisma/client';
|
|
||||||
import kebabCase from 'just-kebab-case';
|
|
||||||
import type { BggLinkDto } from 'boardgamegeekclient/dist/esm/dto/concrete/subdto';
|
|
||||||
import prisma from '$lib/prisma';
|
|
||||||
import { mapAPIGameToBoredGame } from './gameMapper';
|
|
||||||
|
|
||||||
export async function createArtist(locals: App.Locals, externalArtist: BggLinkDto) {
|
|
||||||
try {
|
|
||||||
let dbArtist = await prisma.artist.findFirst({
|
|
||||||
where: {
|
|
||||||
external_id: externalArtist.id
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
slug: true,
|
|
||||||
external_id: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (dbArtist) {
|
|
||||||
console.log('Artist already exists', dbArtist.name);
|
|
||||||
return dbArtist;
|
|
||||||
}
|
|
||||||
console.log('Creating artist', JSON.stringify(externalArtist, null, 2));
|
|
||||||
let artist = await prisma.artist.create({
|
|
||||||
data: {
|
|
||||||
name: externalArtist.value,
|
|
||||||
external_id: externalArtist.id,
|
|
||||||
slug: kebabCase(externalArtist.value)
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
slug: true,
|
|
||||||
external_id: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Created artist', JSON.stringify(artist, null, 2));
|
|
||||||
return artist;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
throw new Error('Something went wrong creating Artist');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createDesigner(locals: App.Locals, externalDesigner: BggLinkDto) {
|
|
||||||
try {
|
|
||||||
let dbDesigner = await prisma.designer.findFirst({
|
|
||||||
where: {
|
|
||||||
external_id: externalDesigner.id
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
slug: true,
|
|
||||||
external_id: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (dbDesigner) {
|
|
||||||
console.log('Designer already exists', dbDesigner.name);
|
|
||||||
return dbDesigner;
|
|
||||||
}
|
|
||||||
console.log('Creating designer', JSON.stringify(externalDesigner, null, 2));
|
|
||||||
let designer = await prisma.designer.create({
|
|
||||||
data: {
|
|
||||||
name: externalDesigner.value,
|
|
||||||
external_id: externalDesigner.id,
|
|
||||||
slug: kebabCase(externalDesigner.value)
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
slug: true,
|
|
||||||
external_id: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Created designer', JSON.stringify(designer, null, 2));
|
|
||||||
return designer;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
throw new Error('Something went wrong creating Designer');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createPublisher(locals: App.Locals, externalPublisher: BggLinkDto) {
|
|
||||||
try {
|
|
||||||
let dbPublisher = await prisma.publisher.findFirst({
|
|
||||||
where: {
|
|
||||||
external_id: externalPublisher.id
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
slug: true,
|
|
||||||
external_id: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (dbPublisher) {
|
|
||||||
console.log('Publisher already exists', dbPublisher.name);
|
|
||||||
return dbPublisher;
|
|
||||||
}
|
|
||||||
console.log('Creating publisher', JSON.stringify(externalPublisher, null, 2));
|
|
||||||
let publisher = await prisma.publisher.create({
|
|
||||||
data: {
|
|
||||||
name: externalPublisher.value,
|
|
||||||
external_id: externalPublisher.id,
|
|
||||||
slug: kebabCase(externalPublisher.value)
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
slug: true,
|
|
||||||
external_id: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Created publisher', JSON.stringify(publisher, null, 2));
|
|
||||||
return publisher;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
throw new Error('Something went wrong creating Publisher');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createCategory(locals: App.Locals, externalCategory: BggLinkDto) {
|
|
||||||
try {
|
|
||||||
let dbCategory = await prisma.category.findFirst({
|
|
||||||
where: {
|
|
||||||
external_id: externalCategory.id
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
slug: true,
|
|
||||||
external_id: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (dbCategory) {
|
|
||||||
console.log('Category already exists', dbCategory.name);
|
|
||||||
return dbCategory;
|
|
||||||
}
|
|
||||||
console.log('Creating category', JSON.stringify(externalCategory, null, 2));
|
|
||||||
let category = await prisma.category.create({
|
|
||||||
data: {
|
|
||||||
name: externalCategory.value,
|
|
||||||
external_id: externalCategory.id,
|
|
||||||
slug: kebabCase(externalCategory.value)
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
slug: true,
|
|
||||||
external_id: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Created category', JSON.stringify(category, null, 2));
|
|
||||||
|
|
||||||
return category;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
throw new Error('Something went wrong creating Category');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createMechanic(locals: App.Locals, externalMechanic: BggLinkDto) {
|
|
||||||
try {
|
|
||||||
let dbMechanic = await prisma.mechanic.findFirst({
|
|
||||||
where: {
|
|
||||||
external_id: externalMechanic.id
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
slug: true,
|
|
||||||
external_id: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (dbMechanic) {
|
|
||||||
console.log('Mechanic already exists', dbMechanic.name);
|
|
||||||
return dbMechanic;
|
|
||||||
}
|
|
||||||
console.log('Creating mechanic', JSON.stringify(externalMechanic, null, 2));
|
|
||||||
let mechanic = await prisma.mechanic.upsert({
|
|
||||||
where: {
|
|
||||||
external_id: externalMechanic.id
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
name: externalMechanic.value,
|
|
||||||
external_id: externalMechanic.id,
|
|
||||||
slug: kebabCase(externalMechanic.value)
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
name: externalMechanic.value,
|
|
||||||
slug: kebabCase(externalMechanic.value)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Created mechanic', JSON.stringify(mechanic, null, 2));
|
|
||||||
|
|
||||||
return mechanic;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
throw new Error('Something went wrong creating Mechanic');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createExpansion(
|
|
||||||
locals: App.Locals,
|
|
||||||
game: Game,
|
|
||||||
externalExpansion: BggLinkDto,
|
|
||||||
gameIsExpansion: boolean,
|
|
||||||
eventFetch: Function
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
let dbExpansionGame = await prisma.game.findUnique({
|
|
||||||
where: {
|
|
||||||
external_id: externalExpansion.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!dbExpansionGame) {
|
|
||||||
const externalGameResponse = await eventFetch(
|
|
||||||
`/api/external/game/${externalExpansion.id}?simplified=true`
|
|
||||||
);
|
|
||||||
if (externalGameResponse.ok) {
|
|
||||||
const externalGame = await externalGameResponse.json();
|
|
||||||
console.log('externalGame', externalGame);
|
|
||||||
let boredGame = mapAPIGameToBoredGame(externalGame);
|
|
||||||
dbExpansionGame = await createOrUpdateGameMinimal(locals, boredGame);
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
`${gameIsExpansion ? 'Base game' : 'Expansion game'} not found and failed to create.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let dbExpansion;
|
|
||||||
let baseGameId;
|
|
||||||
let gameId;
|
|
||||||
if (gameIsExpansion) {
|
|
||||||
console.log(
|
|
||||||
'External expansion is expansion. Looking for base game',
|
|
||||||
JSON.stringify(game, null, 2)
|
|
||||||
);
|
|
||||||
dbExpansion = await prisma.expansion.findFirst({
|
|
||||||
where: {
|
|
||||||
game_id: dbExpansionGame.id
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
base_game_id: true,
|
|
||||||
game_id: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
baseGameId = game.id;
|
|
||||||
gameId = dbExpansionGame.id;
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
'External Expansion is base game. Looking for expansion',
|
|
||||||
JSON.stringify(game, null, 2)
|
|
||||||
);
|
|
||||||
dbExpansion = await prisma.expansion.findFirst({
|
|
||||||
where: {
|
|
||||||
base_game_id: dbExpansionGame.id
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
base_game_id: true,
|
|
||||||
game_id: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
baseGameId = dbExpansionGame.id;
|
|
||||||
gameId = game.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dbExpansion) {
|
|
||||||
console.log('Expansion already exists', JSON.stringify(dbExpansion, null, 2));
|
|
||||||
return dbExpansion;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Creating expansion. baseGameId: ${baseGameId}, gameId: ${gameId}`);
|
|
||||||
let expansion = await prisma.expansion.create({
|
|
||||||
data: {
|
|
||||||
base_game_id: baseGameId,
|
|
||||||
game_id: gameId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Created expansion', JSON.stringify(expansion, null, 2));
|
|
||||||
|
|
||||||
if (gameIsExpansion) {
|
|
||||||
console.log('Connecting current game to expansion');
|
|
||||||
await prisma.game.update({
|
|
||||||
where: {
|
|
||||||
id: gameId
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
expansions: {
|
|
||||||
connect: {
|
|
||||||
id: expansion.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('Connecting current game to base game');
|
|
||||||
await prisma.game.update({
|
|
||||||
where: {
|
|
||||||
id: baseGameId
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
expansions: {
|
|
||||||
connect: {
|
|
||||||
id: expansion.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return expansion;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
throw new Error('Something went wrong creating Expansion');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createOrUpdateGameMinimal(locals: App.Locals, game: Game) {
|
|
||||||
console.log('Creating or updating minimal game data', JSON.stringify(game, null, 2));
|
|
||||||
const externalUrl = `https://boardgamegeek.com/boardgame/${game.external_id}`;
|
|
||||||
return await prisma.game.upsert({
|
|
||||||
where: {
|
|
||||||
external_id: game.external_id
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
name: game.name,
|
|
||||||
slug: kebabCase(game.name),
|
|
||||||
description: game.description,
|
|
||||||
external_id: game.external_id,
|
|
||||||
url: externalUrl,
|
|
||||||
thumb_url: game.thumb_url,
|
|
||||||
image_url: game.image_url,
|
|
||||||
min_age: game.min_age || 0,
|
|
||||||
min_players: game.min_players || 0,
|
|
||||||
max_players: game.max_players || 0,
|
|
||||||
min_playtime: game.min_playtime || 0,
|
|
||||||
max_playtime: game.max_playtime || 0,
|
|
||||||
year_published: game.year_published || 0
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
name: game.name,
|
|
||||||
slug: kebabCase(game.name),
|
|
||||||
description: game.description,
|
|
||||||
external_id: game.external_id,
|
|
||||||
url: externalUrl,
|
|
||||||
thumb_url: game.thumb_url,
|
|
||||||
image_url: game.image_url,
|
|
||||||
min_age: game.min_age || 0,
|
|
||||||
min_players: game.min_players || 0,
|
|
||||||
max_players: game.max_players || 0,
|
|
||||||
min_playtime: game.min_playtime || 0,
|
|
||||||
max_playtime: game.max_playtime || 0,
|
|
||||||
year_published: game.year_published || 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createOrUpdateGame(locals: App.Locals, game: Game) {
|
|
||||||
console.log('Creating or updating game', JSON.stringify(game, null, 2));
|
|
||||||
const categoryIds = game.categories;
|
|
||||||
const mechanicIds = game.mechanics;
|
|
||||||
const publisherIds = game.publishers;
|
|
||||||
const designerIds = game.designers;
|
|
||||||
const artistIds = game.artists;
|
|
||||||
// const expansionIds = game.expansions;
|
|
||||||
const externalUrl = `https://boardgamegeek.com/boardgame/${game.external_id}`;
|
|
||||||
console.log('categoryIds', categoryIds);
|
|
||||||
console.log('mechanicIds', mechanicIds);
|
|
||||||
return await prisma.game.upsert({
|
|
||||||
include: {
|
|
||||||
mechanics: true,
|
|
||||||
publishers: true,
|
|
||||||
designers: true,
|
|
||||||
artists: true,
|
|
||||||
expansions: true
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
external_id: game.external_id
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
name: game.name,
|
|
||||||
slug: kebabCase(game.name),
|
|
||||||
description: game.description,
|
|
||||||
external_id: game.external_id,
|
|
||||||
url: externalUrl,
|
|
||||||
thumb_url: game.thumb_url,
|
|
||||||
image_url: game.image_url,
|
|
||||||
min_age: game.min_age || 0,
|
|
||||||
min_players: game.min_players || 0,
|
|
||||||
max_players: game.max_players || 0,
|
|
||||||
min_playtime: game.min_playtime || 0,
|
|
||||||
max_playtime: game.max_playtime || 0,
|
|
||||||
year_published: game.year_published || 0,
|
|
||||||
last_sync_at: new Date(),
|
|
||||||
categories: {
|
|
||||||
connect: categoryIds
|
|
||||||
},
|
|
||||||
mechanics: {
|
|
||||||
connect: mechanicIds
|
|
||||||
},
|
|
||||||
publishers: {
|
|
||||||
connect: publisherIds
|
|
||||||
},
|
|
||||||
designers: {
|
|
||||||
connect: designerIds
|
|
||||||
},
|
|
||||||
artists: {
|
|
||||||
connect: artistIds
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
name: game.name,
|
|
||||||
slug: kebabCase(game.name),
|
|
||||||
description: game.description,
|
|
||||||
external_id: game.external_id,
|
|
||||||
url: externalUrl,
|
|
||||||
thumb_url: game.thumb_url,
|
|
||||||
image_url: game.image_url,
|
|
||||||
min_age: game.min_age || 0,
|
|
||||||
min_players: game.min_players || 0,
|
|
||||||
max_players: game.max_players || 0,
|
|
||||||
min_playtime: game.min_playtime || 0,
|
|
||||||
max_playtime: game.max_playtime || 0,
|
|
||||||
year_published: game.year_published || 0,
|
|
||||||
last_sync_at: new Date(),
|
|
||||||
categories: {
|
|
||||||
connect: categoryIds
|
|
||||||
},
|
|
||||||
mechanics: {
|
|
||||||
connect: mechanicIds
|
|
||||||
},
|
|
||||||
publishers: {
|
|
||||||
connect: publisherIds
|
|
||||||
},
|
|
||||||
designers: {
|
|
||||||
connect: designerIds
|
|
||||||
},
|
|
||||||
artists: {
|
|
||||||
connect: artistIds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { GameType, SavedGameType } from '$lib/types';
|
import type { GameType, SavedGameType } from '$lib/types';
|
||||||
import type { Game } from '@prisma/client';
|
import type { Game } from '@prisma/client';
|
||||||
import kebabCase from 'just-kebab-case';
|
import kebabCase from 'just-kebab-case';
|
||||||
|
import type { Games } from '../../schema';
|
||||||
|
|
||||||
export function convertToSavedGame(game: GameType | SavedGameType): SavedGameType {
|
export function convertToSavedGame(game: GameType | SavedGameType): SavedGameType {
|
||||||
return {
|
return {
|
||||||
|
|
@ -43,10 +44,9 @@ export function mapSavedGameToGame(game: SavedGameType): GameType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapAPIGameToBoredGame(game: GameType): Game {
|
export function mapAPIGameToBoredGame(game: GameType): Games {
|
||||||
// TODO: Fix types
|
// TODO: Fix types
|
||||||
return {
|
return {
|
||||||
external_id: game.external_id,
|
|
||||||
name: game.name,
|
name: game.name,
|
||||||
slug: kebabCase(game.name),
|
slug: kebabCase(game.name),
|
||||||
thumb_url: game.thumbnail,
|
thumb_url: game.thumbnail,
|
||||||
|
|
@ -57,7 +57,6 @@ export function mapAPIGameToBoredGame(game: GameType): Game {
|
||||||
min_playtime: game.min_playtime,
|
min_playtime: game.min_playtime,
|
||||||
max_playtime: game.max_playtime,
|
max_playtime: game.max_playtime,
|
||||||
min_age: game.min_age,
|
min_age: game.min_age,
|
||||||
description: game.description,
|
description: game.description
|
||||||
playtime: game.playing_time
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
106
src/lib/validations/account.ts
Normal file
106
src/lib/validations/account.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { userSchema } from './zod-schemas';
|
||||||
|
|
||||||
|
export const profileSchema = userSchema.pick({
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
username: true
|
||||||
|
});
|
||||||
|
|
||||||
|
export const changeEmailSchema = userSchema.pick({
|
||||||
|
email: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const changeUserPasswordSchema = z
|
||||||
|
.object({
|
||||||
|
current_password: z.string({ required_error: 'Current Password is required' }),
|
||||||
|
password: z.string({ required_error: 'Password is required' }).trim(),
|
||||||
|
confirm_password: z.string({ required_error: 'Confirm Password is required' }).trim()
|
||||||
|
})
|
||||||
|
.superRefine(({ confirm_password, password }, ctx) => {
|
||||||
|
refinePasswords(confirm_password, password, ctx);
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ChangeUserPasswordSchema = typeof changeUserPasswordSchema;
|
||||||
|
|
||||||
|
export const updateUserPasswordSchema = userSchema
|
||||||
|
.pick({ password: true, confirm_password: true })
|
||||||
|
.superRefine(({ confirm_password, password }, ctx) => {
|
||||||
|
refinePasswords(confirm_password, password, ctx);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const refinePasswords = async function (
|
||||||
|
confirm_password: string,
|
||||||
|
password: string,
|
||||||
|
ctx: z.RefinementCtx
|
||||||
|
) {
|
||||||
|
comparePasswords(confirm_password, password, ctx);
|
||||||
|
checkPasswordStrength(password, ctx);
|
||||||
|
};
|
||||||
|
|
||||||
|
const comparePasswords = async function (
|
||||||
|
confirm_password: string,
|
||||||
|
password: string,
|
||||||
|
ctx: z.RefinementCtx
|
||||||
|
) {
|
||||||
|
if (confirm_password !== password) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: 'custom',
|
||||||
|
message: 'Password and Confirm Password must match',
|
||||||
|
path: ['confirm_password']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkPasswordStrength = async function (password: string, ctx: z.RefinementCtx) {
|
||||||
|
const minimumLength = password.length < 8;
|
||||||
|
const maximumLength = password.length > 128;
|
||||||
|
const containsUppercase = (ch: string) => /[A-Z]/.test(ch);
|
||||||
|
const containsLowercase = (ch: string) => /[a-z]/.test(ch);
|
||||||
|
const containsSpecialChar = (ch: string) => /[`!@#$%^&*()_\-+=\[\]{};':"\\|,.<>\/?~ ]/.test(ch);
|
||||||
|
let countOfUpperCase = 0,
|
||||||
|
countOfLowerCase = 0,
|
||||||
|
countOfNumbers = 0,
|
||||||
|
countOfSpecialChar = 0;
|
||||||
|
for (let i = 0; i < password.length; i++) {
|
||||||
|
const char = password.charAt(i);
|
||||||
|
if (!isNaN(+char)) {
|
||||||
|
countOfNumbers++;
|
||||||
|
} else if (containsUppercase(char)) {
|
||||||
|
countOfUpperCase++;
|
||||||
|
} else if (containsLowercase(char)) {
|
||||||
|
countOfLowerCase++;
|
||||||
|
} else if (containsSpecialChar(char)) {
|
||||||
|
countOfSpecialChar++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let errorMessage = 'Your password:';
|
||||||
|
|
||||||
|
if (countOfLowerCase < 1) {
|
||||||
|
errorMessage = ' Must have at least one lowercase letter. ';
|
||||||
|
}
|
||||||
|
if (countOfNumbers < 1) {
|
||||||
|
errorMessage += ' Must have at least one number. ';
|
||||||
|
}
|
||||||
|
if (countOfUpperCase < 1) {
|
||||||
|
errorMessage += ' Must have at least one uppercase letter. ';
|
||||||
|
}
|
||||||
|
if (countOfSpecialChar < 1) {
|
||||||
|
errorMessage += ' Must have at least one special character.';
|
||||||
|
}
|
||||||
|
if (minimumLength) {
|
||||||
|
errorMessage += ' Be at least 8 characters long.';
|
||||||
|
}
|
||||||
|
if (maximumLength) {
|
||||||
|
errorMessage += ' Be less than 128 characters long.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorMessage.length > 'Your password:'.length) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: 'custom',
|
||||||
|
message: errorMessage,
|
||||||
|
path: ['password']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
21
src/lib/validations/auth.ts
Normal file
21
src/lib/validations/auth.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { refinePasswords } from "./account";
|
||||||
|
import { userSchema } from "./zod-schemas";
|
||||||
|
|
||||||
|
export const signUpSchema = userSchema
|
||||||
|
.pick({
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
email: true,
|
||||||
|
username: true,
|
||||||
|
password: true,
|
||||||
|
confirm_password: true,
|
||||||
|
terms: true
|
||||||
|
})
|
||||||
|
.superRefine(({ confirm_password, password }, ctx) => {
|
||||||
|
refinePasswords(confirm_password, password, ctx);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const signInSchema = userSchema.pick({
|
||||||
|
username: true,
|
||||||
|
password: true
|
||||||
|
});
|
||||||
44
src/lib/validations/zod-schemas.ts
Normal file
44
src/lib/validations/zod-schemas.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export type ListGame = {
|
||||||
|
id: string;
|
||||||
|
game_name: string;
|
||||||
|
game_id: string;
|
||||||
|
collection_id: string;
|
||||||
|
wishlist_id: string;
|
||||||
|
times_played: number;
|
||||||
|
thumb_url: string | null;
|
||||||
|
in_collection: boolean;
|
||||||
|
in_wishlist: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const modifyListGameSchema = z.object({
|
||||||
|
id: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ModifyListGame = typeof modifyListGameSchema;
|
||||||
|
|
||||||
|
export const userSchema = z.object({
|
||||||
|
firstName: z.string().trim().optional(),
|
||||||
|
lastName: z.string().trim().optional(),
|
||||||
|
email: z.string()
|
||||||
|
.trim()
|
||||||
|
.max(64, { message: 'Email must be less than 64 characters' })
|
||||||
|
.optional(),
|
||||||
|
username: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(3, { message: 'Username must be at least 3 characters' })
|
||||||
|
.max(50, { message: 'Username must be less than 50 characters' }),
|
||||||
|
password: z
|
||||||
|
.string({ required_error: 'Password is required' })
|
||||||
|
.trim(),
|
||||||
|
confirm_password: z
|
||||||
|
.string({ required_error: 'Confirm Password is required' })
|
||||||
|
.trim(),
|
||||||
|
verified: z.boolean().default(false),
|
||||||
|
token: z.string().optional(),
|
||||||
|
receiveEmail: z.boolean().default(false),
|
||||||
|
createdAt: z.date().optional(),
|
||||||
|
updatedAt: z.date().optional()
|
||||||
|
});
|
||||||
|
|
@ -117,6 +117,10 @@ export const search_schema = z
|
||||||
|
|
||||||
export type SearchSchema = typeof search_schema;
|
export type SearchSchema = typeof search_schema;
|
||||||
|
|
||||||
|
export const BggForm = z.object({
|
||||||
|
link: z.string().trim().startsWith('https://boardgamegeek.com/boardgame/')
|
||||||
|
})
|
||||||
|
|
||||||
export const collection_search_schema = Search.extend({
|
export const collection_search_schema = Search.extend({
|
||||||
collection_id: z.string()
|
collection_id: z.string()
|
||||||
});
|
});
|
||||||
|
|
@ -222,7 +226,6 @@ const gameSchema = z.object({
|
||||||
image_url: z.string().optional(),
|
image_url: z.string().optional(),
|
||||||
thumb_url: z.string().optional(),
|
thumb_url: z.string().optional(),
|
||||||
url: z.string().optional(),
|
url: z.string().optional(),
|
||||||
rules_url: z.string().optional(),
|
|
||||||
weight_amount: z.number().optional(),
|
weight_amount: z.number().optional(),
|
||||||
weight_units: z.enum(['Medium', 'Heavy']).optional(),
|
weight_units: z.enum(['Medium', 'Heavy']).optional(),
|
||||||
categories: z.array(category_schema).optional(),
|
categories: z.array(category_schema).optional(),
|
||||||
|
|
|
||||||
25
src/migrate.ts
Normal file
25
src/migrate.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import 'dotenv/config';
|
||||||
|
import postgres from 'postgres';
|
||||||
|
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||||
|
import { migrate } from 'drizzle-orm/postgres-js/migrator';
|
||||||
|
|
||||||
|
const connection = postgres({
|
||||||
|
host: process.env.DATABASE_HOST || 'localhost',
|
||||||
|
port: 5432,
|
||||||
|
user: process.env.DATABASE_USER || 'root',
|
||||||
|
password: process.env.DATABASE_PASSWORD || '',
|
||||||
|
database: process.env.DATABASE_DB || 'boredgame',
|
||||||
|
ssl: 'require',
|
||||||
|
max: 1
|
||||||
|
});
|
||||||
|
const db = drizzle(connection);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await migrate(db, { migrationsFolder: 'drizzle' });
|
||||||
|
console.log('Migrations complete');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
await connection.end();
|
||||||
|
process.exit();
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from 'sveltekit-flash-message/server'
|
||||||
import type { PageServerData } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { notSignedInMessage } from '$lib/flashMessages';
|
||||||
|
|
||||||
export const load: PageServerData = async function ({ locals }) {
|
export async function load(event) {
|
||||||
if (!locals?.user?.role?.includes('admin')) redirect(302, '/');
|
const { locals } = event;
|
||||||
|
if (!locals?.user?.role?.includes('admin')) {
|
||||||
|
redirect(302, '/login', notSignedInMessage, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,19 @@
|
||||||
import { type Actions, error, fail, redirect } from '@sveltejs/kit';
|
import { type Actions, error, fail } from '@sveltejs/kit';
|
||||||
|
import { and, eq } from 'drizzle-orm';
|
||||||
import { superValidate } from 'sveltekit-superforms/server';
|
import { superValidate } from 'sveltekit-superforms/server';
|
||||||
import prisma from '$lib/prisma';
|
import { zod } from 'sveltekit-superforms/adapters';
|
||||||
import { modifyListGameSchema, type ListGame } from '$lib/config/zod-schemas.js';
|
import { redirect } from 'sveltekit-flash-message/server'
|
||||||
|
import { modifyListGameSchema, type ListGame } from '$lib/validations/zod-schemas';
|
||||||
import { search_schema } from '$lib/zodValidation.js';
|
import { search_schema } from '$lib/zodValidation.js';
|
||||||
import type { PageServerLoad } from './$types';
|
import db from '$lib/drizzle';
|
||||||
|
import { collection_items, collections, games } from '../../../../schema';
|
||||||
|
import { notSignedInMessage } from '$lib/flashMessages';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ fetch, url, locals }) => {
|
export async function load(event) {
|
||||||
|
const { url, locals } = event;
|
||||||
const user = locals.user;
|
const user = locals.user;
|
||||||
if (!user) {
|
if (!user) {
|
||||||
redirect(302, '/login');
|
redirect(302, '/login', notSignedInMessage, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('locals load', locals);
|
// console.log('locals load', locals);
|
||||||
|
|
@ -24,14 +29,12 @@ export const load: PageServerLoad = async ({ fetch, url, locals }) => {
|
||||||
skip
|
skip
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchForm = await superValidate(searchData, search_schema);
|
const searchForm = await superValidate(searchData, zod(search_schema));
|
||||||
const listManageForm = await superValidate(modifyListGameSchema);
|
const listManageForm = await superValidate(zod(modifyListGameSchema));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let collection = await prisma.collection.findUnique({
|
const collection = await db.query.collections.findFirst({
|
||||||
where: {
|
where: eq(collections.user_id, user.id)
|
||||||
user_id: user.id
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
console.log('collection', collection);
|
console.log('collection', collection);
|
||||||
|
|
||||||
|
|
@ -45,27 +48,25 @@ export const load: PageServerLoad = async ({ fetch, url, locals }) => {
|
||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
let collection_items = await prisma.collectionItem.findMany({
|
const collectionItems = await db.query.collection_items.findMany({
|
||||||
where: {
|
where: eq(collection_items.collection_id, collection.id),
|
||||||
collection_id: collection.id
|
with: {
|
||||||
},
|
|
||||||
include: {
|
|
||||||
game: {
|
game: {
|
||||||
select: {
|
columns: {
|
||||||
id: true,
|
id: true,
|
||||||
name: true,
|
name: true,
|
||||||
thumb_url: true
|
thumb_url: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
skip,
|
offset: skip,
|
||||||
take: limit
|
limit
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('collection_items', collection_items);
|
console.log('collection_items', collectionItems);
|
||||||
|
|
||||||
let collectionItems: ListGame[] = [];
|
const items: ListGame[] = [];
|
||||||
for (const item of collection_items) {
|
for (const item of collectionItems) {
|
||||||
console.log('item', item);
|
console.log('item', item);
|
||||||
const game = item.game;
|
const game = item.game;
|
||||||
if (game) {
|
if (game) {
|
||||||
|
|
@ -77,14 +78,14 @@ export const load: PageServerLoad = async ({ fetch, url, locals }) => {
|
||||||
times_played: item.times_played,
|
times_played: item.times_played,
|
||||||
in_collection: true
|
in_collection: true
|
||||||
};
|
};
|
||||||
collectionItems.push(collectionItem);
|
items.push(collectionItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
searchForm,
|
searchForm,
|
||||||
listManageForm,
|
listManageForm,
|
||||||
collection: collectionItems
|
collection: items
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
@ -100,18 +101,15 @@ export const load: PageServerLoad = async ({ fetch, url, 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 form = await superValidate(event, zod(modifyListGameSchema));
|
||||||
const form = await superValidate(event, modifyListGameSchema);
|
|
||||||
|
|
||||||
if (!event.locals.user) {
|
if (!event.locals.user) {
|
||||||
throw fail(401);
|
throw fail(401);
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = event.locals.user;
|
const user = event.locals.user;
|
||||||
let game = await prisma.game.findUnique({
|
const game = await db.query.games.findFirst({
|
||||||
where: {
|
where: eq(games.id, form.data.id)
|
||||||
id: form.data.id
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!game) {
|
if (!game) {
|
||||||
|
|
@ -125,10 +123,8 @@ export const actions: Actions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const collection = await prisma.collection.findUnique({
|
const collection = await db.query.collections.findFirst({
|
||||||
where: {
|
where: eq(collections.user_id, user.id)
|
||||||
user_id: user.id
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
|
|
@ -136,12 +132,10 @@ export const actions: Actions = {
|
||||||
return error(404, 'Wishlist not found');
|
return error(404, 'Wishlist not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.collectionItem.create({
|
await db.insert(collection_items).values({
|
||||||
data: {
|
game_id: game.id,
|
||||||
game_id: game.id,
|
collection_id: collection.id,
|
||||||
collection_id: collection.id,
|
times_played: 0
|
||||||
times_played: 0
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -153,14 +147,14 @@ 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);
|
||||||
}
|
}
|
||||||
return error(405, 'Method not allowed');
|
return error(405, 'Method not allowed');
|
||||||
},
|
},
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
|
|
@ -168,17 +162,15 @@ export const actions: Actions = {
|
||||||
},
|
},
|
||||||
// Remove game from a wishlist
|
// Remove game from a wishlist
|
||||||
remove: async (event) => {
|
remove: async (event) => {
|
||||||
const { params, locals, request } = event;
|
const { locals } = event;
|
||||||
const form = await superValidate(event, modifyListGameSchema);
|
const form = await superValidate(event, zod(modifyListGameSchema));
|
||||||
|
|
||||||
if (!locals.user) {
|
if (!locals.user) {
|
||||||
throw fail(401);
|
throw fail(401);
|
||||||
}
|
}
|
||||||
|
|
||||||
let game = await prisma.game.findUnique({
|
const game = await db.query.games.findFirst({
|
||||||
where: {
|
where: eq(games.id, form.data.id)
|
||||||
id: form.data.id
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!game) {
|
if (!game) {
|
||||||
|
|
@ -187,10 +179,8 @@ export const actions: Actions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const collection = await prisma.collection.findUnique({
|
const collection = await db.query.collections.findFirst({
|
||||||
where: {
|
where: eq(collections.user_id, locals.user.id)
|
||||||
user_id: locals.user.id
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
|
|
@ -198,12 +188,10 @@ export const actions: Actions = {
|
||||||
return error(404, 'Collection not found');
|
return error(404, 'Collection not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.collectionItem.delete({
|
await db.delete(collection_items).where(and(
|
||||||
where: {
|
eq(collection_items.collection_id, collection.id),
|
||||||
collection_id: collection.id,
|
eq(collection_items.game_id, game.id)
|
||||||
game_id: game.id
|
));
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
form
|
form
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue