mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
commit
f409abb1c6
206 changed files with 5776 additions and 12035 deletions
36
.env.example
Normal file
36
.env.example
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# Private
|
||||
ORIGIN=http://localhost:5173
|
||||
|
||||
NODE_ENV=development
|
||||
|
||||
DATABASE_USER='postgres'
|
||||
DATABASE_PASSWORD='postgres'
|
||||
DATABASE_HOST='localhost'
|
||||
DATABASE_PORT=5432
|
||||
DATABASE_DB='postgres'
|
||||
|
||||
REDIS_URL='redis://127.0.0.1:6379/0'
|
||||
|
||||
DB_MIGRATING='false'
|
||||
DB_SEEDING='false'
|
||||
ADMIN_USERNAME=
|
||||
ADMIN_PASSWORD=
|
||||
|
||||
TWO_FACTOR_TIMEOUT=300000
|
||||
|
||||
# OAuth
|
||||
GITHUB_CLIENT_ID=""
|
||||
GITHUB_CLIENT_SECRET=""
|
||||
GOOGLE_CLIENT_ID=""
|
||||
GOOGLE_CLIENT_SECRET=""
|
||||
|
||||
# Public
|
||||
|
||||
PUBLIC_SITE_NAME='Bored Game'
|
||||
PUBLIC_SITE_URL='http://localhost:5173'
|
||||
PUBLIC_UMAMI_DO_NOT_TRACK=true
|
||||
PUBLIC_UMAMI_URL=
|
||||
PUBLIC_UMAMI_ID=
|
||||
|
||||
# quick setting for key-combo only
|
||||
SVELTE_INSPECTOR_TOGGLE=control-shift-i
|
||||
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
|
|
@ -1,3 +1,9 @@
|
|||
{
|
||||
"cSpell.words": ["iconify", "kickstarter", "lucide", "msrp", "pcss"]
|
||||
"cSpell.words": [
|
||||
"iconify",
|
||||
"kickstarter",
|
||||
"lucide",
|
||||
"msrp",
|
||||
"pcss"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License Copyright (c) 2024 Bradley Shellnut
|
||||
|
||||
Permission is hereby granted,
|
||||
free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
(including the next paragraph) shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
|
@ -3,12 +3,12 @@
|
|||
"style": "default",
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/app.postcss",
|
||||
"css": "src/lib/styles/app.pcss",
|
||||
"baseColor": "slate"
|
||||
},
|
||||
"aliases": {
|
||||
"components": "$lib/components",
|
||||
"utils": "$lib/utils"
|
||||
"utils": "$lib/utils/ui"
|
||||
},
|
||||
"typescript": true
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import 'dotenv/config'
|
||||
import env from './src/lib/server/api/common/env'
|
||||
import { defineConfig } from 'drizzle-kit'
|
||||
import env from './src/env'
|
||||
|
||||
export default defineConfig({
|
||||
dialect: 'postgresql',
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
import { error, json } from '@sveltejs/kit';
|
||||
import { getGame } from '$lib/utils/db/gameUtils.js';
|
||||
import { getGame } from '$lib/utils/db/gameUtils.js'
|
||||
import { error, json } from '@sveltejs/kit'
|
||||
|
||||
export const GET = async ({ locals, params }) => {
|
||||
const game_id = Number(params.id).valueOf();
|
||||
const game_id = Number(params.id).valueOf()
|
||||
|
||||
// TODO: Debounce excessive calls and possibly throttle
|
||||
if (isNaN(game_id) || !isFinite(game_id)) {
|
||||
error(400, { message: 'Invalid game id' });
|
||||
error(400, { message: 'Invalid game id' })
|
||||
}
|
||||
|
||||
try {
|
||||
return json(await getGame(locals, params.id));
|
||||
return json(await getGame(locals, params.id))
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return new Response('Could not get games', {
|
||||
status: 500
|
||||
});
|
||||
console.error(e)
|
||||
return new Response('Could not get gamesTable', {
|
||||
status: 500,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +1,34 @@
|
|||
import { error, json } from '@sveltejs/kit';
|
||||
import db from '../../../../db';
|
||||
import { asc, desc, eq, ilike, or } from 'drizzle-orm';
|
||||
import { games } from '$db/schema';
|
||||
import kebabCase from 'just-kebab-case';
|
||||
import {
|
||||
FilterSchema,
|
||||
PaginationSchema,
|
||||
SearchSchema,
|
||||
SortSchema,
|
||||
} from '$lib/validations/zod-schemas';
|
||||
import { games } from '$db/schema'
|
||||
import { FilterSchema, PaginationSchema, SearchSchema, SortSchema } from '$lib/validations/zod-schemas'
|
||||
import { error, json } from '@sveltejs/kit'
|
||||
import { asc, desc, eq, ilike, or } from 'drizzle-orm'
|
||||
import kebabCase from 'just-kebab-case'
|
||||
import db from '../../../../db'
|
||||
|
||||
// Search a user's collection
|
||||
export const GET = async ({ url, locals }) => {
|
||||
const searchParams = Object.fromEntries(url.searchParams);
|
||||
const searchParams = Object.fromEntries(url.searchParams)
|
||||
|
||||
const searchGames = PaginationSchema.merge(FilterSchema)
|
||||
.merge(SortSchema)
|
||||
.merge(SearchSchema)
|
||||
.parse(searchParams);
|
||||
const searchGames = PaginationSchema.merge(FilterSchema).merge(SortSchema).merge(SearchSchema).parse(searchParams)
|
||||
|
||||
if (searchGames.status !== 'success') {
|
||||
error(400, 'Invalid request');
|
||||
error(400, 'Invalid request')
|
||||
}
|
||||
|
||||
const q = searchParams?.q?.trim() || '';
|
||||
const limit = parseInt(searchParams?.limit) || 10;
|
||||
const skip = parseInt(searchParams?.skip) || 0;
|
||||
const order: OrderDirection = searchParams?.order === 'desc' ? 'desc' : 'asc';
|
||||
const exact = searchParams?.exact === 'true';
|
||||
let orderBy = searchParams?.orderBy || 'slug';
|
||||
const q = searchParams?.q?.trim() || ''
|
||||
const limit = parseInt(searchParams?.limit) || 10
|
||||
const skip = parseInt(searchParams?.skip) || 0
|
||||
const order: OrderDirection = searchParams?.order === 'desc' ? 'desc' : 'asc'
|
||||
const exact = searchParams?.exact === 'true'
|
||||
let orderBy = searchParams?.orderBy || 'slug'
|
||||
|
||||
if (orderBy === 'name') {
|
||||
orderBy = 'slug';
|
||||
orderBy = 'slug'
|
||||
}
|
||||
console.log(
|
||||
`q: ${q}, limit: ${limit}, skip: ${skip}, order: ${order}, exact: ${exact}, orderBy: ${orderBy}`,
|
||||
);
|
||||
console.log(exact);
|
||||
console.log(`q: ${q}, limit: ${limit}, skip: ${skip}, order: ${order}, exact: ${exact}, orderBy: ${orderBy}`)
|
||||
console.log(exact)
|
||||
if (exact) {
|
||||
console.log('Exact Search API');
|
||||
console.log('Exact Search API')
|
||||
const game = await db.query.games.findFirst({
|
||||
where: eq(games.name, q),
|
||||
columns: {
|
||||
|
|
@ -47,14 +37,14 @@ export const GET = async ({ url, locals }) => {
|
|||
slug: true,
|
||||
thumb_url: true,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
if (!game) {
|
||||
error(404, { message: 'No games found' });
|
||||
error(404, { message: 'No gamesTable found' })
|
||||
}
|
||||
const foundGames = [game];
|
||||
console.log('Games found in Exact Search API', JSON.stringify(foundGames, null, 2));
|
||||
return json(foundGames);
|
||||
const foundGames = [game]
|
||||
console.log('Games found in Exact Search API', JSON.stringify(foundGames, null, 2))
|
||||
return json(foundGames)
|
||||
} else {
|
||||
const foundGames =
|
||||
(await db
|
||||
|
|
@ -68,37 +58,37 @@ export const GET = async ({ url, locals }) => {
|
|||
.where(or(ilike(games.name, `%${q}%`), ilike(games.slug, `%${kebabCase(q)}%`)))
|
||||
.orderBy(getOrderDirection(order)(getOrderBy(orderBy)))
|
||||
.offset(skip)
|
||||
.limit(limit)) || [];
|
||||
.limit(limit)) || []
|
||||
// const foundGames = await db.select({
|
||||
// id: games.id,
|
||||
// name: games.name,
|
||||
// slug: games.slug,
|
||||
// thumb_url: games.thumb_url
|
||||
// id: gamesTable.id,
|
||||
// name: gamesTable.name,
|
||||
// slug: gamesTable.slug,
|
||||
// thumb_url: gamesTable.thumb_url
|
||||
// })
|
||||
// .from(games)
|
||||
// .where(sql`to_tsvector('simple', ${games.name}) || to_tsvector('simple', ${games.slug}) @@ to_tsquery('simple', ${q})`)
|
||||
// .from(gamesTable)
|
||||
// .where(sql`to_tsvector('simple', ${gamesTable.name}) || to_tsvector('simple', ${gamesTable.slug}) @@ to_tsquery('simple', ${q})`)
|
||||
// .orderBy(sql`${orderBy} ${order}`).offset(skip).limit(limit) || [];
|
||||
if (foundGames.length === 0) {
|
||||
error(404, { message: 'No games found' });
|
||||
error(404, { message: 'No gamesTable found' })
|
||||
}
|
||||
console.log('Games found in Search API', JSON.stringify(foundGames, null, 2));
|
||||
return json(foundGames);
|
||||
console.log('Games found in Search API', JSON.stringify(foundGames, null, 2))
|
||||
return json(foundGames)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
type OrderDirection = 'asc' | 'desc';
|
||||
type OrderDirection = 'asc' | 'desc'
|
||||
|
||||
const getOrderDirection = (direction: OrderDirection) => {
|
||||
return direction === 'asc' ? asc : desc;
|
||||
};
|
||||
return direction === 'asc' ? asc : desc
|
||||
}
|
||||
|
||||
const getOrderBy = (orderBy: string) => {
|
||||
switch (orderBy) {
|
||||
case 'name':
|
||||
return games.name;
|
||||
return games.name
|
||||
case 'slug':
|
||||
return games.slug;
|
||||
return games.slug
|
||||
default:
|
||||
return games.slug;
|
||||
return games.slug
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import { getPublisher, updatePublisher } from '$lib/utils/db/publisherUtils.js';
|
||||
import type { Publishers } from '$db/schema';
|
||||
import type { Publishers } from '$db/schema'
|
||||
import { getPublisher, updatePublisher } from '$lib/utils/db/publisherUtils.js'
|
||||
|
||||
export async function GET({ locals, params }) {
|
||||
try {
|
||||
return await getPublisher(locals, params.id);
|
||||
return await getPublisher(locals, params.id)
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return new Response('Could not get publishers', {
|
||||
console.error(e)
|
||||
return new Response('Could not get publishersTable', {
|
||||
status: 500,
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT({ locals, params, request }) {
|
||||
const data: Publishers = await request.json();
|
||||
const publisherId = params.id;
|
||||
return await updatePublisher(locals, data, publisherId);
|
||||
const data: Publishers = await request.json()
|
||||
const publisherId = params.id
|
||||
return await updatePublisher(locals, data, publisherId)
|
||||
}
|
||||
|
|
|
|||
66
package.json
66
package.json
|
|
@ -27,56 +27,54 @@
|
|||
"@faker-js/faker": "^8.4.1",
|
||||
"@melt-ui/pp": "^0.3.2",
|
||||
"@melt-ui/svelte": "^0.83.0",
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@sveltejs/adapter-auto": "^3.2.4",
|
||||
"@sveltejs/enhanced-img": "^0.3.4",
|
||||
"@sveltejs/kit": "^2.5.25",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||
"@playwright/test": "^1.47.1",
|
||||
"@sveltejs/adapter-auto": "^3.2.5",
|
||||
"@sveltejs/enhanced-img": "^0.3.8",
|
||||
"@sveltejs/kit": "^2.6.1",
|
||||
"@sveltejs/vite-plugin-svelte": "4.0.0-next.7",
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/node": "^20.16.3",
|
||||
"@types/pg": "^8.11.8",
|
||||
"@types/node": "^20.16.10",
|
||||
"@types/pg": "^8.11.10",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"arctic": "^1.9.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"drizzle-kit": "^0.23.2",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.43.0",
|
||||
"eslint-plugin-svelte": "2.36.0-next.13",
|
||||
"just-clone": "^6.2.0",
|
||||
"just-debounce-it": "^3.2.0",
|
||||
"lucia": "3.2.0",
|
||||
"lucide-svelte": "^0.408.0",
|
||||
"nodemailer": "^6.9.15",
|
||||
"postcss": "^8.4.44",
|
||||
"postcss": "^8.4.47",
|
||||
"postcss-import": "^16.1.0",
|
||||
"postcss-load-config": "^5.1.0",
|
||||
"postcss-preset-env": "^9.6.0",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-svelte": "^3.2.6",
|
||||
"sass": "^1.77.8",
|
||||
"satori": "^0.10.14",
|
||||
"satori-html": "^0.3.2",
|
||||
"prettier-plugin-svelte": "^3.2.7",
|
||||
"svelte": "5.0.0-next.175",
|
||||
"svelte-check": "^3.8.6",
|
||||
"svelte-headless-table": "^0.18.2",
|
||||
"svelte-meta-tags": "^3.1.4",
|
||||
"svelte-preprocess": "^6.0.2",
|
||||
"svelte-sequential-preprocessor": "^2.0.1",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"svelte-sequential-preprocessor": "^2.0.2",
|
||||
"sveltekit-flash-message": "^2.4.4",
|
||||
"sveltekit-rate-limiter": "^0.5.2",
|
||||
"sveltekit-superforms": "^2.17.0",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"sveltekit-superforms": "^2.18.1",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.7.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.3",
|
||||
"tsx": "^4.19.1",
|
||||
"typescript": "^5.6.2",
|
||||
"vite": "^5.4.8",
|
||||
"vitest": "^1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fontsource/fira-mono": "^5.0.14",
|
||||
"@fontsource/fira-mono": "^5.1.0",
|
||||
"@hono/swagger-ui": "^0.4.1",
|
||||
"@hono/zod-openapi": "^0.15.3",
|
||||
"@hono/zod-validator": "^0.2.2",
|
||||
|
|
@ -86,16 +84,20 @@
|
|||
"@lucia-auth/adapter-drizzle": "^1.1.0",
|
||||
"@lukeed/uuid": "^2.0.1",
|
||||
"@neondatabase/serverless": "^0.9.5",
|
||||
"@node-rs/argon2": "^1.8.3",
|
||||
"@oslojs/crypto": "^1.0.1",
|
||||
"@oslojs/encoding": "^1.1.0",
|
||||
"@oslojs/jwt": "^0.2.0",
|
||||
"@oslojs/oauth2": "^0.5.0",
|
||||
"@oslojs/otp": "^1.0.0",
|
||||
"@oslojs/webauthn": "^1.0.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"@sveltejs/adapter-node": "^5.2.2",
|
||||
"@sveltejs/adapter-vercel": "^5.4.3",
|
||||
"@sveltejs/adapter-node": "^5.2.5",
|
||||
"@sveltejs/adapter-vercel": "^5.4.4",
|
||||
"@types/feather-icons": "^4.29.4",
|
||||
"@vercel/og": "^0.5.20",
|
||||
"arctic": "^1.9.2",
|
||||
"bits-ui": "^0.21.13",
|
||||
"boardgamegeekclient": "^1.9.1",
|
||||
"bullmq": "^5.12.13",
|
||||
"bullmq": "^5.14.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cookie": "^0.6.0",
|
||||
|
|
@ -106,7 +108,7 @@
|
|||
"feather-icons": "^4.29.2",
|
||||
"formsnap": "^1.0.1",
|
||||
"handlebars": "^4.7.8",
|
||||
"hono": "^4.5.11",
|
||||
"hono": "^4.6.3",
|
||||
"hono-rate-limiter": "^0.4.0",
|
||||
"html-entities": "^2.5.2",
|
||||
"iconify-icon": "^2.1.0",
|
||||
|
|
@ -116,7 +118,7 @@
|
|||
"loader": "^2.1.1",
|
||||
"open-props": "^1.7.6",
|
||||
"oslo": "^1.2.1",
|
||||
"pg": "^8.12.0",
|
||||
"pg": "^8.13.0",
|
||||
"postgres": "^3.4.4",
|
||||
"qrcode": "^1.5.4",
|
||||
"radix-svelte": "^0.9.0",
|
||||
|
|
@ -128,6 +130,6 @@
|
|||
"tailwind-variants": "^0.2.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tsyringe": "^4.8.0",
|
||||
"zod-to-json-schema": "^3.23.2"
|
||||
"zod-to-json-schema": "^3.23.3"
|
||||
}
|
||||
}
|
||||
3657
pnpm-lock.yaml
3657
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -1,20 +1,15 @@
|
|||
<script lang="ts">
|
||||
import { applyAction, enhance } from '$app/forms';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { ListChecks, ListTodo, LogOut, User } from 'lucide-svelte';
|
||||
import * as DropdownMenu from '$components/ui/dropdown-menu';
|
||||
import * as Avatar from '$components/ui/avatar';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import Logo from '$components/logo.svelte';
|
||||
import type { Users } from '$db/schema';
|
||||
import { applyAction, enhance } from '$app/forms'
|
||||
import { invalidateAll } from '$app/navigation'
|
||||
import Logo from '$components/logo.svelte'
|
||||
import * as Avatar from '$components/ui/avatar'
|
||||
import * as DropdownMenu from '$components/ui/dropdown-menu'
|
||||
import { ListChecks, ListTodo, LogOut, Settings } from 'lucide-svelte'
|
||||
import toast from 'svelte-french-toast'
|
||||
|
||||
type HeaderProps = {
|
||||
user: Users | null;
|
||||
};
|
||||
let { user = null } = $props()
|
||||
|
||||
let { user = null }: HeaderProps = $props();
|
||||
|
||||
let avatar: string = $derived(user?.username?.slice(0, 1).toUpperCase() || ':)');
|
||||
let avatar: string = $derived(user?.username?.slice(0, 1).toUpperCase() || ':)')
|
||||
</script>
|
||||
|
||||
<header>
|
||||
|
|
@ -28,6 +23,15 @@
|
|||
</div>
|
||||
<nav>
|
||||
{#if user}
|
||||
{@render userDropdown()}
|
||||
{:else}
|
||||
<a href="/login"> <span class="flex-auto">Login</span></a>
|
||||
<a href="/signup"> <span class="flex-auto">Sign Up</span></a>
|
||||
{/if}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{#snippet userDropdown()}
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<Avatar.Root asChild>
|
||||
|
|
@ -40,10 +44,10 @@
|
|||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Account</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<a href="/profile">
|
||||
<a href="/settings">
|
||||
<DropdownMenu.Item>
|
||||
<User class="mr-2 h-4 w-4" />
|
||||
<span>Profile</span>
|
||||
<Settings class="mr-2 h-4 w-4" />
|
||||
<span>Settings</span>
|
||||
</DropdownMenu.Item>
|
||||
</a>
|
||||
<a href="/collections">
|
||||
|
|
@ -90,12 +94,7 @@
|
|||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
{:else}
|
||||
<a href="/login"> <span class="flex-auto">Login</span></a>
|
||||
<a href="/signup"> <span class="flex-auto">Sign Up</span></a>
|
||||
{/if}
|
||||
</nav>
|
||||
</header>
|
||||
{/snippet}
|
||||
|
||||
<style lang="postcss">
|
||||
header {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { badgeVariants, type Variant } from ".";
|
||||
import { type Variant, badgeVariants } from "./index.js";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export let href: string | undefined = undefined;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
import { tv, type VariantProps } from "tailwind-variants";
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
export { default as Badge } from "./badge.svelte";
|
||||
|
||||
export const badgeVariants = tv({
|
||||
base: "inline-flex items-center border rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none select-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
base: "focus:ring-ring inline-flex select-none items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2",
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary hover:bg-primary/80 border-transparent text-primary-foreground",
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/80 border-transparent",
|
||||
secondary:
|
||||
"bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground",
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent",
|
||||
destructive:
|
||||
"bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground",
|
||||
outline: "text-foreground"
|
||||
}
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default"
|
||||
}
|
||||
variant: "default",
|
||||
},
|
||||
});
|
||||
|
||||
export type Variant = VariantProps<typeof badgeVariants>["variant"];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLInputAttributes } from "svelte/elements";
|
||||
import type { InputEvents } from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLInputAttributes;
|
||||
type $$Events = InputEvents;
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import { type VariantProps, tv } from "tailwind-variants";
|
|||
import Root from "./toggle.svelte";
|
||||
|
||||
export const toggleVariants = tv({
|
||||
base: "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
|
||||
base: "ring-offset-background hover:bg-muted hover:text-muted-foreground focus-visible:ring-ring data-[state=on]:bg-accent data-[state=on]:text-accent-foreground inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
||||
"border-input hover:bg-accent hover:text-accent-foreground border bg-transparent",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-3",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Toggle as TogglePrimitive } from "bits-ui";
|
||||
import { type Size, type Variant, toggleVariants } from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = TogglePrimitive.Props & {
|
||||
variant?: Variant;
|
||||
|
|
|
|||
|
|
@ -1,36 +1,37 @@
|
|||
<script lang="ts">
|
||||
import { navigating } from '$app/stores';
|
||||
import { onNavigate } from '$app/navigation';
|
||||
let visible = false;
|
||||
let progress = 0;
|
||||
let load_durations: number[] = [];
|
||||
$: average_load = load_durations.reduce((a, b) => a + b, 0) / load_durations.length;
|
||||
const increment = 1;
|
||||
onNavigate((navigation) => {
|
||||
const typical_load_time = average_load || 200; //ms
|
||||
const frequency = typical_load_time / 100;
|
||||
let start = performance.now();
|
||||
import { onNavigate } from '$app/navigation'
|
||||
import { navigating } from '$app/stores'
|
||||
|
||||
let visible = false
|
||||
let progress = 0
|
||||
let load_durations: number[] = []
|
||||
$: average_load = load_durations.reduce((a, b) => a + b, 0) / load_durations.length
|
||||
const increment = 1
|
||||
onNavigate((navigation) => {
|
||||
const typical_load_time = average_load || 200 //ms
|
||||
const frequency = typical_load_time / 100
|
||||
let start = performance.now()
|
||||
// Start the progress bar
|
||||
visible = true;
|
||||
progress = 0;
|
||||
visible = true
|
||||
progress = 0
|
||||
const interval = setInterval(() => {
|
||||
// Increment the progress bar
|
||||
progress += increment;
|
||||
}, frequency);
|
||||
progress += increment
|
||||
}, frequency)
|
||||
// Resolve the promise when the page is done loading
|
||||
$navigating?.complete.then(() => {
|
||||
progress = 100; // Fill out the progress bar
|
||||
clearInterval(interval);
|
||||
progress = 100 // Fill out the progress bar
|
||||
clearInterval(interval)
|
||||
// after 100 ms hide the progress bar
|
||||
setTimeout(() => {
|
||||
visible = false;
|
||||
}, 500);
|
||||
visible = false
|
||||
}, 500)
|
||||
// Log how long that one took
|
||||
const end = performance.now();
|
||||
const duration = end - start;
|
||||
load_durations = [...load_durations, duration];
|
||||
});
|
||||
});
|
||||
const end = performance.now()
|
||||
const duration = end - start
|
||||
load_durations = [...load_durations, duration]
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="progress" class:visible style:--progress={progress}>
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
import satori from 'satori';
|
||||
import { Resvg } from '@resvg/resvg-js';
|
||||
import { html as toReactNode } from 'satori-html';
|
||||
import { dev } from '$app/environment';
|
||||
import { read } from '$app/server';
|
||||
|
||||
// we use a Vite plugin to turn this import into the result of fs.readFileSync during build
|
||||
import firaSansSemiBold from '$lib/fonts/FiraSans-Bold.ttf';
|
||||
|
||||
const fontData = read(firaSansSemiBold).arrayBuffer();
|
||||
|
||||
export async function componentToPng(component,
|
||||
props: Record<string, string | undefined>,
|
||||
height: number, width: number) {
|
||||
const result = component.render(props);
|
||||
const markup = toReactNode(`${result.html}<style lang="css">${result.css.code}</style>`);
|
||||
|
||||
const svg = await satori(markup, {
|
||||
fonts: [
|
||||
{
|
||||
name: 'Fira Sans',
|
||||
data: await fontData,
|
||||
style: 'normal'
|
||||
}
|
||||
],
|
||||
height: +height,
|
||||
width: +width
|
||||
});
|
||||
|
||||
const resvg = new Resvg(svg, {
|
||||
fitTo: {
|
||||
mode: 'width',
|
||||
value: +width
|
||||
}
|
||||
});
|
||||
|
||||
const image = resvg.render();
|
||||
|
||||
return new Response(image.asPng(), {
|
||||
headers: {
|
||||
'content-type': 'image/png',
|
||||
'cache-control': dev ? 'no-cache, no-store' : 'public, immutable, no-transform, max-age=86400'
|
||||
}
|
||||
});
|
||||
}
|
||||
36
src/lib/server/api/common/config.ts
Normal file
36
src/lib/server/api/common/config.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import env from './env'
|
||||
import type { Config } from './types/config'
|
||||
|
||||
const isPreview = process.env.VERCEL_ENV === 'preview' || process.env.VERCEL_ENV === 'development'
|
||||
|
||||
let domain: string
|
||||
if (process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'production') {
|
||||
domain = 'boredgame.vercel.app'
|
||||
} else if (isPreview && process.env.VERCEL_BRANCH_URL !== undefined) {
|
||||
domain = process.env.VERCEL_BRANCH_URL
|
||||
} else {
|
||||
domain = 'localhost'
|
||||
}
|
||||
|
||||
// export const config = { ...env, isProduction: process.env.NODE_ENV === 'production'
|
||||
// || process.env.VERCEL_ENV === 'production', domain };
|
||||
|
||||
export const config: Config = {
|
||||
isProduction: process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'production',
|
||||
domain,
|
||||
api: {
|
||||
origin: env.ORIGIN,
|
||||
},
|
||||
redis: {
|
||||
url: env.REDIS_URL,
|
||||
},
|
||||
postgres: {
|
||||
user: env.DATABASE_USER,
|
||||
password: env.DATABASE_PASSWORD,
|
||||
host: env.DATABASE_HOST,
|
||||
port: env.DATABASE_PORT,
|
||||
database: env.DATABASE_DB,
|
||||
ssl: env.DATABASE_HOST !== 'localhost',
|
||||
max: env.DB_MIGRATING || env.DB_SEEDING ? 1 : undefined,
|
||||
},
|
||||
}
|
||||
|
|
@ -10,8 +10,6 @@ const stringBoolean = z.coerce
|
|||
.default('false')
|
||||
|
||||
const EnvSchema = z.object({
|
||||
ADMIN_USERNAME: z.string(),
|
||||
ADMIN_PASSWORD: z.string(),
|
||||
DATABASE_USER: z.string(),
|
||||
DATABASE_PASSWORD: z.string(),
|
||||
DATABASE_HOST: z.string(),
|
||||
|
|
@ -19,6 +17,10 @@ const EnvSchema = z.object({
|
|||
DATABASE_DB: z.string(),
|
||||
DB_MIGRATING: stringBoolean,
|
||||
DB_SEEDING: stringBoolean,
|
||||
GITHUB_CLIENT_ID: z.string(),
|
||||
GITHUB_CLIENT_SECRET: z.string(),
|
||||
GOOGLE_CLIENT_ID: z.string(),
|
||||
GOOGLE_CLIENT_SECRET: z.string(),
|
||||
NODE_ENV: z.string().default('development'),
|
||||
ORIGIN: z.string(),
|
||||
PUBLIC_SITE_NAME: z.string(),
|
||||
|
|
@ -1,26 +1,26 @@
|
|||
import { StatusCodes } from '$lib/constants/status-codes';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { StatusCodes } from '$lib/constants/status-codes'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
export function TooManyRequests(message: string = 'Too many requests') {
|
||||
return new HTTPException(StatusCodes.TOO_MANY_REQUESTS, { message });
|
||||
export function TooManyRequests(message = 'Too many requests') {
|
||||
return new HTTPException(StatusCodes.TOO_MANY_REQUESTS, { message })
|
||||
}
|
||||
|
||||
export function Forbidden(message: string = 'Forbidden') {
|
||||
return new HTTPException(StatusCodes.FORBIDDEN, { message });
|
||||
export function Forbidden(message = 'Forbidden') {
|
||||
return new HTTPException(StatusCodes.FORBIDDEN, { message })
|
||||
}
|
||||
|
||||
export function Unauthorized(message: string = 'Unauthorized') {
|
||||
return new HTTPException(StatusCodes.UNAUTHORIZED, { message });
|
||||
export function Unauthorized(message = 'Unauthorized') {
|
||||
return new HTTPException(StatusCodes.UNAUTHORIZED, { message })
|
||||
}
|
||||
|
||||
export function NotFound(message: string = 'Not Found') {
|
||||
return new HTTPException(StatusCodes.NOT_FOUND, { message });
|
||||
export function NotFound(message = 'Not Found') {
|
||||
return new HTTPException(StatusCodes.NOT_FOUND, { message })
|
||||
}
|
||||
|
||||
export function BadRequest(message: string = 'Bad Request') {
|
||||
return new HTTPException(StatusCodes.BAD_REQUEST, { message });
|
||||
export function BadRequest(message = 'Bad Request') {
|
||||
return new HTTPException(StatusCodes.BAD_REQUEST, { message })
|
||||
}
|
||||
|
||||
export function InternalError(message: string = 'Internal Error') {
|
||||
return new HTTPException(StatusCodes.INTERNAL_SERVER_ERROR, { message });
|
||||
export function InternalError(message = 'Internal Error') {
|
||||
return new HTTPException(StatusCodes.INTERNAL_SERVER_ERROR, { message })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
import { Hono } from 'hono'
|
||||
import type { BlankSchema } from 'hono/types'
|
||||
import type { HonoTypes } from '../../types'
|
||||
|
||||
export interface Controller {
|
||||
controller: Hono<HonoTypes, BlankSchema, '/'>
|
||||
routes(): any
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import type { DatabaseProvider } from '$lib/server/api/providers/database.provider'
|
||||
|
||||
export interface Repository {
|
||||
trxHost(trx: DatabaseProvider): any
|
||||
}
|
||||
3
src/lib/server/api/common/types/async-service.ts
Normal file
3
src/lib/server/api/common/types/async-service.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export abstract class AsyncService {
|
||||
async init(): Promise<void> {}
|
||||
}
|
||||
33
src/lib/server/api/common/types/config.ts
Normal file
33
src/lib/server/api/common/types/config.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
export interface Config {
|
||||
isProduction: boolean
|
||||
domain: string
|
||||
api: ApiConfig
|
||||
// storage: StorageConfig
|
||||
redis: RedisConfig
|
||||
postgres: PostgresConfig
|
||||
}
|
||||
|
||||
interface ApiConfig {
|
||||
origin: string
|
||||
}
|
||||
|
||||
// interface StorageConfig {
|
||||
// accessKey: string
|
||||
// secretKey: string
|
||||
// bucket: string
|
||||
// url: string
|
||||
// }
|
||||
|
||||
interface RedisConfig {
|
||||
url: string
|
||||
}
|
||||
|
||||
interface PostgresConfig {
|
||||
user: string
|
||||
password: string
|
||||
host: string
|
||||
port: number
|
||||
database: string
|
||||
ssl: boolean
|
||||
max: number | undefined
|
||||
}
|
||||
11
src/lib/server/api/common/types/controller.ts
Normal file
11
src/lib/server/api/common/types/controller.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { Hono } from 'hono'
|
||||
import type { BlankSchema } from 'hono/types'
|
||||
import type { HonoTypes } from './hono'
|
||||
|
||||
export abstract class Controller {
|
||||
protected readonly controller: Hono<HonoTypes, BlankSchema, '/'>
|
||||
constructor() {
|
||||
this.controller = new Hono()
|
||||
}
|
||||
abstract routes(): Hono<HonoTypes, BlankSchema, '/'>
|
||||
}
|
||||
11
src/lib/server/api/common/types/oauth.ts
Normal file
11
src/lib/server/api/common/types/oauth.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export type OAuthUser = {
|
||||
sub: string;
|
||||
given_name?: string;
|
||||
family_name?: string;
|
||||
picture?: string;
|
||||
username: string;
|
||||
email?: string;
|
||||
email_verified?: boolean;
|
||||
}
|
||||
|
||||
export type OAuthProviders = 'github' | 'google' | 'apple'
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import env from '../../../../env';
|
||||
|
||||
const isPreview = process.env.VERCEL_ENV === 'preview' || process.env.VERCEL_ENV === 'development';
|
||||
|
||||
let domain: string;
|
||||
if (process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'production') {
|
||||
domain = 'boredgame.vercel.app';
|
||||
} else if (isPreview && process.env.VERCEL_BRANCH_URL !== undefined) {
|
||||
domain = process.env.VERCEL_BRANCH_URL;
|
||||
} else {
|
||||
domain = 'localhost';
|
||||
}
|
||||
|
||||
export const config = { ...env, isProduction: process.env.NODE_ENV === 'production'
|
||||
|| process.env.VERCEL_ENV === 'production', domain };
|
||||
|
|
@ -1,16 +1,14 @@
|
|||
import 'reflect-metadata'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { Controller } from '$lib/server/api/common/types/controller'
|
||||
import { CollectionsService } from '$lib/server/api/services/collections.service'
|
||||
import { Hono } from 'hono'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { requireAuth } from '../middleware/auth.middleware'
|
||||
import type { HonoTypes } from '../types'
|
||||
import { requireAuth } from '../middleware/require-auth.middleware'
|
||||
|
||||
@injectable()
|
||||
export class CollectionController implements Controller {
|
||||
controller = new Hono<HonoTypes>()
|
||||
|
||||
constructor(@inject(CollectionsService) private readonly collectionsService: CollectionsService) {}
|
||||
export class CollectionController extends Controller {
|
||||
constructor(@inject(CollectionsService) private readonly collectionsService: CollectionsService) {
|
||||
super()
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
|
|
@ -20,6 +18,11 @@ export class CollectionController implements Controller {
|
|||
console.log('collections service', collections)
|
||||
return c.json({ collections })
|
||||
})
|
||||
.get('/count', requireAuth, async (c) => {
|
||||
const user = c.var.user
|
||||
const collections = await this.collectionsService.findAllByUserIdWithDetails(user.id)
|
||||
return c.json({ collections })
|
||||
})
|
||||
.get('/:cuid', requireAuth, async (c) => {
|
||||
const cuid = c.req.param('cuid')
|
||||
const collection = await this.collectionsService.findOneByCuid(cuid)
|
||||
|
|
|
|||
|
|
@ -1,26 +1,27 @@
|
|||
import { StatusCodes } from '$lib/constants/status-codes'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { Controller } from '$lib/server/api/common/types/controller'
|
||||
import { changePasswordDto } from '$lib/server/api/dtos/change-password.dto'
|
||||
import { updateEmailDto } from '$lib/server/api/dtos/update-email.dto'
|
||||
import { updateProfileDto } from '$lib/server/api/dtos/update-profile.dto'
|
||||
import { verifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto'
|
||||
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware'
|
||||
import { LuciaProvider } from '$lib/server/api/providers/lucia.provider'
|
||||
import { IamService } from '$lib/server/api/services/iam.service'
|
||||
import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service'
|
||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
||||
import { zValidator } from '@hono/zod-validator'
|
||||
import { Hono } from 'hono'
|
||||
import { setCookie } from 'hono/cookie'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { requireAuth } from '../middleware/auth.middleware'
|
||||
import type { HonoTypes } from '../types'
|
||||
import { requireAuth } from '../middleware/require-auth.middleware'
|
||||
|
||||
@injectable()
|
||||
export class IamController implements Controller {
|
||||
controller = new Hono<HonoTypes>()
|
||||
|
||||
export class IamController extends Controller {
|
||||
constructor(
|
||||
@inject(IamService) private readonly iamService: IamService,
|
||||
@inject(LuciaProvider) private lucia: LuciaProvider,
|
||||
) {}
|
||||
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
|
||||
@inject(LuciaService) private luciaService: LuciaService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
|
|
@ -47,6 +48,32 @@ export class IamController implements Controller {
|
|||
}
|
||||
return c.json({}, StatusCodes.OK)
|
||||
})
|
||||
.put('/update/password', requireAuth, zValidator('json', changePasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||
const user = c.var.user
|
||||
const { password, confirm_password } = c.req.valid('json')
|
||||
if (password !== confirm_password) {
|
||||
return c.json('Passwords do not match', StatusCodes.BAD_REQUEST)
|
||||
}
|
||||
try {
|
||||
await this.iamService.updatePassword(user.id, { password, confirm_password })
|
||||
await this.luciaService.lucia.invalidateUserSessions(user.id)
|
||||
await this.loginRequestService.createUserSession(user.id, c.req, undefined)
|
||||
const sessionCookie = this.luciaService.lucia.createBlankSessionCookie()
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
maxAge: sessionCookie.attributes.maxAge,
|
||||
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({ status: 'success' })
|
||||
} catch (error) {
|
||||
console.error('Error updating password', error)
|
||||
return c.json('Error updating password', StatusCodes.BAD_REQUEST)
|
||||
}
|
||||
})
|
||||
.post('/update/email', requireAuth, zValidator('json', updateEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||
const user = c.var.user
|
||||
const { email } = c.req.valid('json')
|
||||
|
|
@ -59,7 +86,7 @@ export class IamController implements Controller {
|
|||
.post('/logout', requireAuth, async (c) => {
|
||||
const sessionId = c.var.session.id
|
||||
await this.iamService.logout(sessionId)
|
||||
const sessionCookie = this.lucia.createBlankSessionCookie()
|
||||
const sessionCookie = this.luciaService.lucia.createBlankSessionCookie()
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
maxAge: sessionCookie.attributes.maxAge,
|
||||
|
|
|
|||
|
|
@ -1,30 +1,28 @@
|
|||
import 'reflect-metadata'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { Controller } from '$lib/server/api/common/types/controller'
|
||||
import { signinUsernameDto } from '$lib/server/api/dtos/signin-username.dto'
|
||||
import { LuciaProvider } from '$lib/server/api/providers/lucia.provider'
|
||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
||||
import { zValidator } from '@hono/zod-validator'
|
||||
import { Hono } from 'hono'
|
||||
import { setCookie } from 'hono/cookie'
|
||||
import { TimeSpan } from 'oslo'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { limiter } from '../middleware/rate-limiter.middleware'
|
||||
import { LoginRequestsService } from '../services/loginrequest.service'
|
||||
import type { HonoTypes } from '../types'
|
||||
|
||||
@injectable()
|
||||
export class LoginController implements Controller {
|
||||
controller = new Hono<HonoTypes>()
|
||||
|
||||
export class LoginController extends Controller {
|
||||
constructor(
|
||||
@inject(LoginRequestsService) private readonly loginRequestsService: LoginRequestsService,
|
||||
@inject(LuciaProvider) private lucia: LuciaProvider,
|
||||
) {}
|
||||
@inject(LuciaService) private luciaService: LuciaService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller.post('/', zValidator('json', signinUsernameDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||
const { username, password } = c.req.valid('json')
|
||||
const session = await this.loginRequestsService.verify({ username, password }, c.req)
|
||||
const sessionCookie = this.lucia.createSessionCookie(session.id)
|
||||
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id)
|
||||
console.log('set cookie', sessionCookie)
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
|
|
|
|||
|
|
@ -1,27 +1,24 @@
|
|||
import 'reflect-metadata'
|
||||
import { StatusCodes } from '$lib/constants/status-codes'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { Controller } from '$lib/server/api/common/types/controller'
|
||||
import { verifyTotpDto } from '$lib/server/api/dtos/verify-totp.dto'
|
||||
import { db } from '$lib/server/api/packages/drizzle'
|
||||
import { RecoveryCodesService } from '$lib/server/api/services/recovery-codes.service'
|
||||
import { TotpService } from '$lib/server/api/services/totp.service'
|
||||
import { UsersService } from '$lib/server/api/services/users.service'
|
||||
import { zValidator } from '@hono/zod-validator'
|
||||
import { Hono } from 'hono'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { CredentialsType } from '../databases/tables'
|
||||
import { requireAuth } from '../middleware/auth.middleware'
|
||||
import type { HonoTypes } from '../types'
|
||||
import { requireAuth } from '../middleware/require-auth.middleware'
|
||||
|
||||
@injectable()
|
||||
export class MfaController implements Controller {
|
||||
controller = new Hono<HonoTypes>()
|
||||
|
||||
export class MfaController extends Controller {
|
||||
constructor(
|
||||
@inject(RecoveryCodesService) private readonly recoveryCodesService: RecoveryCodesService,
|
||||
@inject(TotpService) private readonly totpService: TotpService,
|
||||
@inject(UsersService) private readonly usersService: UsersService,
|
||||
) {}
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
|
|
@ -52,8 +49,9 @@ export class MfaController implements Controller {
|
|||
const user = c.var.user
|
||||
// You can only view recovery codes once and that is on creation
|
||||
const existingCodes = await this.recoveryCodesService.findAllRecoveryCodesByUserId(user.id)
|
||||
if (existingCodes) {
|
||||
return c.body('You have already generated recovery codes', StatusCodes.BAD_REQUEST)
|
||||
if (existingCodes && existingCodes.length > 0) {
|
||||
console.log('Recovery Codes found', existingCodes)
|
||||
return c.json({ recoveryCodes: existingCodes })
|
||||
}
|
||||
const recoveryCodes = await this.recoveryCodesService.createRecoveryCodes(user.id)
|
||||
return c.json({ recoveryCodes })
|
||||
|
|
|
|||
150
src/lib/server/api/controllers/oauth.controller.ts
Normal file
150
src/lib/server/api/controllers/oauth.controller.ts
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import 'reflect-metadata'
|
||||
import { Controller } from '$lib/server/api/common/types/controller'
|
||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
||||
import { OAuthService } from '$lib/server/api/services/oauth.service'
|
||||
import { github, google } from '$lib/server/auth'
|
||||
import { OAuth2RequestError } from 'arctic'
|
||||
import { getCookie, setCookie } from 'hono/cookie'
|
||||
import { TimeSpan } from 'oslo'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import type {OAuthUser} from "$lib/server/api/common/types/oauth";
|
||||
|
||||
@injectable()
|
||||
export class OAuthController extends Controller {
|
||||
constructor(
|
||||
@inject(LuciaService) private luciaService: LuciaService,
|
||||
@inject(OAuthService) private oauthService: OAuthService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/github', async (c) => {
|
||||
try {
|
||||
const code = c.req.query('code')?.toString() ?? null
|
||||
const state = c.req.query('state')?.toString() ?? null
|
||||
const storedState = getCookie(c).github_oauth_state ?? null
|
||||
|
||||
if (!code || !state || !storedState || state !== storedState) {
|
||||
return c.body(null, 400)
|
||||
}
|
||||
|
||||
const tokens = await github.validateAuthorizationCode(code)
|
||||
const githubUserResponse = await fetch('https://api.github.com/user', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.accessToken}`,
|
||||
},
|
||||
})
|
||||
const githubUser: GitHubUser = await githubUserResponse.json()
|
||||
|
||||
const oAuthUser: OAuthUser = {
|
||||
sub: `${githubUser.id}`,
|
||||
username: githubUser.login,
|
||||
email: undefined
|
||||
}
|
||||
|
||||
const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'github')
|
||||
|
||||
const session = await this.luciaService.lucia.createSession(userId, {})
|
||||
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id)
|
||||
|
||||
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' })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
// the specific error message depends on the provider
|
||||
if (error instanceof OAuth2RequestError) {
|
||||
// invalid code
|
||||
return c.body(null, 400)
|
||||
}
|
||||
return c.body(null, 500)
|
||||
}
|
||||
})
|
||||
.get('/google', async (c) => {
|
||||
try {
|
||||
const code = c.req.query('code')?.toString() ?? null
|
||||
const state = c.req.query('state')?.toString() ?? null
|
||||
const storedState = getCookie(c).google_oauth_state ?? null
|
||||
const storedCodeVerifier = getCookie(c).google_oauth_code_verifier ?? null
|
||||
|
||||
if (!code || !storedState || !storedCodeVerifier || state !== storedState) {
|
||||
return c.body(null, 400)
|
||||
}
|
||||
|
||||
const tokens = await google.validateAuthorizationCode(code, storedCodeVerifier)
|
||||
const googleUserResponse = await fetch("https://openidconnect.googleapis.com/v1/userinfo", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.accessToken}`,
|
||||
},
|
||||
})
|
||||
const googleUser: GoogleUser = await googleUserResponse.json()
|
||||
|
||||
const oAuthUser: OAuthUser = {
|
||||
sub: googleUser.sub,
|
||||
given_name: googleUser.given_name,
|
||||
family_name: googleUser.family_name,
|
||||
picture: googleUser.picture,
|
||||
username: googleUser.email,
|
||||
email: googleUser.email,
|
||||
email_verified: googleUser.email_verified,
|
||||
}
|
||||
|
||||
const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'google')
|
||||
|
||||
const session = await this.luciaService.lucia.createSession(userId, {})
|
||||
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id)
|
||||
|
||||
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' })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
// the specific error message depends on the provider
|
||||
if (error instanceof OAuth2RequestError) {
|
||||
// invalid code
|
||||
return c.body(null, 400)
|
||||
}
|
||||
return c.body(null, 500)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
interface GitHubUser {
|
||||
id: number
|
||||
login: string
|
||||
}
|
||||
|
||||
interface GoogleUser {
|
||||
sub: string
|
||||
name: string
|
||||
given_name: string
|
||||
family_name: string
|
||||
picture: string
|
||||
email: string
|
||||
email_verified: boolean
|
||||
}
|
||||
|
|
@ -1,26 +1,24 @@
|
|||
import 'reflect-metadata'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { Controller } from '$lib/server/api/common/types/controller'
|
||||
import { signupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto'
|
||||
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware'
|
||||
import { LuciaProvider } from '$lib/server/api/providers/lucia.provider'
|
||||
import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service'
|
||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
||||
import { UsersService } from '$lib/server/api/services/users.service'
|
||||
import { zValidator } from '@hono/zod-validator'
|
||||
import { Hono } from 'hono'
|
||||
import { setCookie } from 'hono/cookie'
|
||||
import { TimeSpan } from 'oslo'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import type { HonoTypes } from '../types'
|
||||
|
||||
@injectable()
|
||||
export class SignupController implements Controller {
|
||||
controller = new Hono<HonoTypes>()
|
||||
|
||||
export class SignupController extends Controller {
|
||||
constructor(
|
||||
@inject(UsersService) private readonly usersService: UsersService,
|
||||
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
|
||||
@inject(LuciaProvider) private lucia: LuciaProvider,
|
||||
) {}
|
||||
@inject(LuciaService) private luciaService: LuciaService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller.post('/', zValidator('json', signupUsernameEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||
|
|
@ -38,7 +36,7 @@ export class SignupController implements Controller {
|
|||
}
|
||||
|
||||
const session = await this.loginRequestService.createUserSession(user.id, c.req, undefined)
|
||||
const sessionCookie = this.lucia.createSessionCookie(session.id)
|
||||
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id)
|
||||
console.log('set cookie', sessionCookie)
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
import 'reflect-metadata'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { Controller } from '$lib/server/api/common/types/controller'
|
||||
import { UsersService } from '$lib/server/api/services/users.service'
|
||||
import { Hono } from 'hono'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { requireAuth } from '../middleware/auth.middleware'
|
||||
import type { HonoTypes } from '../types'
|
||||
import { requireAuth } from '../middleware/require-auth.middleware'
|
||||
|
||||
@injectable()
|
||||
export class UserController implements Controller {
|
||||
controller = new Hono<HonoTypes>()
|
||||
|
||||
constructor(@inject(UsersService) private readonly usersService: UsersService) {}
|
||||
export class UserController extends Controller {
|
||||
constructor(@inject(UsersService) private readonly usersService: UsersService) {
|
||||
super()
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
import 'reflect-metadata'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { Controller } from '$lib/server/api/common/types/controller'
|
||||
import { WishlistsService } from '$lib/server/api/services/wishlists.service'
|
||||
import { Hono } from 'hono'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { requireAuth } from '../middleware/auth.middleware'
|
||||
import type { HonoTypes } from '../types'
|
||||
import { requireAuth } from '../middleware/require-auth.middleware'
|
||||
|
||||
@injectable()
|
||||
export class WishlistController implements Controller {
|
||||
controller = new Hono<HonoTypes>()
|
||||
|
||||
constructor(@inject(WishlistsService) private readonly wishlistsService: WishlistsService) {}
|
||||
export class WishlistController extends Controller {
|
||||
constructor(@inject(WishlistsService) private readonly wishlistsService: WishlistsService) {
|
||||
super()
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { drizzle } from 'drizzle-orm/postgres-js'
|
|||
import { migrate } from 'drizzle-orm/postgres-js/migrator'
|
||||
import postgres from 'postgres'
|
||||
import config from '../../../../../drizzle.config'
|
||||
import env from '../../../../env'
|
||||
import env from '../common/env'
|
||||
|
||||
const connection = postgres({
|
||||
host: env.DATABASE_HOST || 'localhost',
|
||||
|
|
@ -17,7 +17,11 @@ const connection = postgres({
|
|||
const db = drizzle(connection)
|
||||
|
||||
try {
|
||||
await migrate(db, { migrationsFolder: config.out! })
|
||||
if (!config.out) {
|
||||
console.error('No migrations folder specified in drizzle.config.ts')
|
||||
process.exit()
|
||||
}
|
||||
await migrate(db, { migrationsFolder: config.out })
|
||||
console.log('Migrations complete')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,15 @@ CREATE TABLE IF NOT EXISTS "collections" (
|
|||
CONSTRAINT "collections_cuid_unique" UNIQUE("cuid")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "credentials" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"type" text DEFAULT 'password' NOT NULL,
|
||||
"secret_data" text NOT NULL,
|
||||
"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 "expansions" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"cuid" text,
|
||||
|
|
@ -65,6 +74,16 @@ CREATE TABLE IF NOT EXISTS "external_ids" (
|
|||
CONSTRAINT "external_ids_cuid_unique" UNIQUE("cuid")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "federated_identity" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"identity_provider" text NOT NULL,
|
||||
"federated_user_id" text NOT NULL,
|
||||
"federated_username" text NOT NULL,
|
||||
"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 "games" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"cuid" text,
|
||||
|
|
@ -177,9 +196,9 @@ CREATE TABLE IF NOT EXISTS "sessions" (
|
|||
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,
|
||||
"secret" text NOT NULL,
|
||||
"enabled" boolean DEFAULT false NOT NULL,
|
||||
"initiated_time" timestamp with time zone,
|
||||
"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,
|
||||
|
|
@ -202,12 +221,12 @@ 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,
|
||||
"mfa_enabled" boolean DEFAULT false NOT NULL,
|
||||
"theme" text DEFAULT 'system',
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
|
|
@ -278,6 +297,12 @@ EXCEPTION
|
|||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "credentials" ADD CONSTRAINT "credentials_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
|
||||
|
|
@ -290,6 +315,12 @@ EXCEPTION
|
|||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "federated_identity" ADD CONSTRAINT "federated_identity_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 "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
|
||||
|
|
@ -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";
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "users" ADD COLUMN "email_verified" boolean DEFAULT false;--> statement-breakpoint
|
||||
ALTER TABLE "users" ADD COLUMN "picture" text;
|
||||
|
|
@ -1 +0,0 @@
|
|||
ALTER TABLE "two_factor" ALTER COLUMN "initiated_time" DROP NOT NULL;
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS "credentials" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"type" text DEFAULT 'password' NOT NULL,
|
||||
"secret_data" text NOT NULL,
|
||||
"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 "federated_identity" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"identity_provider" text NOT NULL,
|
||||
"federated_user_id" text NOT NULL,
|
||||
"federated_username" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "credentials" ADD CONSTRAINT "credentials_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 "federated_identity" ADD CONSTRAINT "federated_identity_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
|
||||
ALTER TABLE "users" DROP COLUMN IF EXISTS "hashed_password";
|
||||
|
|
@ -1 +0,0 @@
|
|||
ALTER TABLE "users" ADD COLUMN "enabled" boolean DEFAULT false NOT NULL;
|
||||
|
|
@ -1 +0,0 @@
|
|||
ALTER TABLE "users" RENAME COLUMN "enabled" TO "mfa_enabled";
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"id": "e120d11a-bf28-4c96-9f2f-96e23e23c7e2",
|
||||
"id": "4760134e-48bb-47db-b431-56903dad6e24",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
|
|
@ -338,6 +338,70 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"public.credentials": {
|
||||
"name": "credentials",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'password'"
|
||||
},
|
||||
"secret_data": {
|
||||
"name": "secret_data",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"credentials_user_id_users_id_fk": {
|
||||
"name": "credentials_user_id_users_id_fk",
|
||||
"tableFrom": "credentials",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.expansions": {
|
||||
"name": "expansions",
|
||||
"schema": "",
|
||||
|
|
@ -466,6 +530,75 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"public.federated_identity": {
|
||||
"name": "federated_identity",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"identity_provider": {
|
||||
"name": "identity_provider",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"federated_user_id": {
|
||||
"name": "federated_user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"federated_username": {
|
||||
"name": "federated_username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"federated_identity_user_id_users_id_fk": {
|
||||
"name": "federated_identity_user_id_users_id_fk",
|
||||
"tableFrom": "federated_identity",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.games": {
|
||||
"name": "games",
|
||||
"schema": "",
|
||||
|
|
@ -1273,14 +1406,14 @@
|
|||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"two_factor_secret": {
|
||||
"name": "two_factor_secret",
|
||||
"secret": {
|
||||
"name": "secret",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"two_factor_enabled": {
|
||||
"name": "two_factor_enabled",
|
||||
"enabled": {
|
||||
"name": "enabled",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
|
|
@ -1290,7 +1423,7 @@
|
|||
"name": "initiated_time",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
|
|
@ -1461,12 +1594,6 @@
|
|||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"hashed_password": {
|
||||
"name": "hashed_password",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
|
|
@ -1499,6 +1626,13 @@
|
|||
"notNull": false,
|
||||
"default": false
|
||||
},
|
||||
"mfa_enabled": {
|
||||
"name": "mfa_enabled",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"theme": {
|
||||
"name": "theme",
|
||||
"type": "text",
|
||||
|
|
@ -1720,6 +1854,7 @@
|
|||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "52e7c416-89cb-4c6a-9118-68a03cfc2920",
|
||||
"prevId": "e120d11a-bf28-4c96-9f2f-96e23e23c7e2",
|
||||
"id": "e1230cae-67ce-4669-885a-3d3fe4462f9e",
|
||||
"prevId": "4760134e-48bb-47db-b431-56903dad6e24",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
|
|
@ -338,6 +338,70 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"public.credentials": {
|
||||
"name": "credentials",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'password'"
|
||||
},
|
||||
"secret_data": {
|
||||
"name": "secret_data",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"credentials_user_id_users_id_fk": {
|
||||
"name": "credentials_user_id_users_id_fk",
|
||||
"tableFrom": "credentials",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.expansions": {
|
||||
"name": "expansions",
|
||||
"schema": "",
|
||||
|
|
@ -466,6 +530,75 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"public.federated_identity": {
|
||||
"name": "federated_identity",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"identity_provider": {
|
||||
"name": "identity_provider",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"federated_user_id": {
|
||||
"name": "federated_user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"federated_username": {
|
||||
"name": "federated_username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"federated_identity_user_id_users_id_fk": {
|
||||
"name": "federated_identity_user_id_users_id_fk",
|
||||
"tableFrom": "federated_identity",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.games": {
|
||||
"name": "games",
|
||||
"schema": "",
|
||||
|
|
@ -1290,7 +1423,7 @@
|
|||
"name": "initiated_time",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
|
|
@ -1461,12 +1594,6 @@
|
|||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"hashed_password": {
|
||||
"name": "hashed_password",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
|
|
@ -1499,6 +1626,26 @@
|
|||
"notNull": false,
|
||||
"default": false
|
||||
},
|
||||
"email_verified": {
|
||||
"name": "email_verified",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": false
|
||||
},
|
||||
"picture": {
|
||||
"name": "picture",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"mfa_enabled": {
|
||||
"name": "mfa_enabled",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"theme": {
|
||||
"name": "theme",
|
||||
"type": "text",
|
||||
|
|
@ -1720,6 +1867,7 @@
|
|||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -5,43 +5,15 @@
|
|||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1720625651245,
|
||||
"tag": "0000_dazzling_stick",
|
||||
"when": 1725489682980,
|
||||
"tag": "0000_volatile_warhawk",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1723593488634,
|
||||
"tag": "0003_worried_taskmaster",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "7",
|
||||
"when": 1725055403926,
|
||||
"tag": "0004_heavy_sphinx",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "7",
|
||||
"when": 1725055643756,
|
||||
"tag": "0005_true_mathemanic",
|
||||
"when": 1726877846811,
|
||||
"tag": "0001_pink_the_enforcers",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
import { Table, getTableName, sql } from 'drizzle-orm'
|
||||
import env from '../../../../env'
|
||||
import { db, pool } from '../packages/drizzle'
|
||||
import 'reflect-metadata'
|
||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service'
|
||||
import { type Table, getTableName, sql } from 'drizzle-orm'
|
||||
import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
|
||||
import env from '../common/env'
|
||||
import * as seeds from './seeds'
|
||||
import * as schema from './tables'
|
||||
|
||||
const drizzleService = new DrizzleService()
|
||||
|
||||
if (!env.DB_SEEDING) {
|
||||
throw new Error('You must set DB_SEEDING to "true" when running seeds')
|
||||
}
|
||||
|
||||
async function resetTable(db: db, table: Table) {
|
||||
async function resetTable(db: NodePgDatabase<typeof schema>, table: Table) {
|
||||
return db.execute(sql.raw(`TRUNCATE TABLE ${getTableName(table)} RESTART IDENTITY CASCADE`))
|
||||
}
|
||||
|
||||
|
|
@ -19,33 +23,33 @@ for (const table of [
|
|||
schema.collection_items,
|
||||
schema.collections,
|
||||
schema.credentialsTable,
|
||||
schema.expansions,
|
||||
schema.externalIds,
|
||||
schema.expansionsTable,
|
||||
schema.externalIdsTable,
|
||||
schema.federatedIdentityTable,
|
||||
schema.games,
|
||||
schema.gamesToExternalIds,
|
||||
schema.mechanics,
|
||||
schema.mechanicsToExternalIds,
|
||||
schema.gamesTable,
|
||||
schema.gamesToExternalIdsTable,
|
||||
schema.mechanicsTable,
|
||||
schema.mechanicsToExternalIdsTable,
|
||||
schema.mechanics_to_games,
|
||||
schema.password_reset_tokens,
|
||||
schema.publishers,
|
||||
schema.publishersToExternalIds,
|
||||
schema.publishersTable,
|
||||
schema.publishersToExternalIdsTable,
|
||||
schema.publishers_to_games,
|
||||
schema.recoveryCodesTable,
|
||||
schema.roles,
|
||||
schema.rolesTable,
|
||||
schema.sessionsTable,
|
||||
schema.twoFactorTable,
|
||||
schema.user_roles,
|
||||
schema.usersTable,
|
||||
schema.wishlist_items,
|
||||
schema.wishlists,
|
||||
schema.wishlistsTable,
|
||||
]) {
|
||||
// await db.delete(table); // clear tables without truncating / resetting ids
|
||||
await resetTable(db, table)
|
||||
await resetTable(drizzleService.db, table)
|
||||
}
|
||||
|
||||
await seeds.roles(db)
|
||||
await seeds.users(db)
|
||||
await seeds.roles(drizzleService.db)
|
||||
await seeds.users(drizzleService.db)
|
||||
|
||||
await pool.end()
|
||||
await drizzleService.dispose()
|
||||
process.exit()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import * as schema from '$lib/server/api/databases/tables'
|
||||
import { type db } from '$lib/server/api/packages/drizzle'
|
||||
import type { db } from '../../packages/drizzle'
|
||||
import * as schema from '../tables'
|
||||
import roles from './data/roles.json'
|
||||
|
||||
export default async function seed(db: db) {
|
||||
console.log('Creating roles ...')
|
||||
console.log('Creating rolesTable ...')
|
||||
for (const role of roles) {
|
||||
await db.insert(schema.roles).values(role).onConflictDoNothing()
|
||||
await db.insert(schema.rolesTable).values(role).onConflictDoNothing()
|
||||
}
|
||||
console.log('Roles created.')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,24 @@
|
|||
import * as schema from '$lib/server/api/databases/tables'
|
||||
import { type db } from '$lib/server/api/packages/drizzle'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { Argon2id } from 'oslo/password'
|
||||
import { config } from '../../configs/config'
|
||||
import type { db } from '../../packages/drizzle'
|
||||
import { HashingService } from '../../services/hashing.service'
|
||||
import * as schema from '../tables'
|
||||
import users from './data/users.json'
|
||||
|
||||
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'))
|
||||
const hashingService = new HashingService()
|
||||
const adminRole = await db.select().from(schema.rolesTable).where(eq(schema.rolesTable.name, 'admin'))
|
||||
const userRole = await db.select().from(schema.rolesTable).where(eq(schema.rolesTable.name, 'user'))
|
||||
|
||||
console.log('Admin Role: ', adminRole)
|
||||
const adminUser = await db
|
||||
.insert(schema.usersTable)
|
||||
.values({
|
||||
username: `${config.ADMIN_USERNAME}`,
|
||||
username: `${process.env.ADMIN_USERNAME}`,
|
||||
email: '',
|
||||
first_name: 'Brad',
|
||||
last_name: 'S',
|
||||
|
|
@ -43,12 +32,12 @@ export default async function seed(db: db) {
|
|||
await db.insert(schema.credentialsTable).values({
|
||||
user_id: adminUser[0].id,
|
||||
type: schema.CredentialsType.PASSWORD,
|
||||
secret_data: await new Argon2id().hash(`${config.ADMIN_PASSWORD}`),
|
||||
secret_data: await hashingService.hash(`${process.env.ADMIN_PASSWORD}`),
|
||||
})
|
||||
|
||||
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.wishlistsTable).values({ user_id: adminUser[0].id }).onConflictDoNothing()
|
||||
|
||||
await db
|
||||
.insert(schema.user_roles)
|
||||
|
|
@ -71,6 +60,7 @@ export default async function seed(db: db) {
|
|||
.onConflictDoNothing()
|
||||
|
||||
console.log('Admin user given user role.')
|
||||
const hasingService = new HashingService()
|
||||
await Promise.all(
|
||||
users.map(async (user) => {
|
||||
const [insertedUser] = await db
|
||||
|
|
@ -82,14 +72,14 @@ export default async function seed(db: db) {
|
|||
await db.insert(schema.credentialsTable).values({
|
||||
user_id: insertedUser?.id,
|
||||
type: schema.CredentialsType.PASSWORD,
|
||||
secret_data: await new Argon2id().hash(user.password),
|
||||
secret_data: await hasingService.hash(user.password),
|
||||
})
|
||||
await db.insert(schema.collections).values({ user_id: insertedUser?.id })
|
||||
await db.insert(schema.wishlists).values({ user_id: insertedUser?.id })
|
||||
await db.insert(schema.wishlistsTable).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),
|
||||
const foundRole = await db.query.rolesTable.findFirst({
|
||||
where: eq(schema.rolesTable.name, role.name),
|
||||
})
|
||||
if (!foundRole) {
|
||||
throw new Error('Role not found')
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table.utils'
|
||||
import { categoriesToExternalIdsTable } from './categoriesToExternalIdsTable'
|
||||
import { categories_to_games_table } from './categoriesToGames'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { categoriesToExternalIdsTable } from './categoriesToExternalIds.table'
|
||||
import { categories_to_games_table } from './categoriesToGames.table'
|
||||
|
||||
export const categoriesTable = pgTable('categories', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
import { relations } from 'drizzle-orm'
|
||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
||||
import { categoriesTable } from './categories.table'
|
||||
import { externalIdsTable } from './externalIds.table'
|
||||
|
||||
export const categoriesToExternalIdsTable = pgTable(
|
||||
'categories_to_external_ids',
|
||||
{
|
||||
categoryId: uuid('category_id')
|
||||
.notNull()
|
||||
.references(() => categoriesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
externalId: uuid('external_id')
|
||||
.notNull()
|
||||
.references(() => externalIdsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
},
|
||||
(table) => {
|
||||
return {
|
||||
categoriesToExternalIdsPkey: primaryKey({
|
||||
columns: [table.categoryId, table.externalId],
|
||||
}),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export const categoriesToExternalIdsRelations = relations(categoriesToExternalIdsTable, ({ one }) => ({
|
||||
category: one(categoriesTable, {
|
||||
fields: [categoriesToExternalIdsTable.categoryId],
|
||||
references: [categoriesTable.id],
|
||||
}),
|
||||
externalId: one(externalIdsTable, {
|
||||
fields: [categoriesToExternalIdsTable.externalId],
|
||||
references: [externalIdsTable.id],
|
||||
}),
|
||||
}))
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||
import { categoriesTable } from './categories.table';
|
||||
import { externalIds } from './externalIds';
|
||||
import { relations } from 'drizzle-orm';
|
||||
|
||||
export const categoriesToExternalIdsTable = pgTable(
|
||||
'categories_to_external_ids',
|
||||
{
|
||||
categoryId: uuid('category_id')
|
||||
.notNull()
|
||||
.references(() => categoriesTable.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(
|
||||
categoriesToExternalIdsTable,
|
||||
({ one }) => ({
|
||||
category: one(categoriesTable, {
|
||||
fields: [categoriesToExternalIdsTable.categoryId],
|
||||
references: [categoriesTable.id],
|
||||
}),
|
||||
externalId: one(externalIds, {
|
||||
fields: [categoriesToExternalIdsTable.externalId],
|
||||
references: [externalIds.id],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { categoriesTable } from './categories.table';
|
||||
import { games } from './games';
|
||||
import { relations } from 'drizzle-orm'
|
||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
||||
import { gamesTable } from '././games.table'
|
||||
import { categoriesTable } from './categories.table'
|
||||
|
||||
export const categories_to_games_table = pgTable(
|
||||
'categories_to_games',
|
||||
|
|
@ -11,25 +11,24 @@ export const categories_to_games_table = pgTable(
|
|||
.references(() => categoriesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
game_id: uuid('game_id')
|
||||
.notNull()
|
||||
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
.references(() => gamesTable.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_table, ({ one }) => ({
|
||||
category: one(categoriesTable, {
|
||||
fields: [categories_to_games_table.category_id],
|
||||
references: [categoriesTable.id],
|
||||
}),
|
||||
game: one(games, {
|
||||
game: one(gamesTable, {
|
||||
fields: [categories_to_games_table.game_id],
|
||||
references: [games.id],
|
||||
references: [gamesTable.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
}))
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { collections } from './collections'
|
||||
import { games } from './games'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { gamesTable } from '././games.table'
|
||||
import { collections } from './collections.table'
|
||||
|
||||
export const collection_items = pgTable('collection_items', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -15,20 +15,20 @@ export const collection_items = pgTable('collection_items', {
|
|||
.references(() => collections.id, { onDelete: 'cascade' }),
|
||||
game_id: uuid('game_id')
|
||||
.notNull()
|
||||
.references(() => games.id, { onDelete: 'cascade' }),
|
||||
.references(() => gamesTable.id, { onDelete: 'cascade' }),
|
||||
times_played: integer('times_played').default(0),
|
||||
...timestamps,
|
||||
})
|
||||
|
||||
export type CollectionItems = InferSelectModel<typeof collection_items>
|
||||
export type CollectionItemsTable = 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, {
|
||||
game: one(gamesTable, {
|
||||
fields: [collection_items.game_id],
|
||||
references: [games.id],
|
||||
references: [gamesTable.id],
|
||||
}),
|
||||
}))
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table.utils'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { usersTable } from './users.table'
|
||||
import { collection_items } from './collectionItems.table'
|
||||
|
||||
export const collections = pgTable('collections', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -16,11 +17,12 @@ export const collections = pgTable('collections', {
|
|||
...timestamps,
|
||||
})
|
||||
|
||||
export const collection_relations = relations(collections, ({ one }) => ({
|
||||
export const collection_relations = relations(collections, ({ one, many }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [collections.user_id],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
collection_items: many(collection_items),
|
||||
}))
|
||||
|
||||
export type Collections = InferSelectModel<typeof collections>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { type InferSelectModel } from 'drizzle-orm'
|
||||
import type { InferSelectModel } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table.utils'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export enum CredentialsType {
|
||||
|
|
|
|||
32
src/lib/server/api/databases/tables/expansions.table.ts
Normal file
32
src/lib/server/api/databases/tables/expansions.table.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { gamesTable } from '././games.table'
|
||||
|
||||
export const expansionsTable = pgTable('expansions', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
.unique()
|
||||
.$defaultFn(() => cuid2()),
|
||||
base_game_id: uuid('base_game_id')
|
||||
.notNull()
|
||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
game_id: uuid('game_id')
|
||||
.notNull()
|
||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
...timestamps,
|
||||
})
|
||||
|
||||
export type Expansions = InferSelectModel<typeof expansionsTable>
|
||||
|
||||
export const expansion_relations = relations(expansionsTable, ({ one }) => ({
|
||||
baseGame: one(gamesTable, {
|
||||
fields: [expansionsTable.base_game_id],
|
||||
references: [gamesTable.id],
|
||||
}),
|
||||
game: one(gamesTable, {
|
||||
fields: [expansionsTable.game_id],
|
||||
references: [gamesTable.id],
|
||||
}),
|
||||
}))
|
||||
16
src/lib/server/api/databases/tables/externalIds.table.ts
Normal file
16
src/lib/server/api/databases/tables/externalIds.table.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import type { InferSelectModel } from 'drizzle-orm'
|
||||
import { pgEnum, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
|
||||
export const externalIdType = pgEnum('external_id_type', ['game', 'category', 'mechanic', 'publisher', 'designer', 'artist'])
|
||||
|
||||
export const externalIdsTable = 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 externalIdsTable>
|
||||
|
|
@ -1,23 +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',
|
||||
]);
|
||||
|
||||
export 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>;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { type InferSelectModel } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table.utils'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export const federatedIdentityTable = pgTable('federated_identity', {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations, sql } from 'drizzle-orm'
|
||||
import { index, integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table.utils'
|
||||
import { categories_to_games_table } from './categoriesToGames'
|
||||
import { gamesToExternalIds } from './gamesToExternalIds'
|
||||
import { mechanics_to_games } from './mechanicsToGames'
|
||||
import { publishers_to_games } from './publishersToGames'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { categories_to_games_table } from './categoriesToGames.table'
|
||||
import { gamesToExternalIdsTable } from './gamesToExternalIds.table'
|
||||
import { mechanics_to_games } from './mechanicsToGames.table'
|
||||
import { publishers_to_games } from './publishersToGames.table'
|
||||
|
||||
export const games = pgTable(
|
||||
export const gamesTable = pgTable(
|
||||
'games',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -41,11 +41,11 @@ export const games = pgTable(
|
|||
}),
|
||||
)
|
||||
|
||||
export const gameRelations = relations(games, ({ many }) => ({
|
||||
export const gameRelations = relations(gamesTable, ({ many }) => ({
|
||||
categories_to_games: many(categories_to_games_table),
|
||||
mechanics_to_games: many(mechanics_to_games),
|
||||
publishers_to_games: many(publishers_to_games),
|
||||
gamesToExternalIds: many(gamesToExternalIds),
|
||||
gamesToExternalIds: many(gamesToExternalIdsTable),
|
||||
}))
|
||||
|
||||
export type Games = InferSelectModel<typeof games>
|
||||
export type Games = InferSelectModel<typeof gamesTable>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
||||
import { gamesTable } from '././games.table'
|
||||
import { externalIdsTable } from './externalIds.table'
|
||||
import { relations } from 'drizzle-orm'
|
||||
|
||||
export const gamesToExternalIdsTable = pgTable(
|
||||
'games_to_external_ids',
|
||||
{
|
||||
gameId: uuid('game_id')
|
||||
.notNull()
|
||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
externalId: uuid('external_id')
|
||||
.notNull()
|
||||
.references(() => externalIdsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
},
|
||||
(table) => {
|
||||
return {
|
||||
gamesToExternalIdsPkey: primaryKey({
|
||||
columns: [table.gameId, table.externalId],
|
||||
}),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export const gamesToExternalIdsRelations = relations(gamesToExternalIdsTable, ({ one }) => ({
|
||||
game: one(gamesTable, {
|
||||
fields: [gamesToExternalIdsTable.gameId],
|
||||
references: [gamesTable.id],
|
||||
}),
|
||||
externalId: one(externalIdsTable, {
|
||||
fields: [gamesToExternalIdsTable.externalId],
|
||||
references: [externalIdsTable.id],
|
||||
}),
|
||||
}))
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||
import {games} from './games';
|
||||
import {externalIds} from './externalIds';
|
||||
|
||||
export 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],
|
||||
}),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
|
@ -1,26 +1,26 @@
|
|||
export * from './categories.table';
|
||||
export * from './categoriesToExternalIdsTable';
|
||||
export * from './categoriesToGames';
|
||||
export * from './collectionItems';
|
||||
export * from './collections';
|
||||
export * from './credentials.table';
|
||||
export * from './expansions';
|
||||
export * from './externalIds';
|
||||
export * from './federatedIdentity.table';
|
||||
export * from './games';
|
||||
export * from './gamesToExternalIds';
|
||||
export * from './mechanics';
|
||||
export * from './mechanicsToExternalIds';
|
||||
export * from './mechanicsToGames'
|
||||
export * from './passwordResetTokens';
|
||||
export * from './publishers';
|
||||
export * from './publishersToExternalIds';
|
||||
export * from './publishersToGames';
|
||||
export * from './recovery-codes.table';
|
||||
export * from './roles';
|
||||
export * from './sessions.table';
|
||||
export * from './two-factor.table';
|
||||
export * from './userRoles';
|
||||
export * from './users.table';
|
||||
export * from './wishlistItems';
|
||||
export * from './wishlists';
|
||||
export * from './categories.table'
|
||||
export * from './categoriesToExternalIds.table'
|
||||
export * from './categoriesToGames.table'
|
||||
export * from './collectionItems.table'
|
||||
export * from './collections.table'
|
||||
export * from './credentials.table'
|
||||
export * from './expansions.table'
|
||||
export * from './externalIds.table'
|
||||
export * from './federatedIdentity.table'
|
||||
export * from './games.table'
|
||||
export * from './gamesToExternalIds.table'
|
||||
export * from './mechanics.table'
|
||||
export * from './mechanicsToExternalIds.table'
|
||||
export * from './mechanicsToGames.table'
|
||||
export * from './passwordResetTokens.table'
|
||||
export * from './publishers.table'
|
||||
export * from './publishersToExternalIds.table'
|
||||
export * from './publishersToGames.table'
|
||||
export * from './recovery-codes.table'
|
||||
export * from './roles.table'
|
||||
export * from './sessions.table'
|
||||
export * from './two-factor.table'
|
||||
export * from './userRoles.table'
|
||||
export * from './users.table'
|
||||
export * from './wishlistItems.table'
|
||||
export * from './wishlists.table'
|
||||
|
|
|
|||
23
src/lib/server/api/databases/tables/mechanics.table.ts
Normal file
23
src/lib/server/api/databases/tables/mechanics.table.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { mechanicsToExternalIdsTable } from './mechanicsToExternalIds.table'
|
||||
import { mechanics_to_games } from './mechanicsToGames.table'
|
||||
|
||||
export const mechanicsTable = 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 mechanicsTable>
|
||||
|
||||
export const mechanics_relations = relations(mechanicsTable, ({ many }) => ({
|
||||
mechanics_to_games: many(mechanics_to_games),
|
||||
mechanicsToExternalIds: many(mechanicsToExternalIdsTable),
|
||||
}))
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
||||
import { externalIdsTable } from './externalIds.table'
|
||||
import { mechanicsTable } from './mechanics.table'
|
||||
import { relations } from 'drizzle-orm'
|
||||
|
||||
export const mechanicsToExternalIdsTable = pgTable(
|
||||
'mechanics_to_external_ids',
|
||||
{
|
||||
mechanicId: uuid('mechanic_id')
|
||||
.notNull()
|
||||
.references(() => mechanicsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
externalId: uuid('external_id')
|
||||
.notNull()
|
||||
.references(() => externalIdsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
},
|
||||
(table) => {
|
||||
return {
|
||||
mechanicsToExternalIdsPkey: primaryKey({
|
||||
columns: [table.mechanicId, table.externalId],
|
||||
}),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export const mechanicsToExternalIdsRelations = relations(mechanicsToExternalIdsTable, ({ one }) => ({
|
||||
mechanic: one(mechanicsTable, {
|
||||
fields: [mechanicsToExternalIdsTable.mechanicId],
|
||||
references: [mechanicsTable.id],
|
||||
}),
|
||||
externalId: one(externalIdsTable, {
|
||||
fields: [mechanicsToExternalIdsTable.externalId],
|
||||
references: [externalIdsTable.id],
|
||||
}),
|
||||
}))
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||
import {mechanics} from './mechanics';
|
||||
import {externalIds} from './externalIds';
|
||||
|
||||
export 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],
|
||||
}),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { relations } from 'drizzle-orm'
|
||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
||||
import { gamesTable } from '././games.table'
|
||||
import { mechanicsTable } from './mechanics.table'
|
||||
|
||||
export const mechanics_to_games = pgTable(
|
||||
'mechanics_to_games',
|
||||
{
|
||||
mechanic_id: uuid('mechanic_id')
|
||||
.notNull()
|
||||
.references(() => mechanicsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
game_id: uuid('game_id')
|
||||
.notNull()
|
||||
.references(() => gamesTable.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(mechanicsTable, {
|
||||
fields: [mechanics_to_games.mechanic_id],
|
||||
references: [mechanicsTable.id],
|
||||
}),
|
||||
game: one(gamesTable, {
|
||||
fields: [mechanics_to_games.game_id],
|
||||
references: [gamesTable.id],
|
||||
}),
|
||||
}))
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||
import { relations } from 'drizzle-orm';
|
||||
import {mechanics} from './mechanics';
|
||||
import {games} from './games';
|
||||
|
||||
export 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],
|
||||
}),
|
||||
}));
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export const password_reset_tokens = pgTable('password_reset_tokens', {
|
||||
|
|
@ -15,7 +15,7 @@ export const password_reset_tokens = pgTable('password_reset_tokens', {
|
|||
...timestamps,
|
||||
})
|
||||
|
||||
export type PasswordResetTokens = InferSelectModel<typeof password_reset_tokens>
|
||||
export type PasswordResetTokensTable = InferSelectModel<typeof password_reset_tokens>
|
||||
|
||||
export const password_reset_token_relations = relations(password_reset_tokens, ({ one }) => ({
|
||||
user: one(usersTable, {
|
||||
23
src/lib/server/api/databases/tables/publishers.table.ts
Normal file
23
src/lib/server/api/databases/tables/publishers.table.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { publishersToExternalIdsTable } from './publishersToExternalIds.table'
|
||||
import { publishers_to_games } from './publishersToGames.table'
|
||||
|
||||
export const publishersTable = 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 publishersTable>
|
||||
|
||||
export const publishers_relations = relations(publishersTable, ({ many }) => ({
|
||||
publishersToGames: many(publishers_to_games),
|
||||
publishersToExternalIds: many(publishersToExternalIdsTable),
|
||||
}))
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
||||
import { externalIdsTable } from './externalIds.table'
|
||||
import { publishersTable } from './publishers.table'
|
||||
import { relations } from 'drizzle-orm'
|
||||
|
||||
export const publishersToExternalIdsTable = pgTable(
|
||||
'publishers_to_external_ids',
|
||||
{
|
||||
publisherId: uuid('publisher_id')
|
||||
.notNull()
|
||||
.references(() => publishersTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
externalId: uuid('external_id')
|
||||
.notNull()
|
||||
.references(() => externalIdsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
},
|
||||
(table) => {
|
||||
return {
|
||||
publishersToExternalIdsPkey: primaryKey({
|
||||
columns: [table.publisherId, table.externalId],
|
||||
}),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export const publishersToExternalIdsRelations = relations(publishersToExternalIdsTable, ({ one }) => ({
|
||||
publisher: one(publishersTable, {
|
||||
fields: [publishersToExternalIdsTable.publisherId],
|
||||
references: [publishersTable.id],
|
||||
}),
|
||||
externalId: one(externalIdsTable, {
|
||||
fields: [publishersToExternalIdsTable.externalId],
|
||||
references: [externalIdsTable.id],
|
||||
}),
|
||||
}))
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||
import {publishers} from './publishers';
|
||||
import {externalIds} from './externalIds';
|
||||
|
||||
export 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],
|
||||
}),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
|
@ -1,34 +1,34 @@
|
|||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||
import { relations } from 'drizzle-orm';
|
||||
import {publishers} from './publishers';
|
||||
import {games} from './games';
|
||||
import { relations } from 'drizzle-orm'
|
||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
||||
import { gamesTable } from '././games.table'
|
||||
import { publishersTable } from './publishers.table'
|
||||
|
||||
export const publishers_to_games = pgTable(
|
||||
'publishers_to_games',
|
||||
{
|
||||
publisher_id: uuid('publisher_id')
|
||||
.notNull()
|
||||
.references(() => publishers.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
.references(() => publishersTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
game_id: uuid('game_id')
|
||||
.notNull()
|
||||
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
.references(() => gamesTable.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, {
|
||||
publisher: one(publishersTable, {
|
||||
fields: [publishers_to_games.publisher_id],
|
||||
references: [publishers.id],
|
||||
references: [publishersTable.id],
|
||||
}),
|
||||
game: one(games, {
|
||||
game: one(gamesTable, {
|
||||
fields: [publishers_to_games.game_id],
|
||||
references: [games.id],
|
||||
references: [gamesTable.id],
|
||||
}),
|
||||
}));
|
||||
}))
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { timestamps } from '../../common/utils/table.utils'
|
||||
import type { InferSelectModel } from 'drizzle-orm'
|
||||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export const recoveryCodesTable = pgTable('recovery_codes', {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
import { timestamps } from '../../common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { user_roles } from './userRoles'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { user_roles } from './userRoles.table'
|
||||
|
||||
export const roles = pgTable('roles', {
|
||||
export enum RoleName {
|
||||
ADMIN = 'admin',
|
||||
EDITOR = 'editor',
|
||||
MODERATOR = 'moderator',
|
||||
USER = 'user',
|
||||
}
|
||||
|
||||
export const rolesTable = pgTable('roles', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
.unique()
|
||||
|
|
@ -14,8 +21,8 @@ export const roles = pgTable('roles', {
|
|||
...timestamps,
|
||||
})
|
||||
|
||||
export type Roles = InferSelectModel<typeof roles>
|
||||
export type Roles = InferSelectModel<typeof rolesTable>
|
||||
|
||||
export const role_relations = relations(roles, ({ many }) => ({
|
||||
export const role_relations = relations(rolesTable, ({ many }) => ({
|
||||
user_roles: many(user_roles),
|
||||
}))
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { timestamps } from '../../common/utils/table.utils'
|
||||
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 '../../common/utils/table'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export const twoFactorTable = pgTable('two_factor', {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { timestamps } from '../../common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { roles } from './roles'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { rolesTable } from './roles.table'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export const user_roles = pgTable('user_roles', {
|
||||
|
|
@ -15,15 +15,15 @@ export const user_roles = pgTable('user_roles', {
|
|||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
role_id: uuid('role_id')
|
||||
.notNull()
|
||||
.references(() => roles.id, { onDelete: 'cascade' }),
|
||||
.references(() => rolesTable.id, { onDelete: 'cascade' }),
|
||||
primary: boolean('primary').default(false),
|
||||
...timestamps,
|
||||
})
|
||||
|
||||
export const user_role_relations = relations(user_roles, ({ one }) => ({
|
||||
role: one(roles, {
|
||||
role: one(rolesTable, {
|
||||
fields: [user_roles.role_id],
|
||||
references: [roles.id],
|
||||
references: [rolesTable.id],
|
||||
}),
|
||||
user: one(usersTable, {
|
||||
fields: [user_roles.user_id],
|
||||
|
|
@ -31,4 +31,4 @@ export const user_role_relations = relations(user_roles, ({ one }) => ({
|
|||
}),
|
||||
}))
|
||||
|
||||
export type UserRoles = InferSelectModel<typeof user_roles>
|
||||
export type UserRolesTable = InferSelectModel<typeof user_roles>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { timestamps } from '../../common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { user_roles } from './userRoles'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { user_roles } from './userRoles.table'
|
||||
|
||||
export const usersTable = pgTable('users', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -15,6 +15,8 @@ export const usersTable = pgTable('users', {
|
|||
last_name: text('last_name'),
|
||||
verified: boolean('verified').default(false),
|
||||
receive_email: boolean('receive_email').default(false),
|
||||
email_verified: boolean('email_verified').default(false),
|
||||
picture: text('picture'),
|
||||
mfa_enabled: boolean('mfa_enabled').notNull().default(false),
|
||||
theme: text('theme').default('system'),
|
||||
...timestamps,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { timestamps } from '../../common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { games } from './games'
|
||||
import { wishlists } from './wishlists'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { gamesTable } from '././games.table'
|
||||
import { wishlistsTable } from './wishlists.table'
|
||||
|
||||
export const wishlist_items = pgTable('wishlist_items', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -12,22 +12,22 @@ export const wishlist_items = pgTable('wishlist_items', {
|
|||
.$defaultFn(() => cuid2()),
|
||||
wishlist_id: uuid('wishlist_id')
|
||||
.notNull()
|
||||
.references(() => wishlists.id, { onDelete: 'cascade' }),
|
||||
.references(() => wishlistsTable.id, { onDelete: 'cascade' }),
|
||||
game_id: uuid('game_id')
|
||||
.notNull()
|
||||
.references(() => games.id, { onDelete: 'cascade' }),
|
||||
.references(() => gamesTable.id, { onDelete: 'cascade' }),
|
||||
...timestamps,
|
||||
})
|
||||
|
||||
export type WishlistItems = InferSelectModel<typeof wishlist_items>
|
||||
export type WishlistItemsTable = InferSelectModel<typeof wishlist_items>
|
||||
|
||||
export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({
|
||||
wishlist: one(wishlists, {
|
||||
wishlist: one(wishlistsTable, {
|
||||
fields: [wishlist_items.wishlist_id],
|
||||
references: [wishlists.id],
|
||||
references: [wishlistsTable.id],
|
||||
}),
|
||||
game: one(games, {
|
||||
game: one(gamesTable, {
|
||||
fields: [wishlist_items.game_id],
|
||||
references: [games.id],
|
||||
references: [gamesTable.id],
|
||||
}),
|
||||
}))
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { timestamps } from '../../common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export const wishlists = pgTable('wishlists', {
|
||||
export const wishlistsTable = pgTable('wishlists', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
.unique()
|
||||
|
|
@ -16,11 +16,11 @@ export const wishlists = pgTable('wishlists', {
|
|||
...timestamps,
|
||||
})
|
||||
|
||||
export type Wishlists = InferSelectModel<typeof wishlists>
|
||||
export type Wishlists = InferSelectModel<typeof wishlistsTable>
|
||||
|
||||
export const wishlists_relations = relations(wishlists, ({ one }) => ({
|
||||
export const wishlists_relations = relations(wishlistsTable, ({ one }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [wishlists.user_id],
|
||||
fields: [wishlistsTable.user_id],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}))
|
||||
17
src/lib/server/api/dtos/change-password.dto.ts
Normal file
17
src/lib/server/api/dtos/change-password.dto.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { refinePasswords } from '$lib/validations/account'
|
||||
import { z } from 'zod'
|
||||
|
||||
export const changePasswordDto = z
|
||||
.object({
|
||||
password: z.string({ required_error: 'Password is required' }).trim(),
|
||||
confirm_password: z
|
||||
.string({ required_error: 'Confirm Password is required' })
|
||||
.trim()
|
||||
.min(8, { message: 'Must be at least 8 characters' })
|
||||
.max(128, { message: 'Must be less than 128 characters' }),
|
||||
})
|
||||
.superRefine(({ confirm_password, password }, ctx) => {
|
||||
return refinePasswords(confirm_password, password, ctx)
|
||||
})
|
||||
|
||||
export type ChangePasswordDto = z.infer<typeof changePasswordDto>
|
||||
|
|
@ -1,23 +1,14 @@
|
|||
import { z } from "zod";
|
||||
import { z } from 'zod'
|
||||
|
||||
export const updateProfileDto = z.object({
|
||||
firstName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, {message: 'Must be at least 3 characters'})
|
||||
.max(50, {message: 'Must be less than 50 characters'})
|
||||
.min(3, { message: 'Must be at least 3 characters' })
|
||||
.max(50, { message: 'Must be less than 50 characters' })
|
||||
.optional(),
|
||||
lastName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, {message: 'Must be at least 3 characters'})
|
||||
.max(50, {message: 'Must be less than 50 characters'})
|
||||
.optional(),
|
||||
username: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, {message: 'Must be at least 3 characters'})
|
||||
.max(50, {message: 'Must be less than 50 characters'})
|
||||
});
|
||||
lastName: z.string().trim().min(3, { message: 'Must be at least 3 characters' }).max(50, { message: 'Must be less than 50 characters' }).optional(),
|
||||
username: z.string().trim().min(3, { message: 'Must be at least 3 characters' }).max(50, { message: 'Must be less than 50 characters' }),
|
||||
})
|
||||
|
||||
export type UpdateProfileDto = z.infer<typeof updateProfileDto>;
|
||||
export type UpdateProfileDto = z.infer<typeof updateProfileDto>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'reflect-metadata'
|
||||
import { CollectionController } from '$lib/server/api/controllers/collection.controller'
|
||||
import { MfaController } from '$lib/server/api/controllers/mfa.controller'
|
||||
import { OAuthController } from '$lib/server/api/controllers/oauth.controller'
|
||||
import { SignupController } from '$lib/server/api/controllers/signup.controller'
|
||||
import { UserController } from '$lib/server/api/controllers/user.controller'
|
||||
import { WishlistController } from '$lib/server/api/controllers/wishlist.controller'
|
||||
|
|
@ -10,7 +11,7 @@ import { hc } from 'hono/client'
|
|||
import { cors } from 'hono/cors'
|
||||
import { logger } from 'hono/logger'
|
||||
import { container } from 'tsyringe'
|
||||
import { config } from './configs/config'
|
||||
import { config } from './common/config'
|
||||
import { IamController } from './controllers/iam.controller'
|
||||
import { LoginController } from './controllers/login.controller'
|
||||
import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware'
|
||||
|
|
@ -44,6 +45,7 @@ const routes = app
|
|||
.route('/me', container.resolve(IamController).routes())
|
||||
.route('/user', container.resolve(UserController).routes())
|
||||
.route('/login', container.resolve(LoginController).routes())
|
||||
.route('/oauth', container.resolve(OAuthController).routes())
|
||||
.route('/signup', container.resolve(SignupController).routes())
|
||||
.route('/wishlists', container.resolve(WishlistController).routes())
|
||||
.route('/collections', container.resolve(CollectionController).routes())
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ export class AuthCleanupJobs {
|
|||
this.queue = this.jobsService.createQueue('test')
|
||||
|
||||
/* ---------------------------- Register Workers ---------------------------- */
|
||||
this.worker().then((r) => console.log('auth-cleanup job worker started'))
|
||||
this.worker().then(() => console.log('auth-cleanup job worker started'))
|
||||
}
|
||||
|
||||
async deleteStaleEmailVerificationRequests() {
|
||||
await this.queue.add('delete_stale_email_verifiactions', null, {
|
||||
await this.queue.add('delete_stale_email_verifications', null, {
|
||||
repeat: {
|
||||
pattern: '0 0 * * 0', // Runs once a week at midnight on Sunday
|
||||
},
|
||||
|
|
@ -31,7 +31,7 @@ export class AuthCleanupJobs {
|
|||
|
||||
private async worker() {
|
||||
return this.jobsService.createWorker(this.queue.name, async (job) => {
|
||||
if (job.name === 'delete_stale_email_verifiactions') {
|
||||
if (job.name === 'delete_stale_email_verifications') {
|
||||
// delete stale email verifications
|
||||
}
|
||||
if (job.name === 'delete_stale_login_requests') {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
||||
import type { MiddlewareHandler } from 'hono'
|
||||
import { createMiddleware } from 'hono/factory'
|
||||
import type { Session, User } from 'lucia'
|
||||
import { verifyRequestOrigin } from 'oslo/request'
|
||||
import { Unauthorized } from '../common/exceptions'
|
||||
import { lucia } from '../packages/lucia'
|
||||
import type { HonoTypes } from '../types'
|
||||
import { container } from 'tsyringe'
|
||||
import type { HonoTypes } from '../common/types/hono'
|
||||
|
||||
// resolve dependencies from the container
|
||||
const { lucia } = container.resolve(LuciaService)
|
||||
|
||||
export const verifyOrigin: MiddlewareHandler<HonoTypes> = createMiddleware(async (c, next) => {
|
||||
if (c.req.method === 'GET') {
|
||||
|
|
@ -27,7 +29,7 @@ export const validateAuthSession: MiddlewareHandler<HonoTypes> = createMiddlewar
|
|||
}
|
||||
|
||||
const { session, user } = await lucia.validateSession(sessionId)
|
||||
if (session && session.fresh) {
|
||||
if (session?.fresh) {
|
||||
c.header('Set-Cookie', lucia.createSessionCookie(session.id).serialize(), { append: true })
|
||||
}
|
||||
if (!session) {
|
||||
|
|
@ -37,14 +39,3 @@ export const validateAuthSession: MiddlewareHandler<HonoTypes> = createMiddlewar
|
|||
c.set('user', user)
|
||||
return next()
|
||||
})
|
||||
|
||||
export const requireAuth: MiddlewareHandler<{
|
||||
Variables: {
|
||||
session: Session
|
||||
user: User
|
||||
}
|
||||
}> = createMiddleware(async (c, next) => {
|
||||
const user = c.var.user
|
||||
if (!user) throw Unauthorized('You must be logged in to access this resource')
|
||||
return next()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { rateLimiter } from 'hono-rate-limiter'
|
||||
import RedisClient from 'ioredis'
|
||||
import { RedisStore } from 'rate-limit-redis'
|
||||
import { config } from '../configs/config'
|
||||
import type { HonoTypes } from '../types'
|
||||
import { container } from 'tsyringe'
|
||||
import type { HonoTypes } from '../common/types/hono'
|
||||
import { RedisService } from '../services/redis.service'
|
||||
|
||||
const client = new RedisClient(config.REDIS_URL)
|
||||
// resolve dependencies from the container
|
||||
const { client } = container.resolve(RedisService)
|
||||
|
||||
export function limiter({
|
||||
limit,
|
||||
|
|
|
|||
15
src/lib/server/api/middleware/require-auth.middleware.ts
Normal file
15
src/lib/server/api/middleware/require-auth.middleware.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { Unauthorized } from '$lib/server/api/common/exceptions'
|
||||
import type { MiddlewareHandler } from 'hono'
|
||||
import { createMiddleware } from 'hono/factory'
|
||||
import type { Session, User } from 'lucia'
|
||||
|
||||
export const requireAuth: MiddlewareHandler<{
|
||||
Variables: {
|
||||
session: Session
|
||||
user: User
|
||||
}
|
||||
}> = createMiddleware(async (c, next) => {
|
||||
const user = c.var.user
|
||||
if (!user) throw Unauthorized('You must be logged in to access this resource')
|
||||
return next()
|
||||
})
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { Argon2id } from "oslo/password";
|
||||
|
||||
export async function hash(value: string) {
|
||||
const argon2 = new Argon2id()
|
||||
return argon2.hash(value);
|
||||
}
|
||||
|
||||
export function verify(hashedValue: string, value: string) {
|
||||
return new Argon2id().verify(hashedValue, value);
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { drizzle } from 'drizzle-orm/node-postgres'
|
||||
import pg from 'pg'
|
||||
import { config } from '../configs/config'
|
||||
import { config } from '../common/config'
|
||||
import * as schema from '../databases/tables'
|
||||
|
||||
// create the connection
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle'
|
||||
// lib/server/lucia.ts
|
||||
import { Lucia, TimeSpan } from 'lucia'
|
||||
import { config } from '../configs/config'
|
||||
import { sessionsTable, usersTable } from '../databases/tables'
|
||||
import { db } from './drizzle'
|
||||
|
||||
const adapter = new DrizzlePostgreSQLAdapter(db, sessionsTable, usersTable)
|
||||
|
||||
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 {
|
||||
// ...attributes,
|
||||
username: attributes.username,
|
||||
email: attributes.email,
|
||||
firstName: attributes.first_name,
|
||||
lastName: attributes.last_name,
|
||||
mfa_enabled: attributes.mfa_enabled,
|
||||
theme: attributes.theme,
|
||||
}
|
||||
},
|
||||
sessionExpiresIn: new TimeSpan(2, 'w'), // 2 weeks
|
||||
sessionCookie: {
|
||||
name: 'session',
|
||||
expires: false, // session cookies have very long lifespan (2 years)
|
||||
attributes: {
|
||||
// set to `true` when using HTTPS
|
||||
secure: config.isProduction,
|
||||
sameSite: 'strict',
|
||||
domain: config.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
|
||||
first_name: string
|
||||
last_name: string
|
||||
mfa_enabled: boolean
|
||||
theme: string
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue