Deleting all the old DB folder data and slowly migrating each page.

This commit is contained in:
Bradley Shellnut 2024-08-15 16:25:41 -07:00
parent eeca4e4103
commit 60d0706d58
59 changed files with 291 additions and 7103 deletions

View file

@ -1,24 +0,0 @@
import { drizzle } from 'drizzle-orm/node-postgres';
import pg from 'pg';
import env from '../env';
import * as schema from './schema';
// create the connection
export const pool = new pg.Pool({
user: env.DATABASE_USER,
password: env.DATABASE_PASSWORD,
host: env.DATABASE_HOST,
port: Number(env.DATABASE_PORT).valueOf(),
database: env.DATABASE_DB,
ssl: env.DATABASE_HOST !== 'localhost',
max: env.DB_MIGRATING || env.DB_SEEDING ? 1 : undefined,
});
export const db = drizzle(pool, {
schema,
logger: env.NODE_ENV === 'development',
});
export type db = typeof db;
export default db;

View file

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

View file

@ -1,410 +0,0 @@
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')
));

View file

@ -1,2 +0,0 @@
ALTER TABLE "two_factor" RENAME COLUMN "two_factor_secret" TO "secret";--> statement-breakpoint
ALTER TABLE "two_factor" RENAME COLUMN "two_factor_enabled" TO "enabled";

View file

@ -1 +0,0 @@
ALTER TABLE "two_factor" ALTER COLUMN "initiated_time" DROP NOT NULL;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,27 +0,0 @@
{
"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
}
]
}

View file

@ -1,25 +0,0 @@
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<typeof categories>;
export const categories_relations = relations(categories, ({ many }) => ({
categories_to_games: many(categories_to_games),
categoriesToExternalIds: many(categoriesToExternalIds),
}));
export default categories;

View file

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

View file

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

View file

@ -1,36 +0,0 @@
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<typeof collection_items>;
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;

View file

@ -1,28 +0,0 @@
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<typeof collections>;
export default collections;

View file

@ -1,34 +0,0 @@
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<typeof expansions>;
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;

View file

@ -1,25 +0,0 @@
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<typeof externalIds>;
export default externalIds;

View file

@ -1,53 +0,0 @@
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<typeof games>;
export default games;

View file

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

View file

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

View file

@ -1,25 +0,0 @@
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<typeof mechanics>;
export const mechanics_relations = relations(mechanics, ({ many }) => ({
mechanics_to_games: many(mechanicsToGames),
mechanicsToExternalIds: many(mechanicsToExternalIds),
}));
export default mechanics;

View file

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

View file

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

View file

@ -1,27 +0,0 @@
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<typeof password_reset_tokens>;
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;

View file

@ -1,25 +0,0 @@
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<typeof publishers>;
export const publishers_relations = relations(publishers, ({ many }) => ({
publishers_to_games: many(publishers_to_games),
publishersToExternalIds: many(publishersToExternalIds),
}));
export default publishers;

View file

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

View file

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

View file

@ -1,18 +0,0 @@
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<typeof recovery_codes>;
export default recovery_codes;

View file

@ -1,23 +0,0 @@
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<typeof roles>;
export const role_relations = relations(roles, ({ many }) => ({
user_roles: many(user_roles),
}));
export default roles;

View file

@ -1,29 +0,0 @@
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<typeof sessionsTable>;
export default sessionsTable;

View file

@ -1,34 +0,0 @@
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<typeof twoFactorTable>;
export default twoFactorTable;

View file

@ -1,36 +0,0 @@
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<typeof user_roles>;
export default user_roles;

View file

@ -1,29 +0,0 @@
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<typeof usersTable>;
export default usersTable;

View file

@ -1,35 +0,0 @@
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<typeof wishlist_items>;
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;

View file

@ -1,28 +0,0 @@
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<typeof wishlists>;
export const wishlists_relations = relations(wishlists, ({ one }) => ({
user: one(usersTable, {
fields: [wishlists.user_id],
references: [usersTable.id],
}),
}));
export default wishlists;

View file

@ -1,49 +0,0 @@
import { Table, getTableName, sql } from 'drizzle-orm';
import env from '../env';
import { db, pool } from '$db';
import * as schema from './schema';
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();

View file

@ -1,14 +0,0 @@
[
{
"name": "admin"
},
{
"name": "user"
},
{
"name": "editor"
},
{
"name": "moderator"
}
]

View file

@ -1,62 +0,0 @@
[
{
"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
}
]
}
]

View file

@ -1,2 +0,0 @@
export { default as users } from './users';
export { default as roles } from './roles';

View file

@ -1,11 +0,0 @@
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.');
}

View file

@ -1,98 +0,0 @@
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 env from '../../env';
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: `${env.ADMIN_USERNAME}`,
email: '',
hashed_password: await new Argon2id().hash(`${env.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,
});
}),
);
}),
);
}

View file

@ -1,43 +0,0 @@
// import { HTTPException } from 'hono/http-exception';
import { timestamp } from 'drizzle-orm/pg-core';
import { 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 = <T>(values: T[]): T | null => {
if (values.length === 0) return null;
return values[0]!;
};
export const takeFirstOrThrow = <T>(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(),
};

View file

@ -4,7 +4,7 @@ import { hc } from 'hono/client';
import { sequence } from '@sveltejs/kit/hooks'; import { sequence } from '@sveltejs/kit/hooks';
import { redirect, type Handle } from '@sveltejs/kit'; import { redirect, type Handle } from '@sveltejs/kit';
import { dev } from '$app/environment'; import { dev } from '$app/environment';
import { lucia } from '$lib/server/auth'; // import { lucia } from '$lib/server/auth';
import type { ApiRoutes } from '$lib/server/api'; import type { ApiRoutes } from '$lib/server/api';
import { parseApiResponse } from '$lib/utils/api'; import { parseApiResponse } from '$lib/utils/api';
import { StatusCodes } from '$lib/constants/status-codes'; import { StatusCodes } from '$lib/constants/status-codes';
@ -92,7 +92,7 @@ export const authentication: Handle = async function ({ event, resolve }) {
export const handle: Handle = sequence( export const handle: Handle = sequence(
// Sentry.sentryHandle(), // Sentry.sentryHandle(),
authentication, // authentication,
apiClient apiClient
); );
// export const handleError = Sentry.handleErrorWithSentry(); // export const handleError = Sentry.handleErrorWithSentry();

View file

@ -13,8 +13,8 @@ export const signupUsernameEmailDto = z.object({
password: z.string({required_error: 'Password is required'}).trim(), password: z.string({required_error: 'Password is required'}).trim(),
confirm_password: z.string({required_error: 'Confirm Password is required'}).trim() confirm_password: z.string({required_error: 'Confirm Password is required'}).trim()
}) })
.superRefine(({ confirm_password, password }, ctx) => { .superRefine(async ({ confirm_password, password }, ctx) => {
refinePasswords(confirm_password, password, ctx); return await refinePasswords(confirm_password, password, ctx);
}); });
export type SignupUsernameEmailDto = z.infer<typeof signupUsernameEmailDto> export type SignupUsernameEmailDto = z.infer<typeof signupUsernameEmailDto>

View file

@ -1,106 +1,57 @@
import 'reflect-metadata'; import 'reflect-metadata';
import { Hono } from 'hono'; import { Hono } from 'hono';
import { setCookie } from 'hono/cookie';
import {inject, injectable} from 'tsyringe'; import {inject, injectable} from 'tsyringe';
import { zValidator } from '@hono/zod-validator'; import { zValidator } from '@hono/zod-validator';
import { TimeSpan } from 'oslo';
import type { HonoTypes } from '../types'; import type { HonoTypes } from '../types';
import type { Controller } from '../interfaces/controller.interface'; import type { Controller } from '../interfaces/controller.interface';
import { signupUsernameEmailDto } from "$lib/dtos/signup-username-email.dto"; import { signupUsernameEmailDto } from "$lib/dtos/signup-username-email.dto";
import {limiter} from "$lib/server/api/middleware/rate-limiter.middleware"; import {limiter} from "$lib/server/api/middleware/rate-limiter.middleware";
import {UsersService} from "$lib/server/api/services/users.service"; import {UsersService} from "$lib/server/api/services/users.service";
import {LoginRequestsService} from "$lib/server/api/services/loginrequest.service";
import {LuciaProvider} from "$lib/server/api/providers";
@injectable() @injectable()
export class SignupController implements Controller { export class SignupController implements Controller {
controller = new Hono<HonoTypes>(); controller = new Hono<HonoTypes>();
constructor( constructor(
@inject(UsersService) private readonly usersService: UsersService @inject(UsersService) private readonly usersService: UsersService,
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
@inject(LuciaProvider) private lucia: LuciaProvider
) { } ) { }
routes() { routes() {
return this.controller return this.controller
.post('/', zValidator('json', signupUsernameEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { .post('/', zValidator('json', signupUsernameEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
const { firstName, lastName, email, username, password } = await c.req.valid('json'); const { firstName, lastName, email, username, password, confirm_password } = await c.req.valid('json');
const existingUser = await this.usersService.findOneByUsername(username); const existingUser = await this.usersService.findOneByUsername(username);
if (existingUser) { if (existingUser) {
return c.body("User already exists", 400); return c.body("User already exists", 400);
} }
const user = await this.usersService.create(signupUsernameEmailDto); const user = await this.usersService.create({ firstName, lastName, email, username, password, confirm_password });
// const existing_user = await db.query.usersTable.findFirst({ if (!user) {
// where: eq(usersTable.username, form.data.username), return c.body("Failed to create user", 500);
// }); }
//
// if (existing_user) {
// return setError(form, 'username', 'You cannot create an account with that username');
// }
//
// console.log('Creating user');
//
// const hashedPassword = await new Argon2id().hash(form.data.password);
//
// const user = await db
// .insert(usersTable)
// .values({
// username: form.data.username,
// hashed_password: hashedPassword,
// email: form.data.email,
// first_name: form.data.firstName ?? '',
// last_name: form.data.lastName ?? '',
// verified: false,
// receive_email: false,
// theme: 'system',
// })
// .returning();
// console.log('signup user', user);
//
// if (!user || user.length === 0) {
// return fail(400, {
// form,
// message: `Could not create your account. Please try again. If the problem persists, please contact support. Error ID: ${cuid2()}`,
// });
// }
//
// await add_user_to_role(user[0].id, 'user', true);
// await db.insert(collections).values({
// user_id: user[0].id,
// });
// await db.insert(wishlists).values({
// user_id: user[0].id,
// });
//
// try {
// session = await lucia.createSession(user[0].id, {
// ip_country: event.locals.ip,
// ip_address: event.locals.country,
// twoFactorAuthEnabled: false,
// isTwoFactorAuthenticated: false,
// });
// sessionCookie = lucia.createSessionCookie(session.id);
// } catch (e: any) {
// if (e.message.toUpperCase() === `DUPLICATE_KEY_ID`) {
// // key already exists
// console.error('Lucia Error: ', e);
// }
// console.log(e);
// const message = {
// type: 'error',
// message: 'Unable to create your account. Please try again.',
// };
// form.data.password = '';
// form.data.confirm_password = '';
// error(500, message);
// }
//
// event.cookies.set(sessionCookie.name, sessionCookie.value, {
// path: '.',
// ...sessionCookie.attributes,
// });
//
// redirect(302, '/');
return c.json({ message: 'Hello, world!' }); const session = await this.loginRequestService.createUserSession(user.id, c.req, undefined);
const sessionCookie = this.lucia.createSessionCookie(session.id);
console.log("set cookie", sessionCookie);
setCookie(c, sessionCookie.name, sessionCookie.value, {
path: sessionCookie.attributes.path,
maxAge: sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds()
? sessionCookie.attributes.maxAge : new TimeSpan(2, 'w').seconds(),
domain: sessionCookie.attributes.domain,
sameSite: sessionCookie.attributes.sameSite as any,
secure: sessionCookie.attributes.secure,
httpOnly: sessionCookie.attributes.httpOnly,
expires: sessionCookie.attributes.expires
});
return c.json({ message: 'ok' });
}); });
} }
} }

View file

@ -1,8 +1,8 @@
// lib/server/lucia.ts // lib/server/lucia.ts
import { Lucia, TimeSpan } from 'lucia'; import { Lucia, TimeSpan } from 'lucia';
import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle'; import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle';
import db from '$db/index'; import { db } from '../database';
import { sessionsTable, usersTable } from '$db/schema'; import { sessionsTable, usersTable } from '../database/tables';
import { config } from '../../common/config'; import { config } from '../../common/config';
const adapter = new DrizzlePostgreSQLAdapter(db, sessionsTable, usersTable); const adapter = new DrizzlePostgreSQLAdapter(db, sessionsTable, usersTable);
@ -18,7 +18,12 @@ export const lucia = new Lucia(adapter, {
}, },
getUserAttributes: (attributes) => { getUserAttributes: (attributes) => {
return { return {
...attributes, // ...attributes,
username: attributes.username,
email: attributes.email,
firstName: attributes.firstName,
lastName: attributes.lastName,
theme: attributes.theme,
}; };
}, },
sessionExpiresIn: new TimeSpan(2, 'w'), // 2 weeks sessionExpiresIn: new TimeSpan(2, 'w'), // 2 weeks

View file

@ -1,4 +1,5 @@
import { pgTable, text, uuid } from "drizzle-orm/pg-core"; import { pgTable, text, uuid } from "drizzle-orm/pg-core";
import { type InferSelectModel } from 'drizzle-orm';
import { timestamps } from '../utils'; import { timestamps } from '../utils';
import { usersTable } from "./users.table"; import { usersTable } from "./users.table";
@ -17,4 +18,6 @@ export const credentialsTable = pgTable('credentials', {
type: text('type').notNull().default(CredentialsType.PASSWORD), type: text('type').notNull().default(CredentialsType.PASSWORD),
secret_data: text('secret_data').notNull(), secret_data: text('secret_data').notNull(),
...timestamps ...timestamps
}); });
export type Credentials = InferSelectModel<typeof credentialsTable>;

View file

@ -45,7 +45,6 @@ export const requireAuth: MiddlewareHandler<{
}; };
}> = createMiddleware(async (c, next) => { }> = createMiddleware(async (c, next) => {
const user = c.var.user; const user = c.var.user;
const session = c.var.session;
if (!user) throw Unauthorized('You must be logged in to access this resource'); if (!user) throw Unauthorized('You must be logged in to access this resource');
return next(); return next();
}); });

View file

@ -8,6 +8,7 @@ import { UsersRepository } from '../repositories/users.repository';
import { CredentialsRepository } from '../repositories/credentials.repository'; import { CredentialsRepository } from '../repositories/credentials.repository';
import type { HonoRequest } from 'hono'; import type { HonoRequest } from 'hono';
import type {SigninUsernameDto} from "$lib/dtos/signin-username.dto"; import type {SigninUsernameDto} from "$lib/dtos/signin-username.dto";
import type {Credentials} from "$lib/server/api/infrastructure/database/tables";
@injectable() @injectable()
export class LoginRequestsService { export class LoginRequestsService {
@ -53,17 +54,23 @@ export class LoginRequestsService {
const totpCredentials = await this.credentialsRepository.findTOTPCredentialsByUserId(existingUser.id); const totpCredentials = await this.credentialsRepository.findTOTPCredentialsByUserId(existingUser.id);
return this.lucia.createSession(existingUser.id, { return await this.createUserSession(existingUser.id, req, totpCredentials);
ip_country: requestIpCountry || 'unknown',
ip_address: requestIpAddress || 'unknown',
twoFactorAuthEnabled:
!!totpCredentials &&
totpCredentials?.secret_data !== null &&
totpCredentials?.secret_data !== '',
isTwoFactorAuthenticated: false,
});
} }
async createUserSession(existingUserId: string, req: HonoRequest, totpCredentials: Credentials | undefined) {
const requestIpAddress = req.header('x-real-ip');
const requestIpCountry = req.header('x-vercel-ip-country');
return this.lucia.createSession(existingUserId, {
ip_country: requestIpCountry || 'unknown',
ip_address: requestIpAddress || 'unknown',
twoFactorAuthEnabled:
!!totpCredentials &&
totpCredentials?.secret_data !== null &&
totpCredentials?.secret_data !== '',
isTwoFactorAuthenticated: false,
});
}
// Create a new user and send a welcome email - or other onboarding process // Create a new user and send a welcome email - or other onboarding process
private async handleNewUserRegistration(email: string) { private async handleNewUserRegistration(email: string) {
const newUser = await this.usersRepository.create({ email, verified: true }) const newUser = await this.usersRepository.create({ email, verified: true })

View file

@ -1,8 +1,8 @@
import { generateIdFromEntropySize, type Session, type User } from 'lucia'; import { generateIdFromEntropySize, type Session, type User } from 'lucia';
import { TimeSpan, createDate } from 'oslo'; import { TimeSpan, createDate } from 'oslo';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import db from '../../db'; import { db } from './api/infrastructure/database/index';
import { password_reset_tokens } from '$db/schema'; import { password_reset_tokens } from './api/infrastructure/database/tables';
export async function createPasswordResetToken(userId: string): Promise<string> { export async function createPasswordResetToken(userId: string): Promise<string> {
// optionally invalidate all existing tokens // optionally invalidate all existing tokens

View file

@ -1,68 +0,0 @@
// lib/server/lucia.ts
import { Lucia, TimeSpan } from 'lucia';
import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle';
import db from '../../db';
import { sessionsTable, usersTable } from '$db/schema';
const adapter = new DrizzlePostgreSQLAdapter(db, sessionsTable, usersTable);
let domain;
if (process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'production') {
domain = 'boredgame.vercel.app';
} else if (process.env.VERCEL_ENV === 'preview' || process.env.VERCEL_ENV === 'development') {
domain = process.env.VERCEL_BRANCH_URL;
} else {
domain = 'localhost';
}
export const lucia = new Lucia(adapter, {
getSessionAttributes: (attributes) => {
return {
ipCountry: attributes.ip_country,
ipAddress: attributes.ip_address,
isTwoFactorAuthEnabled: attributes.twoFactorAuthEnabled,
isTwoFactorAuthenticated: attributes.isTwoFactorAuthenticated,
};
},
getUserAttributes: (attributes) => {
return {
username: attributes.username,
email: attributes.email,
firstName: attributes.firstName,
lastName: attributes.lastName,
theme: attributes.theme,
};
},
sessionExpiresIn: new TimeSpan(30, 'd'), // 30 days
sessionCookie: {
name: 'session',
expires: false, // session cookies have very long lifespan (2 years)
attributes: {
// set to `true` when using HTTPS
secure: process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'production',
sameSite: 'strict',
domain,
},
},
});
declare module 'lucia' {
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: DatabaseUserAttributes;
DatabaseSessionAttributes: DatabaseSessionAttributes;
}
interface DatabaseSessionAttributes {
ip_country: string;
ip_address: string;
twoFactorAuthEnabled: boolean;
isTwoFactorAuthenticated: boolean;
}
interface DatabaseUserAttributes {
username: string;
email: string;
firstName: string;
lastName: string;
theme: string;
}
}

View file

@ -11,8 +11,8 @@ export const signUpSchema = userSchema
password: true, password: true,
confirm_password: true, confirm_password: true,
}) })
.superRefine(({ confirm_password, password }, ctx) => { .superRefine(async ({ confirm_password, password }, ctx) => {
refinePasswords(confirm_password, password, ctx); return await refinePasswords(confirm_password, password, ctx);
}); });
export const signInSchema = z.object({ export const signInSchema = z.object({

View file

@ -60,18 +60,18 @@ export const actions: Actions = {
where: eq(usersTable.id, user!.id), where: eq(usersTable.id, user!.id),
}); });
if (!dbUser?.hashed_password) { // if (!dbUser?.hashed_password) {
form.data.password = ''; // form.data.password = '';
form.data.confirm_password = ''; // form.data.confirm_password = '';
form.data.current_password = ''; // form.data.current_password = '';
return setError( // return setError(
form, // form,
'Error occurred. Please try again or contact support if you need further help.', // 'Error occurred. Please try again or contact support if you need further help.',
); // );
} // }
const currentPasswordVerified = await new Argon2id().verify( const currentPasswordVerified = await new Argon2id().verify(
dbUser.hashed_password, // dbUser.hashed_password,
form.data.current_password, form.data.current_password,
); );
@ -86,10 +86,10 @@ export const actions: Actions = {
} }
const hashedPassword = await new Argon2id().hash(form.data.password); const hashedPassword = await new Argon2id().hash(form.data.password);
await lucia.invalidateUserSessions(user.id); await lucia.invalidateUserSessions(user.id);
await db // await db
.update(usersTable) // .update(usersTable)
.set({ hashed_password: hashedPassword }) // .set({ hashed_password: hashedPassword })
.where(eq(usersTable.id, user.id)); // .where(eq(usersTable.id, user.id));
await lucia.createSession(user.id, { await lucia.createSession(user.id, {
country: event.locals.session?.ipCountry ?? 'unknown', country: event.locals.session?.ipCountry ?? 'unknown',
}); });

View file

@ -115,14 +115,14 @@ export const actions: Actions = {
where: eq(usersTable.id, user!.id!), where: eq(usersTable.id, user!.id!),
}); });
if (!dbUser?.hashed_password) { // if (!dbUser?.hashed_password) {
addTwoFactorForm.data.current_password = ''; // addTwoFactorForm.data.current_password = '';
addTwoFactorForm.data.two_factor_code = ''; // addTwoFactorForm.data.two_factor_code = '';
return setError( // return setError(
addTwoFactorForm, // addTwoFactorForm,
'Error occurred. Please try again or contact support if you need further help.', // 'Error occurred. Please try again or contact support if you need further help.',
); // );
} // }
const twoFactorDetails = await db.query.twoFactor.findFirst({ const twoFactorDetails = await db.query.twoFactor.findFirst({
where: eq(twoFactor.userId, dbUser?.id), where: eq(twoFactor.userId, dbUser?.id),
@ -147,7 +147,7 @@ export const actions: Actions = {
} }
const currentPasswordVerified = await new Argon2id().verify( const currentPasswordVerified = await new Argon2id().verify(
dbUser.hashed_password, // dbUser.hashed_password,
addTwoFactorForm.data.current_password, addTwoFactorForm.data.current_password,
); );
@ -194,16 +194,16 @@ export const actions: Actions = {
where: eq(usersTable.id, user.id), where: eq(usersTable.id, user.id),
}); });
if (!dbUser?.hashed_password) { // if (!dbUser?.hashed_password) {
removeTwoFactorForm.data.current_password = ''; // removeTwoFactorForm.data.current_password = '';
return setError( // return setError(
removeTwoFactorForm, // removeTwoFactorForm,
'Error occurred. Please try again or contact support if you need further help.', // 'Error occurred. Please try again or contact support if you need further help.',
); // );
} // }
const currentPasswordVerified = await new Argon2id().verify( const currentPasswordVerified = await new Argon2id().verify(
dbUser.hashed_password, // dbUser.hashed_password,
removeTwoFactorForm.data.current_password, removeTwoFactorForm.data.current_password,
); );

View file

@ -1,23 +1,24 @@
import { loadFlash } from 'sveltekit-flash-message/server'; import { loadFlash } from 'sveltekit-flash-message/server';
import type { LayoutServerLoad } from '../$types'; import type { LayoutServerLoad } from '../$types';
import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils'; // import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils';
import { lucia } from '$lib/server/auth'; // import { lucia } from '$lib/server/auth';
export const load: LayoutServerLoad = loadFlash(async (event) => { export const load: LayoutServerLoad = loadFlash(async (event) => {
const { url, locals, cookies } = event; const { url, locals, cookies } = event;
const { user, session } = locals; const authedUser = await locals.getAuthedUserOrThrow();
if (userNotFullyAuthenticated(user, session)) { // if (userNotFullyAuthenticated(user, session)) {
await lucia.invalidateSession(locals.session!.id!); // await lucia.invalidateSession(locals.session!.id!);
const sessionCookie = lucia.createBlankSessionCookie(); // const sessionCookie = lucia.createBlankSessionCookie();
cookies.set(sessionCookie.name, sessionCookie.value, { // cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.', // path: '.',
...sessionCookie.attributes, // ...sessionCookie.attributes,
}); // });
} // }
return { return {
url: url.pathname, url: url.pathname,
user: userFullyAuthenticated(user, session) ? locals.user : null, // user: userFullyAuthenticated(user, session) ? locals.user : null,
user: authedUser,
}; };
}); });

View file

@ -1,13 +1,14 @@
import type { MetaTagsProps } from 'svelte-meta-tags'; import type { MetaTagsProps } from 'svelte-meta-tags';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import db from '../../db'; import {db} from '$lib/server/api/infrastructure/database/index';
import { collections, usersTable, wishlists } from '$db/schema'; import { collections, usersTable, wishlists } from '$lib/server/api/infrastructure/database/tables';
import { userFullyAuthenticated } from '$lib/server/auth-utils'; // import { userFullyAuthenticated } from '$lib/server/auth-utils';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
const { locals, url } = event; const { locals, url } = event;
const { user, session } = locals;
const authedUser = await locals.getAuthedUser();
const image = { const image = {
url: `${ url: `${
@ -41,9 +42,9 @@ export const load: PageServerLoad = async (event) => {
}, },
}); });
if (userFullyAuthenticated(user, session)) { // if (userFullyAuthenticated(user, session)) {
const dbUser = await db.query.usersTable.findFirst({ const dbUser = await db.query.usersTable.findFirst({
where: eq(usersTable.id, user!.id!), where: eq(usersTable.id, authedUser!.id!),
}); });
console.log('Sending back user details'); console.log('Sending back user details');
@ -52,14 +53,14 @@ export const load: PageServerLoad = async (event) => {
cuid: true, cuid: true,
name: true, name: true,
}, },
where: eq(wishlists.user_id, user!.id!), where: eq(wishlists.user_id, authedUser!.id!),
}); });
const userCollection = await db.query.collections.findMany({ const userCollection = await db.query.collections.findMany({
columns: { columns: {
cuid: true, cuid: true,
name: true, name: true,
}, },
where: eq(collections.user_id, user!.id!), where: eq(collections.user_id, authedUser!.id!),
}); });
console.log('Wishlists', userWishlists); console.log('Wishlists', userWishlists);
@ -74,7 +75,7 @@ export const load: PageServerLoad = async (event) => {
wishlists: userWishlists, wishlists: userWishlists,
collections: userCollection, collections: userCollection,
}; };
} // }
return { metaTagsChild: metaTags, user: null, wishlists: [], collections: [] }; return { metaTagsChild: metaTags, user: null, wishlists: [], collections: [] };
}; };

View file

@ -4,10 +4,9 @@ import { Argon2id } from 'oslo/password';
import { zod } from 'sveltekit-superforms/adapters'; import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server'; import { setError, superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import { RateLimiter } from 'sveltekit-rate-limiter/server'; import { db } from '../../../lib/server/api/infrastructure/database/index';
import db from '../../../db'; import { lucia } from '../../../lib/server/api/infrastructure/auth/lucia';
import { lucia } from '$lib/server/auth'; import { credentialsTable, usersTable } from '../../../lib/server/api/infrastructure/database/tables';
import { twoFactor, usersTable, type Users } from '$db/schema';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import {signinUsernameDto} from "$lib/dtos/signin-username.dto"; import {signinUsernameDto} from "$lib/dtos/signin-username.dto";
@ -41,10 +40,6 @@ export const load: PageServerLoad = async (event) => {
export const actions: Actions = { export const actions: Actions = {
default: async (event) => { default: async (event) => {
// if (await limiter.isLimited(event)) {
// throw error(429);
// }
const { locals } = event; const { locals } = event;
const authedUser = await locals.getAuthedUser(); const authedUser = await locals.getAuthedUser();
@ -66,68 +61,68 @@ export const actions: Actions = {
}); });
} }
let session; // let session;
let sessionCookie; // let sessionCookie;
const user: Users | undefined = await db.query.usersTable.findFirst({ // const user: Users | undefined = await db.query.usersTable.findFirst({
where: or(eq(usersTable.username, form.data.username), eq(usersTable.email, form.data.username)), // where: or(eq(usersTable.username, form.data.username), eq(usersTable.email, form.data.username)),
}); // });
//
if (!user) { // if (!user) {
form.data.password = ''; // form.data.password = '';
return setError(form, 'username', 'Your username or password is incorrect.'); // return setError(form, 'username', 'Your username or password is incorrect.');
} // }
//
let twoFactorDetails; // let twoFactorDetails;
//
try { try {
const password = form.data.password; // const password = form.data.password;
console.log('user', JSON.stringify(user, null, 2)); // console.log('user', JSON.stringify(user, null, 2));
//
if (!user?.hashed_password) { // if (!user?.hashed_password) {
console.log('invalid username/password'); // console.log('invalid username/password');
form.data.password = ''; // form.data.password = '';
return setError(form, 'password', 'Your username or password is incorrect.'); // return setError(form, 'password', 'Your username or password is incorrect.');
} // }
//
const validPassword = await new Argon2id().verify(user.hashed_password, password); // const validPassword = await new Argon2id().verify(user.hashed_password, password);
if (!validPassword) { // if (!validPassword) {
console.log('invalid password'); // console.log('invalid password');
form.data.password = ''; // form.data.password = '';
return setError(form, 'password', 'Your username or password is incorrect.'); // return setError(form, 'password', 'Your username or password is incorrect.');
} // }
//
console.log('ip', locals.ip); // console.log('ip', locals.ip);
console.log('country', locals.country); // console.log('country', locals.country);
//
twoFactorDetails = await db.query.twoFactor.findFirst({ // twoFactorDetails = await db.query.twoFactor.findFirst({
where: eq(twoFactor.userId, user?.id), // where: eq(twoFactor.userId, user?.id),
}); // });
//
if (twoFactorDetails?.secret && twoFactorDetails?.enabled) { // if (twoFactorDetails?.secret && twoFactorDetails?.enabled) {
await db.update(twoFactor).set({ // await db.update(twoFactor).set({
initiatedTime: new Date(), // initiatedTime: new Date(),
}); // });
//
session = await lucia.createSession(user.id, { // session = await lucia.createSession(user.id, {
ip_country: locals.country, // ip_country: locals.country,
ip_address: locals.ip, // ip_address: locals.ip,
twoFactorAuthEnabled: // twoFactorAuthEnabled:
twoFactorDetails?.enabled && // twoFactorDetails?.enabled &&
twoFactorDetails?.secret !== null && // twoFactorDetails?.secret !== null &&
twoFactorDetails?.secret !== '', // twoFactorDetails?.secret !== '',
isTwoFactorAuthenticated: false, // isTwoFactorAuthenticated: false,
}); // });
} else { // } else {
session = await lucia.createSession(user.id, { // session = await lucia.createSession(user.id, {
ip_country: locals.country, // ip_country: locals.country,
ip_address: locals.ip, // ip_address: locals.ip,
twoFactorAuthEnabled: false, // twoFactorAuthEnabled: false,
isTwoFactorAuthenticated: false, // isTwoFactorAuthenticated: false,
}); // });
} // }
console.log('logging in session', session); // console.log('logging in session', session);
sessionCookie = lucia.createSessionCookie(session.id); // sessionCookie = lucia.createSessionCookie(session.id);
console.log('logging in session cookie', sessionCookie); // console.log('logging in session cookie', sessionCookie);
} catch (e) { } catch (e) {
// TODO: need to return error message to the client // TODO: need to return error message to the client
console.error(e); console.error(e);
@ -135,26 +130,26 @@ export const actions: Actions = {
return setError(form, '', 'Your username or password is incorrect.'); return setError(form, '', 'Your username or password is incorrect.');
} }
console.log('setting session cookie', sessionCookie); // console.log('setting session cookie', sessionCookie);
event.cookies.set(sessionCookie.name, sessionCookie.value, { // event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.', // path: '.',
...sessionCookie.attributes, // ...sessionCookie.attributes,
}); // });
form.data.username = ''; form.data.username = '';
form.data.password = ''; form.data.password = '';
if ( // if (
twoFactorDetails?.enabled && // twoFactorDetails?.enabled &&
twoFactorDetails?.secret !== null && // twoFactorDetails?.secret !== null &&
twoFactorDetails?.secret !== '' // twoFactorDetails?.secret !== ''
) { // ) {
console.log('redirecting to TOTP page'); // console.log('redirecting to TOTP page');
const message = { type: 'success', message: 'Please enter your TOTP code.' } as const; // const message = { type: 'success', message: 'Please enter your TOTP code.' } as const;
redirect(302, '/totp', message, event); // redirect(302, '/totp', message, event);
} else { // } else {
const message = { type: 'success', message: 'Signed In!' } as const; // const message = { type: 'success', message: 'Signed In!' } as const;
redirect(302, '/', message, event); // redirect(302, '/', message, event);
} // }
}, },
}; };

View file

@ -12,6 +12,7 @@ import { add_user_to_role } from '$server/roles';
import db from '../../../db'; import db from '../../../db';
import { collections, usersTable, wishlists } from '$db/schema'; import { collections, usersTable, wishlists } from '$db/schema';
import { createId as cuid2 } from '@paralleldrive/cuid2'; import { createId as cuid2 } from '@paralleldrive/cuid2';
import {signupUsernameEmailDto} from "$lib/dtos/signup-username-email.dto";
const limiter = new RateLimiter({ const limiter = new RateLimiter({
// A rate is defined by [number, unit] // A rate is defined by [number, unit]
@ -55,7 +56,7 @@ export const load: PageServerLoad = async (event) => {
// } // }
return { return {
form: await superValidate(zod(signUpSchema), { form: await superValidate(zod(signupUsernameEmailDto), {
defaults: signUpDefaults, defaults: signUpDefaults,
}), }),
}; };
@ -63,11 +64,20 @@ export const load: PageServerLoad = async (event) => {
export const actions: Actions = { export const actions: Actions = {
default: async (event) => { default: async (event) => {
if (await limiter.isLimited(event)) { const { locals } = event;
throw error(429);
const authedUser = await locals.getAuthedUser();
if (authedUser) {
const message = { type: 'success', message: 'You are already signed in' } as const;
throw redirect('/', message, event);
} }
// fail(401, { message: 'Sign-up not yet available. Please add your email to the waitlist!' });
const form = await superValidate(event, zod(signUpSchema)); const form = await superValidate(event, zod(signupUsernameEmailDto));
const { error } = await locals.api.signup.$post({ json: form.data }).then(locals.parseApiResponse);
if (error) return setError(form, 'username', error);
if (!form.valid) { if (!form.valid) {
form.data.password = ''; form.data.password = '';
form.data.confirm_password = ''; form.data.confirm_password = '';
@ -76,80 +86,80 @@ export const actions: Actions = {
}); });
} }
let session; // let session;
let sessionCookie; // let sessionCookie;
// Adding user to the db // // Adding user to the db
console.log('Check if user already exists'); // console.log('Check if user already exists');
//
const existing_user = await db.query.usersTable.findFirst({ // const existing_user = await db.query.usersTable.findFirst({
where: eq(usersTable.username, form.data.username), // where: eq(usersTable.username, form.data.username),
}); // });
//
if (existing_user) { // if (existing_user) {
return setError(form, 'username', 'You cannot create an account with that username'); // return setError(form, 'username', 'You cannot create an account with that username');
} // }
//
console.log('Creating user'); // console.log('Creating user');
//
const hashedPassword = await new Argon2id().hash(form.data.password); // const hashedPassword = await new Argon2id().hash(form.data.password);
//
const user = await db // const user = await db
.insert(usersTable) // .insert(usersTable)
.values({ // .values({
username: form.data.username, // username: form.data.username,
hashed_password: hashedPassword, // hashed_password: hashedPassword,
email: form.data.email, // email: form.data.email,
first_name: form.data.firstName ?? '', // first_name: form.data.firstName ?? '',
last_name: form.data.lastName ?? '', // last_name: form.data.lastName ?? '',
verified: false, // verified: false,
receive_email: false, // receive_email: false,
theme: 'system', // theme: 'system',
}) // })
.returning(); // .returning();
console.log('signup user', user); // console.log('signup user', user);
//
if (!user || user.length === 0) { // if (!user || user.length === 0) {
return fail(400, { // return fail(400, {
form, // form,
message: `Could not create your account. Please try again. If the problem persists, please contact support. Error ID: ${cuid2()}`, // message: `Could not create your account. Please try again. If the problem persists, please contact support. Error ID: ${cuid2()}`,
}); // });
} // }
//
await add_user_to_role(user[0].id, 'user', true); // await add_user_to_role(user[0].id, 'user', true);
await db.insert(collections).values({ // await db.insert(collections).values({
user_id: user[0].id, // user_id: user[0].id,
}); // });
await db.insert(wishlists).values({ // await db.insert(wishlists).values({
user_id: user[0].id, // user_id: user[0].id,
}); // });
//
try { // try {
session = await lucia.createSession(user[0].id, { // session = await lucia.createSession(user[0].id, {
ip_country: event.locals.ip, // ip_country: event.locals.ip,
ip_address: event.locals.country, // ip_address: event.locals.country,
twoFactorAuthEnabled: false, // twoFactorAuthEnabled: false,
isTwoFactorAuthenticated: false, // isTwoFactorAuthenticated: false,
}); // });
sessionCookie = lucia.createSessionCookie(session.id); // sessionCookie = lucia.createSessionCookie(session.id);
} catch (e: any) { // } catch (e: any) {
if (e.message.toUpperCase() === `DUPLICATE_KEY_ID`) { // if (e.message.toUpperCase() === `DUPLICATE_KEY_ID`) {
// key already exists // // key already exists
console.error('Lucia Error: ', e); // console.error('Lucia Error: ', e);
} // }
console.log(e); // console.log(e);
const message = { // const message = {
type: 'error', // type: 'error',
message: 'Unable to create your account. Please try again.', // message: 'Unable to create your account. Please try again.',
}; // };
form.data.password = ''; // form.data.password = '';
form.data.confirm_password = ''; // form.data.confirm_password = '';
error(500, message); // error(500, message);
} // }
//
event.cookies.set(sessionCookie.name, sessionCookie.value, { // event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.', // path: '.',
...sessionCookie.attributes, // ...sessionCookie.attributes,
}); // });
redirect(302, '/'); redirect(302, '/');
// const message = { type: 'success', message: 'Signed Up!' } as const; // const message = { type: 'success', message: 'Signed Up!' } as const;

View file

@ -1,9 +1,11 @@
import { loadFlash } from 'sveltekit-flash-message/server'; import { loadFlash } from 'sveltekit-flash-message/server';
import type { LayoutServerLoad } from './$types'; import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = loadFlash(async ({ url, locals }) => { export const load: LayoutServerLoad = loadFlash(async (event) => {
const { locals, url } = event;
const user = await locals.getAuthedUser();
return { return {
url: url.pathname, url: url.pathname,
user: locals.user user,
}; };
}); });

View file

@ -34,7 +34,7 @@ export async function POST({ request, params }) {
await lucia.invalidateUserSessions(token.user_id); await lucia.invalidateUserSessions(token.user_id);
const hashPassword = await new Argon2id().hash(password); const hashPassword = await new Argon2id().hash(password);
await db.update(usersTable).set({ hashed_password: hashPassword }).where(eq(usersTable.id, token.user_id)); // await db.update(usersTable).set({ hashed_password: hashPassword }).where(eq(usersTable.id, token.user_id));
const session = await lucia.createSession(token.user_id, {}); const session = await lucia.createSession(token.user_id, {});
const sessionCookie = lucia.createSessionCookie(session.id); const sessionCookie = lucia.createSessionCookie(session.id);