mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Copying over tables, migrations, seeds, etc. Creating DTOs for future use.
This commit is contained in:
parent
d70b3061b5
commit
16191509b4
56 changed files with 6900 additions and 13 deletions
|
|
@ -4,8 +4,8 @@ import env from './src/env';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
dialect: 'postgresql',
|
dialect: 'postgresql',
|
||||||
schema: './src/db/schema/index.ts',
|
out: './src/lib/server/api/infrastructure/database/migrations',
|
||||||
out: './src/db/migrations',
|
schema: './src/lib/server/api/infrastructure/database/tables/index.ts',
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
host: env.DATABASE_HOST || 'localhost',
|
host: env.DATABASE_HOST || 'localhost',
|
||||||
port: Number(env.DATABASE_PORT) || 5432,
|
port: Number(env.DATABASE_PORT) || 5432,
|
||||||
|
|
@ -18,4 +18,8 @@ export default defineConfig({
|
||||||
verbose: true,
|
verbose: true,
|
||||||
// Always as for confirmation
|
// Always as for confirmation
|
||||||
strict: true,
|
strict: true,
|
||||||
|
migrations: {
|
||||||
|
table: 'migrations',
|
||||||
|
schema: 'public'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@ for (const table of [
|
||||||
schema.publishers_to_games,
|
schema.publishers_to_games,
|
||||||
schema.recoveryCodes,
|
schema.recoveryCodes,
|
||||||
schema.roles,
|
schema.roles,
|
||||||
schema.sessions,
|
schema.sessionsTable,
|
||||||
schema.userRoles,
|
schema.userRoles,
|
||||||
schema.users,
|
schema.usersTable,
|
||||||
schema.twoFactor,
|
schema.twoFactor,
|
||||||
schema.wishlists,
|
schema.wishlists,
|
||||||
schema.wishlist_items,
|
schema.wishlist_items,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { eq } from 'drizzle-orm';
|
|
||||||
import { type db } from '$db';
|
import { type db } from '$db';
|
||||||
import * as schema from '$db/schema';
|
import * as schema from '$db/schema';
|
||||||
import roles from './data/roles.json';
|
import roles from './data/roles.json';
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export default async function seed(db: db) {
|
||||||
|
|
||||||
console.log('Admin Role: ', adminRole);
|
console.log('Admin Role: ', adminRole);
|
||||||
const adminUser = await db
|
const adminUser = await db
|
||||||
.insert(schema.users)
|
.insert(schema.usersTable)
|
||||||
.values({
|
.values({
|
||||||
username: `${env.ADMIN_USERNAME}`,
|
username: `${env.ADMIN_USERNAME}`,
|
||||||
email: '',
|
email: '',
|
||||||
|
|
@ -73,7 +73,7 @@ export default async function seed(db: db) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
users.map(async (user) => {
|
users.map(async (user) => {
|
||||||
const [insertedUser] = await db
|
const [insertedUser] = await db
|
||||||
.insert(schema.users)
|
.insert(schema.usersTable)
|
||||||
.values({
|
.values({
|
||||||
...user,
|
...user,
|
||||||
hashed_password: await new Argon2id().hash(user.password),
|
hashed_password: await new Argon2id().hash(user.password),
|
||||||
|
|
|
||||||
20
src/lib/dtos/register-emailpassword.dto.ts
Normal file
20
src/lib/dtos/register-emailpassword.dto.ts
Normal file
|
|
@ -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<typeof registerEmailPasswordDto>;
|
||||||
12
src/lib/dtos/signin-email.dto.ts
Normal file
12
src/lib/dtos/signin-email.dto.ts
Normal file
|
|
@ -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<typeof signInEmailDto>;
|
||||||
|
|
@ -1,10 +1,23 @@
|
||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
|
import { zValidator } from '@hono/zod-validator';
|
||||||
import { requireAuth } from "../middleware/auth.middleware";
|
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 users = new Hono()
|
||||||
const user = c.var.user;
|
.get('/me', requireAuth, async (c) => {
|
||||||
return c.json({ user });
|
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 default users;
|
||||||
export type UsersType = typeof users
|
export type UsersType = typeof users
|
||||||
|
|
@ -13,11 +13,14 @@ app.use(verifyOrigin).use(validateAuthSession);
|
||||||
/* --------------------------------- Routes --------------------------------- */
|
/* --------------------------------- Routes --------------------------------- */
|
||||||
const routes = app
|
const routes = app
|
||||||
.route('/iam', users)
|
.route('/iam', users)
|
||||||
|
.get('/', (c) => c.json({ message: 'Server is healthy' }));
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Exports */
|
/* Exports */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
export const rpc = hc<typeof routes>(config.ORIGIN);
|
export type AppType = typeof routes;
|
||||||
|
|
||||||
|
export const rpc = hc<AppType>(config.ORIGIN);
|
||||||
export type ApiClient = typeof rpc;
|
export type ApiClient = typeof rpc;
|
||||||
export type ApiRoutes = typeof routes;
|
export type ApiRoutes = typeof routes;
|
||||||
export { app };
|
export { app };
|
||||||
22
src/lib/server/api/infrastructure/database/index.ts
Normal file
22
src/lib/server/api/infrastructure/database/index.ts
Normal file
|
|
@ -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;
|
||||||
26
src/lib/server/api/infrastructure/database/migrate.ts
Normal file
26
src/lib/server/api/infrastructure/database/migrate.ts
Normal file
|
|
@ -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();
|
||||||
|
|
@ -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')
|
||||||
|
));
|
||||||
|
|
@ -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";
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
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
|
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
49
src/lib/server/api/infrastructure/database/seed.ts
Normal file
49
src/lib/server/api/infrastructure/database/seed.ts
Normal file
|
|
@ -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();
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "admin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "editor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "moderator"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as users } from './users';
|
||||||
|
export { default as roles } from './roles';
|
||||||
11
src/lib/server/api/infrastructure/database/seeds/roles.ts
Normal file
11
src/lib/server/api/infrastructure/database/seeds/roles.ts
Normal file
|
|
@ -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.');
|
||||||
|
}
|
||||||
98
src/lib/server/api/infrastructure/database/seeds/users.ts
Normal file
98
src/lib/server/api/infrastructure/database/seeds/users.ts
Normal file
|
|
@ -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,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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<typeof categories>;
|
||||||
|
|
||||||
|
export const categories_relations = relations(categories, ({ many }) => ({
|
||||||
|
categories_to_games: many(categories_to_games),
|
||||||
|
categoriesToExternalIds: many(categoriesToExternalIds),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default categories;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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<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;
|
||||||
|
|
@ -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<typeof collections>;
|
||||||
|
|
||||||
|
export default collections;
|
||||||
|
|
@ -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<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;
|
||||||
|
|
@ -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<typeof externalIds>;
|
||||||
|
|
||||||
|
export default externalIds;
|
||||||
53
src/lib/server/api/infrastructure/database/tables/games.ts
Normal file
53
src/lib/server/api/infrastructure/database/tables/games.ts
Normal file
|
|
@ -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<typeof games>;
|
||||||
|
|
||||||
|
export default games;
|
||||||
|
|
@ -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;
|
||||||
36
src/lib/server/api/infrastructure/database/tables/index.ts
Normal file
36
src/lib/server/api/infrastructure/database/tables/index.ts
Normal file
|
|
@ -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';
|
||||||
|
|
@ -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<typeof mechanics>;
|
||||||
|
|
||||||
|
export const mechanics_relations = relations(mechanics, ({ many }) => ({
|
||||||
|
mechanics_to_games: many(mechanicsToGames),
|
||||||
|
mechanicsToExternalIds: many(mechanicsToExternalIds),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default mechanics;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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<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;
|
||||||
|
|
@ -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<typeof publishers>;
|
||||||
|
|
||||||
|
export const publishers_relations = relations(publishers, ({ many }) => ({
|
||||||
|
publishers_to_games: many(publishers_to_games),
|
||||||
|
publishersToExternalIds: many(publishersToExternalIds),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default publishers;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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<typeof recovery_codes>;
|
||||||
|
|
||||||
|
export default recovery_codes;
|
||||||
23
src/lib/server/api/infrastructure/database/tables/roles.ts
Normal file
23
src/lib/server/api/infrastructure/database/tables/roles.ts
Normal file
|
|
@ -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<typeof roles>;
|
||||||
|
|
||||||
|
export const role_relations = relations(roles, ({ many }) => ({
|
||||||
|
user_roles: many(user_roles),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default roles;
|
||||||
|
|
@ -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<typeof sessionsTable>;
|
||||||
|
|
||||||
|
export default sessionsTable;
|
||||||
|
|
@ -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<typeof twoFactorTable>;
|
||||||
|
|
||||||
|
export default twoFactorTable;
|
||||||
|
|
@ -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<typeof user_roles>;
|
||||||
|
|
||||||
|
export default user_roles;
|
||||||
|
|
@ -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<typeof usersTable>;
|
||||||
|
|
||||||
|
export default usersTable;
|
||||||
|
|
@ -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<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;
|
||||||
|
|
@ -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<typeof wishlists>;
|
||||||
|
|
||||||
|
export const wishlists_relations = relations(wishlists, ({ one }) => ({
|
||||||
|
user: one(usersTable, {
|
||||||
|
fields: [wishlists.user_id],
|
||||||
|
references: [usersTable.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default wishlists;
|
||||||
42
src/lib/server/api/infrastructure/database/utils.ts
Normal file
42
src/lib/server/api/infrastructure/database/utils.ts
Normal file
|
|
@ -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 = <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()
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<html lang='en'>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
|
||||||
|
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
|
||||||
|
<title>Email Change Request</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p class='title'>Email address change notice </p>
|
||||||
|
<p>
|
||||||
|
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.</p>
|
||||||
|
</body>
|
||||||
|
<style>
|
||||||
|
.title { font-size: 24px; font-weight: 700; } .token-text { font-size: 24px; font-weight: 700; margin-top: 8px; }
|
||||||
|
.token-title { font-size: 18px; font-weight: 700; margin-bottom: 0px; }
|
||||||
|
.center { display: flex; justify-content: center; align-items: center; flex-direction: column;}
|
||||||
|
.token-subtext { font-size: 12px; margin-top: 0px; }
|
||||||
|
</style>
|
||||||
|
</html>
|
||||||
10
src/lib/server/api/providers/database.provider.ts
Normal file
10
src/lib/server/api/providers/database.provider.ts
Normal file
|
|
@ -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>(DatabaseProvider, { useValue: db });
|
||||||
3
src/lib/server/api/providers/index.ts
Normal file
3
src/lib/server/api/providers/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './database.provider';
|
||||||
|
export * from './lucia.provider';
|
||||||
|
export * from './redis.provider';
|
||||||
11
src/lib/server/api/providers/lucia.provider.ts
Normal file
11
src/lib/server/api/providers/lucia.provider.ts
Normal file
|
|
@ -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>(LuciaProvider, { useValue: lucia });
|
||||||
14
src/lib/server/api/providers/redis.provider.ts
Normal file
14
src/lib/server/api/providers/redis.provider.ts
Normal file
|
|
@ -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>(RedisProvider, {
|
||||||
|
useValue: new RedisClient(config.REDIS_URL)
|
||||||
|
});
|
||||||
62
src/lib/server/api/services/login-requests.service.ts
Normal file
62
src/lib/server/api/services/login-requests.service.ts
Normal file
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,10 @@
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"sourceMap": 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
|
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||||
//
|
//
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue