Merge pull request #14 from BradNut/drizzle

Merge Drizzle to shadcn
This commit is contained in:
Bradley Shellnut 2024-03-05 06:24:26 +00:00 committed by GitHub
commit 196dfd48f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
137 changed files with 25129 additions and 2501 deletions

20
drizzle.config.ts Normal file
View 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
});

View 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 $$;

View 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());

View 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";

View file

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

View 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 $$;

View 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 $$;

View 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;

View 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 $$;

View 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;

View 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;

View file

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

View 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 $$;

View 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;

View 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";

View file

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

View 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 $$;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

118
drizzle/meta/_journal.json Normal file
View 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
}
]
}

View file

@ -16,57 +16,61 @@
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write .",
"site:update": "pnpm update -i -L",
"db:studio": "prisma studio",
"db:push": "prisma db push",
"db:generate": "prisma generate",
"db:seed": "prisma db seed",
"i-changed-the-schema": "pnpm run db:push && pnpm run db:generate"
"generate": "drizzle-kit generate:pg",
"migrate": "tsx ./src/migrate.ts",
"seed": "tsx ./src/seed.ts",
"push": "drizzle-kit push:pg"
},
"prisma": {
"seed": "node --loader ts-node/esm prisma/seed.ts"
},
"devDependencies": {
"@melt-ui/pp": "^0.3.0",
"@melt-ui/svelte": "^0.73.0",
"@playwright/test": "^1.41.2",
"@melt-ui/svelte": "^0.75.2",
"@playwright/test": "^1.42.0",
"@resvg/resvg-js": "^2.6.0",
"@sveltejs/adapter-auto": "^3.1.1",
"@sveltejs/enhanced-img": "^0.1.8",
"@sveltejs/kit": "^2.5.0",
"@sveltejs/kit": "^2.5.2",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@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/parser": "^6.21.0",
"autoprefixer": "^10.4.17",
"eslint": "^8.56.0",
"autoprefixer": "^10.4.18",
"dotenv": "^16.4.5",
"drizzle-kit": "^0.20.14",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1",
"just-clone": "^6.2.0",
"just-debounce-it": "^3.2.0",
"postcss": "^8.4.35",
"postcss-import": "^16.0.0",
"postcss-import": "^16.0.1",
"postcss-load-config": "^5.0.3",
"postcss-preset-env": "^9.3.0",
"postcss-preset-env": "^9.4.0",
"prettier": "^3.2.5",
"prettier-plugin-svelte": "^3.1.2",
"prettier-plugin-svelte": "^3.2.2",
"prisma": "^5.9.1",
"sass": "^1.70.0",
"sass": "^1.71.1",
"satori": "^0.10.13",
"satori-html": "^0.3.2",
"svelte": "^4.2.10",
"svelte-check": "^3.6.4",
"svelte-meta-tags": "^3.1.0",
"svelte": "^4.2.12",
"svelte-check": "^3.6.6",
"svelte-meta-tags": "^3.1.1",
"svelte-preprocess": "^5.1.3",
"svelte-sequential-preprocessor": "^2.0.1",
"sveltekit-flash-message": "^2.4.1",
"sveltekit-superforms": "^1.13.4",
"sveltekit-flash-message": "^2.4.2",
"sveltekit-rate-limiter": "^0.4.3",
"sveltekit-superforms": "^2.7.0",
"tailwindcss": "^3.4.1",
"ts-node": "^10.9.2",
"tslib": "^2.6.1",
"tsx": "^4.7.1",
"typescript": "^5.3.3",
"vite": "^5.1.1",
"vitest": "^1.2.2",
"vite": "^5.1.5",
"vitest": "^1.3.1",
"zod": "^3.22.4"
},
"type": "module",
@ -75,37 +79,45 @@
"pnpm": ">=8"
},
"dependencies": {
"@fontsource/fira-mono": "^5.0.8",
"@fontsource/fira-mono": "^5.0.12",
"@iconify-icons/line-md": "^1.2.26",
"@iconify-icons/mdi": "^1.2.47",
"@lucia-auth/adapter-drizzle": "^1.0.2",
"@lucia-auth/adapter-prisma": "4.0.0",
"@lukeed/uuid": "^2.0.1",
"@neondatabase/serverless": "^0.9.0",
"@paralleldrive/cuid2": "^2.2.2",
"@planetscale/database": "^1.16.0",
"@prisma/client": "^5.9.1",
"@sentry/sveltekit": "^7.88.0",
"@sentry/sveltekit": "^7.100.1",
"@sveltejs/adapter-vercel": "^5.1.0",
"@types/feather-icons": "^4.29.4",
"@vercel/og": "^0.5.20",
"bits-ui": "^0.17.0",
"bits-ui": "^0.19.3",
"boardgamegeekclient": "^1.9.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cookie": "^0.6.0",
"drizzle-orm": "^0.29.4",
"feather-icons": "^4.29.1",
"formsnap": "^0.4.3",
"html-entities": "^2.4.0",
"formsnap": "^0.5.1",
"html-entities": "^2.5.2",
"iconify-icon": "^2.0.0",
"just-kebab-case": "^4.2.0",
"loader": "^2.1.1",
"lucia": "3.0.1",
"lucide-svelte": "^0.323.0",
"open-props": "^1.6.18",
"oslo": "^1.1.0",
"lucide-svelte": "^0.344.0",
"mysql2": "^3.9.2",
"nanoid": "^5.0.6",
"open-props": "^1.6.20",
"oslo": "^1.1.3",
"pg": "^8.11.3",
"postgres": "^3.4.3",
"radix-svelte": "^0.9.0",
"svelte-french-toast": "^1.2.0",
"svelte-lazy-loader": "^1.0.0",
"tailwind-merge": "^2.2.1",
"tailwind-variants": "^0.1.20",
"tailwind-variants": "^0.2.0",
"tailwindcss-animate": "^1.0.6",
"zod-to-json-schema": "^3.22.4"
}

File diff suppressed because it is too large Load diff

View file

@ -158,7 +158,6 @@ model Game {
image_url String?
thumb_url String?
url String?
rules_url String?
categories Category[]
mechanics Mechanic[]
designers Designer[]

View file

@ -4,7 +4,7 @@
<meta name="robots" content="noindex, nofollow" />
<meta charset="utf-8" />
<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" />
<script>
// const htmlElement = document.documentElement;

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 35 KiB

View file

@ -1,12 +1,9 @@
<script>
import { PUBLIC_SITE_URL } from "$env/static/public";
</script>
<footer>
<p>Built by <a target="__blank" href="https://bradleyshellnut.com">Bradley Shellnut</a></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>
<p>Bored Game &copy; {new Date().getFullYear()} | Built by <a target="__blank" href="https://bradleyshellnut.com">Bradley Shellnut</a> | {PUBLIC_SITE_URL}</p>
</footer>
<style lang="postcss">

View file

@ -1,23 +1,24 @@
<script lang="ts">
import { applyAction, enhance } from '$app/forms';
import toast from 'svelte-french-toast';
import { ListChecks, ListTodo, LogOut, User } from 'lucide-svelte';
import * as DropdownMenu from "$components/ui/dropdown-menu";
import * as Avatar from "$components/ui/avatar";
import Logo from '$components/logo.svelte';
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() || '?';
</script>
<header>
<div class="corner">
<a href="/" class="logo" title="Home">
<a href="/" title="Home">
<div class="logo-image">
<Logo />
</div>
Bored Game
</a>
</div>
<!-- <TextSearch /> -->
@ -113,23 +114,23 @@
}
.corner {
width: 3em;
height: 3em;
margin-left: 1rem;
}
.corner a {
display: flex;
align-items: center;
justify-content: center;
place-items: center;
gap: 0.5rem;
width: 100%;
height: 100%;
font-size: 1.125rem;
line-height: 1.75rem;
font-weight: 500;
}
.logo-image {
width: 2rem;
height: 2rem;
object-fit: contain;
}
nav {
@ -154,8 +155,4 @@
text-decoration: underline;
color: var(--accent-color);
}
.separator {
@apply m-[5px] h-[1px] bg-black;
}
</style>

View file

@ -1,6 +1,5 @@
<script lang="ts">
export let kind = 'primary';
export let size;
export let icon = false;
export let disabled = false;
</script>
@ -27,18 +26,4 @@
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>

View file

@ -1,5 +1,14 @@
<script lang="ts">
import logo from '$lib/assets/bored-game.png';
</script>
<img src={logo} alt="Bored Game Home" />
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-go-game" viewBox="0 0 24 24"
stroke-width="1" stroke="var(--fg)" fill="none" stroke-linecap="round" stroke-linejoin="round">
<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" />
<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

View file

@ -1,38 +1,54 @@
<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 * 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>
<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>
<Form.Item>
<Form.Field {config} name="q">
<Form.Label for="label">Search</Form.Label>
<Form.Input />
<Form.Validation />
</Form.Field>
<Form.Field {config} name="skip">
<Form.Input type="hidden" />
</Form.Field>
<Form.Field {config} name="limit">
<Form.Input type="hidden" />
</Form.Field>
</Form.Item>
<Form.Field {form} name="q">
<Form.Control let:attrs>
<Form.Label>Search</Form.Label>
<Input {...attrs} bind:value={$formData.q} />
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="skip">
<Form.Control let:attrs>
<Input type="hidden" />
</Form.Control>
</Form.Field>
<Form.Field {form} name="limit">
<Form.Control let:attrs>
<Input type="hidden" />
</Form.Control>
</Form.Field>
</fieldset>
<fieldset>
<div class="flex items-center space-x-2">
<Form.Field {config} name="exact">
<Form.Label>Exact Search</Form.Label>
<Form.Checkbox class="mt-0" />
<Form.Field {form} name="exact">
<Form.Control let:attrs>
<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>
</div>
</fieldset>
<Form.Button>Submit</Form.Button>
</Form.Root>
</form>
</search>
<style lang="postcss">

View file

@ -1,16 +1,18 @@
<script lang="ts">
import { zodClient } from 'sveltekit-superforms/adapters';
import { ConicGradient } from '@skeletonlabs/skeleton';
import type { ConicStop } from '@skeletonlabs/skeleton';
import { i } from "@inlang/sdk-js";
import { superForm } from 'sveltekit-superforms/client';
//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 { i } from "@inlang/sdk-js";
import { signInSchema } from '$lib/validations/auth';
export let data;
const signInSchema = userSchema.pick({ email: true, password: true });
const { form, errors, enhance, delayed } = superForm(data.form, {
taintedMessage: null,
validators: signInSchema,
validators: zodClient(signInSchema),
delayMs: 0
});
const conicStops: ConicStop[] = [

View file

@ -1,19 +1,13 @@
<script lang="ts">
import { zodClient } from 'sveltekit-superforms/adapters';
import { superForm } from 'sveltekit-superforms/client';
import { userSchema } from '$lib/config/zod-schemas';
import { signUpSchema } from '$lib/validations/auth';
export let data;
const signUpSchema = userSchema.pick({
firstName: true,
lastName: true,
username: true,
email: true,
password: true
});
const { form, errors, enhance, delayed } = superForm(data.form, {
const { form, errors, enhance } = superForm(data.form, {
taintedMessage: null,
validators: signUpSchema,
validators: zodClient(signUpSchema),
delayMs: 0
});
// $: termsValue = $form.terms as Writable<boolean>;

View file

@ -9,10 +9,7 @@
</script>
<AvatarPrimitive.Fallback
class={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
class={cn("flex h-full w-full items-center justify-center rounded-full bg-muted", className)}
{...$$restProps}
>
<slot />

View file

@ -11,10 +11,7 @@
<AvatarPrimitive.Root
{delayMs}
class={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
class={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
{...$$restProps}
>
<slot />

View file

@ -9,5 +9,5 @@ export {
//
Root as Avatar,
Image as AvatarImage,
Fallback as AvatarFallback
Fallback as AvatarFallback,
};

View file

@ -12,19 +12,19 @@ const buttonVariants = tv({
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
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: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10"
}
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default"
}
size: "default",
},
});
type Variant = VariantProps<typeof buttonVariants>["variant"];
@ -45,5 +45,5 @@ export {
Root as Button,
type Props as ButtonProps,
type Events as ButtonEvents,
buttonVariants
buttonVariants,
};

View file

@ -9,10 +9,7 @@
</script>
<div
class={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
class={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
{...$$restProps}
>
<slot />

View file

@ -18,7 +18,7 @@ export {
Description as CardDescription,
Footer as CardFooter,
Header as CardHeader,
Title as CardTitle
Title as CardTitle,
};
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";

View file

@ -6,7 +6,7 @@
export let transition: $$Props["transition"] = slide;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 150
duration: 150,
};
</script>

View file

@ -11,5 +11,5 @@ export {
//
Root as Collapsible,
Content as CollapsibleContent,
Trigger as CollapsibleTrigger
Trigger as CollapsibleTrigger,
};

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import Check from "lucide-svelte/icons/check";
import { cn } from "$lib/utils";
import { Check } from "lucide-svelte";
type $$Props = DropdownMenuPrimitive.CheckboxItemProps;
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents;
@ -14,7 +14,7 @@
<DropdownMenuPrimitive.CheckboxItem
bind:checked
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
)}
{...$$restProps}

View file

@ -6,6 +6,7 @@
type $$Events = DropdownMenuPrimitive.ContentEvents;
let className: $$Props["class"] = undefined;
export let sideOffset: $$Props["sideOffset"] = 4;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = undefined;
export { className as class };
@ -14,6 +15,7 @@
<DropdownMenuPrimitive.Content
{transition}
{transitionConfig}
{sideOffset}
class={cn(
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md focus:outline-none",
className

View file

@ -14,7 +14,7 @@
<DropdownMenuPrimitive.Item
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",
className
)}

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import Circle from "lucide-svelte/icons/circle";
import { cn } from "$lib/utils";
import { Circle } from "lucide-svelte";
type $$Props = DropdownMenuPrimitive.RadioItemProps;
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
@ -13,7 +13,7 @@
<DropdownMenuPrimitive.RadioItem
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
)}
{value}

View file

@ -8,9 +8,6 @@
export { className as class };
</script>
<span
class={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...$$restProps}
>
<span class={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...$$restProps}>
<slot />
</span>

View file

@ -9,7 +9,7 @@
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = {
x: -10,
y: 0
y: 0,
};
export { className as class };
</script>

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import ChevronRight from "lucide-svelte/icons/chevron-right";
import { cn } from "$lib/utils";
import { ChevronRight } from "lucide-svelte";
type $$Props = DropdownMenuPrimitive.SubTriggerProps & {
inset?: boolean;
@ -15,7 +15,7 @@
<DropdownMenuPrimitive.SubTrigger
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",
className
)}

View file

@ -44,5 +44,5 @@ export {
RadioGroup as DropdownMenuRadioGroup,
SubContent as DropdownMenuSubContent,
SubTrigger as DropdownMenuSubTrigger,
CheckboxItem as DropdownMenuCheckboxItem
CheckboxItem as DropdownMenuCheckboxItem,
};

View file

@ -1,9 +1,9 @@
<script lang="ts">
import * as Button from "$lib/components/ui/button";
type $$Props = Button.Props;
type $$Events = Button.Events;
</script>
<Button.Root type="submit" {...$$restProps} on:click on:keydown>
<Button.Root type="submit" {...$$restProps}>
<slot />
</Button.Root>

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { Form as FormPrimitive } from "formsnap";
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements";
@ -8,6 +8,10 @@
export { className as class };
</script>
<FormPrimitive.Description class={cn("text-sm text-muted-foreground", className)} {...$$restProps}>
<slot />
<FormPrimitive.Description
class={cn("text-sm text-muted-foreground", className)}
{...$$restProps}
let:descriptionAttrs
>
<slot {descriptionAttrs} />
</FormPrimitive.Description>

View 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>

View 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>

View 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>

View 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>

View file

@ -1,6 +1,6 @@
<script lang="ts">
import type { Label as LabelPrimitive } from "bits-ui";
import { getFormField } from "formsnap";
import { getFormControl } from "formsnap";
import { cn } from "$lib/utils";
import { Label } from "$lib/components/ui/label";
@ -9,9 +9,9 @@
let className: $$Props["class"] = undefined;
export { className as class };
const { errors, ids } = getFormField();
const { labelAttrs } = getFormControl();
</script>
<Label for={$ids.input} class={cn($errors && "text-destructive", className)} {...$$restProps}>
<slot />
<Label {...$labelAttrs} class={cn("data-[fs-error]:text-destructive", className)} {...$$restProps}>
<slot {labelAttrs} />
</Label>

View 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>

View file

@ -1,82 +1,33 @@
import { Form as FormPrimitive, getFormField } 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 * as FormPrimitive from "formsnap";
import Description from "./form-description.svelte";
import Label from "./form-label.svelte";
import Validation from "./form-validation.svelte";
import Checkbox from "./form-checkbox.svelte";
import Switch from "./form-switch.svelte";
import NativeSelect from "./form-native-select.svelte";
import RadioGroup from "./form-radio-group.svelte";
import Select from "./form-select.svelte";
import SelectTrigger from "./form-select-trigger.svelte";
import FieldErrors from "./form-field-errors.svelte";
import Field from "./form-field.svelte";
import Fieldset from "./form-fieldset.svelte";
import Legend from "./form-legend.svelte";
import ElementField from "./form-element-field.svelte";
import Button from "./form-button.svelte";
const Root = FormPrimitive.Root;
const Field = FormPrimitive.Field;
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 {
Root,
Field,
Control,
Item,
Input,
Label,
Button,
Switch,
Select,
Checkbox,
Textarea,
Validation,
RadioGroup,
RadioItem,
FieldErrors,
Description,
SelectContent,
SelectLabel,
SelectGroup,
SelectItem,
SelectSeparator,
SelectTrigger,
NativeSelect,
NativeRadio,
Fieldset,
Legend,
ElementField,
//
Root as Form,
Field as FormField,
Control as FormControl,
Item as FormItem,
Input as FormInput,
Textarea as FormTextarea,
Description as FormDescription,
Label as FormLabel,
Validation as FormValidation,
NativeSelect as FormNativeSelect,
NativeRadio as FormNativeRadio,
Checkbox as FormCheckbox,
Switch as FormSwitch,
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
FieldErrors as FormFieldErrors,
Fieldset as FormFieldset,
Legend as FormLegend,
ElementField as FormElementField,
Button as FormButton,
};

View file

@ -3,5 +3,5 @@ import Root from "./label.svelte";
export {
Root,
//
Root as Label
Root as Label,
};

View file

@ -20,5 +20,5 @@ export {
Link as PaginationLink,
PrevButton as PaginationPrevButton,
NextButton as PaginationNextButton,
Ellipsis as PaginationEllipsis
Ellipsis as PaginationEllipsis,
};

View file

@ -1,6 +1,6 @@
<script lang="ts">
import MoreHorizontal from "lucide-svelte/icons/more-horizontal";
import { cn } from "$lib/utils";
import { MoreHorizontal } from "lucide-svelte";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLSpanElement>;

View file

@ -23,7 +23,7 @@
class={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
size
size,
}),
className
)}

View file

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

View file

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

View file

@ -28,7 +28,7 @@
asChild
{...$$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} />
</nav>
</PaginationPrimitive.Root>

View file

@ -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
View 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
View file

@ -0,0 +1 @@
export const notSignedInMessage = { type: 'error', message: 'You are not signed in' } as const;

View file

@ -5,7 +5,7 @@ import { dev } from '$app/environment';
import { read } from '$app/server';
// 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();

View 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;
}

View file

@ -1,10 +1,11 @@
// lib/server/lucia.ts
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 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, {
getSessionAttributes: (attributes) => {

View file

@ -26,6 +26,7 @@
--input: 20 5.9% 90%;
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
--fg: #2c3e50;
}
.dark {
--background: 20 14.3% 4.1%;
@ -47,6 +48,7 @@
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--ring: 35.5 91.7% 32.9%;
--fg: #ffff;
}
}

View file

@ -0,0 +1,3 @@
export const normalizeEmail = (email: string): string => {
return decodeURIComponent(email).toLowerCase().trim();
};

View 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');
}
}

View 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');
// }
// }

View 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
// }
// }
// });

View 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');
}
}

View 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');
}
}

View file

@ -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
}
}
});
}

View file

@ -1,6 +1,7 @@
import type { GameType, SavedGameType } from '$lib/types';
import type { Game } from '@prisma/client';
import kebabCase from 'just-kebab-case';
import type { Games } from '../../schema';
export function convertToSavedGame(game: GameType | SavedGameType): SavedGameType {
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
return {
external_id: game.external_id,
name: game.name,
slug: kebabCase(game.name),
thumb_url: game.thumbnail,
@ -57,7 +57,6 @@ export function mapAPIGameToBoredGame(game: GameType): Game {
min_playtime: game.min_playtime,
max_playtime: game.max_playtime,
min_age: game.min_age,
description: game.description,
playtime: game.playing_time
description: game.description
};
}

View 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']
});
}
};

View 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
});

View 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()
});

View file

@ -117,6 +117,10 @@ export const search_schema = z
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({
collection_id: z.string()
});
@ -222,7 +226,6 @@ const gameSchema = z.object({
image_url: z.string().optional(),
thumb_url: z.string().optional(),
url: z.string().optional(),
rules_url: z.string().optional(),
weight_amount: z.number().optional(),
weight_units: z.enum(['Medium', 'Heavy']).optional(),
categories: z.array(category_schema).optional(),

25
src/migrate.ts Normal file
View 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();

View file

@ -1,6 +1,12 @@
import { redirect } from '@sveltejs/kit';
import type { PageServerData } from './$types';
import { redirect } from 'sveltekit-flash-message/server'
import type { PageServerLoad } from './$types';
import { notSignedInMessage } from '$lib/flashMessages';
export const load: PageServerData = async function ({ locals }) {
if (!locals?.user?.role?.includes('admin')) redirect(302, '/');
export async function load(event) {
const { locals } = event;
if (!locals?.user?.role?.includes('admin')) {
redirect(302, '/login', notSignedInMessage, event);
}
return {}
};

View file

@ -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 prisma from '$lib/prisma';
import { modifyListGameSchema, type ListGame } from '$lib/config/zod-schemas.js';
import { zod } from 'sveltekit-superforms/adapters';
import { redirect } from 'sveltekit-flash-message/server'
import { modifyListGameSchema, type ListGame } from '$lib/validations/zod-schemas';
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;
if (!user) {
redirect(302, '/login');
redirect(302, '/login', notSignedInMessage, event);
}
// console.log('locals load', locals);
@ -24,14 +29,12 @@ export const load: PageServerLoad = async ({ fetch, url, locals }) => {
skip
};
const searchForm = await superValidate(searchData, search_schema);
const listManageForm = await superValidate(modifyListGameSchema);
const searchForm = await superValidate(searchData, zod(search_schema));
const listManageForm = await superValidate(zod(modifyListGameSchema));
try {
let collection = await prisma.collection.findUnique({
where: {
user_id: user.id
}
const collection = await db.query.collections.findFirst({
where: eq(collections.user_id, user.id)
});
console.log('collection', collection);
@ -45,27 +48,25 @@ export const load: PageServerLoad = async ({ fetch, url, locals }) => {
// });
}
let collection_items = await prisma.collectionItem.findMany({
where: {
collection_id: collection.id
},
include: {
const collectionItems = await db.query.collection_items.findMany({
where: eq(collection_items.collection_id, collection.id),
with: {
game: {
select: {
columns: {
id: true,
name: true,
thumb_url: true
}
}
},
skip,
take: limit
offset: skip,
limit
});
console.log('collection_items', collection_items);
console.log('collection_items', collectionItems);
let collectionItems: ListGame[] = [];
for (const item of collection_items) {
const items: ListGame[] = [];
for (const item of collectionItems) {
console.log('item', item);
const game = item.game;
if (game) {
@ -77,14 +78,14 @@ export const load: PageServerLoad = async ({ fetch, url, locals }) => {
times_played: item.times_played,
in_collection: true
};
collectionItems.push(collectionItem);
items.push(collectionItem);
}
}
return {
searchForm,
listManageForm,
collection: collectionItems
collection: items
};
} catch (e) {
console.error(e);
@ -100,18 +101,15 @@ export const load: PageServerLoad = async ({ fetch, url, locals }) => {
export const actions: Actions = {
// Add game to a wishlist
add: async (event) => {
const { params, locals, request } = event;
const form = await superValidate(event, modifyListGameSchema);
const form = await superValidate(event, zod(modifyListGameSchema));
if (!event.locals.user) {
throw fail(401);
}
const user = event.locals.user;
let game = await prisma.game.findUnique({
where: {
id: form.data.id
}
const game = await db.query.games.findFirst({
where: eq(games.id, form.data.id)
});
if (!game) {
@ -125,10 +123,8 @@ export const actions: Actions = {
}
try {
const collection = await prisma.collection.findUnique({
where: {
user_id: user.id
}
const collection = await db.query.collections.findFirst({
where: eq(collections.user_id, user.id)
});
if (!collection) {
@ -136,12 +132,10 @@ export const actions: Actions = {
return error(404, 'Wishlist not found');
}
await prisma.collectionItem.create({
data: {
game_id: game.id,
collection_id: collection.id,
times_played: 0
}
await db.insert(collection_items).values({
game_id: game.id,
collection_id: collection.id,
times_played: 0
});
return {
@ -153,14 +147,14 @@ export const actions: Actions = {
}
},
// Create new wishlist
create: async ({ params, locals, request }) => {
create: async ({ locals }) => {
if (!locals.user) {
throw fail(401);
}
return error(405, 'Method not allowed');
},
// Delete a wishlist
delete: async ({ params, locals, request }) => {
delete: async ({ locals }) => {
if (!locals.user) {
throw fail(401);
}
@ -168,17 +162,15 @@ export const actions: Actions = {
},
// Remove game from a wishlist
remove: async (event) => {
const { params, locals, request } = event;
const form = await superValidate(event, modifyListGameSchema);
const { locals } = event;
const form = await superValidate(event, zod(modifyListGameSchema));
if (!locals.user) {
throw fail(401);
}
let game = await prisma.game.findUnique({
where: {
id: form.data.id
}
const game = await db.query.games.findFirst({
where: eq(games.id, form.data.id)
});
if (!game) {
@ -187,10 +179,8 @@ export const actions: Actions = {
}
try {
const collection = await prisma.collection.findUnique({
where: {
user_id: locals.user.id
}
const collection = await db.query.collections.findFirst({
where: eq(collections.user_id, locals.user.id)
});
if (!collection) {
@ -198,12 +188,10 @@ export const actions: Actions = {
return error(404, 'Collection not found');
}
await prisma.collectionItem.delete({
where: {
collection_id: collection.id,
game_id: game.id
}
});
await db.delete(collection_items).where(and(
eq(collection_items.collection_id, collection.id),
eq(collection_items.game_id, game.id)
));
return {
form

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