diff --git a/drizzle.config.ts b/drizzle.config.ts index 78d6c71..e981991 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -4,8 +4,8 @@ import env from './src/env'; export default defineConfig({ dialect: 'postgresql', - schema: './src/db/schema/index.ts', - out: './src/db/migrations', + out: './src/lib/server/api/infrastructure/database/migrations', + schema: './src/lib/server/api/infrastructure/database/tables/index.ts', dbCredentials: { host: env.DATABASE_HOST || 'localhost', port: Number(env.DATABASE_PORT) || 5432, @@ -18,4 +18,8 @@ export default defineConfig({ verbose: true, // Always as for confirmation strict: true, + migrations: { + table: 'migrations', + schema: 'public' + } }); diff --git a/src/db/seed.ts b/src/db/seed.ts index fc6391f..7220575 100644 --- a/src/db/seed.ts +++ b/src/db/seed.ts @@ -31,9 +31,9 @@ for (const table of [ schema.publishers_to_games, schema.recoveryCodes, schema.roles, - schema.sessions, + schema.sessionsTable, schema.userRoles, - schema.users, + schema.usersTable, schema.twoFactor, schema.wishlists, schema.wishlist_items, diff --git a/src/db/seeds/roles.ts b/src/db/seeds/roles.ts index 04d1724..46b94e0 100644 --- a/src/db/seeds/roles.ts +++ b/src/db/seeds/roles.ts @@ -1,4 +1,3 @@ -import { eq } from 'drizzle-orm'; import { type db } from '$db'; import * as schema from '$db/schema'; import roles from './data/roles.json'; diff --git a/src/db/seeds/users.ts b/src/db/seeds/users.ts index 0c8c739..c13741d 100644 --- a/src/db/seeds/users.ts +++ b/src/db/seeds/users.ts @@ -27,7 +27,7 @@ export default async function seed(db: db) { console.log('Admin Role: ', adminRole); const adminUser = await db - .insert(schema.users) + .insert(schema.usersTable) .values({ username: `${env.ADMIN_USERNAME}`, email: '', @@ -73,7 +73,7 @@ export default async function seed(db: db) { await Promise.all( users.map(async (user) => { const [insertedUser] = await db - .insert(schema.users) + .insert(schema.usersTable) .values({ ...user, hashed_password: await new Argon2id().hash(user.password), diff --git a/src/lib/dtos/register-emailpassword.dto.ts b/src/lib/dtos/register-emailpassword.dto.ts new file mode 100644 index 0000000..ce32b51 --- /dev/null +++ b/src/lib/dtos/register-emailpassword.dto.ts @@ -0,0 +1,20 @@ +import { z } from "zod"; +import { refinePasswords } from "$lib/validations/account"; + +export const registerEmailPasswordDto = 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: 'Must be at least 3 characters' }) + .max(50, { message: '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(), +}) + .superRefine(({ confirm_password, password }, ctx) => { + refinePasswords(confirm_password, password, ctx); + }); + +export type RegisterEmailPasswordDto = z.infer; \ No newline at end of file diff --git a/src/lib/dtos/signin-email.dto.ts b/src/lib/dtos/signin-email.dto.ts new file mode 100644 index 0000000..1ade43a --- /dev/null +++ b/src/lib/dtos/signin-email.dto.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +export const signInEmailDto = z.object({ + username: z + .string() + .trim() + .min(3, { message: 'Must be at least 3 characters' }) + .max(50, { message: 'Must be less than 50 characters' }), + password: z.string({ required_error: 'Password is required' }).trim(), +}); + +export type SignInEmailDto = z.infer; diff --git a/src/lib/server/api/controllers/iam.controller.ts b/src/lib/server/api/controllers/iam.controller.ts index f52fe78..5be066e 100644 --- a/src/lib/server/api/controllers/iam.controller.ts +++ b/src/lib/server/api/controllers/iam.controller.ts @@ -1,10 +1,23 @@ import { Hono } from 'hono'; +import { zValidator } from '@hono/zod-validator'; import { requireAuth } from "../middleware/auth.middleware"; +import { registerEmailPasswordDto } from '$lib/dtos/register-emailpassword.dto'; +import { limiter } from '../middleware/rate-limiter.middleware'; -const users = new Hono().get('/me', requireAuth, async (c) => { - const user = c.var.user; - return c.json({ user }); -}); +const users = new Hono() + .get('/me', requireAuth, async (c) => { + const user = c.var.user; + return c.json({ user }); + }) + .get('/user', requireAuth, async (c) => { + const user = c.var.user; + return c.json({ user }); + }) + .post('/login/request', zValidator('json', registerEmailPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => { + const { email } = c.req.valid('json'); + await this.loginRequestsService.create({ email }); + return c.json({ message: 'Verification email sent' }); + }); export default users; export type UsersType = typeof users \ No newline at end of file diff --git a/src/lib/server/api/index.ts b/src/lib/server/api/index.ts index 960cdd4..a5476dd 100644 --- a/src/lib/server/api/index.ts +++ b/src/lib/server/api/index.ts @@ -13,11 +13,14 @@ app.use(verifyOrigin).use(validateAuthSession); /* --------------------------------- Routes --------------------------------- */ const routes = app .route('/iam', users) + .get('/', (c) => c.json({ message: 'Server is healthy' })); /* -------------------------------------------------------------------------- */ /* Exports */ /* -------------------------------------------------------------------------- */ -export const rpc = hc(config.ORIGIN); +export type AppType = typeof routes; + +export const rpc = hc(config.ORIGIN); export type ApiClient = typeof rpc; export type ApiRoutes = typeof routes; export { app }; \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/index.ts b/src/lib/server/api/infrastructure/database/index.ts new file mode 100644 index 0000000..556e598 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/index.ts @@ -0,0 +1,22 @@ +import { drizzle } from 'drizzle-orm/node-postgres'; +import pg from 'pg'; +import { config } from '../../common/config'; +import * as schema from './tables'; + +// create the connection +export const pool = new pg.Pool({ + user: config.DATABASE_USER, + password: config.DATABASE_PASSWORD, + host: config.DATABASE_HOST, + port: Number(config.DATABASE_PORT).valueOf(), + database: config.DATABASE_DB, + ssl: config.DATABASE_HOST !== 'localhost', + max: config.DB_MIGRATING || config.DB_SEEDING ? 1 : undefined, +}); + +export const db = drizzle(pool, { + schema, + logger: config.NODE_ENV === 'development', +}); + +export type db = typeof db; diff --git a/src/lib/server/api/infrastructure/database/migrate.ts b/src/lib/server/api/infrastructure/database/migrate.ts new file mode 100644 index 0000000..a1198be --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrate.ts @@ -0,0 +1,26 @@ +import 'dotenv/config'; +import postgres from 'postgres'; +import { drizzle } from 'drizzle-orm/postgres-js'; +import { migrate } from 'drizzle-orm/postgres-js/migrator'; +import env from '../env'; +import config from '../../drizzle.config'; + +const connection = postgres({ + host: env.DATABASE_HOST || 'localhost', + port: env.DATABASE_PORT, + user: env.DATABASE_USER || 'root', + password: env.DATABASE_PASSWORD || '', + database: env.DATABASE_DB || 'boredgame', + ssl: env.NODE_ENV === 'development' ? false : 'require', + max: 1, +}); +const db = drizzle(connection); + +try { + await migrate(db, { migrationsFolder: config.out! }); + console.log('Migrations complete'); +} catch (e) { + console.error(e); +} + +process.exit(); diff --git a/src/lib/server/api/infrastructure/database/migrations/0000_dazzling_stick.sql b/src/lib/server/api/infrastructure/database/migrations/0000_dazzling_stick.sql new file mode 100644 index 0000000..d02362e --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrations/0000_dazzling_stick.sql @@ -0,0 +1,410 @@ +DO $$ BEGIN + CREATE TYPE "public"."external_id_type" AS ENUM('game', 'category', 'mechanic', 'publisher', 'designer', 'artist'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "categories" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text, + "name" text, + "slug" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "categories_cuid_unique" UNIQUE("cuid") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "categories_to_external_ids" ( + "category_id" uuid NOT NULL, + "external_id" uuid NOT NULL, + CONSTRAINT "categories_to_external_ids_category_id_external_id_pk" PRIMARY KEY("category_id","external_id") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "categories_to_games" ( + "category_id" uuid NOT NULL, + "game_id" uuid NOT NULL, + CONSTRAINT "categories_to_games_category_id_game_id_pk" PRIMARY KEY("category_id","game_id") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "collection_items" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text, + "collection_id" uuid NOT NULL, + "game_id" uuid NOT NULL, + "times_played" integer DEFAULT 0, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "collection_items_cuid_unique" UNIQUE("cuid") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "collections" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text, + "user_id" uuid NOT NULL, + "name" text DEFAULT 'My Collection' NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "collections_cuid_unique" UNIQUE("cuid") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "expansions" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text, + "base_game_id" uuid NOT NULL, + "game_id" uuid NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "expansions_cuid_unique" UNIQUE("cuid") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "external_ids" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text, + "type" "external_id_type", + "external_id" text NOT NULL, + CONSTRAINT "external_ids_cuid_unique" UNIQUE("cuid") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "games" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text, + "name" text NOT NULL, + "slug" text NOT NULL, + "description" text, + "year_published" integer, + "min_players" integer, + "max_players" integer, + "playtime" integer, + "min_playtime" integer, + "max_playtime" integer, + "min_age" integer, + "image_url" text, + "thumb_url" text, + "url" text, + "last_sync_at" timestamp, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "games_cuid_unique" UNIQUE("cuid") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "games_to_external_ids" ( + "game_id" uuid NOT NULL, + "external_id" uuid NOT NULL, + CONSTRAINT "games_to_external_ids_game_id_external_id_pk" PRIMARY KEY("game_id","external_id") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "mechanics" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text, + "name" text, + "slug" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "mechanics_cuid_unique" UNIQUE("cuid") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "mechanics_to_external_ids" ( + "mechanic_id" uuid NOT NULL, + "external_id" uuid NOT NULL, + CONSTRAINT "mechanics_to_external_ids_mechanic_id_external_id_pk" PRIMARY KEY("mechanic_id","external_id") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "mechanics_to_games" ( + "mechanic_id" uuid NOT NULL, + "game_id" uuid NOT NULL, + CONSTRAINT "mechanics_to_games_mechanic_id_game_id_pk" PRIMARY KEY("mechanic_id","game_id") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "password_reset_tokens" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" uuid NOT NULL, + "expires_at" timestamp, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "publishers" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text, + "name" text, + "slug" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "publishers_cuid_unique" UNIQUE("cuid") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "publishers_to_external_ids" ( + "publisher_id" uuid NOT NULL, + "external_id" uuid NOT NULL, + CONSTRAINT "publishers_to_external_ids_publisher_id_external_id_pk" PRIMARY KEY("publisher_id","external_id") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "publishers_to_games" ( + "publisher_id" uuid NOT NULL, + "game_id" uuid NOT NULL, + CONSTRAINT "publishers_to_games_publisher_id_game_id_pk" PRIMARY KEY("publisher_id","game_id") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "recovery_codes" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" uuid NOT NULL, + "code" text NOT NULL, + "used" boolean DEFAULT false, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "roles" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text NOT NULL, + "name" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "roles_cuid_unique" UNIQUE("cuid"), + CONSTRAINT "roles_name_unique" UNIQUE("name") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "sessions" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" uuid NOT NULL, + "expires_at" timestamp with time zone NOT NULL, + "ip_country" text, + "ip_address" text, + "two_factor_auth_enabled" boolean DEFAULT false, + "is_two_factor_authenticated" boolean DEFAULT false +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "two_factor" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text, + "two_factor_secret" text NOT NULL, + "two_factor_enabled" boolean DEFAULT false NOT NULL, + "initiated_time" timestamp with time zone NOT NULL, + "user_id" uuid NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "two_factor_cuid_unique" UNIQUE("cuid"), + CONSTRAINT "two_factor_user_id_unique" UNIQUE("user_id") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "user_roles" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text, + "user_id" uuid NOT NULL, + "role_id" uuid NOT NULL, + "primary" boolean DEFAULT false, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "user_roles_cuid_unique" UNIQUE("cuid") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "users" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text, + "username" text, + "hashed_password" text, + "email" text, + "first_name" text, + "last_name" text, + "verified" boolean DEFAULT false, + "receive_email" boolean DEFAULT false, + "theme" text DEFAULT 'system', + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "users_cuid_unique" UNIQUE("cuid"), + CONSTRAINT "users_username_unique" UNIQUE("username"), + CONSTRAINT "users_email_unique" UNIQUE("email") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "wishlist_items" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text, + "wishlist_id" uuid NOT NULL, + "game_id" uuid NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "wishlist_items_cuid_unique" UNIQUE("cuid") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "wishlists" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cuid" text, + "user_id" uuid NOT NULL, + "name" text DEFAULT 'My Wishlist' NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "wishlists_cuid_unique" UNIQUE("cuid") +); +--> 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 "public"."categories"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "categories_to_external_ids" ADD CONSTRAINT "categories_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "public"."external_ids"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "categories_to_games" ADD CONSTRAINT "categories_to_games_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "categories_to_games" ADD CONSTRAINT "categories_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "collection_items" ADD CONSTRAINT "collection_items_collection_id_collections_id_fk" FOREIGN KEY ("collection_id") REFERENCES "public"."collections"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "collection_items" ADD CONSTRAINT "collection_items_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "collections" ADD CONSTRAINT "collections_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "expansions" ADD CONSTRAINT "expansions_base_game_id_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "expansions" ADD CONSTRAINT "expansions_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "games_to_external_ids" ADD CONSTRAINT "games_to_external_ids_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "games_to_external_ids" ADD CONSTRAINT "games_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "public"."external_ids"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "mechanics_to_external_ids" ADD CONSTRAINT "mechanics_to_external_ids_mechanic_id_mechanics_id_fk" FOREIGN KEY ("mechanic_id") REFERENCES "public"."mechanics"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "mechanics_to_external_ids" ADD CONSTRAINT "mechanics_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "public"."external_ids"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "mechanics_to_games" ADD CONSTRAINT "mechanics_to_games_mechanic_id_mechanics_id_fk" FOREIGN KEY ("mechanic_id") REFERENCES "public"."mechanics"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "mechanics_to_games" ADD CONSTRAINT "mechanics_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "password_reset_tokens" ADD CONSTRAINT "password_reset_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "publishers_to_external_ids" ADD CONSTRAINT "publishers_to_external_ids_publisher_id_publishers_id_fk" FOREIGN KEY ("publisher_id") REFERENCES "public"."publishers"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "publishers_to_external_ids" ADD CONSTRAINT "publishers_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "public"."external_ids"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "publishers_to_games" ADD CONSTRAINT "publishers_to_games_publisher_id_publishers_id_fk" FOREIGN KEY ("publisher_id") REFERENCES "public"."publishers"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "publishers_to_games" ADD CONSTRAINT "publishers_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "recovery_codes" ADD CONSTRAINT "recovery_codes_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "two_factor" ADD CONSTRAINT "two_factor_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "wishlist_items" ADD CONSTRAINT "wishlist_items_wishlist_id_wishlists_id_fk" FOREIGN KEY ("wishlist_id") REFERENCES "public"."wishlists"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "wishlist_items" ADD CONSTRAINT "wishlist_items_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "wishlists" ADD CONSTRAINT "wishlists_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "search_index" ON "games" USING gin (( + setweight(to_tsvector('english', "name"), 'A') || + setweight(to_tsvector('english', "slug"), 'B') + )); \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/migrations/0001_noisy_sally_floyd.sql b/src/lib/server/api/infrastructure/database/migrations/0001_noisy_sally_floyd.sql new file mode 100644 index 0000000..5809aa6 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrations/0001_noisy_sally_floyd.sql @@ -0,0 +1,2 @@ +ALTER TABLE "two_factor" RENAME COLUMN "two_factor_secret" TO "secret";--> statement-breakpoint +ALTER TABLE "two_factor" RENAME COLUMN "two_factor_enabled" TO "enabled"; \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/migrations/0002_fancy_valkyrie.sql b/src/lib/server/api/infrastructure/database/migrations/0002_fancy_valkyrie.sql new file mode 100644 index 0000000..e05cd36 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrations/0002_fancy_valkyrie.sql @@ -0,0 +1 @@ +ALTER TABLE "two_factor" ALTER COLUMN "initiated_time" DROP NOT NULL; \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/0000_snapshot.json b/src/lib/server/api/infrastructure/database/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..91e2a6e --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrations/meta/0000_snapshot.json @@ -0,0 +1,1728 @@ +{ + "id": "e120d11a-bf28-4c96-9f2f-96e23e23c7e2", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "categories_cuid_unique": { + "name": "categories_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.categories_to_external_ids": { + "name": "categories_to_external_ids", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_external_ids_category_id_categories_id_fk": { + "name": "categories_to_external_ids_category_id_categories_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_external_ids_external_id_external_ids_id_fk": { + "name": "categories_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_external_ids_category_id_external_id_pk": { + "name": "categories_to_external_ids_category_id_external_id_pk", + "columns": [ + "category_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.categories_to_games": { + "name": "categories_to_games", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_games_category_id_categories_id_fk": { + "name": "categories_to_games_category_id_categories_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_games_game_id_games_id_fk": { + "name": "categories_to_games_game_id_games_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_games_category_id_game_id_pk": { + "name": "categories_to_games_category_id_game_id_pk", + "columns": [ + "category_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.collection_items": { + "name": "collection_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "times_played": { + "name": "times_played", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "collection_items_collection_id_collections_id_fk": { + "name": "collection_items_collection_id_collections_id_fk", + "tableFrom": "collection_items", + "tableTo": "collections", + "columnsFrom": [ + "collection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collection_items_game_id_games_id_fk": { + "name": "collection_items_game_id_games_id_fk", + "tableFrom": "collection_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collection_items_cuid_unique": { + "name": "collection_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.collections": { + "name": "collections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Collection'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "collections_user_id_users_id_fk": { + "name": "collections_user_id_users_id_fk", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collections_cuid_unique": { + "name": "collections_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.expansions": { + "name": "expansions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_game_id": { + "name": "base_game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "expansions_base_game_id_games_id_fk": { + "name": "expansions_base_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "base_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "expansions_game_id_games_id_fk": { + "name": "expansions_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "expansions_cuid_unique": { + "name": "expansions_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.external_ids": { + "name": "external_ids", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "external_id_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "external_ids_cuid_unique": { + "name": "external_ids_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.games": { + "name": "games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year_published": { + "name": "year_published", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_players": { + "name": "min_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_players": { + "name": "max_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime": { + "name": "playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_playtime": { + "name": "min_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_playtime": { + "name": "max_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_age": { + "name": "min_age", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thumb_url": { + "name": "thumb_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "search_index": { + "name": "search_index", + "columns": [ + { + "expression": "(\n\t\t\t\tsetweight(to_tsvector('english', \"name\"), 'A') ||\n setweight(to_tsvector('english', \"slug\"), 'B')\n )", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "games_cuid_unique": { + "name": "games_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.games_to_external_ids": { + "name": "games_to_external_ids", + "schema": "", + "columns": { + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "games_to_external_ids_game_id_games_id_fk": { + "name": "games_to_external_ids_game_id_games_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "games_to_external_ids_external_id_external_ids_id_fk": { + "name": "games_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "games_to_external_ids_game_id_external_id_pk": { + "name": "games_to_external_ids_game_id_external_id_pk", + "columns": [ + "game_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics": { + "name": "mechanics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mechanics_cuid_unique": { + "name": "mechanics_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.mechanics_to_external_ids": { + "name": "mechanics_to_external_ids", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_external_ids_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_external_ids_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_external_ids_external_id_external_ids_id_fk": { + "name": "mechanics_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_external_ids_mechanic_id_external_id_pk": { + "name": "mechanics_to_external_ids_mechanic_id_external_id_pk", + "columns": [ + "mechanic_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics_to_games": { + "name": "mechanics_to_games", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_games_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_games_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_games_game_id_games_id_fk": { + "name": "mechanics_to_games_game_id_games_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_games_mechanic_id_game_id_pk": { + "name": "mechanics_to_games_mechanic_id_game_id_pk", + "columns": [ + "mechanic_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.password_reset_tokens": { + "name": "password_reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.publishers": { + "name": "publishers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "publishers_cuid_unique": { + "name": "publishers_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.publishers_to_external_ids": { + "name": "publishers_to_external_ids", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_external_ids_publisher_id_publishers_id_fk": { + "name": "publishers_to_external_ids_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_external_ids_external_id_external_ids_id_fk": { + "name": "publishers_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_external_ids_publisher_id_external_id_pk": { + "name": "publishers_to_external_ids_publisher_id_external_id_pk", + "columns": [ + "publisher_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.publishers_to_games": { + "name": "publishers_to_games", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_games_publisher_id_publishers_id_fk": { + "name": "publishers_to_games_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_games_game_id_games_id_fk": { + "name": "publishers_to_games_game_id_games_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_games_publisher_id_game_id_pk": { + "name": "publishers_to_games_publisher_id_game_id_pk", + "columns": [ + "publisher_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.recovery_codes": { + "name": "recovery_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "used": { + "name": "used", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "recovery_codes_user_id_users_id_fk": { + "name": "recovery_codes_user_id_users_id_fk", + "tableFrom": "recovery_codes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_cuid_unique": { + "name": "roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + } + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_country": { + "name": "ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "two_factor_auth_enabled": { + "name": "two_factor_auth_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_two_factor_authenticated": { + "name": "is_two_factor_authenticated", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "two_factor_secret": { + "name": "two_factor_secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "initiated_time": { + "name": "initiated_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_users_id_fk": { + "name": "two_factor_user_id_users_id_fk", + "tableFrom": "two_factor", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "two_factor_cuid_unique": { + "name": "two_factor_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "two_factor_user_id_unique": { + "name": "two_factor_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "primary": { + "name": "primary", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_roles_cuid_unique": { + "name": "user_roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hashed_password": { + "name": "hashed_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "receive_email": { + "name": "receive_email", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'system'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_cuid_unique": { + "name": "users_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.wishlist_items": { + "name": "wishlist_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wishlist_id": { + "name": "wishlist_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wishlist_items_wishlist_id_wishlists_id_fk": { + "name": "wishlist_items_wishlist_id_wishlists_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "wishlists", + "columnsFrom": [ + "wishlist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wishlist_items_game_id_games_id_fk": { + "name": "wishlist_items_game_id_games_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlist_items_cuid_unique": { + "name": "wishlist_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.wishlists": { + "name": "wishlists", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Wishlist'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wishlists_user_id_users_id_fk": { + "name": "wishlists_user_id_users_id_fk", + "tableFrom": "wishlists", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlists_cuid_unique": { + "name": "wishlists_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + } + }, + "enums": { + "public.external_id_type": { + "name": "external_id_type", + "schema": "public", + "values": [ + "game", + "category", + "mechanic", + "publisher", + "designer", + "artist" + ] + } + }, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/0001_snapshot.json b/src/lib/server/api/infrastructure/database/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..4de88b6 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrations/meta/0001_snapshot.json @@ -0,0 +1,1728 @@ +{ + "id": "52e7c416-89cb-4c6a-9118-68a03cfc2920", + "prevId": "e120d11a-bf28-4c96-9f2f-96e23e23c7e2", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "categories_cuid_unique": { + "name": "categories_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.categories_to_external_ids": { + "name": "categories_to_external_ids", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_external_ids_category_id_categories_id_fk": { + "name": "categories_to_external_ids_category_id_categories_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_external_ids_external_id_external_ids_id_fk": { + "name": "categories_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_external_ids_category_id_external_id_pk": { + "name": "categories_to_external_ids_category_id_external_id_pk", + "columns": [ + "category_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.categories_to_games": { + "name": "categories_to_games", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_games_category_id_categories_id_fk": { + "name": "categories_to_games_category_id_categories_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_games_game_id_games_id_fk": { + "name": "categories_to_games_game_id_games_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_games_category_id_game_id_pk": { + "name": "categories_to_games_category_id_game_id_pk", + "columns": [ + "category_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.collection_items": { + "name": "collection_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "times_played": { + "name": "times_played", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "collection_items_collection_id_collections_id_fk": { + "name": "collection_items_collection_id_collections_id_fk", + "tableFrom": "collection_items", + "tableTo": "collections", + "columnsFrom": [ + "collection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collection_items_game_id_games_id_fk": { + "name": "collection_items_game_id_games_id_fk", + "tableFrom": "collection_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collection_items_cuid_unique": { + "name": "collection_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.collections": { + "name": "collections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Collection'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "collections_user_id_users_id_fk": { + "name": "collections_user_id_users_id_fk", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collections_cuid_unique": { + "name": "collections_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.expansions": { + "name": "expansions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_game_id": { + "name": "base_game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "expansions_base_game_id_games_id_fk": { + "name": "expansions_base_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "base_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "expansions_game_id_games_id_fk": { + "name": "expansions_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "expansions_cuid_unique": { + "name": "expansions_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.external_ids": { + "name": "external_ids", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "external_id_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "external_ids_cuid_unique": { + "name": "external_ids_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.games": { + "name": "games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year_published": { + "name": "year_published", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_players": { + "name": "min_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_players": { + "name": "max_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime": { + "name": "playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_playtime": { + "name": "min_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_playtime": { + "name": "max_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_age": { + "name": "min_age", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thumb_url": { + "name": "thumb_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "search_index": { + "name": "search_index", + "columns": [ + { + "expression": "(\n\t\t\t\tsetweight(to_tsvector('english', \"name\"), 'A') ||\n setweight(to_tsvector('english', \"slug\"), 'B')\n )", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "games_cuid_unique": { + "name": "games_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.games_to_external_ids": { + "name": "games_to_external_ids", + "schema": "", + "columns": { + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "games_to_external_ids_game_id_games_id_fk": { + "name": "games_to_external_ids_game_id_games_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "games_to_external_ids_external_id_external_ids_id_fk": { + "name": "games_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "games_to_external_ids_game_id_external_id_pk": { + "name": "games_to_external_ids_game_id_external_id_pk", + "columns": [ + "game_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics": { + "name": "mechanics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mechanics_cuid_unique": { + "name": "mechanics_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.mechanics_to_external_ids": { + "name": "mechanics_to_external_ids", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_external_ids_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_external_ids_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_external_ids_external_id_external_ids_id_fk": { + "name": "mechanics_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_external_ids_mechanic_id_external_id_pk": { + "name": "mechanics_to_external_ids_mechanic_id_external_id_pk", + "columns": [ + "mechanic_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics_to_games": { + "name": "mechanics_to_games", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_games_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_games_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_games_game_id_games_id_fk": { + "name": "mechanics_to_games_game_id_games_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_games_mechanic_id_game_id_pk": { + "name": "mechanics_to_games_mechanic_id_game_id_pk", + "columns": [ + "mechanic_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.password_reset_tokens": { + "name": "password_reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.publishers": { + "name": "publishers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "publishers_cuid_unique": { + "name": "publishers_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.publishers_to_external_ids": { + "name": "publishers_to_external_ids", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_external_ids_publisher_id_publishers_id_fk": { + "name": "publishers_to_external_ids_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_external_ids_external_id_external_ids_id_fk": { + "name": "publishers_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_external_ids_publisher_id_external_id_pk": { + "name": "publishers_to_external_ids_publisher_id_external_id_pk", + "columns": [ + "publisher_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.publishers_to_games": { + "name": "publishers_to_games", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_games_publisher_id_publishers_id_fk": { + "name": "publishers_to_games_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_games_game_id_games_id_fk": { + "name": "publishers_to_games_game_id_games_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_games_publisher_id_game_id_pk": { + "name": "publishers_to_games_publisher_id_game_id_pk", + "columns": [ + "publisher_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.recovery_codes": { + "name": "recovery_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "used": { + "name": "used", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "recovery_codes_user_id_users_id_fk": { + "name": "recovery_codes_user_id_users_id_fk", + "tableFrom": "recovery_codes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_cuid_unique": { + "name": "roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + } + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_country": { + "name": "ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "two_factor_auth_enabled": { + "name": "two_factor_auth_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_two_factor_authenticated": { + "name": "is_two_factor_authenticated", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "initiated_time": { + "name": "initiated_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_users_id_fk": { + "name": "two_factor_user_id_users_id_fk", + "tableFrom": "two_factor", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "two_factor_cuid_unique": { + "name": "two_factor_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "two_factor_user_id_unique": { + "name": "two_factor_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "primary": { + "name": "primary", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_roles_cuid_unique": { + "name": "user_roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hashed_password": { + "name": "hashed_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "receive_email": { + "name": "receive_email", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'system'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_cuid_unique": { + "name": "users_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.wishlist_items": { + "name": "wishlist_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wishlist_id": { + "name": "wishlist_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wishlist_items_wishlist_id_wishlists_id_fk": { + "name": "wishlist_items_wishlist_id_wishlists_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "wishlists", + "columnsFrom": [ + "wishlist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wishlist_items_game_id_games_id_fk": { + "name": "wishlist_items_game_id_games_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlist_items_cuid_unique": { + "name": "wishlist_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.wishlists": { + "name": "wishlists", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Wishlist'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wishlists_user_id_users_id_fk": { + "name": "wishlists_user_id_users_id_fk", + "tableFrom": "wishlists", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlists_cuid_unique": { + "name": "wishlists_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + } + }, + "enums": { + "public.external_id_type": { + "name": "external_id_type", + "schema": "public", + "values": [ + "game", + "category", + "mechanic", + "publisher", + "designer", + "artist" + ] + } + }, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/0002_snapshot.json b/src/lib/server/api/infrastructure/database/migrations/meta/0002_snapshot.json new file mode 100644 index 0000000..8f50271 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrations/meta/0002_snapshot.json @@ -0,0 +1,1728 @@ +{ + "id": "79adee85-e57c-4a9f-87df-835457b68129", + "prevId": "52e7c416-89cb-4c6a-9118-68a03cfc2920", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "categories_cuid_unique": { + "name": "categories_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.categories_to_external_ids": { + "name": "categories_to_external_ids", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_external_ids_category_id_categories_id_fk": { + "name": "categories_to_external_ids_category_id_categories_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_external_ids_external_id_external_ids_id_fk": { + "name": "categories_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_external_ids_category_id_external_id_pk": { + "name": "categories_to_external_ids_category_id_external_id_pk", + "columns": [ + "category_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.categories_to_games": { + "name": "categories_to_games", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_games_category_id_categories_id_fk": { + "name": "categories_to_games_category_id_categories_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_games_game_id_games_id_fk": { + "name": "categories_to_games_game_id_games_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_games_category_id_game_id_pk": { + "name": "categories_to_games_category_id_game_id_pk", + "columns": [ + "category_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.collection_items": { + "name": "collection_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "times_played": { + "name": "times_played", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "collection_items_collection_id_collections_id_fk": { + "name": "collection_items_collection_id_collections_id_fk", + "tableFrom": "collection_items", + "tableTo": "collections", + "columnsFrom": [ + "collection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collection_items_game_id_games_id_fk": { + "name": "collection_items_game_id_games_id_fk", + "tableFrom": "collection_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collection_items_cuid_unique": { + "name": "collection_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.collections": { + "name": "collections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Collection'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "collections_user_id_users_id_fk": { + "name": "collections_user_id_users_id_fk", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collections_cuid_unique": { + "name": "collections_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.expansions": { + "name": "expansions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_game_id": { + "name": "base_game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "expansions_base_game_id_games_id_fk": { + "name": "expansions_base_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "base_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "expansions_game_id_games_id_fk": { + "name": "expansions_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "expansions_cuid_unique": { + "name": "expansions_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.external_ids": { + "name": "external_ids", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "external_id_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "external_ids_cuid_unique": { + "name": "external_ids_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.games": { + "name": "games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year_published": { + "name": "year_published", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_players": { + "name": "min_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_players": { + "name": "max_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime": { + "name": "playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_playtime": { + "name": "min_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_playtime": { + "name": "max_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_age": { + "name": "min_age", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thumb_url": { + "name": "thumb_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "search_index": { + "name": "search_index", + "columns": [ + { + "expression": "(\n\t\t\t\tsetweight(to_tsvector('english', \"name\"), 'A') ||\n setweight(to_tsvector('english', \"slug\"), 'B')\n )", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "games_cuid_unique": { + "name": "games_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.games_to_external_ids": { + "name": "games_to_external_ids", + "schema": "", + "columns": { + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "games_to_external_ids_game_id_games_id_fk": { + "name": "games_to_external_ids_game_id_games_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "games_to_external_ids_external_id_external_ids_id_fk": { + "name": "games_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "games_to_external_ids_game_id_external_id_pk": { + "name": "games_to_external_ids_game_id_external_id_pk", + "columns": [ + "game_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics": { + "name": "mechanics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mechanics_cuid_unique": { + "name": "mechanics_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.mechanics_to_external_ids": { + "name": "mechanics_to_external_ids", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_external_ids_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_external_ids_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_external_ids_external_id_external_ids_id_fk": { + "name": "mechanics_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_external_ids_mechanic_id_external_id_pk": { + "name": "mechanics_to_external_ids_mechanic_id_external_id_pk", + "columns": [ + "mechanic_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics_to_games": { + "name": "mechanics_to_games", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_games_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_games_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_games_game_id_games_id_fk": { + "name": "mechanics_to_games_game_id_games_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_games_mechanic_id_game_id_pk": { + "name": "mechanics_to_games_mechanic_id_game_id_pk", + "columns": [ + "mechanic_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.password_reset_tokens": { + "name": "password_reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.publishers": { + "name": "publishers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "publishers_cuid_unique": { + "name": "publishers_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.publishers_to_external_ids": { + "name": "publishers_to_external_ids", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_external_ids_publisher_id_publishers_id_fk": { + "name": "publishers_to_external_ids_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_external_ids_external_id_external_ids_id_fk": { + "name": "publishers_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_external_ids_publisher_id_external_id_pk": { + "name": "publishers_to_external_ids_publisher_id_external_id_pk", + "columns": [ + "publisher_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.publishers_to_games": { + "name": "publishers_to_games", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_games_publisher_id_publishers_id_fk": { + "name": "publishers_to_games_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_games_game_id_games_id_fk": { + "name": "publishers_to_games_game_id_games_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_games_publisher_id_game_id_pk": { + "name": "publishers_to_games_publisher_id_game_id_pk", + "columns": [ + "publisher_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.recovery_codes": { + "name": "recovery_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "used": { + "name": "used", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "recovery_codes_user_id_users_id_fk": { + "name": "recovery_codes_user_id_users_id_fk", + "tableFrom": "recovery_codes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_cuid_unique": { + "name": "roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + } + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_country": { + "name": "ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "two_factor_auth_enabled": { + "name": "two_factor_auth_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_two_factor_authenticated": { + "name": "is_two_factor_authenticated", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "initiated_time": { + "name": "initiated_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_users_id_fk": { + "name": "two_factor_user_id_users_id_fk", + "tableFrom": "two_factor", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "two_factor_cuid_unique": { + "name": "two_factor_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "two_factor_user_id_unique": { + "name": "two_factor_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "primary": { + "name": "primary", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_roles_cuid_unique": { + "name": "user_roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hashed_password": { + "name": "hashed_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "receive_email": { + "name": "receive_email", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'system'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_cuid_unique": { + "name": "users_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.wishlist_items": { + "name": "wishlist_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wishlist_id": { + "name": "wishlist_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wishlist_items_wishlist_id_wishlists_id_fk": { + "name": "wishlist_items_wishlist_id_wishlists_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "wishlists", + "columnsFrom": [ + "wishlist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wishlist_items_game_id_games_id_fk": { + "name": "wishlist_items_game_id_games_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlist_items_cuid_unique": { + "name": "wishlist_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.wishlists": { + "name": "wishlists", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Wishlist'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wishlists_user_id_users_id_fk": { + "name": "wishlists_user_id_users_id_fk", + "tableFrom": "wishlists", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlists_cuid_unique": { + "name": "wishlists_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + } + }, + "enums": { + "public.external_id_type": { + "name": "external_id_type", + "schema": "public", + "values": [ + "game", + "category", + "mechanic", + "publisher", + "designer", + "artist" + ] + } + }, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/_journal.json b/src/lib/server/api/infrastructure/database/migrations/meta/_journal.json new file mode 100644 index 0000000..18ded62 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrations/meta/_journal.json @@ -0,0 +1,27 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1720625651245, + "tag": "0000_dazzling_stick", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1720625948784, + "tag": "0001_noisy_sally_floyd", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1720626020902, + "tag": "0002_fancy_valkyrie", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/seed.ts b/src/lib/server/api/infrastructure/database/seed.ts new file mode 100644 index 0000000..016f5b9 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/seed.ts @@ -0,0 +1,49 @@ +import { Table, getTableName, sql } from 'drizzle-orm'; +import env from '../env'; +import { db, pool } from '$db'; +import * as schema from './tables'; +import * as seeds from './seeds'; + +if (!env.DB_SEEDING) { + throw new Error('You must set DB_SEEDING to "true" when running seeds'); +} + +async function resetTable(db: db, table: Table) { + return db.execute(sql.raw(`TRUNCATE TABLE ${getTableName(table)} RESTART IDENTITY CASCADE`)); +} + +for (const table of [ + schema.categories, + schema.categoriesToExternalIds, + schema.categories_to_games, + schema.collection_items, + schema.collections, + schema.expansions, + schema.externalIds, + schema.games, + schema.gamesToExternalIds, + schema.mechanics, + schema.mechanicsToExternalIds, + schema.mechanics_to_games, + schema.password_reset_tokens, + schema.publishers, + schema.publishersToExternalIds, + schema.publishers_to_games, + schema.recoveryCodes, + schema.roles, + schema.sessionsTable, + schema.userRoles, + schema.usersTable, + schema.twoFactor, + schema.wishlists, + schema.wishlist_items, +]) { + // await db.delete(table); // clear tables without truncating / resetting ids + await resetTable(db, table); +} + +await seeds.roles(db); +await seeds.users(db); + +await pool.end(); +process.exit(); diff --git a/src/lib/server/api/infrastructure/database/seeds/data/roles.json b/src/lib/server/api/infrastructure/database/seeds/data/roles.json new file mode 100644 index 0000000..3209c58 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/seeds/data/roles.json @@ -0,0 +1,14 @@ +[ + { + "name": "admin" + }, + { + "name": "user" + }, + { + "name": "editor" + }, + { + "name": "moderator" + } +] \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/seeds/data/users.json b/src/lib/server/api/infrastructure/database/seeds/data/users.json new file mode 100644 index 0000000..61fa25d --- /dev/null +++ b/src/lib/server/api/infrastructure/database/seeds/data/users.json @@ -0,0 +1,62 @@ +[ + { + "first_name": "John", + "last_name": "Smith", + "username": "john.smith", + "email": "john.smith@example.com", + "password": "password", + "roles": [ + { + "name": "user", + "primary": true + } + ] + }, + { + "first_name": "Jane", + "last_name": "Doe", + "username": "jane.doe", + "email": "jane.doe@example.com", + "password": "password", + "roles": [ + { + "name": "user", + "primary": true + } + ] + }, + { + "first_name": "Michael", + "last_name": "Editor", + "username": "michael.editor", + "email": "michael.editor@example.com", + "password": "password", + "roles": [ + { + "name": "editor", + "primary": true + }, + { + "name": "user", + "primary": false + } + ] + }, + { + "first_name": "Jane", + "last_name": "Moderator", + "username": "jane.moderator", + "email": "jane.moderator@example.com", + "password": "password", + "roles": [ + { + "name": "moderator", + "primary": true + }, + { + "name": "user", + "primary": false + } + ] + } +] \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/seeds/index.ts b/src/lib/server/api/infrastructure/database/seeds/index.ts new file mode 100644 index 0000000..0a05205 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/seeds/index.ts @@ -0,0 +1,2 @@ +export { default as users } from './users'; +export { default as roles } from './roles'; diff --git a/src/lib/server/api/infrastructure/database/seeds/roles.ts b/src/lib/server/api/infrastructure/database/seeds/roles.ts new file mode 100644 index 0000000..46b94e0 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/seeds/roles.ts @@ -0,0 +1,11 @@ +import { type db } from '$db'; +import * as schema from '$db/schema'; +import roles from './data/roles.json'; + +export default async function seed(db: db) { + console.log('Creating roles ...'); + for (const role of roles) { + await db.insert(schema.roles).values(role).onConflictDoNothing(); + } + console.log('Roles created.'); +} diff --git a/src/lib/server/api/infrastructure/database/seeds/users.ts b/src/lib/server/api/infrastructure/database/seeds/users.ts new file mode 100644 index 0000000..eba6ed2 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/seeds/users.ts @@ -0,0 +1,98 @@ +import { eq } from 'drizzle-orm'; +import { Argon2id } from 'oslo/password'; +import { type db } from '$db'; +import * as schema from '$db/schema'; +import users from './data/users.json'; +import { config } from '../../../common/config'; + +type JsonUser = { + id: string; + username: string; + email: string; + password: string; + roles: { + name: string; + primary: boolean; + }[]; +}; + +type JsonRole = { + name: string; + primary: boolean; +}; + +export default async function seed(db: db) { + const adminRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'admin')); + const userRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'user')); + + console.log('Admin Role: ', adminRole); + const adminUser = await db + .insert(schema.usersTable) + .values({ + username: `${config.ADMIN_USERNAME}`, + email: '', + hashed_password: await new Argon2id().hash(`${config.ADMIN_PASSWORD}`), + first_name: 'Brad', + last_name: 'S', + verified: true, + }) + .returning() + .onConflictDoNothing(); + + console.log('Admin user created.', adminUser); + + await db + .insert(schema.collections) + .values({ user_id: adminUser[0].id }) + .onConflictDoNothing(); + + await db + .insert(schema.wishlists) + .values({ user_id: adminUser[0].id }) + .onConflictDoNothing(); + + await db + .insert(schema.userRoles) + .values({ + user_id: adminUser[0].id, + role_id: adminRole[0].id, + }) + .onConflictDoNothing(); + + console.log('Admin user given admin role.'); + + await db + .insert(schema.userRoles) + .values({ + user_id: adminUser[0].id, + role_id: userRole[0].id, + }) + .onConflictDoNothing(); + + console.log('Admin user given user role.'); + await Promise.all( + users.map(async (user) => { + const [insertedUser] = await db + .insert(schema.usersTable) + .values({ + ...user, + hashed_password: await new Argon2id().hash(user.password), + }) + .returning(); + await db.insert(schema.collections).values({ user_id: insertedUser?.id }); + await db.insert(schema.wishlists).values({ user_id: insertedUser?.id }); + await Promise.all( + user.roles.map(async (role: JsonRole) => { + const foundRole = await db.query.roles.findFirst({ + where: eq(schema.roles.name, role.name), + }); + await db.insert(schema.userRoles).values({ + user_id: insertedUser?.id, + role_id: foundRole?.id, + primary: role?.primary, + }); + }), + ); + }), + ); +} diff --git a/src/lib/server/api/infrastructure/database/tables/categories.ts b/src/lib/server/api/infrastructure/database/tables/categories.ts new file mode 100644 index 0000000..5936858 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/categories.ts @@ -0,0 +1,25 @@ +import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import categoriesToExternalIds from './categoriesToExternalIds'; +import categories_to_games from './categoriesToGames'; +import { timestamps } from '../utils'; + +const categories = pgTable('categories', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + name: text('name'), + slug: text('slug'), + ...timestamps, +}); + +export type Categories = InferSelectModel; + +export const categories_relations = relations(categories, ({ many }) => ({ + categories_to_games: many(categories_to_games), + categoriesToExternalIds: many(categoriesToExternalIds), +})); + +export default categories; diff --git a/src/lib/server/api/infrastructure/database/tables/categoriesToExternalIds.ts b/src/lib/server/api/infrastructure/database/tables/categoriesToExternalIds.ts new file mode 100644 index 0000000..352b732 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/categoriesToExternalIds.ts @@ -0,0 +1,39 @@ +import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; +import categories from './categories'; +import externalIds from './externalIds'; +import { relations } from 'drizzle-orm'; + +const categoriesToExternalIds = pgTable( + 'categories_to_external_ids', + { + categoryId: uuid('category_id') + .notNull() + .references(() => categories.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + externalId: uuid('external_id') + .notNull() + .references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + }, + (table) => { + return { + categoriesToExternalIdsPkey: primaryKey({ + columns: [table.categoryId, table.externalId], + }), + }; + }, +); + +export const categoriesToExternalIdsRelations = relations( + categoriesToExternalIds, + ({ one }) => ({ + category: one(categories, { + fields: [categoriesToExternalIds.categoryId], + references: [categories.id], + }), + externalId: one(externalIds, { + fields: [categoriesToExternalIds.externalId], + references: [externalIds.id], + }), + }), +); + +export default categoriesToExternalIds; diff --git a/src/lib/server/api/infrastructure/database/tables/categoriesToGames.ts b/src/lib/server/api/infrastructure/database/tables/categoriesToGames.ts new file mode 100644 index 0000000..7afb8e2 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/categoriesToGames.ts @@ -0,0 +1,36 @@ +import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; +import { relations } from 'drizzle-orm'; +import categories from './categories'; +import games from './games'; + +const categories_to_games = pgTable( + 'categories_to_games', + { + category_id: uuid('category_id') + .notNull() + .references(() => categories.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + game_id: uuid('game_id') + .notNull() + .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + }, + (table) => { + return { + categoriesToGamesPkey: primaryKey({ + columns: [table.category_id, table.game_id], + }), + }; + }, +); + +export const categories_to_games_relations = relations(categories_to_games, ({ one }) => ({ + category: one(categories, { + fields: [categories_to_games.category_id], + references: [categories.id], + }), + game: one(games, { + fields: [categories_to_games.game_id], + references: [games.id], + }), +})); + +export default categories_to_games; diff --git a/src/lib/server/api/infrastructure/database/tables/collectionItems.ts b/src/lib/server/api/infrastructure/database/tables/collectionItems.ts new file mode 100644 index 0000000..cf1805c --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/collectionItems.ts @@ -0,0 +1,36 @@ +import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import collections from './collections'; +import games from './games'; +import { timestamps } from '../utils'; + +const collection_items = pgTable('collection_items', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + collection_id: uuid('collection_id') + .notNull() + .references(() => collections.id, { onDelete: 'cascade' }), + game_id: uuid('game_id') + .notNull() + .references(() => games.id, { onDelete: 'cascade' }), + times_played: integer('times_played').default(0), + ...timestamps, +}); + +export type CollectionItems = InferSelectModel; + +export const collection_item_relations = relations(collection_items, ({ one }) => ({ + collection: one(collections, { + fields: [collection_items.collection_id], + references: [collections.id], + }), + game: one(games, { + fields: [collection_items.game_id], + references: [games.id], + }), +})); + +export default collection_items; diff --git a/src/lib/server/api/infrastructure/database/tables/collections.ts b/src/lib/server/api/infrastructure/database/tables/collections.ts new file mode 100644 index 0000000..1a56443 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/collections.ts @@ -0,0 +1,28 @@ +import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import usersTable from './users.table'; +import { timestamps } from '../utils'; + +const collections = pgTable('collections', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + user_id: uuid('user_id') + .notNull() + .references(() => usersTable.id, { onDelete: 'cascade' }), + name: text('name').notNull().default('My Collection'), + ...timestamps, +}); + +export const collection_relations = relations(collections, ({ one }) => ({ + user: one(usersTable, { + fields: [collections.user_id], + references: [usersTable.id], + }), +})); + +export type Collections = InferSelectModel; + +export default collections; diff --git a/src/lib/server/api/infrastructure/database/tables/expansions.ts b/src/lib/server/api/infrastructure/database/tables/expansions.ts new file mode 100644 index 0000000..bdbf2e3 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/expansions.ts @@ -0,0 +1,34 @@ +import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import games from './games'; +import { timestamps } from '../utils'; + +export const expansions = pgTable('expansions', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + base_game_id: uuid('base_game_id') + .notNull() + .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + game_id: uuid('game_id') + .notNull() + .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + ...timestamps, +}); + +export type Expansions = InferSelectModel; + +export const expansion_relations = relations(expansions, ({ one }) => ({ + baseGame: one(games, { + fields: [expansions.base_game_id], + references: [games.id], + }), + game: one(games, { + fields: [expansions.game_id], + references: [games.id], + }), +})); + +export default expansions; diff --git a/src/lib/server/api/infrastructure/database/tables/externalIds.ts b/src/lib/server/api/infrastructure/database/tables/externalIds.ts new file mode 100644 index 0000000..5d0a481 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/externalIds.ts @@ -0,0 +1,25 @@ +import { pgEnum, pgTable, text, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import type { InferSelectModel } from 'drizzle-orm'; + +export const externalIdType = pgEnum('external_id_type', [ + 'game', + 'category', + 'mechanic', + 'publisher', + 'designer', + 'artist', +]); + +const externalIds = pgTable('external_ids', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + type: externalIdType('type'), + externalId: text('external_id').notNull(), +}); + +export type ExternalIds = InferSelectModel; + +export default externalIds; diff --git a/src/lib/server/api/infrastructure/database/tables/games.ts b/src/lib/server/api/infrastructure/database/tables/games.ts new file mode 100644 index 0000000..91d5cfa --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/games.ts @@ -0,0 +1,53 @@ +import { index, integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations, sql } from 'drizzle-orm'; +import categoriesToGames from './categoriesToGames'; +import gamesToExternalIds from './gamesToExternalIds'; +import mechanicsToGames from './mechanicsToGames'; +import publishersToGames from './publishersToGames'; +import { timestamps } from '../utils'; + +const games = pgTable( + 'games', + { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + name: text('name').notNull(), + slug: text('slug').notNull(), + description: text('description'), + year_published: integer('year_published'), + min_players: integer('min_players'), + max_players: integer('max_players'), + playtime: integer('playtime'), + min_playtime: integer('min_playtime'), + max_playtime: integer('max_playtime'), + min_age: integer('min_age'), + image_url: text('image_url'), + thumb_url: text('thumb_url'), + url: text('url'), + last_sync_at: timestamp('last_sync_at'), + ...timestamps, + }, + (table) => ({ + searchIndex: index('search_index').using( + 'gin', + sql`( + setweight(to_tsvector('english', ${table.name}), 'A') || + setweight(to_tsvector('english', ${table.slug}), 'B') + )`, + ), + }), +); + +export const gameRelations = relations(games, ({ many }) => ({ + categories_to_games: many(categoriesToGames), + mechanics_to_games: many(mechanicsToGames), + publishers_to_games: many(publishersToGames), + gamesToExternalIds: many(gamesToExternalIds), +})); + +export type Games = InferSelectModel; + +export default games; diff --git a/src/lib/server/api/infrastructure/database/tables/gamesToExternalIds.ts b/src/lib/server/api/infrastructure/database/tables/gamesToExternalIds.ts new file mode 100644 index 0000000..66edec9 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/gamesToExternalIds.ts @@ -0,0 +1,24 @@ +import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; +import games from './games'; +import externalIds from './externalIds'; + +const gamesToExternalIds = pgTable( + 'games_to_external_ids', + { + gameId: uuid('game_id') + .notNull() + .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + externalId: uuid('external_id') + .notNull() + .references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + }, + (table) => { + return { + gamesToExternalIdsPkey: primaryKey({ + columns: [table.gameId, table.externalId], + }), + }; + }, +); + +export default gamesToExternalIds; diff --git a/src/lib/server/api/infrastructure/database/tables/index.ts b/src/lib/server/api/infrastructure/database/tables/index.ts new file mode 100644 index 0000000..e7bf9ff --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/index.ts @@ -0,0 +1,36 @@ +export { default as usersTable, userRelations as user_relations, type Users } from './users.table'; +export { default as recoveryCodes, type RecoveryCodes } from './recoveryCodes'; +export { + default as password_reset_tokens, + password_reset_token_relations, + type PasswordResetTokens, +} from './passwordResetTokens'; +export { default as sessionsTable, type Sessions } from './sessions.table'; +export { default as roles, role_relations, type Roles } from './roles'; +export { default as userRoles, user_role_relations, type UserRoles } from './userRoles'; +export { default as collections, collection_relations, type Collections } from './collections'; +export { + default as collection_items, + collection_item_relations, + type CollectionItems, +} from './collectionItems'; +export { default as wishlists, wishlists_relations, type Wishlists } from './wishlists'; +export { + default as wishlist_items, + wishlist_item_relations, + type WishlistItems, +} from './wishlistItems'; +export { default as externalIds, type ExternalIds, externalIdType } from './externalIds'; +export { default as games, gameRelations, type Games } from './games'; +export { default as gamesToExternalIds } from './gamesToExternalIds'; +export { default as expansions, expansion_relations, type Expansions } from './expansions'; +export { default as publishers, publishers_relations, type Publishers } from './publishers'; +export { default as publishers_to_games, publishers_to_games_relations } from './publishersToGames'; +export { default as publishersToExternalIds } from './publishersToExternalIds'; +export { default as categories, categories_relations, type Categories } from './categories'; +export { default as categoriesToExternalIds } from './categoriesToExternalIds'; +export { default as categories_to_games, categories_to_games_relations } from './categoriesToGames'; +export { default as mechanics, mechanics_relations, type Mechanics } from './mechanics'; +export { default as mechanicsToExternalIds } from './mechanicsToExternalIds'; +export { default as mechanics_to_games, mechanics_to_games_relations } from './mechanicsToGames'; +export { default as twoFactor } from './two-factor.table'; diff --git a/src/lib/server/api/infrastructure/database/tables/mechanics.ts b/src/lib/server/api/infrastructure/database/tables/mechanics.ts new file mode 100644 index 0000000..3a80fb0 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/mechanics.ts @@ -0,0 +1,25 @@ +import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import mechanicsToGames from './mechanicsToGames'; +import mechanicsToExternalIds from './mechanicsToExternalIds'; +import { timestamps } from '../utils'; + +const mechanics = pgTable('mechanics', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + name: text('name'), + slug: text('slug'), + ...timestamps, +}); + +export type Mechanics = InferSelectModel; + +export const mechanics_relations = relations(mechanics, ({ many }) => ({ + mechanics_to_games: many(mechanicsToGames), + mechanicsToExternalIds: many(mechanicsToExternalIds), +})); + +export default mechanics; diff --git a/src/lib/server/api/infrastructure/database/tables/mechanicsToExternalIds.ts b/src/lib/server/api/infrastructure/database/tables/mechanicsToExternalIds.ts new file mode 100644 index 0000000..8fab637 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/mechanicsToExternalIds.ts @@ -0,0 +1,24 @@ +import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; +import mechanics from './mechanics'; +import externalIds from './externalIds'; + +const mechanicsToExternalIds = pgTable( + 'mechanics_to_external_ids', + { + mechanicId: uuid('mechanic_id') + .notNull() + .references(() => mechanics.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + externalId: uuid('external_id') + .notNull() + .references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + }, + (table) => { + return { + mechanicsToExternalIdsPkey: primaryKey({ + columns: [table.mechanicId, table.externalId], + }), + }; + }, +); + +export default mechanicsToExternalIds; diff --git a/src/lib/server/api/infrastructure/database/tables/mechanicsToGames.ts b/src/lib/server/api/infrastructure/database/tables/mechanicsToGames.ts new file mode 100644 index 0000000..aa549c4 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/mechanicsToGames.ts @@ -0,0 +1,36 @@ +import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; +import { relations } from 'drizzle-orm'; +import mechanics from './mechanics'; +import games from './games'; + +const mechanics_to_games = pgTable( + 'mechanics_to_games', + { + mechanic_id: uuid('mechanic_id') + .notNull() + .references(() => mechanics.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + game_id: uuid('game_id') + .notNull() + .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + }, + (table) => { + return { + mechanicsToGamesPkey: primaryKey({ + columns: [table.mechanic_id, table.game_id], + }), + }; + }, +); + +export const mechanics_to_games_relations = relations(mechanics_to_games, ({ one }) => ({ + mechanic: one(mechanics, { + fields: [mechanics_to_games.mechanic_id], + references: [mechanics.id], + }), + game: one(games, { + fields: [mechanics_to_games.game_id], + references: [games.id], + }), +})); + +export default mechanics_to_games; diff --git a/src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts b/src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts new file mode 100644 index 0000000..a849bc1 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts @@ -0,0 +1,27 @@ +import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import usersTable from './users.table'; +import { timestamps } from '../utils'; + +const password_reset_tokens = pgTable('password_reset_tokens', { + id: text('id') + .primaryKey() + .$defaultFn(() => cuid2()), + user_id: uuid('user_id') + .notNull() + .references(() => usersTable.id, { onDelete: 'cascade' }), + expires_at: timestamp('expires_at'), + ...timestamps, +}); + +export type PasswordResetTokens = InferSelectModel; + +export const password_reset_token_relations = relations(password_reset_tokens, ({ one }) => ({ + user: one(usersTable, { + fields: [password_reset_tokens.user_id], + references: [usersTable.id], + }), +})); + +export default password_reset_tokens; diff --git a/src/lib/server/api/infrastructure/database/tables/publishers.ts b/src/lib/server/api/infrastructure/database/tables/publishers.ts new file mode 100644 index 0000000..b83a3ef --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/publishers.ts @@ -0,0 +1,25 @@ +import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import publishers_to_games from './publishersToGames'; +import publishersToExternalIds from './publishersToExternalIds'; +import { timestamps } from '../utils'; + +const publishers = pgTable('publishers', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + name: text('name'), + slug: text('slug'), + ...timestamps, +}); + +export type Publishers = InferSelectModel; + +export const publishers_relations = relations(publishers, ({ many }) => ({ + publishers_to_games: many(publishers_to_games), + publishersToExternalIds: many(publishersToExternalIds), +})); + +export default publishers; diff --git a/src/lib/server/api/infrastructure/database/tables/publishersToExternalIds.ts b/src/lib/server/api/infrastructure/database/tables/publishersToExternalIds.ts new file mode 100644 index 0000000..898e6da --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/publishersToExternalIds.ts @@ -0,0 +1,24 @@ +import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; +import publishers from './publishers'; +import externalIds from './externalIds'; + +const publishersToExternalIds = pgTable( + 'publishers_to_external_ids', + { + publisherId: uuid('publisher_id') + .notNull() + .references(() => publishers.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + externalId: uuid('external_id') + .notNull() + .references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + }, + (table) => { + return { + publishersToExternalIdsPkey: primaryKey({ + columns: [table.publisherId, table.externalId], + }), + }; + }, +); + +export default publishersToExternalIds; diff --git a/src/lib/server/api/infrastructure/database/tables/publishersToGames.ts b/src/lib/server/api/infrastructure/database/tables/publishersToGames.ts new file mode 100644 index 0000000..86de77d --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/publishersToGames.ts @@ -0,0 +1,36 @@ +import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; +import { relations } from 'drizzle-orm'; +import publishers from './publishers'; +import games from './games'; + +const publishers_to_games = pgTable( + 'publishers_to_games', + { + publisher_id: uuid('publisher_id') + .notNull() + .references(() => publishers.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + game_id: uuid('game_id') + .notNull() + .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + }, + (table) => { + return { + publishersToGamesPkey: primaryKey({ + columns: [table.publisher_id, table.game_id], + }), + }; + }, +); + +export const publishers_to_games_relations = relations(publishers_to_games, ({ one }) => ({ + publisher: one(publishers, { + fields: [publishers_to_games.publisher_id], + references: [publishers.id], + }), + game: one(games, { + fields: [publishers_to_games.game_id], + references: [games.id], + }), +})); + +export default publishers_to_games; diff --git a/src/lib/server/api/infrastructure/database/tables/recoveryCodes.ts b/src/lib/server/api/infrastructure/database/tables/recoveryCodes.ts new file mode 100644 index 0000000..b63c167 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/recoveryCodes.ts @@ -0,0 +1,18 @@ +import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; +import type { InferSelectModel } from 'drizzle-orm'; +import usersTable from './users.table'; +import { timestamps } from '../utils'; + +const recovery_codes = pgTable('recovery_codes', { + id: uuid('id').primaryKey().defaultRandom(), + userId: uuid('user_id') + .notNull() + .references(() => usersTable.id), + code: text('code').notNull(), + used: boolean('used').default(false), + ...timestamps, +}); + +export type RecoveryCodes = InferSelectModel; + +export default recovery_codes; diff --git a/src/lib/server/api/infrastructure/database/tables/roles.ts b/src/lib/server/api/infrastructure/database/tables/roles.ts new file mode 100644 index 0000000..43768b0 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/roles.ts @@ -0,0 +1,23 @@ +import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import user_roles from './userRoles'; +import { timestamps } from '../utils'; + +const roles = pgTable('roles', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()) + .notNull(), + name: text('name').unique().notNull(), + ...timestamps, +}); + +export type Roles = InferSelectModel; + +export const role_relations = relations(roles, ({ many }) => ({ + user_roles: many(user_roles), +})); + +export default roles; diff --git a/src/lib/server/api/infrastructure/database/tables/sessions.table.ts b/src/lib/server/api/infrastructure/database/tables/sessions.table.ts new file mode 100644 index 0000000..813b2b8 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/sessions.table.ts @@ -0,0 +1,29 @@ +import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; +import { relations, type InferSelectModel } from 'drizzle-orm'; +import usersTable from './users.table'; + +const sessionsTable = pgTable('sessions', { + id: text('id').primaryKey(), + userId: uuid('user_id') + .notNull() + .references(() => usersTable.id), + expiresAt: timestamp('expires_at', { + withTimezone: true, + mode: 'date', + }).notNull(), + ipCountry: text('ip_country'), + ipAddress: text('ip_address'), + twoFactorAuthEnabled: boolean('two_factor_auth_enabled').default(false), + isTwoFactorAuthenticated: boolean('is_two_factor_authenticated').default(false), +}); + +export const sessionsRelations = relations(sessionsTable, ({ one }) => ({ + user: one(usersTable, { + fields: [sessionsTable.userId], + references: [usersTable.id], + }) +})); + +export type Sessions = InferSelectModel; + +export default sessionsTable; diff --git a/src/lib/server/api/infrastructure/database/tables/two-factor.table.ts b/src/lib/server/api/infrastructure/database/tables/two-factor.table.ts new file mode 100644 index 0000000..d488c1d --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/two-factor.table.ts @@ -0,0 +1,34 @@ +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; +import { timestamps } from '../utils'; +import usersTable from './users.table'; + +const twoFactorTable = pgTable('two_factor', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + secret: text('secret').notNull(), + enabled: boolean('enabled').notNull().default(false), + initiatedTime: timestamp('initiated_time', { + mode: 'date', + withTimezone: true, + }), + userId: uuid('user_id') + .notNull() + .references(() => usersTable.id) + .unique(), + ...timestamps, +}); + +export const emailVerificationsRelations = relations(twoFactorTable, ({ one }) => ({ + user: one(usersTable, { + fields: [twoFactorTable.userId], + references: [usersTable.id], + }), +})); + +export type TwoFactor = InferSelectModel; + +export default twoFactorTable; diff --git a/src/lib/server/api/infrastructure/database/tables/userRoles.ts b/src/lib/server/api/infrastructure/database/tables/userRoles.ts new file mode 100644 index 0000000..d241788 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/userRoles.ts @@ -0,0 +1,36 @@ +import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import usersTable from './users.table'; +import roles from './roles'; +import { timestamps } from '../utils'; + +const user_roles = pgTable('user_roles', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + user_id: uuid('user_id') + .notNull() + .references(() => usersTable.id, { onDelete: 'cascade' }), + role_id: uuid('role_id') + .notNull() + .references(() => roles.id, { onDelete: 'cascade' }), + primary: boolean('primary').default(false), + ...timestamps, +}); + +export const user_role_relations = relations(user_roles, ({ one }) => ({ + role: one(roles, { + fields: [user_roles.role_id], + references: [roles.id], + }), + user: one(usersTable, { + fields: [user_roles.user_id], + references: [usersTable.id], + }), +})); + +export type UserRoles = InferSelectModel; + +export default user_roles; diff --git a/src/lib/server/api/infrastructure/database/tables/users.table.ts b/src/lib/server/api/infrastructure/database/tables/users.table.ts new file mode 100644 index 0000000..e3e40cb --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/users.table.ts @@ -0,0 +1,29 @@ +import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import { timestamps } from '../utils'; +import user_roles from './userRoles'; + +const usersTable = pgTable('users', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + username: text('username').unique(), + hashed_password: text('hashed_password'), + email: text('email').unique(), + first_name: text('first_name'), + last_name: text('last_name'), + verified: boolean('verified').default(false), + receive_email: boolean('receive_email').default(false), + theme: text('theme').default('system'), + ...timestamps, +}); + +export const userRelations = relations(usersTable, ({ many }) => ({ + user_roles: many(user_roles), +})); + +export type Users = InferSelectModel; + +export default usersTable; diff --git a/src/lib/server/api/infrastructure/database/tables/wishlistItems.ts b/src/lib/server/api/infrastructure/database/tables/wishlistItems.ts new file mode 100644 index 0000000..ac4bc61 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/wishlistItems.ts @@ -0,0 +1,35 @@ +import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import wishlists from './wishlists'; +import games from './games'; +import { timestamps } from '../utils'; + +const wishlist_items = pgTable('wishlist_items', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + wishlist_id: uuid('wishlist_id') + .notNull() + .references(() => wishlists.id, { onDelete: 'cascade' }), + game_id: uuid('game_id') + .notNull() + .references(() => games.id, { onDelete: 'cascade' }), + ...timestamps, +}); + +export type WishlistItems = InferSelectModel; + +export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({ + wishlist: one(wishlists, { + fields: [wishlist_items.wishlist_id], + references: [wishlists.id], + }), + game: one(games, { + fields: [wishlist_items.game_id], + references: [games.id], + }), +})); + +export default wishlist_items; diff --git a/src/lib/server/api/infrastructure/database/tables/wishlists.ts b/src/lib/server/api/infrastructure/database/tables/wishlists.ts new file mode 100644 index 0000000..673308a --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/wishlists.ts @@ -0,0 +1,28 @@ +import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import usersTable from './users.table'; +import { timestamps } from '../utils'; + +const wishlists = pgTable('wishlists', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + user_id: uuid('user_id') + .notNull() + .references(() => usersTable.id, { onDelete: 'cascade' }), + name: text('name').notNull().default('My Wishlist'), + ...timestamps, +}); + +export type Wishlists = InferSelectModel; + +export const wishlists_relations = relations(wishlists, ({ one }) => ({ + user: one(usersTable, { + fields: [wishlists.user_id], + references: [usersTable.id], + }), +})); + +export default wishlists; diff --git a/src/lib/server/api/infrastructure/database/utils.ts b/src/lib/server/api/infrastructure/database/utils.ts new file mode 100644 index 0000000..5b926ef --- /dev/null +++ b/src/lib/server/api/infrastructure/database/utils.ts @@ -0,0 +1,42 @@ +import { HTTPException } from 'hono/http-exception'; +import { timestamp, customType } from 'drizzle-orm/pg-core'; + +export const citext = customType<{ data: string }>({ + dataType() { + return 'citext'; + } +}); + +export const cuid2 = customType<{ data: string }>({ + dataType() { + return 'text'; + } +}); + +export const takeFirst = (values: T[]): T | null => { + if (values.length === 0) return null; + return values[0]!; +}; + +export const takeFirstOrThrow = (values: T[]): T => { + if (values.length === 0) + throw new HTTPException(404, { + message: 'Resource not found' + }); + return values[0]!; +}; + +export const timestamps = { + createdAt: timestamp('created_at', { + mode: 'date', + withTimezone: true + }) + .notNull() + .defaultNow(), + updatedAt: timestamp('updated_at', { + mode: 'date', + withTimezone: true + }) + .notNull() + .defaultNow() +}; diff --git a/src/lib/server/api/infrastructure/email-templates/email-change-notice.hbs b/src/lib/server/api/infrastructure/email-templates/email-change-notice.hbs new file mode 100644 index 0000000..4d67bf9 --- /dev/null +++ b/src/lib/server/api/infrastructure/email-templates/email-change-notice.hbs @@ -0,0 +1,18 @@ + + + + + Email Change Request + + +

Email address change notice

+

+ An update to your email address has been requested. If this is unexpected or you did not perform this action, please login and secure your account.

+ + + \ No newline at end of file diff --git a/src/lib/server/api/providers/database.provider.ts b/src/lib/server/api/providers/database.provider.ts new file mode 100644 index 0000000..f4221d5 --- /dev/null +++ b/src/lib/server/api/providers/database.provider.ts @@ -0,0 +1,10 @@ +import { db } from '../infrastructure/database'; + +// Symbol +export const DatabaseProvider = Symbol('DATABASE_TOKEN'); + +// Type +export type DatabaseProvider = typeof db; + +// Register +container.register(DatabaseProvider, { useValue: db }); diff --git a/src/lib/server/api/providers/index.ts b/src/lib/server/api/providers/index.ts new file mode 100644 index 0000000..0ac960b --- /dev/null +++ b/src/lib/server/api/providers/index.ts @@ -0,0 +1,3 @@ +export * from './database.provider'; +export * from './lucia.provider'; +export * from './redis.provider'; diff --git a/src/lib/server/api/providers/lucia.provider.ts b/src/lib/server/api/providers/lucia.provider.ts new file mode 100644 index 0000000..6546d43 --- /dev/null +++ b/src/lib/server/api/providers/lucia.provider.ts @@ -0,0 +1,11 @@ +import { container } from 'tsyringe'; +import { lucia } from '../infrastructure/auth/lucia'; + +// Symbol +export const LuciaProvider = Symbol('LUCIA_PROVIDER'); + +// Type +export type LuciaProvider = typeof lucia; + +// Register +container.register(LuciaProvider, { useValue: lucia }); diff --git a/src/lib/server/api/providers/redis.provider.ts b/src/lib/server/api/providers/redis.provider.ts new file mode 100644 index 0000000..26496de --- /dev/null +++ b/src/lib/server/api/providers/redis.provider.ts @@ -0,0 +1,14 @@ +import { container } from 'tsyringe'; +import RedisClient from 'ioredis' +import { config } from '../common/config'; + +// Symbol +export const RedisProvider = Symbol('REDIS_TOKEN'); + +// Type +export type RedisProvider = RedisClient; + +// Register +container.register(RedisProvider, { + useValue: new RedisClient(config.REDIS_URL) +}); diff --git a/src/lib/server/api/services/login-requests.service.ts b/src/lib/server/api/services/login-requests.service.ts new file mode 100644 index 0000000..e831e93 --- /dev/null +++ b/src/lib/server/api/services/login-requests.service.ts @@ -0,0 +1,62 @@ +import { BadRequest } from '../common/errors'; +import { DatabaseProvider } from '../providers'; +import { MailerService } from './mailer.service'; +import { TokensService } from './tokens.service'; +import { LuciaProvider } from '../providers/lucia.provider'; +import { UsersRepository } from '../repositories/users.repository'; +import type { SignInEmailDto } from '../../../dtos/signin-email.dto'; +import type { RegisterEmailDto } from '../../../dtos/register-email.dto'; +import { LoginRequestsRepository } from '../repositories/login-requests.repository'; + +export class LoginRequestsService { + async create(data: RegisterEmailDto) { + // generate a token, expiry date, and hash + const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm'); + // save the login request to the database - ensuring we save the hashedToken + await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry }); + // send the login request email + await this.mailerService.sendLoginRequest({ + to: data.email, + props: { token: token } + }); + } + + async verify(data: SignInEmailDto) { + const validLoginRequest = await this.fetchValidRequest(data.email, data.token); + if (!validLoginRequest) throw BadRequest('Invalid token'); + + let existingUser = await this.usersRepository.findOneByEmail(data.email); + + if (!existingUser) { + const newUser = await this.handleNewUserRegistration(data.email); + return this.lucia.createSession(newUser.id, {}); + } + + return this.lucia.createSession(existingUser.id, {}); + } + + // Create a new user and send a welcome email - or other onboarding process + private async handleNewUserRegistration(email: string) { + const newUser = await this.usersRepository.create({ email, verified: true, avatar: null }) + this.mailerService.sendWelcome({ to: email, props: null }); + // TODO: add whatever onboarding process or extra data you need here + return newUser + } + + // Fetch a valid request from the database, verify the token and burn the request if it is valid + private async fetchValidRequest(email: string, token: string) { + return await this.db.transaction(async (trx) => { + // fetch the login request + const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email) + if (!loginRequest) return null; + + // check if the token is valid + const isValidRequest = await this.tokensService.verifyHashedToken(loginRequest.hashedToken, token); + if (!isValidRequest) return null + + // if the token is valid, burn the request + await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id); + return loginRequest + }) + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 7b98d09..388ed92 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,10 @@ "resolveJsonModule": true, "skipLibCheck": true, "sourceMap": true, - "strict": true + "strict": true, + "moduleResolution": "bundler", + "experimentalDecorators": true, + "emitDecoratorMetadata": true } // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias //