mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Formatting files, upgrading Drizzle, using snake case in Drizzle, update tables, fix unique ID with new Drizzle.
This commit is contained in:
parent
05b74e4c59
commit
3993596986
120 changed files with 8343 additions and 2338 deletions
|
|
@ -1,11 +1,12 @@
|
||||||
import 'dotenv/config'
|
import 'dotenv/config';
|
||||||
import env from './src/lib/server/api/common/env'
|
import { defineConfig } from 'drizzle-kit';
|
||||||
import { defineConfig } from 'drizzle-kit'
|
import env from './src/lib/server/api/common/env';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
dialect: 'postgresql',
|
dialect: 'postgresql',
|
||||||
out: './src/lib/server/api/databases/migrations',
|
out: './src/lib/server/api/databases/postgres/migrations',
|
||||||
schema: './src/lib/server/api/databases/tables/index.ts',
|
schema: './src/lib/server/api/databases/postgres/tables/index.ts',
|
||||||
|
casing: 'snake_case',
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
host: env.DATABASE_HOST || 'localhost',
|
host: env.DATABASE_HOST || 'localhost',
|
||||||
port: Number(env.DATABASE_PORT) || 5432,
|
port: Number(env.DATABASE_PORT) || 5432,
|
||||||
|
|
@ -22,4 +23,4 @@ export default defineConfig({
|
||||||
table: 'migrations',
|
table: 'migrations',
|
||||||
schema: 'public',
|
schema: 'public',
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,34 @@
|
||||||
import { PUBLIC_SITE_URL } from '$env/static/public'
|
import { PUBLIC_SITE_URL } from '$env/static/public';
|
||||||
import { createPasswordResetToken } from '$lib/server/auth-utils.js'
|
import { createPasswordResetToken } from '$lib/server/auth-utils.js';
|
||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit';
|
||||||
import { eq } from 'drizzle-orm'
|
import { eq } from 'drizzle-orm';
|
||||||
import { usersTable } from '../../src/lib/server/api/databases/tables'
|
import { usersTable } from '../../src/lib/server/api/databases/postgres/tables';
|
||||||
import { db } from '../../src/lib/server/api/packages/drizzle'
|
import { db } from '../../src/lib/server/api/packages/drizzle';
|
||||||
|
|
||||||
export async function POST({ locals, request }) {
|
export async function POST({ locals, request }) {
|
||||||
const { email }: { email: string } = await request.json()
|
const { email }: { email: string } = await request.json();
|
||||||
|
|
||||||
if (!locals.user) {
|
if (!locals.user) {
|
||||||
error(401, { message: 'Unauthorized' })
|
error(401, { message: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await db.query.usersTable.findFirst({
|
const user = await db.query.usersTable.findFirst({
|
||||||
where: eq(usersTable.email, email),
|
where: eq(usersTable.email, email),
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
error(200, {
|
error(200, {
|
||||||
message: 'Email sent! Please check your email for a link to reset your password.',
|
message: 'Email sent! Please check your email for a link to reset your password.',
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const verificationToken = await createPasswordResetToken(user.id)
|
const verificationToken = await createPasswordResetToken(user.id);
|
||||||
const verificationLink = PUBLIC_SITE_URL + verificationToken
|
const verificationLink = PUBLIC_SITE_URL + verificationToken;
|
||||||
|
|
||||||
// TODO: send email
|
// TODO: send email
|
||||||
console.log('Verification link: ' + verificationLink)
|
console.log('Verification link: ' + verificationLink);
|
||||||
|
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 200,
|
status: 200,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,34 @@
|
||||||
import { eq } from 'drizzle-orm'
|
import { eq } from 'drizzle-orm';
|
||||||
import { isWithinExpirationDate } from 'oslo'
|
import { isWithinExpirationDate } from 'oslo';
|
||||||
import { password_reset_tokens } from '../../../src/lib/server/api/databases/tables'
|
import { password_reset_tokens } from '../../../src/lib/server/api/databases/postgres/tables';
|
||||||
// import { lucia } from '$lib/server/lucia';
|
// import { lucia } from '$lib/server/lucia';
|
||||||
import { db } from '../../../src/lib/server/api/packages/drizzle'
|
import { db } from '../../../src/lib/server/api/packages/drizzle';
|
||||||
|
|
||||||
export async function POST({ request, params }) {
|
export async function POST({ request, params }) {
|
||||||
const { password } = await request.json()
|
const { password } = await request.json();
|
||||||
|
|
||||||
if (typeof password !== 'string' || password.length < 8) {
|
if (typeof password !== 'string' || password.length < 8) {
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 400,
|
status: 400,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const verificationToken = params.token
|
const verificationToken = params.token;
|
||||||
|
|
||||||
const token = await db.query.password_reset_tokens.findFirst({
|
const token = await db.query.password_reset_tokens.findFirst({
|
||||||
where: eq(password_reset_tokens.id, verificationToken),
|
where: eq(password_reset_tokens.id, verificationToken),
|
||||||
})
|
});
|
||||||
if (!token) {
|
if (!token) {
|
||||||
await db.delete(password_reset_tokens).where(eq(password_reset_tokens.id, verificationToken))
|
await db.delete(password_reset_tokens).where(eq(password_reset_tokens.id, verificationToken));
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 400,
|
status: 400,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!token?.expires_at || !isWithinExpirationDate(token.expires_at)) {
|
if (!token?.expires_at || !isWithinExpirationDate(token.expires_at)) {
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 400,
|
status: 400,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// await lucia.invalidateUserSessions(token.user_id);
|
// await lucia.invalidateUserSessions(token.user_id);
|
||||||
|
|
@ -44,5 +44,5 @@ export async function POST({ request, params }) {
|
||||||
Location: '/',
|
Location: '/',
|
||||||
'Set-Cookie': sessionCookie.serialize(),
|
'Set-Cookie': sessionCookie.serialize(),
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
package.json
58
package.json
|
|
@ -5,8 +5,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"db:push": "drizzle-kit push",
|
"db:push": "drizzle-kit push",
|
||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:migrate": "tsx src/lib/server/api/databases/migrate.ts",
|
"db:migrate": "tsx src/lib/server/api/databases/postgres/migrate.ts",
|
||||||
"db:seed": "tsx src/lib/server/api/databases/seed.ts",
|
"db:seed": "tsx src/lib/server/api/databases/postgres/seed.ts",
|
||||||
"db:studio": "drizzle-kit studio --verbose",
|
"db:studio": "drizzle-kit studio --verbose",
|
||||||
"dev": "NODE_OPTIONS=\"--inspect\" vite dev --host",
|
"dev": "NODE_OPTIONS=\"--inspect\" vite dev --host",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
|
@ -27,20 +27,20 @@
|
||||||
"@faker-js/faker": "^8.4.1",
|
"@faker-js/faker": "^8.4.1",
|
||||||
"@melt-ui/pp": "^0.3.2",
|
"@melt-ui/pp": "^0.3.2",
|
||||||
"@melt-ui/svelte": "^0.83.0",
|
"@melt-ui/svelte": "^0.83.0",
|
||||||
"@playwright/test": "^1.48.0",
|
"@playwright/test": "^1.48.2",
|
||||||
"@sveltejs/adapter-auto": "^3.2.5",
|
"@sveltejs/adapter-auto": "^3.3.1",
|
||||||
"@sveltejs/enhanced-img": "^0.3.9",
|
"@sveltejs/enhanced-img": "^0.3.10",
|
||||||
"@sveltejs/kit": "^2.7.1",
|
"@sveltejs/kit": "^2.7.5",
|
||||||
"@sveltejs/vite-plugin-svelte": "4.0.0-next.7",
|
"@sveltejs/vite-plugin-svelte": "4.0.0-next.7",
|
||||||
"@types/cookie": "^0.6.0",
|
"@types/cookie": "^0.6.0",
|
||||||
"@types/node": "^20.16.11",
|
"@types/node": "^20.17.6",
|
||||||
"@types/pg": "^8.11.10",
|
"@types/pg": "^8.11.10",
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||||
"@typescript-eslint/parser": "^7.18.0",
|
"@typescript-eslint/parser": "^7.18.0",
|
||||||
"arctic": "^1.9.2",
|
"arctic": "^1.9.2",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"drizzle-kit": "^0.23.2",
|
"drizzle-kit": "^0.27.1",
|
||||||
"eslint": "^8.57.1",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "2.36.0-next.13",
|
"eslint-plugin-svelte": "2.36.0-next.13",
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
"just-debounce-it": "^3.2.0",
|
"just-debounce-it": "^3.2.0",
|
||||||
"lucia": "3.2.0",
|
"lucia": "3.2.0",
|
||||||
"lucide-svelte": "^0.408.0",
|
"lucide-svelte": "^0.408.0",
|
||||||
"nodemailer": "^6.9.15",
|
"nodemailer": "^6.9.16",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"postcss-import": "^16.1.0",
|
"postcss-import": "^16.1.0",
|
||||||
"postcss-load-config": "^5.1.0",
|
"postcss-load-config": "^5.1.0",
|
||||||
|
|
@ -57,18 +57,18 @@
|
||||||
"prettier-plugin-svelte": "^3.2.7",
|
"prettier-plugin-svelte": "^3.2.7",
|
||||||
"svelte": "5.0.0-next.175",
|
"svelte": "5.0.0-next.175",
|
||||||
"svelte-check": "^3.8.6",
|
"svelte-check": "^3.8.6",
|
||||||
"svelte-headless-table": "^0.18.2",
|
"svelte-headless-table": "^0.18.3",
|
||||||
"svelte-meta-tags": "^3.1.4",
|
"svelte-meta-tags": "^3.1.4",
|
||||||
"svelte-preprocess": "^6.0.3",
|
"svelte-preprocess": "^6.0.3",
|
||||||
"svelte-sequential-preprocessor": "^2.0.2",
|
"svelte-sequential-preprocessor": "^2.0.2",
|
||||||
"sveltekit-flash-message": "^2.4.4",
|
"sveltekit-flash-message": "^2.4.4",
|
||||||
"sveltekit-superforms": "^2.19.1",
|
"sveltekit-superforms": "^2.20.0",
|
||||||
"tailwindcss": "^3.4.13",
|
"tailwindcss": "^3.4.14",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tslib": "^2.7.0",
|
"tslib": "^2.8.1",
|
||||||
"tsx": "^4.19.1",
|
"tsx": "^4.19.2",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"vite": "^5.4.9",
|
"vite": "^5.4.10",
|
||||||
"vitest": "^1.6.0",
|
"vitest": "^1.6.0",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
|
|
@ -92,27 +92,27 @@
|
||||||
"@oslojs/otp": "^1.0.0",
|
"@oslojs/otp": "^1.0.0",
|
||||||
"@oslojs/webauthn": "^1.0.0",
|
"@oslojs/webauthn": "^1.0.0",
|
||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
"@scalar/hono-api-reference": "^0.5.154",
|
"@scalar/hono-api-reference": "^0.5.158",
|
||||||
"@sveltejs/adapter-node": "^5.2.7",
|
"@sveltejs/adapter-node": "^5.2.9",
|
||||||
"@sveltejs/adapter-vercel": "^5.4.5",
|
"@sveltejs/adapter-vercel": "^5.4.6",
|
||||||
"@types/feather-icons": "^4.29.4",
|
"@types/feather-icons": "^4.29.4",
|
||||||
"bits-ui": "^0.21.16",
|
"bits-ui": "^0.21.16",
|
||||||
"boardgamegeekclient": "^1.9.1",
|
"boardgamegeekclient": "^1.9.1",
|
||||||
"bullmq": "^5.20.0",
|
"bullmq": "^5.24.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cookie": "^0.6.0",
|
"cookie": "^1.0.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"dotenv-expand": "^11.0.6",
|
"dotenv-expand": "^11.0.6",
|
||||||
"drizzle-orm": "^0.32.2",
|
"drizzle-orm": "^0.36.0",
|
||||||
"drizzle-zod": "^0.5.1",
|
"drizzle-zod": "^0.5.1",
|
||||||
"feather-icons": "^4.29.2",
|
"feather-icons": "^4.29.2",
|
||||||
"formsnap": "^1.0.1",
|
"formsnap": "^1.0.1",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"hono": "^4.6.4",
|
"hono": "^4.6.9",
|
||||||
"hono-pino": "^0.3.0",
|
"hono-pino": "^0.3.0",
|
||||||
"hono-rate-limiter": "^0.4.0",
|
"hono-rate-limiter": "^0.4.0",
|
||||||
"hono-zod-openapi": "^0.3.0",
|
"hono-zod-openapi": "^0.3.1",
|
||||||
"html-entities": "^2.5.2",
|
"html-entities": "^2.5.2",
|
||||||
"iconify-icon": "^2.1.0",
|
"iconify-icon": "^2.1.0",
|
||||||
"ioredis": "^5.4.1",
|
"ioredis": "^5.4.1",
|
||||||
|
|
@ -122,21 +122,21 @@
|
||||||
"mode-watcher": "^0.4.1",
|
"mode-watcher": "^0.4.1",
|
||||||
"open-props": "^1.7.7",
|
"open-props": "^1.7.7",
|
||||||
"oslo": "^1.2.1",
|
"oslo": "^1.2.1",
|
||||||
"pg": "^8.13.0",
|
"pg": "^8.13.1",
|
||||||
"pino": "^9.4.0",
|
"pino": "^9.5.0",
|
||||||
"pino-pretty": "^11.2.2",
|
"pino-pretty": "^11.3.0",
|
||||||
"postgres": "^3.4.4",
|
"postgres": "^3.4.5",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"radix-svelte": "^0.9.0",
|
"radix-svelte": "^0.9.0",
|
||||||
"rate-limit-redis": "^4.2.0",
|
"rate-limit-redis": "^4.2.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"stoker": "^1.2.3",
|
"stoker": "^1.3.0",
|
||||||
"svelte-lazy-loader": "^1.0.0",
|
"svelte-lazy-loader": "^1.0.0",
|
||||||
"svelte-sonner": "^0.3.28",
|
"svelte-sonner": "^0.3.28",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
"tailwind-variants": "^0.2.1",
|
"tailwind-variants": "^0.2.1",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tsyringe": "^4.8.0",
|
"tsyringe": "^4.8.0",
|
||||||
"zod-to-json-schema": "^3.23.3"
|
"zod-to-json-schema": "^3.23.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
652
pnpm-lock.yaml
652
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -18,5 +18,7 @@ export const config: Config = {
|
||||||
database: env.DATABASE_DB,
|
database: env.DATABASE_DB,
|
||||||
ssl: false, // env.DATABASE_HOST !== 'localhost',
|
ssl: false, // env.DATABASE_HOST !== 'localhost',
|
||||||
max: env.DB_MIGRATING || env.DB_SEEDING ? 1 : undefined,
|
max: env.DB_MIGRATING || env.DB_SEEDING ? 1 : undefined,
|
||||||
|
migrating: env.DB_MIGRATING,
|
||||||
|
seeding: env.DB_SEEDING,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
export interface Config {
|
export interface Config {
|
||||||
isProduction: boolean
|
isProduction: boolean;
|
||||||
domain: string
|
domain: string;
|
||||||
api: ApiConfig
|
api: ApiConfig;
|
||||||
// storage: StorageConfig
|
// storage: StorageConfig
|
||||||
redis: RedisConfig
|
redis: RedisConfig;
|
||||||
postgres: PostgresConfig
|
postgres: PostgresConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ApiConfig {
|
interface ApiConfig {
|
||||||
origin: string
|
origin: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// interface StorageConfig {
|
// interface StorageConfig {
|
||||||
|
|
@ -19,15 +19,17 @@ interface ApiConfig {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
interface RedisConfig {
|
interface RedisConfig {
|
||||||
url: string
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PostgresConfig {
|
interface PostgresConfig {
|
||||||
user: string
|
user: string;
|
||||||
password: string
|
password: string;
|
||||||
host: string
|
host: string;
|
||||||
port: number
|
port: number;
|
||||||
database: string
|
database: string;
|
||||||
ssl: boolean
|
ssl: boolean;
|
||||||
max: number | undefined
|
max: number | undefined;
|
||||||
|
migrating: boolean;
|
||||||
|
seeding: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
src/lib/server/api/common/utils/cookies.ts
Normal file
34
src/lib/server/api/common/utils/cookies.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { config } from '$lib/server/api/common/config';
|
||||||
|
import env from '$lib/server/api/common/env';
|
||||||
|
|
||||||
|
export function createSessionTokenCookie(token: string, expiresAt: Date) {
|
||||||
|
return {
|
||||||
|
name: 'session',
|
||||||
|
value: token,
|
||||||
|
attributes: {
|
||||||
|
path: '/',
|
||||||
|
maxAge: 60 * 60 * 24 * 30,
|
||||||
|
domain: env.DOMAIN,
|
||||||
|
sameSite: 'lax',
|
||||||
|
secure: config.isProduction,
|
||||||
|
httpOnly: true,
|
||||||
|
expires: expiresAt,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createBlankSessionTokenCookie() {
|
||||||
|
return {
|
||||||
|
name: 'session',
|
||||||
|
value: '',
|
||||||
|
attributes: {
|
||||||
|
path: '/',
|
||||||
|
maxAge: 0,
|
||||||
|
domain: env.DOMAIN,
|
||||||
|
sameSite: 'lax',
|
||||||
|
secure: config.isProduction,
|
||||||
|
httpOnly: true,
|
||||||
|
expires: new Date(0),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import { timestamp } from 'drizzle-orm/pg-core'
|
import { timestamp } from 'drizzle-orm/pg-core';
|
||||||
import { customType } from 'drizzle-orm/pg-core'
|
import { customType } from 'drizzle-orm/pg-core';
|
||||||
|
|
||||||
export const citext = customType<{ data: string }>({
|
export const citext = customType<{ data: string }>({
|
||||||
dataType() {
|
dataType() {
|
||||||
return 'citext'
|
return 'citext';
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
export const cuid2 = customType<{ data: string }>({
|
export const cuid2 = customType<{ data: string }>({
|
||||||
dataType() {
|
dataType() {
|
||||||
return 'text'
|
return 'text';
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
export const timestamps = {
|
export const timestamps = {
|
||||||
createdAt: timestamp('created_at', {
|
createdAt: timestamp('created_at', {
|
||||||
|
|
@ -25,5 +25,6 @@ export const timestamps = {
|
||||||
withTimezone: true,
|
withTimezone: true,
|
||||||
})
|
})
|
||||||
.notNull()
|
.notNull()
|
||||||
.defaultNow(),
|
.defaultNow()
|
||||||
}
|
.$onUpdate(() => new Date()),
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { StatusCodes } from '$lib/constants/status-codes';
|
import { StatusCodes } from '$lib/constants/status-codes';
|
||||||
import { unauthorizedSchema } from '$lib/server/api/common/exceptions';
|
import { unauthorizedSchema } from '$lib/server/api/common/exceptions';
|
||||||
import cuidParamsSchema from '$lib/server/api/common/openapi/cuidParamsSchema';
|
import cuidParamsSchema from '$lib/server/api/common/openapi/cuidParamsSchema';
|
||||||
import { selectCollectionSchema } from '$lib/server/api/databases/tables';
|
|
||||||
import { z } from '@hono/zod-openapi';
|
import { z } from '@hono/zod-openapi';
|
||||||
import { IdParamsSchema } from 'stoker/openapi/schemas';
|
import { IdParamsSchema } from 'stoker/openapi/schemas';
|
||||||
import { createErrorSchema } from 'stoker/openapi/schemas';
|
import { createErrorSchema } from 'stoker/openapi/schemas';
|
||||||
import { taggedAuthRoute } from '../common/openapi/create-auth-route';
|
import { taggedAuthRoute } from '../common/openapi/create-auth-route';
|
||||||
|
import { selectCollectionSchema } from '../databases/postgres/tables';
|
||||||
|
|
||||||
const tag = 'Collection';
|
const tag = 'Collection';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { StatusCodes } from '$lib/constants/status-codes';
|
import { StatusCodes } from '$lib/constants/status-codes';
|
||||||
import { Controller } from '$lib/server/api/common/types/controller';
|
import { Controller } from '$lib/server/api/common/types/controller';
|
||||||
|
import { deleteSessionTokenCookie } from '$lib/server/api/common/utils/cookies';
|
||||||
import { changePasswordDto } from '$lib/server/api/dtos/change-password.dto';
|
import { changePasswordDto } from '$lib/server/api/dtos/change-password.dto';
|
||||||
import { updateEmailDto } from '$lib/server/api/dtos/update-email.dto';
|
import { updateEmailDto } from '$lib/server/api/dtos/update-email.dto';
|
||||||
import { updateProfileDto } from '$lib/server/api/dtos/update-profile.dto';
|
import { updateProfileDto } from '$lib/server/api/dtos/update-profile.dto';
|
||||||
|
|
@ -7,7 +8,7 @@ import { verifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto';
|
||||||
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware';
|
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware';
|
||||||
import { IamService } from '$lib/server/api/services/iam.service';
|
import { IamService } from '$lib/server/api/services/iam.service';
|
||||||
import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service';
|
import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service';
|
||||||
import { LuciaService } from '$lib/server/api/services/lucia.service';
|
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||||
import { zValidator } from '@hono/zod-validator';
|
import { zValidator } from '@hono/zod-validator';
|
||||||
import { openApi } from 'hono-zod-openapi';
|
import { openApi } from 'hono-zod-openapi';
|
||||||
import { setCookie } from 'hono/cookie';
|
import { setCookie } from 'hono/cookie';
|
||||||
|
|
@ -20,7 +21,7 @@ export class IamController extends Controller {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(IamService) private readonly iamService: IamService,
|
@inject(IamService) private readonly iamService: IamService,
|
||||||
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
|
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
|
||||||
@inject(LuciaService) private luciaService: LuciaService,
|
@inject(SessionsService) private sessionsService: SessionsService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
@ -78,18 +79,9 @@ export class IamController extends Controller {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.iamService.updatePassword(user.id, { password, confirm_password });
|
await this.iamService.updatePassword(user.id, { password, confirm_password });
|
||||||
await this.luciaService.lucia.invalidateUserSessions(user.id);
|
await this.sessionsService.invalidateSession(user.id);
|
||||||
await this.loginRequestService.createUserSession(user.id, c.req, undefined);
|
await this.loginRequestService.createUserSession(user.id, c.req, undefined);
|
||||||
const sessionCookie = this.luciaService.lucia.createBlankSessionCookie();
|
deleteSessionTokenCookie(c);
|
||||||
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' });
|
return c.json({ status: 'success' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating password', error);
|
console.error('Error updating password', error);
|
||||||
|
|
@ -116,16 +108,7 @@ export class IamController extends Controller {
|
||||||
.post('/logout', requireAuth, openApi(logout), async (c) => {
|
.post('/logout', requireAuth, openApi(logout), async (c) => {
|
||||||
const sessionId = c.var.session.id;
|
const sessionId = c.var.session.id;
|
||||||
await this.iamService.logout(sessionId);
|
await this.iamService.logout(sessionId);
|
||||||
const sessionCookie = this.luciaService.lucia.createBlankSessionCookie();
|
deleteSessionTokenCookie(c);
|
||||||
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' });
|
return c.json({ status: 'success' });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { StatusCodes } from '$lib/constants/status-codes';
|
import { StatusCodes } from '$lib/constants/status-codes';
|
||||||
import { unauthorizedSchema } from '$lib/server/api/common/exceptions';
|
import { unauthorizedSchema } from '$lib/server/api/common/exceptions';
|
||||||
import { selectUserSchema } from '$lib/server/api/databases/tables/users.table';
|
import { selectUserSchema } from '$lib/server/api/databases/postgres/tables/users.table';
|
||||||
import { updateProfileDto } from '$lib/server/api/dtos/update-profile.dto';
|
import { updateProfileDto } from '$lib/server/api/dtos/update-profile.dto';
|
||||||
import { createErrorSchema } from 'stoker/openapi/schemas';
|
import { createErrorSchema } from 'stoker/openapi/schemas';
|
||||||
import { taggedAuthRoute } from '../common/openapi/create-auth-route';
|
import { taggedAuthRoute } from '../common/openapi/create-auth-route';
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,50 @@
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata';
|
||||||
import { Controller } from '$lib/server/api/common/types/controller'
|
import { Controller } from '$lib/server/api/common/types/controller';
|
||||||
import { signinUsernameDto } from '$lib/server/api/dtos/signin-username.dto'
|
import { signinUsernameDto } from '$lib/server/api/dtos/signin-username.dto';
|
||||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||||
import { zValidator } from '@hono/zod-validator'
|
import { zValidator } from '@hono/zod-validator';
|
||||||
import { setCookie } from 'hono/cookie'
|
|
||||||
import { TimeSpan } from 'oslo'
|
|
||||||
import { openApi } from 'hono-zod-openapi';
|
import { openApi } from 'hono-zod-openapi';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { setCookie } from 'hono/cookie';
|
||||||
import { limiter } from '../middleware/rate-limiter.middleware'
|
import { TimeSpan } from 'oslo';
|
||||||
import { LoginRequestsService } from '../services/loginrequest.service'
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { signinUsername } from './login.routes'
|
import { limiter } from '../middleware/rate-limiter.middleware';
|
||||||
|
import { LoginRequestsService } from '../services/loginrequest.service';
|
||||||
|
import { signinUsername } from './login.routes';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class LoginController extends Controller {
|
export class LoginController extends Controller {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(LoginRequestsService) private readonly loginRequestsService: LoginRequestsService,
|
@inject(LoginRequestsService) private readonly loginRequestsService: LoginRequestsService,
|
||||||
@inject(LuciaService) private luciaService: LuciaService,
|
@inject(SessionsService) private luciaService: SessionsService,
|
||||||
) {
|
) {
|
||||||
super()
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
routes() {
|
routes() {
|
||||||
return this.controller.post('/', openApi(signinUsername), zValidator('json', signinUsernameDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
return this.controller.post(
|
||||||
const { username, password } = c.req.valid('json')
|
'/',
|
||||||
const session = await this.loginRequestsService.verify({ username, password }, c.req)
|
openApi(signinUsername),
|
||||||
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id)
|
zValidator('json', signinUsernameDto),
|
||||||
console.log('set cookie', sessionCookie)
|
limiter({ limit: 10, minutes: 60 }),
|
||||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
async (c) => {
|
||||||
path: sessionCookie.attributes.path,
|
const { username, password } = c.req.valid('json');
|
||||||
maxAge:
|
const session = await this.loginRequestsService.verify({ username, password }, c.req);
|
||||||
sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds()
|
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id);
|
||||||
? sessionCookie.attributes.maxAge
|
console.log('set cookie', sessionCookie);
|
||||||
: new TimeSpan(2, 'w').seconds(),
|
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||||
domain: sessionCookie.attributes.domain,
|
path: sessionCookie.attributes.path,
|
||||||
sameSite: sessionCookie.attributes.sameSite as any,
|
maxAge:
|
||||||
secure: sessionCookie.attributes.secure,
|
sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds()
|
||||||
httpOnly: sessionCookie.attributes.httpOnly,
|
? sessionCookie.attributes.maxAge
|
||||||
expires: sessionCookie.attributes.expires,
|
: new TimeSpan(2, 'w').seconds(),
|
||||||
})
|
domain: sessionCookie.attributes.domain,
|
||||||
return c.json({ message: 'ok' })
|
sameSite: sessionCookie.attributes.sameSite as any,
|
||||||
})
|
secure: sessionCookie.attributes.secure,
|
||||||
|
httpOnly: sessionCookie.attributes.httpOnly,
|
||||||
|
expires: sessionCookie.attributes.expires,
|
||||||
|
});
|
||||||
|
return c.json({ message: 'ok' });
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata';
|
||||||
import { StatusCodes } from '$lib/constants/status-codes'
|
import { StatusCodes } from '$lib/constants/status-codes';
|
||||||
import { Controller } from '$lib/server/api/common/types/controller'
|
import { Controller } from '$lib/server/api/common/types/controller';
|
||||||
import { verifyTotpDto } from '$lib/server/api/dtos/verify-totp.dto'
|
import { verifyTotpDto } from '$lib/server/api/dtos/verify-totp.dto';
|
||||||
import { RecoveryCodesService } from '$lib/server/api/services/recovery-codes.service'
|
import { RecoveryCodesService } from '$lib/server/api/services/recovery-codes.service';
|
||||||
import { TotpService } from '$lib/server/api/services/totp.service'
|
import { TotpService } from '$lib/server/api/services/totp.service';
|
||||||
import { UsersService } from '$lib/server/api/services/users.service'
|
import { UsersService } from '$lib/server/api/services/users.service';
|
||||||
import { zValidator } from '@hono/zod-validator'
|
import { zValidator } from '@hono/zod-validator';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { CredentialsType } from '../databases/tables'
|
import { CredentialsType } from '../databases/postgres/tables';
|
||||||
import { requireAuth } from '../middleware/require-auth.middleware'
|
import { requireAuth } from '../middleware/require-auth.middleware';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class MfaController extends Controller {
|
export class MfaController extends Controller {
|
||||||
|
|
@ -17,59 +17,59 @@ export class MfaController extends Controller {
|
||||||
@inject(TotpService) private readonly totpService: TotpService,
|
@inject(TotpService) private readonly totpService: TotpService,
|
||||||
@inject(UsersService) private readonly usersService: UsersService,
|
@inject(UsersService) private readonly usersService: UsersService,
|
||||||
) {
|
) {
|
||||||
super()
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
routes() {
|
routes() {
|
||||||
return this.controller
|
return this.controller
|
||||||
.get('/totp', requireAuth, async (c) => {
|
.get('/totp', requireAuth, async (c) => {
|
||||||
const user = c.var.user
|
const user = c.var.user;
|
||||||
const totpCredential = await this.totpService.findOneByUserId(user.id)
|
const totpCredential = await this.totpService.findOneByUserId(user.id);
|
||||||
return c.json({ totpCredential })
|
return c.json({ totpCredential });
|
||||||
})
|
})
|
||||||
.post('/totp', requireAuth, async (c) => {
|
.post('/totp', requireAuth, async (c) => {
|
||||||
const user = c.var.user
|
const user = c.var.user;
|
||||||
const totpCredential = await this.totpService.create(user.id)
|
const totpCredential = await this.totpService.create(user.id);
|
||||||
return c.json({ totpCredential })
|
return c.json({ totpCredential });
|
||||||
})
|
})
|
||||||
.delete('/totp', requireAuth, async (c) => {
|
.delete('/totp', requireAuth, async (c) => {
|
||||||
const user = c.var.user
|
const user = c.var.user;
|
||||||
try {
|
try {
|
||||||
await this.totpService.deleteOneByUserIdAndType(user.id, CredentialsType.TOTP)
|
await this.totpService.deleteOneByUserIdAndType(user.id, CredentialsType.TOTP);
|
||||||
await this.recoveryCodesService.deleteAllRecoveryCodesByUserId(user.id)
|
await this.recoveryCodesService.deleteAllRecoveryCodesByUserId(user.id);
|
||||||
await this.usersService.updateUser(user.id, { mfa_enabled: false })
|
await this.usersService.updateUser(user.id, { mfa_enabled: false });
|
||||||
console.log('TOTP deleted')
|
console.log('TOTP deleted');
|
||||||
return c.body(null, StatusCodes.NO_CONTENT)
|
return c.body(null, StatusCodes.NO_CONTENT);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
return c.status(StatusCodes.INTERNAL_SERVER_ERROR)
|
return c.status(StatusCodes.INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.get('/totp/recoveryCodes', requireAuth, async (c) => {
|
.get('/totp/recoveryCodes', requireAuth, async (c) => {
|
||||||
const user = c.var.user
|
const user = c.var.user;
|
||||||
// You can only view recovery codes once and that is on creation
|
// You can only view recovery codes once and that is on creation
|
||||||
const existingCodes = await this.recoveryCodesService.findAllRecoveryCodesByUserId(user.id)
|
const existingCodes = await this.recoveryCodesService.findAllRecoveryCodesByUserId(user.id);
|
||||||
if (existingCodes && existingCodes.length > 0) {
|
if (existingCodes && existingCodes.length > 0) {
|
||||||
console.log('Recovery Codes found', existingCodes)
|
console.log('Recovery Codes found', existingCodes);
|
||||||
return c.json({ recoveryCodes: existingCodes })
|
return c.json({ recoveryCodes: existingCodes });
|
||||||
}
|
}
|
||||||
const recoveryCodes = await this.recoveryCodesService.createRecoveryCodes(user.id)
|
const recoveryCodes = await this.recoveryCodesService.createRecoveryCodes(user.id);
|
||||||
return c.json({ recoveryCodes })
|
return c.json({ recoveryCodes });
|
||||||
})
|
})
|
||||||
.post('/totp/verify', requireAuth, zValidator('json', verifyTotpDto), async (c) => {
|
.post('/totp/verify', requireAuth, zValidator('json', verifyTotpDto), async (c) => {
|
||||||
try {
|
try {
|
||||||
const user = c.var.user
|
const user = c.var.user;
|
||||||
const { code } = c.req.valid('json')
|
const { code } = c.req.valid('json');
|
||||||
const verified = await this.totpService.verify(user.id, code)
|
const verified = await this.totpService.verify(user.id, code);
|
||||||
if (verified) {
|
if (verified) {
|
||||||
await this.usersService.updateUser(user.id, { mfa_enabled: true })
|
await this.usersService.updateUser(user.id, { mfa_enabled: true });
|
||||||
return c.json({}, StatusCodes.OK)
|
return c.json({}, StatusCodes.OK);
|
||||||
}
|
}
|
||||||
return c.json('Invalid code', StatusCodes.BAD_REQUEST)
|
return c.json('Invalid code', StatusCodes.BAD_REQUEST);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
return c.status(StatusCodes.INTERNAL_SERVER_ERROR)
|
return c.status(StatusCodes.INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,56 @@
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata';
|
||||||
import { Controller } from '$lib/server/api/common/types/controller'
|
import { Controller } from '$lib/server/api/common/types/controller';
|
||||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
import type { OAuthUser } from '$lib/server/api/common/types/oauth';
|
||||||
import { OAuthService } from '$lib/server/api/services/oauth.service'
|
import { createSessionTokenCookie } from '$lib/server/api/common/utils/cookies';
|
||||||
import { github, google } from '$lib/server/auth'
|
import { OAuthService } from '$lib/server/api/services/oauth.service';
|
||||||
import { OAuth2RequestError } from 'arctic'
|
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||||
import { getCookie, setCookie } from 'hono/cookie'
|
import { github, google } from '$lib/server/auth';
|
||||||
import { TimeSpan } from 'oslo'
|
import { OAuth2RequestError } from 'arctic';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { getCookie, setCookie } from 'hono/cookie';
|
||||||
import type {OAuthUser} from "$lib/server/api/common/types/oauth";
|
import { TimeSpan } from 'oslo';
|
||||||
|
import { inject, injectable } from 'tsyringe';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class OAuthController extends Controller {
|
export class OAuthController extends Controller {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(LuciaService) private luciaService: LuciaService,
|
@inject(SessionsService) private sessionsService: SessionsService,
|
||||||
@inject(OAuthService) private oauthService: OAuthService,
|
@inject(OAuthService) private oauthService: OAuthService,
|
||||||
) {
|
) {
|
||||||
super()
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
routes() {
|
routes() {
|
||||||
return this.controller
|
return this.controller
|
||||||
.get('/github', async (c) => {
|
.get('/github', async (c) => {
|
||||||
try {
|
try {
|
||||||
const code = c.req.query('code')?.toString() ?? null
|
const code = c.req.query('code')?.toString() ?? null;
|
||||||
const state = c.req.query('state')?.toString() ?? null
|
const state = c.req.query('state')?.toString() ?? null;
|
||||||
const storedState = getCookie(c).github_oauth_state ?? null
|
const storedState = getCookie(c).github_oauth_state ?? null;
|
||||||
|
|
||||||
if (!code || !state || !storedState || state !== storedState) {
|
if (!code || !state || !storedState || state !== storedState) {
|
||||||
return c.body(null, 400)
|
return c.body(null, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokens = await github.validateAuthorizationCode(code)
|
const tokens = await github.validateAuthorizationCode(code);
|
||||||
const githubUserResponse = await fetch('https://api.github.com/user', {
|
const githubUserResponse = await fetch('https://api.github.com/user', {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${tokens.accessToken}`,
|
Authorization: `Bearer ${tokens.accessToken}`,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
const githubUser: GitHubUser = await githubUserResponse.json()
|
const githubUser: GitHubUser = await githubUserResponse.json();
|
||||||
|
|
||||||
const oAuthUser: OAuthUser = {
|
const oAuthUser: OAuthUser = {
|
||||||
sub: `${githubUser.id}`,
|
sub: `${githubUser.id}`,
|
||||||
username: githubUser.login,
|
username: githubUser.login,
|
||||||
email: undefined
|
email: undefined,
|
||||||
}
|
};
|
||||||
|
|
||||||
const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'github')
|
const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'github');
|
||||||
|
|
||||||
const session = await this.luciaService.lucia.createSession(userId, {})
|
const sessionToken = this.sessionsService.generateSessionToken();
|
||||||
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id)
|
const session = await this.sessionsService.createSession(sessionToken, userId,
|
||||||
|
req.);
|
||||||
|
const sessionCookie = createSessionTokenCookie(session.id, new Date(new TimeSpan(2, 'w').milliseconds()));
|
||||||
|
|
||||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||||
path: sessionCookie.attributes.path,
|
path: sessionCookie.attributes.path,
|
||||||
|
|
@ -60,37 +63,37 @@ export class OAuthController extends Controller {
|
||||||
secure: sessionCookie.attributes.secure,
|
secure: sessionCookie.attributes.secure,
|
||||||
httpOnly: sessionCookie.attributes.httpOnly,
|
httpOnly: sessionCookie.attributes.httpOnly,
|
||||||
expires: sessionCookie.attributes.expires,
|
expires: sessionCookie.attributes.expires,
|
||||||
})
|
});
|
||||||
|
|
||||||
return c.json({ message: 'ok' })
|
return c.json({ message: 'ok' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error);
|
||||||
// the specific error message depends on the provider
|
// the specific error message depends on the provider
|
||||||
if (error instanceof OAuth2RequestError) {
|
if (error instanceof OAuth2RequestError) {
|
||||||
// invalid code
|
// invalid code
|
||||||
return c.body(null, 400)
|
return c.body(null, 400);
|
||||||
}
|
}
|
||||||
return c.body(null, 500)
|
return c.body(null, 500);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.get('/google', async (c) => {
|
.get('/google', async (c) => {
|
||||||
try {
|
try {
|
||||||
const code = c.req.query('code')?.toString() ?? null
|
const code = c.req.query('code')?.toString() ?? null;
|
||||||
const state = c.req.query('state')?.toString() ?? null
|
const state = c.req.query('state')?.toString() ?? null;
|
||||||
const storedState = getCookie(c).google_oauth_state ?? null
|
const storedState = getCookie(c).google_oauth_state ?? null;
|
||||||
const storedCodeVerifier = getCookie(c).google_oauth_code_verifier ?? null
|
const storedCodeVerifier = getCookie(c).google_oauth_code_verifier ?? null;
|
||||||
|
|
||||||
if (!code || !storedState || !storedCodeVerifier || state !== storedState) {
|
if (!code || !storedState || !storedCodeVerifier || state !== storedState) {
|
||||||
return c.body(null, 400)
|
return c.body(null, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokens = await google.validateAuthorizationCode(code, storedCodeVerifier)
|
const tokens = await google.validateAuthorizationCode(code, storedCodeVerifier);
|
||||||
const googleUserResponse = await fetch("https://openidconnect.googleapis.com/v1/userinfo", {
|
const googleUserResponse = await fetch('https://openidconnect.googleapis.com/v1/userinfo', {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${tokens.accessToken}`,
|
Authorization: `Bearer ${tokens.accessToken}`,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
const googleUser: GoogleUser = await googleUserResponse.json()
|
const googleUser: GoogleUser = await googleUserResponse.json();
|
||||||
|
|
||||||
const oAuthUser: OAuthUser = {
|
const oAuthUser: OAuthUser = {
|
||||||
sub: googleUser.sub,
|
sub: googleUser.sub,
|
||||||
|
|
@ -100,12 +103,12 @@ export class OAuthController extends Controller {
|
||||||
username: googleUser.email,
|
username: googleUser.email,
|
||||||
email: googleUser.email,
|
email: googleUser.email,
|
||||||
email_verified: googleUser.email_verified,
|
email_verified: googleUser.email_verified,
|
||||||
}
|
};
|
||||||
|
|
||||||
const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'google')
|
const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'google');
|
||||||
|
|
||||||
const session = await this.luciaService.lucia.createSession(userId, {})
|
const session = await this.luciaService.lucia.createSession(userId, {});
|
||||||
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id)
|
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id);
|
||||||
|
|
||||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||||
path: sessionCookie.attributes.path,
|
path: sessionCookie.attributes.path,
|
||||||
|
|
@ -118,33 +121,33 @@ export class OAuthController extends Controller {
|
||||||
secure: sessionCookie.attributes.secure,
|
secure: sessionCookie.attributes.secure,
|
||||||
httpOnly: sessionCookie.attributes.httpOnly,
|
httpOnly: sessionCookie.attributes.httpOnly,
|
||||||
expires: sessionCookie.attributes.expires,
|
expires: sessionCookie.attributes.expires,
|
||||||
})
|
});
|
||||||
|
|
||||||
return c.json({ message: 'ok' })
|
return c.json({ message: 'ok' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error);
|
||||||
// the specific error message depends on the provider
|
// the specific error message depends on the provider
|
||||||
if (error instanceof OAuth2RequestError) {
|
if (error instanceof OAuth2RequestError) {
|
||||||
// invalid code
|
// invalid code
|
||||||
return c.body(null, 400)
|
return c.body(null, 400);
|
||||||
}
|
}
|
||||||
return c.body(null, 500)
|
return c.body(null, 500);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GitHubUser {
|
interface GitHubUser {
|
||||||
id: number
|
id: number;
|
||||||
login: string
|
login: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GoogleUser {
|
interface GoogleUser {
|
||||||
sub: string
|
sub: string;
|
||||||
name: string
|
name: string;
|
||||||
given_name: string
|
given_name: string;
|
||||||
family_name: string
|
family_name: string;
|
||||||
picture: string
|
picture: string;
|
||||||
email: string
|
email: string;
|
||||||
email_verified: boolean
|
email_verified: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,43 @@
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata';
|
||||||
import { Controller } from '$lib/server/api/common/types/controller'
|
import { Controller } from '$lib/server/api/common/types/controller';
|
||||||
import { signupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto'
|
import { signupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto';
|
||||||
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware'
|
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware';
|
||||||
import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service'
|
import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service';
|
||||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||||
import { UsersService } from '$lib/server/api/services/users.service'
|
import { UsersService } from '$lib/server/api/services/users.service';
|
||||||
import { zValidator } from '@hono/zod-validator'
|
import { zValidator } from '@hono/zod-validator';
|
||||||
import { setCookie } from 'hono/cookie'
|
import { setCookie } from 'hono/cookie';
|
||||||
import { TimeSpan } from 'oslo'
|
import { TimeSpan } from 'oslo';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { inject, injectable } from 'tsyringe';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SignupController extends Controller {
|
export class SignupController extends Controller {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(UsersService) private readonly usersService: UsersService,
|
@inject(UsersService) private readonly usersService: UsersService,
|
||||||
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
|
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
|
||||||
@inject(LuciaService) private luciaService: LuciaService,
|
@inject(SessionsService) private luciaService: SessionsService,
|
||||||
) {
|
) {
|
||||||
super()
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
routes() {
|
routes() {
|
||||||
return this.controller.post('/', zValidator('json', signupUsernameEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
return this.controller.post('/', zValidator('json', signupUsernameEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||||
const { firstName, lastName, email, username, password, confirm_password } = await c.req.valid('json')
|
const { firstName, lastName, email, username, password, confirm_password } = await c.req.valid('json');
|
||||||
const existingUser = await this.usersService.findOneByUsername(username)
|
const existingUser = await this.usersService.findOneByUsername(username);
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
return c.body('User already exists', 400)
|
return c.body('User already exists', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await this.usersService.create({ firstName, lastName, email, username, password, confirm_password })
|
const user = await this.usersService.create({ firstName, lastName, email, username, password, confirm_password });
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return c.body('Failed to create user', 500)
|
return c.body('Failed to create user', 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = await this.loginRequestService.createUserSession(user.id, c.req, undefined)
|
const session = await this.loginRequestService.createUserSession(user.id, c.req, undefined);
|
||||||
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id)
|
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id);
|
||||||
console.log('set cookie', sessionCookie)
|
console.log('set cookie', sessionCookie);
|
||||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||||
path: sessionCookie.attributes.path,
|
path: sessionCookie.attributes.path,
|
||||||
maxAge:
|
maxAge:
|
||||||
|
|
@ -49,8 +49,8 @@ export class SignupController extends Controller {
|
||||||
secure: sessionCookie.attributes.secure,
|
secure: sessionCookie.attributes.secure,
|
||||||
httpOnly: sessionCookie.attributes.httpOnly,
|
httpOnly: sessionCookie.attributes.httpOnly,
|
||||||
expires: sessionCookie.attributes.expires,
|
expires: sessionCookie.attributes.expires,
|
||||||
})
|
});
|
||||||
return c.json({ message: 'ok' })
|
return c.json({ message: 'ok' });
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import 'dotenv/config'
|
|
||||||
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 '../common/env'
|
|
||||||
|
|
||||||
const connection = postgres({
|
|
||||||
host: env.DATABASE_HOST || 'localhost',
|
|
||||||
port: env.DATABASE_PORT,
|
|
||||||
user: env.DATABASE_USER || 'root',
|
|
||||||
password: env.DATABASE_PASSWORD || '',
|
|
||||||
database: env.DATABASE_DB || 'boredgame',
|
|
||||||
ssl: false, // env.NODE_ENV === 'development' ? false : 'require',
|
|
||||||
max: 1,
|
|
||||||
})
|
|
||||||
const db = drizzle(connection)
|
|
||||||
|
|
||||||
try {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
process.exit()
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"version": "7",
|
|
||||||
"dialect": "postgresql",
|
|
||||||
"entries": [
|
|
||||||
{
|
|
||||||
"idx": 0,
|
|
||||||
"version": "7",
|
|
||||||
"when": 1725489682980,
|
|
||||||
"tag": "0000_volatile_warhawk",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 1,
|
|
||||||
"version": "7",
|
|
||||||
"when": 1726877846811,
|
|
||||||
"tag": "0001_pink_the_enforcers",
|
|
||||||
"breakpoints": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
33
src/lib/server/api/databases/postgres/migrate.ts
Normal file
33
src/lib/server/api/databases/postgres/migrate.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import 'dotenv/config';
|
||||||
|
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 '../../common/env';
|
||||||
|
|
||||||
|
const connection = postgres({
|
||||||
|
host: env.DATABASE_HOST || 'localhost',
|
||||||
|
port: env.DATABASE_PORT,
|
||||||
|
user: env.DATABASE_USER || 'root',
|
||||||
|
password: env.DATABASE_PASSWORD || '',
|
||||||
|
database: env.DATABASE_DB || 'boredgame',
|
||||||
|
ssl: false, // env.NODE_ENV === 'development' ? false : 'require',
|
||||||
|
max: 1,
|
||||||
|
});
|
||||||
|
const db = drizzle(connection);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!config.out) {
|
||||||
|
console.error('No migrations folder specified in drizzle.config.ts');
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
if (!env.DB_MIGRATING) {
|
||||||
|
throw new Error('You must set DB_MIGRATING to "true" when running migrations.');
|
||||||
|
}
|
||||||
|
await migrate(db, { migrationsFolder: config.out });
|
||||||
|
console.log('Migrations complete');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit();
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
ALTER TABLE "sessions" DROP CONSTRAINT "sessions_user_id_users_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE "two_factor" DROP CONSTRAINT "two_factor_user_id_unique";--> statement-breakpoint
|
||||||
|
ALTER TABLE "two_factor" ADD CONSTRAINT "two_factor_userId_unique" UNIQUE("user_id");
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE "two_factor" DROP CONSTRAINT "two_factor_userId_unique";--> statement-breakpoint
|
||||||
|
ALTER TABLE "two_factor" ADD CONSTRAINT "two_factor_user_id_unique" UNIQUE("user_id");
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1725489682980,
|
||||||
|
"tag": "0000_volatile_warhawk",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1726877846811,
|
||||||
|
"tag": "0001_pink_the_enforcers",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1729273181237,
|
||||||
|
"tag": "0002_mysterious_justice",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1730914649946,
|
||||||
|
"tag": "0003_far_siren",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 4,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1730914773500,
|
||||||
|
"tag": "0004_lush_virginia_dare",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { collections } from '$lib/server/api/databases/tables'
|
import { collections } from '$lib/server/api/databases/postgres/tables';
|
||||||
import { createInsertSchema, createSelectSchema } from 'drizzle-zod'
|
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
|
||||||
import type { z } from 'zod'
|
import type { z } from 'zod';
|
||||||
|
|
||||||
export const InsertCollectionSchema = createInsertSchema(collections, {
|
export const InsertCollectionSchema = createInsertSchema(collections, {
|
||||||
name: (schema) =>
|
name: (schema) =>
|
||||||
|
|
@ -10,15 +10,15 @@ export const InsertCollectionSchema = createInsertSchema(collections, {
|
||||||
cuid: true,
|
cuid: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
export type InsertCollectionSchema = z.infer<typeof InsertCollectionSchema>
|
export type InsertCollectionSchema = z.infer<typeof InsertCollectionSchema>;
|
||||||
|
|
||||||
export const SelectCollectionSchema = createSelectSchema(collections).omit({
|
export const SelectCollectionSchema = createSelectSchema(collections).omit({
|
||||||
id: true,
|
id: true,
|
||||||
user_id: true,
|
user_id: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
export type SelectUserSchema = z.infer<typeof SelectCollectionSchema>
|
export type SelectUserSchema = z.infer<typeof SelectCollectionSchema>;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { usersTable } from '$lib/server/api/databases/tables'
|
import { usersTable } from '$lib/server/api/databases/postgres/tables';
|
||||||
import { createInsertSchema, createSelectSchema } from 'drizzle-zod'
|
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
|
||||||
import type { z } from 'zod'
|
import type { z } from 'zod';
|
||||||
|
|
||||||
export const InsertUserSchema = createInsertSchema(usersTable, {
|
export const InsertUserSchema = createInsertSchema(usersTable, {
|
||||||
email: (schema) => schema.email.max(64).email().optional(),
|
email: (schema) => schema.email.max(64).email().optional(),
|
||||||
|
|
@ -15,10 +15,10 @@ export const InsertUserSchema = createInsertSchema(usersTable, {
|
||||||
cuid: true,
|
cuid: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
export type InsertUserSchema = z.infer<typeof InsertUserSchema>
|
export type InsertUserSchema = z.infer<typeof InsertUserSchema>;
|
||||||
|
|
||||||
export const SelectUserSchema = createSelectSchema(usersTable)
|
export const SelectUserSchema = createSelectSchema(usersTable);
|
||||||
|
|
||||||
export type SelectUserSchema = z.infer<typeof SelectUserSchema>
|
export type SelectUserSchema = z.infer<typeof SelectUserSchema>;
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata';
|
||||||
import { type Table, getTableName, sql } from 'drizzle-orm'
|
import { type Table, getTableName, sql } from 'drizzle-orm';
|
||||||
import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
|
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||||
import env from '../common/env'
|
import env from '../../common/env';
|
||||||
import { DrizzleService } from '../services/drizzle.service'
|
import { DrizzleService } from '../../services/drizzle.service';
|
||||||
import * as seeds from './seeds'
|
import * as seeds from './seeds';
|
||||||
import * as schema from './tables'
|
import * as schema from './tables';
|
||||||
|
|
||||||
const drizzleService = new DrizzleService()
|
const drizzleService = new DrizzleService();
|
||||||
|
|
||||||
if (!env.DB_SEEDING) {
|
if (!env.DB_SEEDING) {
|
||||||
throw new Error('You must set DB_SEEDING to "true" when running seeds')
|
throw new Error('You must set DB_SEEDING to "true" when running seeds');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetTable(db: NodePgDatabase<typeof schema>, table: Table) {
|
async function resetTable(db: NodePgDatabase<typeof schema>, table: Table) {
|
||||||
return db.execute(sql.raw(`TRUNCATE TABLE ${getTableName(table)} RESTART IDENTITY CASCADE`))
|
return db.execute(sql.raw(`TRUNCATE TABLE ${getTableName(table)} RESTART IDENTITY CASCADE`));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const table of [
|
for (const table of [
|
||||||
|
|
@ -45,11 +45,11 @@ for (const table of [
|
||||||
schema.wishlistsTable,
|
schema.wishlistsTable,
|
||||||
]) {
|
]) {
|
||||||
// await db.delete(table); // clear tables without truncating / resetting ids
|
// await db.delete(table); // clear tables without truncating / resetting ids
|
||||||
await resetTable(drizzleService.db, table)
|
await resetTable(drizzleService.db, table);
|
||||||
}
|
}
|
||||||
|
|
||||||
await seeds.roles(drizzleService.db)
|
await seeds.roles(drizzleService.db);
|
||||||
await seeds.users(drizzleService.db)
|
await seeds.users(drizzleService.db);
|
||||||
|
|
||||||
await drizzleService.dispose()
|
await drizzleService.dispose();
|
||||||
process.exit()
|
process.exit();
|
||||||
11
src/lib/server/api/databases/postgres/seeds/roles.ts
Normal file
11
src/lib/server/api/databases/postgres/seeds/roles.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
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 rolesTable ...');
|
||||||
|
for (const role of roles) {
|
||||||
|
await db.insert(schema.rolesTable).values(role).onConflictDoNothing();
|
||||||
|
}
|
||||||
|
console.log('Roles created.');
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import type { db } from '../../packages/drizzle';
|
import type { db } from '../../../packages/drizzle';
|
||||||
import { HashingService } from '../../services/hashing.service';
|
import { HashingService } from '../../../services/hashing.service';
|
||||||
import * as schema from '../tables';
|
import * as schema from '../tables';
|
||||||
import users from './data/users.json';
|
import users from './data/users.json';
|
||||||
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { timestamps } from '../../common/utils/table'
|
import { timestamps } from '../../../common/utils/table';
|
||||||
import { categoriesToExternalIdsTable } from './categoriesToExternalIds.table'
|
import { categoriesToExternalIdsTable } from './categoriesToExternalIds.table';
|
||||||
import { categories_to_games_table } from './categoriesToGames.table'
|
import { categories_to_games_table } from './categoriesToGames.table';
|
||||||
|
|
||||||
export const categoriesTable = pgTable('categories', {
|
export const categoriesTable = pgTable('categories', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text()
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
name: text('name'),
|
name: text(),
|
||||||
slug: text('slug'),
|
slug: text(),
|
||||||
...timestamps,
|
...timestamps,
|
||||||
})
|
});
|
||||||
|
|
||||||
export type Categories = InferSelectModel<typeof categoriesTable>
|
export type Categories = InferSelectModel<typeof categoriesTable>;
|
||||||
|
|
||||||
export const categories_relations = relations(categoriesTable, ({ many }) => ({
|
export const categories_relations = relations(categoriesTable, ({ many }) => ({
|
||||||
categories_to_games: many(categories_to_games_table),
|
categories_to_games: many(categories_to_games_table),
|
||||||
categoriesToExternalIds: many(categoriesToExternalIdsTable),
|
categoriesToExternalIds: many(categoriesToExternalIdsTable),
|
||||||
}))
|
}));
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { relations } from 'drizzle-orm'
|
import { relations } from 'drizzle-orm';
|
||||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { categoriesTable } from './categories.table'
|
import { categoriesTable } from './categories.table';
|
||||||
import { externalIdsTable } from './externalIds.table'
|
import { externalIdsTable } from './externalIds.table';
|
||||||
|
|
||||||
export const categoriesToExternalIdsTable = pgTable(
|
export const categoriesToExternalIdsTable = pgTable(
|
||||||
'categories_to_external_ids',
|
'categories_to_external_ids',
|
||||||
{
|
{
|
||||||
categoryId: uuid('category_id')
|
categoryId: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => categoriesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => categoriesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
externalId: uuid('external_id')
|
externalId: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => externalIdsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => externalIdsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
},
|
},
|
||||||
|
|
@ -18,9 +18,9 @@ export const categoriesToExternalIdsTable = pgTable(
|
||||||
categoriesToExternalIdsPkey: primaryKey({
|
categoriesToExternalIdsPkey: primaryKey({
|
||||||
columns: [table.categoryId, table.externalId],
|
columns: [table.categoryId, table.externalId],
|
||||||
}),
|
}),
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
export const categoriesToExternalIdsRelations = relations(categoriesToExternalIdsTable, ({ one }) => ({
|
export const categoriesToExternalIdsRelations = relations(categoriesToExternalIdsTable, ({ one }) => ({
|
||||||
category: one(categoriesTable, {
|
category: one(categoriesTable, {
|
||||||
|
|
@ -31,4 +31,4 @@ export const categoriesToExternalIdsRelations = relations(categoriesToExternalId
|
||||||
fields: [categoriesToExternalIdsTable.externalId],
|
fields: [categoriesToExternalIdsTable.externalId],
|
||||||
references: [externalIdsTable.id],
|
references: [externalIdsTable.id],
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { relations } from 'drizzle-orm'
|
import { relations } from 'drizzle-orm';
|
||||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { gamesTable } from '././games.table'
|
import { categoriesTable } from './categories.table';
|
||||||
import { categoriesTable } from './categories.table'
|
import { gamesTable } from './games.table';
|
||||||
|
|
||||||
export const categories_to_games_table = pgTable(
|
export const categories_to_games_table = pgTable(
|
||||||
'categories_to_games',
|
'categories_to_games',
|
||||||
{
|
{
|
||||||
category_id: uuid('category_id')
|
category_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => categoriesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => categoriesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
game_id: uuid('game_id')
|
game_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
},
|
},
|
||||||
|
|
@ -18,9 +18,9 @@ export const categories_to_games_table = pgTable(
|
||||||
categoriesToGamesPkey: primaryKey({
|
categoriesToGamesPkey: primaryKey({
|
||||||
columns: [table.category_id, table.game_id],
|
columns: [table.category_id, table.game_id],
|
||||||
}),
|
}),
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
export const categories_to_games_relations = relations(categories_to_games_table, ({ one }) => ({
|
export const categories_to_games_relations = relations(categories_to_games_table, ({ one }) => ({
|
||||||
category: one(categoriesTable, {
|
category: one(categoriesTable, {
|
||||||
|
|
@ -31,4 +31,4 @@ export const categories_to_games_relations = relations(categories_to_games_table
|
||||||
fields: [categories_to_games_table.game_id],
|
fields: [categories_to_games_table.game_id],
|
||||||
references: [gamesTable.id],
|
references: [gamesTable.id],
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||||
import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { timestamps } from '../../common/utils/table'
|
import { timestamps } from '../../../common/utils/table';
|
||||||
import { gamesTable } from '././games.table'
|
import { collections } from './collections.table';
|
||||||
import { collections } from './collections.table'
|
import { gamesTable } from './games.table';
|
||||||
|
|
||||||
export const collection_items = pgTable('collection_items', {
|
export const collection_items = pgTable('collection_items', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text()
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
collection_id: uuid('collection_id')
|
collection_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => collections.id, { onDelete: 'cascade' }),
|
.references(() => collections.id, { onDelete: 'cascade' }),
|
||||||
game_id: uuid('game_id')
|
game_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => gamesTable.id, { onDelete: 'cascade' }),
|
.references(() => gamesTable.id, { onDelete: 'cascade' }),
|
||||||
times_played: integer('times_played').default(0),
|
times_played: integer().default(0),
|
||||||
...timestamps,
|
...timestamps,
|
||||||
})
|
});
|
||||||
|
|
||||||
export type CollectionItemsTable = InferSelectModel<typeof collection_items>
|
export type CollectionItemsTable = InferSelectModel<typeof collection_items>;
|
||||||
|
|
||||||
export const collection_item_relations = relations(collection_items, ({ one }) => ({
|
export const collection_item_relations = relations(collection_items, ({ one }) => ({
|
||||||
collection: one(collections, {
|
collection: one(collections, {
|
||||||
|
|
@ -31,4 +31,4 @@ export const collection_item_relations = relations(collection_items, ({ one }) =
|
||||||
fields: [collection_items.game_id],
|
fields: [collection_items.game_id],
|
||||||
references: [gamesTable.id],
|
references: [gamesTable.id],
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
@ -2,19 +2,19 @@ import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { createSelectSchema } from 'drizzle-zod';
|
import { createSelectSchema } from 'drizzle-zod';
|
||||||
import { timestamps } from '../../common/utils/table';
|
import { timestamps } from '../../../common/utils/table';
|
||||||
import { collection_items } from './collectionItems.table';
|
import { collection_items } from './collectionItems.table';
|
||||||
import { usersTable } from './users.table';
|
import { usersTable } from './users.table';
|
||||||
|
|
||||||
export const collections = pgTable('collections', {
|
export const collections = pgTable('collections', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text()
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
user_id: uuid('user_id')
|
user_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||||
name: text('name').notNull().default('My Collection'),
|
name: text().notNull().default('My Collection'),
|
||||||
...timestamps,
|
...timestamps,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import type { InferSelectModel } from 'drizzle-orm';
|
||||||
|
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||||
|
import { timestamps } from '../../../common/utils/table';
|
||||||
|
import { usersTable } from './users.table';
|
||||||
|
|
||||||
|
export enum CredentialsType {
|
||||||
|
SECRET = 'secret',
|
||||||
|
PASSWORD = 'password',
|
||||||
|
TOTP = 'totp',
|
||||||
|
HOTP = 'hotp',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const credentialsTable = pgTable('credentials', {
|
||||||
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
|
user_id: uuid()
|
||||||
|
.notNull()
|
||||||
|
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||||
|
type: text().notNull().default(CredentialsType.PASSWORD),
|
||||||
|
secret_data: text().notNull(),
|
||||||
|
...timestamps,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Credentials = InferSelectModel<typeof credentialsTable>;
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { timestamps } from '../../common/utils/table'
|
import { timestamps } from '../../../common/utils/table';
|
||||||
import { gamesTable } from '././games.table'
|
import { gamesTable } from './games.table';
|
||||||
|
|
||||||
export const expansionsTable = pgTable('expansions', {
|
export const expansionsTable = pgTable('expansions', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text()
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
base_game_id: uuid('base_game_id')
|
base_game_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
game_id: uuid('game_id')
|
game_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
...timestamps,
|
...timestamps,
|
||||||
})
|
});
|
||||||
|
|
||||||
export type Expansions = InferSelectModel<typeof expansionsTable>
|
export type Expansions = InferSelectModel<typeof expansionsTable>;
|
||||||
|
|
||||||
export const expansion_relations = relations(expansionsTable, ({ one }) => ({
|
export const expansion_relations = relations(expansionsTable, ({ one }) => ({
|
||||||
baseGame: one(gamesTable, {
|
baseGame: one(gamesTable, {
|
||||||
|
|
@ -29,4 +29,4 @@ export const expansion_relations = relations(expansionsTable, ({ one }) => ({
|
||||||
fields: [expansionsTable.game_id],
|
fields: [expansionsTable.game_id],
|
||||||
references: [gamesTable.id],
|
references: [gamesTable.id],
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
32
src/lib/server/api/databases/postgres/tables/expansions.ts
Normal file
32
src/lib/server/api/databases/postgres/tables/expansions.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 expansions = pgTable('expansions', {
|
||||||
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
|
cuid: text()
|
||||||
|
.unique()
|
||||||
|
.$defaultFn(() => cuid2()),
|
||||||
|
base_game_id: uuid()
|
||||||
|
.notNull()
|
||||||
|
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
|
game_id: uuid()
|
||||||
|
.notNull()
|
||||||
|
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
|
...timestamps,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Expansions = InferSelectModel<typeof expansions>;
|
||||||
|
|
||||||
|
export const expansion_relations = relations(expansions, ({ one }) => ({
|
||||||
|
baseGame: one(gamesTable, {
|
||||||
|
fields: [expansions.base_game_id],
|
||||||
|
references: [gamesTable.id],
|
||||||
|
}),
|
||||||
|
game: one(gamesTable, {
|
||||||
|
fields: [expansions.game_id],
|
||||||
|
references: [gamesTable.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
@ -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().primaryKey().defaultRandom(),
|
||||||
|
cuid: text()
|
||||||
|
.unique()
|
||||||
|
.$defaultFn(() => cuid2()),
|
||||||
|
type: externalIdType(),
|
||||||
|
externalId: text().notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ExternalIds = InferSelectModel<typeof externalIdsTable>;
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import type { InferSelectModel } 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 federatedIdentityTable = pgTable('federated_identity', {
|
||||||
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
|
user_id: uuid()
|
||||||
|
.notNull()
|
||||||
|
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||||
|
identity_provider: text().notNull(),
|
||||||
|
federated_user_id: text().notNull(),
|
||||||
|
federated_username: text().notNull(),
|
||||||
|
...timestamps,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FederatedIdentity = InferSelectModel<typeof federatedIdentityTable>;
|
||||||
51
src/lib/server/api/databases/postgres/tables/games.table.ts
Normal file
51
src/lib/server/api/databases/postgres/tables/games.table.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
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';
|
||||||
|
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 gamesTable = pgTable(
|
||||||
|
'games',
|
||||||
|
{
|
||||||
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
|
cuid: text()
|
||||||
|
.unique()
|
||||||
|
.$defaultFn(() => cuid2()),
|
||||||
|
name: text().notNull(),
|
||||||
|
slug: text().notNull(),
|
||||||
|
description: text(),
|
||||||
|
year_published: integer(),
|
||||||
|
min_players: integer(),
|
||||||
|
max_players: integer(),
|
||||||
|
playtime: integer(),
|
||||||
|
min_playtime: integer(),
|
||||||
|
max_playtime: integer(),
|
||||||
|
min_age: integer(),
|
||||||
|
image_url: text(),
|
||||||
|
thumb_url: text(),
|
||||||
|
url: text(),
|
||||||
|
last_sync_at: timestamp(),
|
||||||
|
...timestamps,
|
||||||
|
},
|
||||||
|
(table) => ({
|
||||||
|
searchIndex: index('search_index').using(
|
||||||
|
'gin',
|
||||||
|
sql`(
|
||||||
|
setweight(to_tsvector('english', ${table.name}), 'A') ||
|
||||||
|
setweight(to_tsvector('english', ${table.slug}), 'B')
|
||||||
|
)`,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const gameRelations = relations(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(gamesToExternalIdsTable),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export type Games = InferSelectModel<typeof gamesTable>;
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
import { relations } from 'drizzle-orm';
|
||||||
import { gamesTable } from '././games.table'
|
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { externalIdsTable } from './externalIds.table'
|
import { externalIdsTable } from './externalIds.table';
|
||||||
import { relations } from 'drizzle-orm'
|
import { gamesTable } from './games.table';
|
||||||
|
|
||||||
export const gamesToExternalIdsTable = pgTable(
|
export const gamesToExternalIdsTable = pgTable(
|
||||||
'games_to_external_ids',
|
'games_to_external_ids',
|
||||||
{
|
{
|
||||||
gameId: uuid('game_id')
|
gameId: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
externalId: uuid('external_id')
|
externalId: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => externalIdsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => externalIdsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
},
|
},
|
||||||
|
|
@ -18,9 +18,9 @@ export const gamesToExternalIdsTable = pgTable(
|
||||||
gamesToExternalIdsPkey: primaryKey({
|
gamesToExternalIdsPkey: primaryKey({
|
||||||
columns: [table.gameId, table.externalId],
|
columns: [table.gameId, table.externalId],
|
||||||
}),
|
}),
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
export const gamesToExternalIdsRelations = relations(gamesToExternalIdsTable, ({ one }) => ({
|
export const gamesToExternalIdsRelations = relations(gamesToExternalIdsTable, ({ one }) => ({
|
||||||
game: one(gamesTable, {
|
game: one(gamesTable, {
|
||||||
|
|
@ -31,4 +31,4 @@ export const gamesToExternalIdsRelations = relations(gamesToExternalIdsTable, ({
|
||||||
fields: [gamesToExternalIdsTable.externalId],
|
fields: [gamesToExternalIdsTable.externalId],
|
||||||
references: [externalIdsTable.id],
|
references: [externalIdsTable.id],
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
@ -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().primaryKey().defaultRandom(),
|
||||||
|
cuid: text()
|
||||||
|
.unique()
|
||||||
|
.$defaultFn(() => cuid2()),
|
||||||
|
name: text(),
|
||||||
|
slug: text(),
|
||||||
|
...timestamps,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Mechanics = InferSelectModel<typeof mechanicsTable>;
|
||||||
|
|
||||||
|
export const mechanics_relations = relations(mechanicsTable, ({ many }) => ({
|
||||||
|
mechanics_to_games: many(mechanics_to_games),
|
||||||
|
mechanicsToExternalIds: many(mechanicsToExternalIdsTable),
|
||||||
|
}));
|
||||||
23
src/lib/server/api/databases/postgres/tables/mechanics.ts
Normal file
23
src/lib/server/api/databases/postgres/tables/mechanics.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 mechanics = pgTable('mechanics', {
|
||||||
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
|
cuid: text()
|
||||||
|
.unique()
|
||||||
|
.$defaultFn(() => cuid2()),
|
||||||
|
name: text(),
|
||||||
|
slug: text(),
|
||||||
|
...timestamps,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Mechanics = InferSelectModel<typeof mechanics>;
|
||||||
|
|
||||||
|
export const mechanics_relations = relations(mechanics, ({ many }) => ({
|
||||||
|
mechanics_to_games: many(mechanics_to_games),
|
||||||
|
mechanicsToExternalIds: many(mechanicsToExternalIdsTable),
|
||||||
|
}));
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { relations } from 'drizzle-orm'
|
import { relations } from 'drizzle-orm';
|
||||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { gamesTable } from '././games.table'
|
import { gamesTable } from './games.table';
|
||||||
import { mechanicsTable } from './mechanics.table'
|
import { mechanicsTable } from './mechanics.table';
|
||||||
|
|
||||||
export const mechanics_to_games = pgTable(
|
export const mechanics_to_games = pgTable(
|
||||||
'mechanics_to_games',
|
'mechanics_to_games',
|
||||||
{
|
{
|
||||||
mechanic_id: uuid('mechanic_id')
|
mechanic_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => mechanicsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => mechanicsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
game_id: uuid('game_id')
|
game_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
},
|
},
|
||||||
|
|
@ -18,9 +18,9 @@ export const mechanics_to_games = pgTable(
|
||||||
mechanicsToGamesPkey: primaryKey({
|
mechanicsToGamesPkey: primaryKey({
|
||||||
columns: [table.mechanic_id, table.game_id],
|
columns: [table.mechanic_id, table.game_id],
|
||||||
}),
|
}),
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
export const mechanics_to_games_relations = relations(mechanics_to_games, ({ one }) => ({
|
export const mechanics_to_games_relations = relations(mechanics_to_games, ({ one }) => ({
|
||||||
mechanic: one(mechanicsTable, {
|
mechanic: one(mechanicsTable, {
|
||||||
|
|
@ -31,4 +31,4 @@ export const mechanics_to_games_relations = relations(mechanics_to_games, ({ one
|
||||||
fields: [mechanics_to_games.game_id],
|
fields: [mechanics_to_games.game_id],
|
||||||
references: [gamesTable.id],
|
references: [gamesTable.id],
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||||
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
|
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { timestamps } from '../../common/utils/table'
|
import { timestamps } from '../../../common/utils/table';
|
||||||
import { usersTable } from './users.table'
|
import { usersTable } from './users.table';
|
||||||
|
|
||||||
export const password_reset_tokens = pgTable('password_reset_tokens', {
|
export const password_reset_tokens = pgTable('password_reset_tokens', {
|
||||||
id: text('id')
|
id: text()
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
user_id: uuid('user_id')
|
user_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||||
expires_at: timestamp('expires_at'),
|
expires_at: timestamp(),
|
||||||
...timestamps,
|
...timestamps,
|
||||||
})
|
});
|
||||||
|
|
||||||
export type PasswordResetTokensTable = InferSelectModel<typeof password_reset_tokens>
|
export type PasswordResetTokensTable = InferSelectModel<typeof password_reset_tokens>;
|
||||||
|
|
||||||
export const password_reset_token_relations = relations(password_reset_tokens, ({ one }) => ({
|
export const password_reset_token_relations = relations(password_reset_tokens, ({ one }) => ({
|
||||||
user: one(usersTable, {
|
user: one(usersTable, {
|
||||||
fields: [password_reset_tokens.user_id],
|
fields: [password_reset_tokens.user_id],
|
||||||
references: [usersTable.id],
|
references: [usersTable.id],
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { timestamps } from '../../common/utils/table'
|
import { timestamps } from '../../../common/utils/table';
|
||||||
import { publishersToExternalIdsTable } from './publishersToExternalIds.table'
|
import { publishersToExternalIdsTable } from './publishersToExternalIds.table';
|
||||||
import { publishers_to_games } from './publishersToGames.table'
|
import { publishers_to_games } from './publishersToGames.table';
|
||||||
|
|
||||||
export const publishersTable = pgTable('publishers', {
|
export const publishersTable = pgTable('publishers', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text()
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
name: text('name'),
|
name: text(),
|
||||||
slug: text('slug'),
|
slug: text(),
|
||||||
...timestamps,
|
...timestamps,
|
||||||
})
|
});
|
||||||
|
|
||||||
export type Publishers = InferSelectModel<typeof publishersTable>
|
export type Publishers = InferSelectModel<typeof publishersTable>;
|
||||||
|
|
||||||
export const publishers_relations = relations(publishersTable, ({ many }) => ({
|
export const publishers_relations = relations(publishersTable, ({ many }) => ({
|
||||||
publishersToGames: many(publishers_to_games),
|
publishersToGames: many(publishers_to_games),
|
||||||
publishersToExternalIds: many(publishersToExternalIdsTable),
|
publishersToExternalIds: many(publishersToExternalIdsTable),
|
||||||
}))
|
}));
|
||||||
23
src/lib/server/api/databases/postgres/tables/publishers.ts
Normal file
23
src/lib/server/api/databases/postgres/tables/publishers.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 publishers = pgTable('publishers', {
|
||||||
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
|
cuid: text()
|
||||||
|
.unique()
|
||||||
|
.$defaultFn(() => cuid2()),
|
||||||
|
name: text(),
|
||||||
|
slug: text(),
|
||||||
|
...timestamps,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Publishers = InferSelectModel<typeof publishers>;
|
||||||
|
|
||||||
|
export const publishers_relations = relations(publishers, ({ many }) => ({
|
||||||
|
publishers_to_games: many(publishers_to_games),
|
||||||
|
publishersToExternalIds: many(publishersToExternalIdsTable),
|
||||||
|
}));
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
import { relations } from 'drizzle-orm';
|
||||||
import { externalIdsTable } from './externalIds.table'
|
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { publishersTable } from './publishers.table'
|
import { externalIdsTable } from './externalIds.table';
|
||||||
import { relations } from 'drizzle-orm'
|
import { publishersTable } from './publishers.table';
|
||||||
|
|
||||||
export const publishersToExternalIdsTable = pgTable(
|
export const publishersToExternalIdsTable = pgTable(
|
||||||
'publishers_to_external_ids',
|
'publishers_to_external_ids',
|
||||||
{
|
{
|
||||||
publisherId: uuid('publisher_id')
|
publisherId: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => publishersTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => publishersTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
externalId: uuid('external_id')
|
externalId: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => externalIdsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => externalIdsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
},
|
},
|
||||||
|
|
@ -18,9 +18,9 @@ export const publishersToExternalIdsTable = pgTable(
|
||||||
publishersToExternalIdsPkey: primaryKey({
|
publishersToExternalIdsPkey: primaryKey({
|
||||||
columns: [table.publisherId, table.externalId],
|
columns: [table.publisherId, table.externalId],
|
||||||
}),
|
}),
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
export const publishersToExternalIdsRelations = relations(publishersToExternalIdsTable, ({ one }) => ({
|
export const publishersToExternalIdsRelations = relations(publishersToExternalIdsTable, ({ one }) => ({
|
||||||
publisher: one(publishersTable, {
|
publisher: one(publishersTable, {
|
||||||
|
|
@ -31,4 +31,4 @@ export const publishersToExternalIdsRelations = relations(publishersToExternalId
|
||||||
fields: [publishersToExternalIdsTable.externalId],
|
fields: [publishersToExternalIdsTable.externalId],
|
||||||
references: [externalIdsTable.id],
|
references: [externalIdsTable.id],
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { relations } from 'drizzle-orm'
|
import { relations } from 'drizzle-orm';
|
||||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { gamesTable } from '././games.table'
|
import { gamesTable } from './games.table';
|
||||||
import { publishersTable } from './publishers.table'
|
import { publishersTable } from './publishers.table';
|
||||||
|
|
||||||
export const publishers_to_games = pgTable(
|
export const publishers_to_games = pgTable(
|
||||||
'publishers_to_games',
|
'publishers_to_games',
|
||||||
{
|
{
|
||||||
publisher_id: uuid('publisher_id')
|
publisher_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => publishersTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => publishersTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
game_id: uuid('game_id')
|
game_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||||
},
|
},
|
||||||
|
|
@ -18,9 +18,9 @@ export const publishers_to_games = pgTable(
|
||||||
publishersToGamesPkey: primaryKey({
|
publishersToGamesPkey: primaryKey({
|
||||||
columns: [table.publisher_id, table.game_id],
|
columns: [table.publisher_id, table.game_id],
|
||||||
}),
|
}),
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
export const publishers_to_games_relations = relations(publishers_to_games, ({ one }) => ({
|
export const publishers_to_games_relations = relations(publishers_to_games, ({ one }) => ({
|
||||||
publisher: one(publishersTable, {
|
publisher: one(publishersTable, {
|
||||||
|
|
@ -31,4 +31,4 @@ export const publishers_to_games_relations = relations(publishers_to_games, ({ o
|
||||||
fields: [publishers_to_games.game_id],
|
fields: [publishers_to_games.game_id],
|
||||||
references: [gamesTable.id],
|
references: [gamesTable.id],
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
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', {
|
||||||
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
|
userId: uuid()
|
||||||
|
.notNull()
|
||||||
|
.references(() => usersTable.id),
|
||||||
|
code: text().notNull(),
|
||||||
|
used: boolean().default(false),
|
||||||
|
...timestamps,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type RecoveryCodesTable = InferSelectModel<typeof recoveryCodesTable>;
|
||||||
28
src/lib/server/api/databases/postgres/tables/roles.table.ts
Normal file
28
src/lib/server/api/databases/postgres/tables/roles.table.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
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 { user_roles } from './userRoles.table';
|
||||||
|
|
||||||
|
export enum RoleName {
|
||||||
|
ADMIN = 'admin',
|
||||||
|
EDITOR = 'editor',
|
||||||
|
MODERATOR = 'moderator',
|
||||||
|
USER = 'user',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const rolesTable = pgTable('roles', {
|
||||||
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
|
cuid: text()
|
||||||
|
.unique()
|
||||||
|
.$defaultFn(() => cuid2())
|
||||||
|
.notNull(),
|
||||||
|
name: text().unique().notNull(),
|
||||||
|
...timestamps,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Roles = InferSelectModel<typeof rolesTable>;
|
||||||
|
|
||||||
|
export const role_relations = relations(rolesTable, ({ many }) => ({
|
||||||
|
user_roles: many(user_roles),
|
||||||
|
}));
|
||||||
|
|
@ -1,27 +1,28 @@
|
||||||
|
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||||
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { relations, type InferSelectModel } from 'drizzle-orm';
|
import { cuid2 } from '../../../common/utils/table';
|
||||||
import { usersTable } from './users.table';
|
import { usersTable } from './users.table';
|
||||||
|
|
||||||
export const sessionsTable = pgTable('sessions', {
|
export const sessionsTable = pgTable('sessions', {
|
||||||
id: text('id').primaryKey(),
|
id: cuid2().primaryKey(),
|
||||||
userId: uuid('user_id')
|
userId: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => usersTable.id),
|
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||||
expiresAt: timestamp('expires_at', {
|
expiresAt: timestamp({
|
||||||
withTimezone: true,
|
withTimezone: true,
|
||||||
mode: 'date',
|
mode: 'date',
|
||||||
}).notNull(),
|
}).notNull(),
|
||||||
ipCountry: text('ip_country'),
|
ipCountry: text(),
|
||||||
ipAddress: text('ip_address'),
|
ipAddress: text(),
|
||||||
twoFactorAuthEnabled: boolean('two_factor_auth_enabled').default(false),
|
twoFactorAuthEnabled: boolean().default(false),
|
||||||
isTwoFactorAuthenticated: boolean('is_two_factor_authenticated').default(false),
|
isTwoFactorAuthenticated: boolean().default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sessionsRelations = relations(sessionsTable, ({ one }) => ({
|
export const sessionsRelations = relations(sessionsTable, ({ one }) => ({
|
||||||
user: one(usersTable, {
|
user: one(usersTable, {
|
||||||
fields: [sessionsTable.userId],
|
fields: [sessionsTable.userId],
|
||||||
references: [usersTable.id],
|
references: [usersTable.id],
|
||||||
})
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export type Sessions = InferSelectModel<typeof sessionsTable>;
|
export type Sessions = InferSelectModel<typeof sessionsTable>;
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
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', {
|
||||||
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
|
cuid: text()
|
||||||
|
.unique()
|
||||||
|
.$defaultFn(() => cuid2()),
|
||||||
|
secret: text().notNull(),
|
||||||
|
enabled: boolean().notNull().default(false),
|
||||||
|
initiatedTime: timestamp({
|
||||||
|
mode: 'date',
|
||||||
|
withTimezone: true,
|
||||||
|
}),
|
||||||
|
userId: uuid()
|
||||||
|
.notNull()
|
||||||
|
.references(() => usersTable.id)
|
||||||
|
.unique('two_factor_user_id_unique'),
|
||||||
|
...timestamps,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const emailVerificationsRelations = relations(twoFactorTable, ({ one }) => ({
|
||||||
|
user: one(usersTable, {
|
||||||
|
fields: [twoFactorTable.userId],
|
||||||
|
references: [usersTable.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export type TwoFactor = InferSelectModel<typeof twoFactorTable>;
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { timestamps } from '../../common/utils/table'
|
import { timestamps } from '../../../common/utils/table';
|
||||||
import { rolesTable } from './roles.table'
|
import { rolesTable } from './roles.table';
|
||||||
import { usersTable } from './users.table'
|
import { usersTable } from './users.table';
|
||||||
|
|
||||||
export const user_roles = pgTable('user_roles', {
|
export const user_roles = pgTable('user_roles', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text()
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
user_id: uuid('user_id')
|
user_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||||
role_id: uuid('role_id')
|
role_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => rolesTable.id, { onDelete: 'cascade' }),
|
.references(() => rolesTable.id, { onDelete: 'cascade' }),
|
||||||
primary: boolean('primary').default(false),
|
primary: boolean().default(false),
|
||||||
...timestamps,
|
...timestamps,
|
||||||
})
|
});
|
||||||
|
|
||||||
export const user_role_relations = relations(user_roles, ({ one }) => ({
|
export const user_role_relations = relations(user_roles, ({ one }) => ({
|
||||||
role: one(rolesTable, {
|
role: one(rolesTable, {
|
||||||
|
|
@ -29,6 +29,6 @@ export const user_role_relations = relations(user_roles, ({ one }) => ({
|
||||||
fields: [user_roles.user_id],
|
fields: [user_roles.user_id],
|
||||||
references: [usersTable.id],
|
references: [usersTable.id],
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
||||||
export type UserRolesTable = InferSelectModel<typeof user_roles>
|
export type UserRolesTable = InferSelectModel<typeof user_roles>;
|
||||||
|
|
@ -2,24 +2,24 @@ import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { createSelectSchema } from 'drizzle-zod';
|
import { createSelectSchema } from 'drizzle-zod';
|
||||||
import { timestamps } from '../../common/utils/table';
|
import { timestamps } from '../../../common/utils/table';
|
||||||
import { user_roles } from './userRoles.table';
|
import { user_roles } from './userRoles.table';
|
||||||
|
|
||||||
export const usersTable = pgTable('users', {
|
export const usersTable = pgTable('users', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text()
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
username: text('username').unique(),
|
username: text().unique(),
|
||||||
email: text('email').unique(),
|
email: text().unique(),
|
||||||
first_name: text('first_name'),
|
first_name: text(),
|
||||||
last_name: text('last_name'),
|
last_name: text(),
|
||||||
verified: boolean('verified').default(false),
|
verified: boolean().default(false),
|
||||||
receive_email: boolean('receive_email').default(false),
|
receive_email: boolean().default(false),
|
||||||
email_verified: boolean('email_verified').default(false),
|
email_verified: boolean().default(false),
|
||||||
picture: text('picture'),
|
picture: text(),
|
||||||
mfa_enabled: boolean('mfa_enabled').notNull().default(false),
|
mfa_enabled: boolean().notNull().default(false),
|
||||||
theme: text('theme').default('system'),
|
theme: text().default('system'),
|
||||||
...timestamps,
|
...timestamps,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { timestamps } from '../../common/utils/table'
|
import { timestamps } from '../../../common/utils/table';
|
||||||
import { gamesTable } from '././games.table'
|
import { gamesTable } from './games.table';
|
||||||
import { wishlistsTable } from './wishlists.table'
|
import { wishlistsTable } from './wishlists.table';
|
||||||
|
|
||||||
export const wishlist_items = pgTable('wishlist_items', {
|
export const wishlist_items = pgTable('wishlist_items', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text()
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
wishlist_id: uuid('wishlist_id')
|
wishlist_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => wishlistsTable.id, { onDelete: 'cascade' }),
|
.references(() => wishlistsTable.id, { onDelete: 'cascade' }),
|
||||||
game_id: uuid('game_id')
|
game_id: uuid()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => gamesTable.id, { onDelete: 'cascade' }),
|
.references(() => gamesTable.id, { onDelete: 'cascade' }),
|
||||||
...timestamps,
|
...timestamps,
|
||||||
})
|
});
|
||||||
|
|
||||||
export type WishlistItemsTable = InferSelectModel<typeof wishlist_items>
|
export type WishlistItemsTable = InferSelectModel<typeof wishlist_items>;
|
||||||
|
|
||||||
export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({
|
export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({
|
||||||
wishlist: one(wishlistsTable, {
|
wishlist: one(wishlistsTable, {
|
||||||
|
|
@ -30,4 +30,4 @@ export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({
|
||||||
fields: [wishlist_items.game_id],
|
fields: [wishlist_items.game_id],
|
||||||
references: [gamesTable.id],
|
references: [gamesTable.id],
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
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 wishlistsTable = pgTable('wishlists', {
|
||||||
|
id: uuid().primaryKey().defaultRandom(),
|
||||||
|
cuid: text()
|
||||||
|
.unique()
|
||||||
|
.$defaultFn(() => cuid2()),
|
||||||
|
user_id: uuid()
|
||||||
|
.notNull()
|
||||||
|
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||||
|
name: text().notNull().default('My Wishlist'),
|
||||||
|
...timestamps,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Wishlists = InferSelectModel<typeof wishlistsTable>;
|
||||||
|
|
||||||
|
export const wishlists_relations = relations(wishlistsTable, ({ one }) => ({
|
||||||
|
user: one(usersTable, {
|
||||||
|
fields: [wishlistsTable.user_id],
|
||||||
|
references: [usersTable.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
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 rolesTable ...')
|
|
||||||
for (const role of roles) {
|
|
||||||
await db.insert(schema.rolesTable).values(role).onConflictDoNothing()
|
|
||||||
}
|
|
||||||
console.log('Roles created.')
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import type { InferSelectModel } from 'drizzle-orm'
|
|
||||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
|
||||||
import { timestamps } from '../../common/utils/table'
|
|
||||||
import { usersTable } from './users.table'
|
|
||||||
|
|
||||||
export enum CredentialsType {
|
|
||||||
SECRET = 'secret',
|
|
||||||
PASSWORD = 'password',
|
|
||||||
TOTP = 'totp',
|
|
||||||
HOTP = 'hotp',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const credentialsTable = pgTable('credentials', {
|
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
|
||||||
user_id: uuid('user_id')
|
|
||||||
.notNull()
|
|
||||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
|
||||||
type: text('type').notNull().default(CredentialsType.PASSWORD),
|
|
||||||
secret_data: text('secret_data').notNull(),
|
|
||||||
...timestamps,
|
|
||||||
})
|
|
||||||
|
|
||||||
export type Credentials = InferSelectModel<typeof credentialsTable>
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
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 { games } from './games'
|
|
||||||
|
|
||||||
export const expansions = pgTable('expansions', {
|
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
|
||||||
cuid: text('cuid')
|
|
||||||
.unique()
|
|
||||||
.$defaultFn(() => cuid2()),
|
|
||||||
base_game_id: uuid('base_game_id')
|
|
||||||
.notNull()
|
|
||||||
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
|
||||||
game_id: uuid('game_id')
|
|
||||||
.notNull()
|
|
||||||
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
|
||||||
...timestamps,
|
|
||||||
})
|
|
||||||
|
|
||||||
export type Expansions = InferSelectModel<typeof expansions>
|
|
||||||
|
|
||||||
export const expansion_relations = relations(expansions, ({ one }) => ({
|
|
||||||
baseGame: one(games, {
|
|
||||||
fields: [expansions.base_game_id],
|
|
||||||
references: [games.id],
|
|
||||||
}),
|
|
||||||
game: one(games, {
|
|
||||||
fields: [expansions.game_id],
|
|
||||||
references: [games.id],
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
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,17 +0,0 @@
|
||||||
import { type InferSelectModel } 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 federatedIdentityTable = pgTable('federated_identity', {
|
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
|
||||||
user_id: uuid('user_id')
|
|
||||||
.notNull()
|
|
||||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
|
||||||
identity_provider: text('identity_provider').notNull(),
|
|
||||||
federated_user_id: text('federated_user_id').notNull(),
|
|
||||||
federated_username: text('federated_username').notNull(),
|
|
||||||
...timestamps,
|
|
||||||
})
|
|
||||||
|
|
||||||
export type FederatedIdentity = InferSelectModel<typeof federatedIdentityTable>
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
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'
|
|
||||||
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 gamesTable = pgTable(
|
|
||||||
'games',
|
|
||||||
{
|
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
|
||||||
cuid: text('cuid')
|
|
||||||
.unique()
|
|
||||||
.$defaultFn(() => cuid2()),
|
|
||||||
name: text('name').notNull(),
|
|
||||||
slug: text('slug').notNull(),
|
|
||||||
description: text('description'),
|
|
||||||
year_published: integer('year_published'),
|
|
||||||
min_players: integer('min_players'),
|
|
||||||
max_players: integer('max_players'),
|
|
||||||
playtime: integer('playtime'),
|
|
||||||
min_playtime: integer('min_playtime'),
|
|
||||||
max_playtime: integer('max_playtime'),
|
|
||||||
min_age: integer('min_age'),
|
|
||||||
image_url: text('image_url'),
|
|
||||||
thumb_url: text('thumb_url'),
|
|
||||||
url: text('url'),
|
|
||||||
last_sync_at: timestamp('last_sync_at'),
|
|
||||||
...timestamps,
|
|
||||||
},
|
|
||||||
(table) => ({
|
|
||||||
searchIndex: index('search_index').using(
|
|
||||||
'gin',
|
|
||||||
sql`(
|
|
||||||
setweight(to_tsvector('english', ${table.name}), 'A') ||
|
|
||||||
setweight(to_tsvector('english', ${table.slug}), 'B')
|
|
||||||
)`,
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const gameRelations = relations(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(gamesToExternalIdsTable),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export type Games = InferSelectModel<typeof gamesTable>
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
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),
|
|
||||||
}))
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
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 { mechanicsToExternalIds } from './mechanicsToExternalIds'
|
|
||||||
import { mechanics_to_games } from './mechanicsToGames'
|
|
||||||
|
|
||||||
export const mechanics = pgTable('mechanics', {
|
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
|
||||||
cuid: text('cuid')
|
|
||||||
.unique()
|
|
||||||
.$defaultFn(() => cuid2()),
|
|
||||||
name: text('name'),
|
|
||||||
slug: text('slug'),
|
|
||||||
...timestamps,
|
|
||||||
})
|
|
||||||
|
|
||||||
export type Mechanics = InferSelectModel<typeof mechanics>
|
|
||||||
|
|
||||||
export const mechanics_relations = relations(mechanics, ({ many }) => ({
|
|
||||||
mechanics_to_games: many(mechanics_to_games),
|
|
||||||
mechanicsToExternalIds: many(mechanicsToExternalIds),
|
|
||||||
}))
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
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 { publishersToExternalIds } from './publishersToExternalIds'
|
|
||||||
import { publishers_to_games } from './publishersToGames'
|
|
||||||
|
|
||||||
export const publishers = pgTable('publishers', {
|
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
|
||||||
cuid: text('cuid')
|
|
||||||
.unique()
|
|
||||||
.$defaultFn(() => cuid2()),
|
|
||||||
name: text('name'),
|
|
||||||
slug: text('slug'),
|
|
||||||
...timestamps,
|
|
||||||
})
|
|
||||||
|
|
||||||
export type Publishers = InferSelectModel<typeof publishers>
|
|
||||||
|
|
||||||
export const publishers_relations = relations(publishers, ({ many }) => ({
|
|
||||||
publishers_to_games: many(publishers_to_games),
|
|
||||||
publishersToExternalIds: many(publishersToExternalIds),
|
|
||||||
}))
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
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', {
|
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
|
||||||
userId: uuid('user_id')
|
|
||||||
.notNull()
|
|
||||||
.references(() => usersTable.id),
|
|
||||||
code: text('code').notNull(),
|
|
||||||
used: boolean('used').default(false),
|
|
||||||
...timestamps,
|
|
||||||
})
|
|
||||||
|
|
||||||
export type RecoveryCodesTable = InferSelectModel<typeof recoveryCodesTable>
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
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 { user_roles } from './userRoles.table'
|
|
||||||
|
|
||||||
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()
|
|
||||||
.$defaultFn(() => cuid2())
|
|
||||||
.notNull(),
|
|
||||||
name: text('name').unique().notNull(),
|
|
||||||
...timestamps,
|
|
||||||
})
|
|
||||||
|
|
||||||
export type Roles = InferSelectModel<typeof rolesTable>
|
|
||||||
|
|
||||||
export const role_relations = relations(rolesTable, ({ many }) => ({
|
|
||||||
user_roles: many(user_roles),
|
|
||||||
}))
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
|
||||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
|
||||||
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
|
|
||||||
import { timestamps } from '../../common/utils/table'
|
|
||||||
import { usersTable } from './users.table'
|
|
||||||
|
|
||||||
export const twoFactorTable = pgTable('two_factor', {
|
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
|
||||||
cuid: text('cuid')
|
|
||||||
.unique()
|
|
||||||
.$defaultFn(() => cuid2()),
|
|
||||||
secret: text('secret').notNull(),
|
|
||||||
enabled: boolean('enabled').notNull().default(false),
|
|
||||||
initiatedTime: timestamp('initiated_time', {
|
|
||||||
mode: 'date',
|
|
||||||
withTimezone: true,
|
|
||||||
}),
|
|
||||||
userId: uuid('user_id')
|
|
||||||
.notNull()
|
|
||||||
.references(() => usersTable.id)
|
|
||||||
.unique(),
|
|
||||||
...timestamps,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const emailVerificationsRelations = relations(twoFactorTable, ({ one }) => ({
|
|
||||||
user: one(usersTable, {
|
|
||||||
fields: [twoFactorTable.userId],
|
|
||||||
references: [usersTable.id],
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export type TwoFactor = InferSelectModel<typeof twoFactorTable>
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
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 wishlistsTable = pgTable('wishlists', {
|
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
|
||||||
cuid: text('cuid')
|
|
||||||
.unique()
|
|
||||||
.$defaultFn(() => cuid2()),
|
|
||||||
user_id: uuid('user_id')
|
|
||||||
.notNull()
|
|
||||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
|
||||||
name: text('name').notNull().default('My Wishlist'),
|
|
||||||
...timestamps,
|
|
||||||
})
|
|
||||||
|
|
||||||
export type Wishlists = InferSelectModel<typeof wishlistsTable>
|
|
||||||
|
|
||||||
export const wishlists_relations = relations(wishlistsTable, ({ one }) => ({
|
|
||||||
user: one(usersTable, {
|
|
||||||
fields: [wishlistsTable.user_id],
|
|
||||||
references: [usersTable.id],
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { LuciaService } from '$lib/server/api/services/lucia.service';
|
import { createSessionTokenCookie } from '$lib/server/api/common/utils/cookies';
|
||||||
|
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||||
import type { MiddlewareHandler } from 'hono';
|
import type { MiddlewareHandler } from 'hono';
|
||||||
import { createMiddleware } from 'hono/factory';
|
import { createMiddleware } from 'hono/factory';
|
||||||
import { verifyRequestOrigin } from 'oslo/request';
|
import { verifyRequestOrigin } from 'oslo/request';
|
||||||
|
|
@ -6,7 +7,7 @@ import { container } from 'tsyringe';
|
||||||
import type { AppBindings } from '../common/types/hono';
|
import type { AppBindings } from '../common/types/hono';
|
||||||
|
|
||||||
// resolve dependencies from the container
|
// resolve dependencies from the container
|
||||||
const { lucia } = container.resolve(LuciaService);
|
const sessionService = container.resolve(SessionsService);
|
||||||
|
|
||||||
export const verifyOrigin: MiddlewareHandler<AppBindings> = createMiddleware(async (c, next) => {
|
export const verifyOrigin: MiddlewareHandler<AppBindings> = createMiddleware(async (c, next) => {
|
||||||
if (c.req.method === 'GET') {
|
if (c.req.method === 'GET') {
|
||||||
|
|
@ -30,7 +31,7 @@ export const validateAuthSession: MiddlewareHandler<AppBindings> = createMiddlew
|
||||||
|
|
||||||
const { session, user } = await lucia.validateSession(sessionId);
|
const { session, user } = await lucia.validateSession(sessionId);
|
||||||
if (session?.fresh) {
|
if (session?.fresh) {
|
||||||
c.header('Set-Cookie', lucia.createSessionCookie(session.id).serialize(), { append: true });
|
c.header('Set-Cookie', createSessionTokenCookie(session.id).serialize(), { append: true });
|
||||||
}
|
}
|
||||||
if (!session) {
|
if (!session) {
|
||||||
c.header('Set-Cookie', lucia.createBlankSessionCookie().serialize(), { append: true });
|
c.header('Set-Cookie', lucia.createBlankSessionCookie().serialize(), { append: true });
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,24 @@
|
||||||
import { drizzle } from 'drizzle-orm/node-postgres'
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
import pg from 'pg'
|
import { Pool } from 'pg';
|
||||||
import { config } from '../common/config'
|
import { config } from '../common/config';
|
||||||
import * as schema from '../databases/tables'
|
import * as schema from '../databases/postgres/tables';
|
||||||
|
|
||||||
// create the connection
|
// create the connection
|
||||||
export const pool = new pg.Pool({
|
export const pool = new Pool({
|
||||||
user: config.DATABASE_USER,
|
user: config.postgres.user,
|
||||||
password: config.DATABASE_PASSWORD,
|
password: config.postgres.password,
|
||||||
host: config.DATABASE_HOST,
|
host: config.postgres.host,
|
||||||
port: Number(config.DATABASE_PORT).valueOf(),
|
port: Number(config.postgres.port).valueOf(),
|
||||||
database: config.DATABASE_DB,
|
database: config.postgres.database,
|
||||||
ssl: config.DATABASE_HOST !== 'localhost',
|
ssl: config.postgres.host !== 'localhost',
|
||||||
max: config.DB_MIGRATING || config.DB_SEEDING ? 1 : undefined,
|
max: config.postgres.migrating || config.postgres.seeding ? 1 : undefined,
|
||||||
})
|
});
|
||||||
|
|
||||||
export const db = drizzle(pool, {
|
export const db = drizzle({
|
||||||
|
client: pool,
|
||||||
|
casing: 'snake_case',
|
||||||
schema,
|
schema,
|
||||||
logger: config.NODE_ENV === 'development',
|
logger: !config.isProduction,
|
||||||
})
|
});
|
||||||
|
|
||||||
export type db = typeof db
|
export type db = typeof db;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import { takeFirstOrThrow } from '$lib/server/api/common/utils/repository'
|
import { takeFirstOrThrow } from '$lib/server/api/common/utils/repository';
|
||||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service'
|
import { DrizzleService } from '$lib/server/api/services/drizzle.service';
|
||||||
import { type InferInsertModel, eq } from 'drizzle-orm'
|
import { type InferInsertModel, eq } from 'drizzle-orm';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { collections } from '../databases/tables'
|
import { collections } from '../databases/postgres/tables';
|
||||||
|
|
||||||
export type CreateCollection = InferInsertModel<typeof collections>
|
export type CreateCollection = InferInsertModel<typeof collections>;
|
||||||
export type UpdateCollection = Partial<CreateCollection>
|
export type UpdateCollection = Partial<CreateCollection>;
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class CollectionsRepository {
|
export class CollectionsRepository {
|
||||||
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
|
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
|
||||||
|
|
||||||
async findAll(db = this.drizzle.db) {
|
async findAll(db = this.drizzle.db) {
|
||||||
return db.query.collections.findMany()
|
return db.query.collections.findMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneById(id: string, db = this.drizzle.db) {
|
async findOneById(id: string, db = this.drizzle.db) {
|
||||||
|
|
@ -22,7 +22,7 @@ export class CollectionsRepository {
|
||||||
cuid: true,
|
cuid: true,
|
||||||
name: true,
|
name: true,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByCuid(cuid: string, db = this.drizzle.db) {
|
async findOneByCuid(cuid: string, db = this.drizzle.db) {
|
||||||
|
|
@ -32,7 +32,7 @@ export class CollectionsRepository {
|
||||||
cuid: true,
|
cuid: true,
|
||||||
name: true,
|
name: true,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByUserId(userId: string, db = this.drizzle.db) {
|
async findOneByUserId(userId: string, db = this.drizzle.db) {
|
||||||
|
|
@ -42,7 +42,7 @@ export class CollectionsRepository {
|
||||||
cuid: true,
|
cuid: true,
|
||||||
name: true,
|
name: true,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllByUserId(userId: string, db = this.drizzle.db) {
|
async findAllByUserId(userId: string, db = this.drizzle.db) {
|
||||||
|
|
@ -53,7 +53,7 @@ export class CollectionsRepository {
|
||||||
name: true,
|
name: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllByUserIdWithDetails(userId: string, db = this.drizzle.db) {
|
async findAllByUserIdWithDetails(userId: string, db = this.drizzle.db) {
|
||||||
|
|
@ -70,14 +70,14 @@ export class CollectionsRepository {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(data: CreateCollection, db = this.drizzle.db) {
|
async create(data: CreateCollection, db = this.drizzle.db) {
|
||||||
return db.insert(collections).values(data).returning().then(takeFirstOrThrow)
|
return db.insert(collections).values(data).returning().then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, data: UpdateCollection, db = this.drizzle.db) {
|
async update(id: string, data: UpdateCollection, db = this.drizzle.db) {
|
||||||
return db.update(collections).set(data).where(eq(collections.id, id)).returning().then(takeFirstOrThrow)
|
return db.update(collections).set(data).where(eq(collections.id, id)).returning().then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata';
|
||||||
import { CredentialsType, credentialsTable } from '$lib/server/api/databases/tables/credentials.table'
|
import { CredentialsType, credentialsTable } from '$lib/server/api/databases/postgres/tables/credentials.table';
|
||||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service'
|
import { DrizzleService } from '$lib/server/api/services/drizzle.service';
|
||||||
import { type InferInsertModel, and, eq } from 'drizzle-orm'
|
import { type InferInsertModel, and, eq } from 'drizzle-orm';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { takeFirstOrThrow } from '../common/utils/repository'
|
import { takeFirstOrThrow } from '../common/utils/repository';
|
||||||
|
|
||||||
export type CreateCredentials = InferInsertModel<typeof credentialsTable>
|
export type CreateCredentials = InferInsertModel<typeof credentialsTable>;
|
||||||
export type UpdateCredentials = Partial<CreateCredentials>
|
export type UpdateCredentials = Partial<CreateCredentials>;
|
||||||
export type DeleteCredentials = Pick<CreateCredentials, 'id'>
|
export type DeleteCredentials = Pick<CreateCredentials, 'id'>;
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class CredentialsRepository {
|
export class CredentialsRepository {
|
||||||
|
|
@ -16,56 +16,56 @@ export class CredentialsRepository {
|
||||||
async findOneByUserId(userId: string, db = this.drizzle.db) {
|
async findOneByUserId(userId: string, db = this.drizzle.db) {
|
||||||
return db.query.credentialsTable.findFirst({
|
return db.query.credentialsTable.findFirst({
|
||||||
where: eq(credentialsTable.user_id, userId),
|
where: eq(credentialsTable.user_id, userId),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByUserIdAndType(userId: string, type: CredentialsType, db = this.drizzle.db) {
|
async findOneByUserIdAndType(userId: string, type: CredentialsType, db = this.drizzle.db) {
|
||||||
return db.query.credentialsTable.findFirst({
|
return db.query.credentialsTable.findFirst({
|
||||||
where: and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, type)),
|
where: and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, type)),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findPasswordCredentialsByUserId(userId: string, db = this.drizzle.db) {
|
async findPasswordCredentialsByUserId(userId: string, db = this.drizzle.db) {
|
||||||
return db.query.credentialsTable.findFirst({
|
return db.query.credentialsTable.findFirst({
|
||||||
where: and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, CredentialsType.PASSWORD)),
|
where: and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, CredentialsType.PASSWORD)),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findTOTPCredentialsByUserId(userId: string, db = this.drizzle.db) {
|
async findTOTPCredentialsByUserId(userId: string, db = this.drizzle.db) {
|
||||||
return db.query.credentialsTable.findFirst({
|
return db.query.credentialsTable.findFirst({
|
||||||
where: and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, CredentialsType.TOTP)),
|
where: and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, CredentialsType.TOTP)),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneById(id: string, db = this.drizzle.db) {
|
async findOneById(id: string, db = this.drizzle.db) {
|
||||||
return db.query.credentialsTable.findFirst({
|
return db.query.credentialsTable.findFirst({
|
||||||
where: eq(credentialsTable.id, id),
|
where: eq(credentialsTable.id, id),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByIdOrThrow(id: string, db = this.drizzle.db) {
|
async findOneByIdOrThrow(id: string, db = this.drizzle.db) {
|
||||||
const credentials = await this.findOneById(id, db)
|
const credentials = await this.findOneById(id, db);
|
||||||
if (!credentials) throw Error('Credentials not found')
|
if (!credentials) throw Error('Credentials not found');
|
||||||
return credentials
|
return credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(data: CreateCredentials, db = this.drizzle.db) {
|
async create(data: CreateCredentials, db = this.drizzle.db) {
|
||||||
return db.insert(credentialsTable).values(data).returning().then(takeFirstOrThrow)
|
return db.insert(credentialsTable).values(data).returning().then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, data: UpdateCredentials, db = this.drizzle.db) {
|
async update(id: string, data: UpdateCredentials, db = this.drizzle.db) {
|
||||||
return db.update(credentialsTable).set(data).where(eq(credentialsTable.id, id)).returning().then(takeFirstOrThrow)
|
return db.update(credentialsTable).set(data).where(eq(credentialsTable.id, id)).returning().then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string, db = this.drizzle.db) {
|
async delete(id: string, db = this.drizzle.db) {
|
||||||
return db.delete(credentialsTable).where(eq(credentialsTable.id, id))
|
return db.delete(credentialsTable).where(eq(credentialsTable.id, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteByUserId(userId: string, db = this.drizzle.db) {
|
async deleteByUserId(userId: string, db = this.drizzle.db) {
|
||||||
return db.delete(credentialsTable).where(eq(credentialsTable.user_id, userId))
|
return db.delete(credentialsTable).where(eq(credentialsTable.user_id, userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteByUserIdAndType(userId: string, type: CredentialsType, db = this.drizzle.db) {
|
async deleteByUserIdAndType(userId: string, type: CredentialsType, db = this.drizzle.db) {
|
||||||
return db.delete(credentialsTable).where(and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, type)))
|
return db.delete(credentialsTable).where(and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, type)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { type InferInsertModel, and, eq } from 'drizzle-orm'
|
import { type InferInsertModel, and, eq } from 'drizzle-orm';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { takeFirstOrThrow } from '../common/utils/repository'
|
import { takeFirstOrThrow } from '../common/utils/repository';
|
||||||
import { federatedIdentityTable } from '../databases/tables'
|
import { federatedIdentityTable } from '../databases/postgres/tables';
|
||||||
import { DrizzleService } from '../services/drizzle.service'
|
import { DrizzleService } from '../services/drizzle.service';
|
||||||
|
|
||||||
export type CreateFederatedIdentity = InferInsertModel<typeof federatedIdentityTable>
|
export type CreateFederatedIdentity = InferInsertModel<typeof federatedIdentityTable>;
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class FederatedIdentityRepository {
|
export class FederatedIdentityRepository {
|
||||||
|
|
@ -13,16 +13,16 @@ export class FederatedIdentityRepository {
|
||||||
async findOneByUserIdAndProvider(userId: string, provider: string) {
|
async findOneByUserIdAndProvider(userId: string, provider: string) {
|
||||||
return this.drizzle.db.query.federatedIdentityTable.findFirst({
|
return this.drizzle.db.query.federatedIdentityTable.findFirst({
|
||||||
where: and(eq(federatedIdentityTable.user_id, userId), eq(federatedIdentityTable.identity_provider, provider)),
|
where: and(eq(federatedIdentityTable.user_id, userId), eq(federatedIdentityTable.identity_provider, provider)),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByFederatedUserIdAndProvider(federatedUserId: string, provider: string) {
|
async findOneByFederatedUserIdAndProvider(federatedUserId: string, provider: string) {
|
||||||
return this.drizzle.db.query.federatedIdentityTable.findFirst({
|
return this.drizzle.db.query.federatedIdentityTable.findFirst({
|
||||||
where: and(eq(federatedIdentityTable.federated_user_id, federatedUserId), eq(federatedIdentityTable.identity_provider, provider)),
|
where: and(eq(federatedIdentityTable.federated_user_id, federatedUserId), eq(federatedIdentityTable.identity_provider, provider)),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(data: CreateFederatedIdentity, db = this.drizzle.db) {
|
async create(data: CreateFederatedIdentity, db = this.drizzle.db) {
|
||||||
return db.insert(federatedIdentityTable).values(data).returning().then(takeFirstOrThrow)
|
return db.insert(federatedIdentityTable).values(data).returning().then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,27 @@
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata';
|
||||||
import { takeFirstOrThrow } from '$lib/server/api/common/utils/repository'
|
import { takeFirstOrThrow } from '$lib/server/api/common/utils/repository';
|
||||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service'
|
import { DrizzleService } from '$lib/server/api/services/drizzle.service';
|
||||||
import { type InferInsertModel, eq } from 'drizzle-orm'
|
import { type InferInsertModel, eq } from 'drizzle-orm';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { recoveryCodesTable } from '../databases/tables'
|
import { recoveryCodesTable } from '../databases/postgres/tables';
|
||||||
|
|
||||||
export type CreateRecoveryCodes = InferInsertModel<typeof recoveryCodesTable>
|
export type CreateRecoveryCodes = InferInsertModel<typeof recoveryCodesTable>;
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class RecoveryCodesRepository {
|
export class RecoveryCodesRepository {
|
||||||
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
|
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
|
||||||
|
|
||||||
async create(data: CreateRecoveryCodes, db = this.drizzle.db) {
|
async create(data: CreateRecoveryCodes, db = this.drizzle.db) {
|
||||||
return db.insert(recoveryCodesTable).values(data).returning().then(takeFirstOrThrow)
|
return db.insert(recoveryCodesTable).values(data).returning().then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllByUserId(userId: string, db = this.drizzle.db) {
|
async findAllByUserId(userId: string, db = this.drizzle.db) {
|
||||||
return db.query.recoveryCodesTable.findMany({
|
return db.query.recoveryCodesTable.findMany({
|
||||||
where: eq(recoveryCodesTable.userId, userId),
|
where: eq(recoveryCodesTable.userId, userId),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteAllByUserId(userId: string, db = this.drizzle.db) {
|
async deleteAllByUserId(userId: string, db = this.drizzle.db) {
|
||||||
return db.delete(recoveryCodesTable).where(eq(recoveryCodesTable.userId, userId))
|
return db.delete(recoveryCodesTable).where(eq(recoveryCodesTable.userId, userId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service'
|
import { DrizzleService } from '$lib/server/api/services/drizzle.service';
|
||||||
import { type InferInsertModel, eq } from 'drizzle-orm'
|
import { type InferInsertModel, eq } from 'drizzle-orm';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { takeFirstOrThrow } from '../common/utils/repository'
|
import { takeFirstOrThrow } from '../common/utils/repository';
|
||||||
import { rolesTable } from '../databases/tables'
|
import { rolesTable } from '../databases/postgres/tables';
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Repository */
|
/* Repository */
|
||||||
|
|
@ -20,8 +20,8 @@ storing data. They should not contain any business logic, only database queries.
|
||||||
In our case the method 'trxHost' is used to set the transaction context.
|
In our case the method 'trxHost' is used to set the transaction context.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type CreateRole = InferInsertModel<typeof rolesTable>
|
export type CreateRole = InferInsertModel<typeof rolesTable>;
|
||||||
export type UpdateRole = Partial<CreateRole>
|
export type UpdateRole = Partial<CreateRole>;
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class RolesRepository {
|
export class RolesRepository {
|
||||||
|
|
@ -30,40 +30,40 @@ export class RolesRepository {
|
||||||
async findOneById(id: string, db = this.drizzle.db) {
|
async findOneById(id: string, db = this.drizzle.db) {
|
||||||
return db.query.rolesTable.findFirst({
|
return db.query.rolesTable.findFirst({
|
||||||
where: eq(rolesTable.id, id),
|
where: eq(rolesTable.id, id),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByIdOrThrow(id: string, db = this.drizzle.db) {
|
async findOneByIdOrThrow(id: string, db = this.drizzle.db) {
|
||||||
const role = await this.findOneById(id, db)
|
const role = await this.findOneById(id, db);
|
||||||
if (!role) throw Error('Role not found')
|
if (!role) throw Error('Role not found');
|
||||||
return role
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll(db = this.drizzle.db) {
|
async findAll(db = this.drizzle.db) {
|
||||||
return db.query.rolesTable.findMany()
|
return db.query.rolesTable.findMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByName(name: string, db = this.drizzle.db) {
|
async findOneByName(name: string, db = this.drizzle.db) {
|
||||||
return db.query.rolesTable.findFirst({
|
return db.query.rolesTable.findFirst({
|
||||||
where: eq(rolesTable.name, name),
|
where: eq(rolesTable.name, name),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByNameOrThrow(name: string, db = this.drizzle.db) {
|
async findOneByNameOrThrow(name: string, db = this.drizzle.db) {
|
||||||
const role = await this.findOneByName(name, db)
|
const role = await this.findOneByName(name, db);
|
||||||
if (!role) throw Error('Role not found')
|
if (!role) throw Error('Role not found');
|
||||||
return role
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(data: CreateRole, db = this.drizzle.db) {
|
async create(data: CreateRole, db = this.drizzle.db) {
|
||||||
return db.insert(rolesTable).values(data).returning().then(takeFirstOrThrow)
|
return db.insert(rolesTable).values(data).returning().then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, data: UpdateRole, db = this.drizzle.db) {
|
async update(id: string, data: UpdateRole, db = this.drizzle.db) {
|
||||||
return db.update(rolesTable).set(data).where(eq(rolesTable.id, id)).returning().then(takeFirstOrThrow)
|
return db.update(rolesTable).set(data).where(eq(rolesTable.id, id)).returning().then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string, db = this.drizzle.db) {
|
async delete(id: string, db = this.drizzle.db) {
|
||||||
return db.delete(rolesTable).where(eq(rolesTable.id, id)).returning().then(takeFirstOrThrow)
|
return db.delete(rolesTable).where(eq(rolesTable.id, id)).returning().then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
33
src/lib/server/api/repositories/sessions.repository.ts
Normal file
33
src/lib/server/api/repositories/sessions.repository.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import 'reflect-metadata';
|
||||||
|
import { takeFirstOrThrow } from '$lib/server/api/common/utils/repository';
|
||||||
|
import { DrizzleService } from '$lib/server/api/services/drizzle.service';
|
||||||
|
import { type InferInsertModel, eq } from 'drizzle-orm';
|
||||||
|
import { inject, injectable } from 'tsyringe';
|
||||||
|
import { sessionsTable, usersTable } from '../databases/postgres/tables';
|
||||||
|
|
||||||
|
export type CreateSession = InferInsertModel<typeof sessionsTable>;
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SessionsRepository {
|
||||||
|
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
|
||||||
|
|
||||||
|
async create(data: CreateSession, db = this.drizzle.db) {
|
||||||
|
return db.insert(sessionsTable).values(data).returning().then(takeFirstOrThrow);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findBySessionId(sessionId: string, db = this.drizzle.db) {
|
||||||
|
return await db
|
||||||
|
.select({ user: usersTable, session: sessionsTable })
|
||||||
|
.from(sessionsTable)
|
||||||
|
.innerJoin(usersTable, eq(sessionsTable.userId, usersTable.id))
|
||||||
|
.where(eq(sessionsTable.id, sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteBySessionId(sessionId: string, db = this.drizzle.db) {
|
||||||
|
return db.delete(sessionsTable).where(eq(sessionsTable.id, sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSessionExpiresAt(sessionId: string, expiresAt: Date, db = this.drizzle.db) {
|
||||||
|
db.update(sessionsTable).set({ expiresAt }).where(eq(sessionsTable.id, sessionId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service'
|
import { DrizzleService } from '$lib/server/api/services/drizzle.service';
|
||||||
import { type InferInsertModel, eq } from 'drizzle-orm'
|
import { type InferInsertModel, eq } from 'drizzle-orm';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { takeFirstOrThrow } from '../common/utils/repository'
|
import { takeFirstOrThrow } from '../common/utils/repository';
|
||||||
import { user_roles } from '../databases/tables'
|
import { user_roles } from '../databases/postgres/tables';
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Repository */
|
/* Repository */
|
||||||
|
|
@ -20,8 +20,8 @@ storing data. They should not contain any business logic, only database queries.
|
||||||
In our case the method 'trxHost' is used to set the transaction context.
|
In our case the method 'trxHost' is used to set the transaction context.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type CreateUserRole = InferInsertModel<typeof user_roles>
|
export type CreateUserRole = InferInsertModel<typeof user_roles>;
|
||||||
export type UpdateUserRole = Partial<CreateUserRole>
|
export type UpdateUserRole = Partial<CreateUserRole>;
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class UserRolesRepository {
|
export class UserRolesRepository {
|
||||||
|
|
@ -30,26 +30,26 @@ export class UserRolesRepository {
|
||||||
async findOneById(id: string, db = this.drizzle.db) {
|
async findOneById(id: string, db = this.drizzle.db) {
|
||||||
return db.query.user_roles.findFirst({
|
return db.query.user_roles.findFirst({
|
||||||
where: eq(user_roles.id, id),
|
where: eq(user_roles.id, id),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByIdOrThrow(id: string, db = this.drizzle.db) {
|
async findOneByIdOrThrow(id: string, db = this.drizzle.db) {
|
||||||
const userRole = await this.findOneById(id, db)
|
const userRole = await this.findOneById(id, db);
|
||||||
if (!userRole) throw Error('User not found')
|
if (!userRole) throw Error('User not found');
|
||||||
return userRole
|
return userRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllByUserId(userId: string, db = this.drizzle.db) {
|
async findAllByUserId(userId: string, db = this.drizzle.db) {
|
||||||
return db.query.user_roles.findMany({
|
return db.query.user_roles.findMany({
|
||||||
where: eq(user_roles.user_id, userId),
|
where: eq(user_roles.user_id, userId),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(data: CreateUserRole, db = this.drizzle.db) {
|
async create(data: CreateUserRole, db = this.drizzle.db) {
|
||||||
return db.insert(user_roles).values(data).returning().then(takeFirstOrThrow)
|
return db.insert(user_roles).values(data).returning().then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string, db = this.drizzle.db) {
|
async delete(id: string, db = this.drizzle.db) {
|
||||||
return db.delete(user_roles).where(eq(user_roles.id, id)).returning().then(takeFirstOrThrow)
|
return db.delete(user_roles).where(eq(user_roles.id, id)).returning().then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { usersTable } from '$lib/server/api/databases/tables/users.table';
|
import { usersTable } from '$lib/server/api/databases/postgres/tables/users.table';
|
||||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service';
|
import { DrizzleService } from '$lib/server/api/services/drizzle.service';
|
||||||
import { type InferInsertModel, eq } from 'drizzle-orm';
|
import { type InferInsertModel, eq } from 'drizzle-orm';
|
||||||
import { inject, injectable } from 'tsyringe';
|
import { inject, injectable } from 'tsyringe';
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service'
|
import { DrizzleService } from '$lib/server/api/services/drizzle.service';
|
||||||
import { type InferInsertModel, eq } from 'drizzle-orm'
|
import { type InferInsertModel, eq } from 'drizzle-orm';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { takeFirstOrThrow } from '../common/utils/repository'
|
import { takeFirstOrThrow } from '../common/utils/repository';
|
||||||
import { wishlistsTable } from '../databases/tables'
|
import { wishlistsTable } from '../databases/postgres/tables';
|
||||||
|
|
||||||
export type CreateWishlist = InferInsertModel<typeof wishlistsTable>
|
export type CreateWishlist = InferInsertModel<typeof wishlistsTable>;
|
||||||
export type UpdateWishlist = Partial<CreateWishlist>
|
export type UpdateWishlist = Partial<CreateWishlist>;
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class WishlistsRepository {
|
export class WishlistsRepository {
|
||||||
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
|
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
|
||||||
|
|
||||||
async findAll(db = this.drizzle.db) {
|
async findAll(db = this.drizzle.db) {
|
||||||
return db.query.wishlistsTable.findMany()
|
return db.query.wishlistsTable.findMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneById(id: string, db = this.drizzle.db) {
|
async findOneById(id: string, db = this.drizzle.db) {
|
||||||
|
|
@ -22,7 +22,7 @@ export class WishlistsRepository {
|
||||||
cuid: true,
|
cuid: true,
|
||||||
name: true,
|
name: true,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByCuid(cuid: string, db = this.drizzle.db) {
|
async findOneByCuid(cuid: string, db = this.drizzle.db) {
|
||||||
|
|
@ -32,7 +32,7 @@ export class WishlistsRepository {
|
||||||
cuid: true,
|
cuid: true,
|
||||||
name: true,
|
name: true,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByUserId(userId: string, db = this.drizzle.db) {
|
async findOneByUserId(userId: string, db = this.drizzle.db) {
|
||||||
|
|
@ -42,7 +42,7 @@ export class WishlistsRepository {
|
||||||
cuid: true,
|
cuid: true,
|
||||||
name: true,
|
name: true,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllByUserId(userId: string, db = this.drizzle.db) {
|
async findAllByUserId(userId: string, db = this.drizzle.db) {
|
||||||
|
|
@ -53,14 +53,14 @@ export class WishlistsRepository {
|
||||||
name: true,
|
name: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(data: CreateWishlist, db = this.drizzle.db) {
|
async create(data: CreateWishlist, db = this.drizzle.db) {
|
||||||
return db.insert(wishlistsTable).values(data).returning().then(takeFirstOrThrow)
|
return db.insert(wishlistsTable).values(data).returning().then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, data: UpdateWishlist, db = this.drizzle.db) {
|
async update(id: string, data: UpdateWishlist, db = this.drizzle.db) {
|
||||||
return db.update(wishlistsTable).set(data).where(eq(wishlistsTable.id, id)).returning().then(takeFirstOrThrow)
|
return db.update(wishlistsTable).set(data).where(eq(wishlistsTable.id, id)).returning().then(takeFirstOrThrow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { type NodePgDatabase, drizzle } from 'drizzle-orm/node-postgres';
|
||||||
import pg from 'pg';
|
import pg from 'pg';
|
||||||
import { type Disposable, injectable } from 'tsyringe';
|
import { type Disposable, injectable } from 'tsyringe';
|
||||||
import { config } from '../common/config';
|
import { config } from '../common/config';
|
||||||
import * as schema from '../databases/tables';
|
import * as schema from '../databases/postgres/tables';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class DrizzleService implements Disposable {
|
export class DrizzleService implements Disposable {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { CredentialsType } from '$lib/server/api/databases/tables'
|
import type { ChangePasswordDto } from '$lib/server/api/dtos/change-password.dto';
|
||||||
import type { ChangePasswordDto } from '$lib/server/api/dtos/change-password.dto'
|
import type { UpdateEmailDto } from '$lib/server/api/dtos/update-email.dto';
|
||||||
import type { UpdateEmailDto } from '$lib/server/api/dtos/update-email.dto'
|
import type { UpdateProfileDto } from '$lib/server/api/dtos/update-profile.dto';
|
||||||
import type { UpdateProfileDto } from '$lib/server/api/dtos/update-profile.dto'
|
import type { VerifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto';
|
||||||
import type { VerifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto'
|
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
import { UsersService } from '$lib/server/api/services/users.service';
|
||||||
import { UsersService } from '$lib/server/api/services/users.service'
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { CredentialsType } from '../databases/postgres/tables';
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Service */
|
/* Service */
|
||||||
|
|
@ -27,60 +27,60 @@ simple as possible. This makes the service easier to read, test and understand.
|
||||||
@injectable()
|
@injectable()
|
||||||
export class IamService {
|
export class IamService {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(LuciaService) private luciaService: LuciaService,
|
@inject(SessionsService) private luciaService: SessionsService,
|
||||||
@inject(UsersService) private readonly usersService: UsersService,
|
@inject(UsersService) private readonly usersService: UsersService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async logout(sessionId: string) {
|
async logout(sessionId: string) {
|
||||||
return this.luciaService.lucia.invalidateSession(sessionId)
|
return this.luciaService.lucia.invalidateSession(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProfile(userId: string, data: UpdateProfileDto) {
|
async updateProfile(userId: string, data: UpdateProfileDto) {
|
||||||
const user = await this.usersService.findOneById(userId)
|
const user = await this.usersService.findOneById(userId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
error: 'User not found',
|
error: 'User not found',
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingUserForNewUsername = await this.usersService.findOneByUsername(data.username)
|
const existingUserForNewUsername = await this.usersService.findOneByUsername(data.username);
|
||||||
if (existingUserForNewUsername && existingUserForNewUsername.id !== user.id) {
|
if (existingUserForNewUsername && existingUserForNewUsername.id !== user.id) {
|
||||||
return {
|
return {
|
||||||
error: 'Username already in use',
|
error: 'Username already in use',
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.usersService.updateUser(user.id, {
|
return this.usersService.updateUser(user.id, {
|
||||||
first_name: data.firstName,
|
first_name: data.firstName,
|
||||||
last_name: data.lastName,
|
last_name: data.lastName,
|
||||||
username: data.username !== user.username ? data.username : user.username,
|
username: data.username !== user.username ? data.username : user.username,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateEmail(userId: string, data: UpdateEmailDto) {
|
async updateEmail(userId: string, data: UpdateEmailDto) {
|
||||||
const { email } = data
|
const { email } = data;
|
||||||
|
|
||||||
const existingUserEmail = await this.usersService.findOneByEmail(email)
|
const existingUserEmail = await this.usersService.findOneByEmail(email);
|
||||||
if (existingUserEmail && existingUserEmail.id !== userId) {
|
if (existingUserEmail && existingUserEmail.id !== userId) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.usersService.updateUser(userId, {
|
return this.usersService.updateUser(userId, {
|
||||||
email,
|
email,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updatePassword(userId: string, data: ChangePasswordDto) {
|
async updatePassword(userId: string, data: ChangePasswordDto) {
|
||||||
const { password } = data
|
const { password } = data;
|
||||||
await this.usersService.updatePassword(userId, password)
|
await this.usersService.updatePassword(userId, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyPassword(userId: string, data: VerifyPasswordDto) {
|
async verifyPassword(userId: string, data: VerifyPasswordDto) {
|
||||||
const user = await this.usersService.findOneById(userId)
|
const user = await this.usersService.findOneById(userId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
const { password } = data
|
const { password } = data;
|
||||||
return this.usersService.verifyPassword(userId, { password })
|
return this.usersService.verifyPassword(userId, { password });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
import type { SigninUsernameDto } from '$lib/server/api/dtos/signin-username.dto'
|
import type { SigninUsernameDto } from '$lib/server/api/dtos/signin-username.dto';
|
||||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||||
import type { HonoRequest } from 'hono'
|
import type { HonoRequest } from 'hono';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { BadRequest } from '../common/exceptions'
|
import { BadRequest } from '../common/exceptions';
|
||||||
import type { Credentials } from '../databases/tables'
|
import type { Credentials } from '../databases/postgres/tables';
|
||||||
import { DatabaseProvider } from '../providers/database.provider'
|
import { DatabaseProvider } from '../providers/database.provider';
|
||||||
import { CredentialsRepository } from '../repositories/credentials.repository'
|
import { CredentialsRepository } from '../repositories/credentials.repository';
|
||||||
import { UsersRepository } from '../repositories/users.repository'
|
import { UsersRepository } from '../repositories/users.repository';
|
||||||
import { MailerService } from './mailer.service'
|
import { MailerService } from './mailer.service';
|
||||||
import { TokensService } from './tokens.service'
|
import { TokensService } from './tokens.service';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class LoginRequestsService {
|
export class LoginRequestsService {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(LuciaService) private luciaService: LuciaService,
|
@inject(SessionsService) private sessionsService: SessionsService,
|
||||||
@inject(DatabaseProvider) private readonly db: DatabaseProvider,
|
@inject(DatabaseProvider) private readonly db: DatabaseProvider,
|
||||||
@inject(TokensService) private readonly tokensService: TokensService,
|
@inject(TokensService) private readonly tokensService: TokensService,
|
||||||
@inject(MailerService) private readonly mailerService: MailerService,
|
@inject(MailerService) private readonly mailerService: MailerService,
|
||||||
|
|
@ -34,46 +34,48 @@ export class LoginRequestsService {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
async verify(data: SigninUsernameDto, req: HonoRequest) {
|
async verify(data: SigninUsernameDto, req: HonoRequest) {
|
||||||
const requestIpAddress = req.header('x-real-ip')
|
const requestIpAddress = req.header('x-real-ip');
|
||||||
const requestIpCountry = req.header('x-vercel-ip-country')
|
const requestIpCountry = req.header('x-vercel-ip-country');
|
||||||
const existingUser = await this.usersRepository.findOneByUsername(data.username)
|
const existingUser = await this.usersRepository.findOneByUsername(data.username);
|
||||||
|
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
throw BadRequest('User not found')
|
throw BadRequest('User not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const credential = await this.credentialsRepository.findPasswordCredentialsByUserId(existingUser.id)
|
const credential = await this.credentialsRepository.findPasswordCredentialsByUserId(existingUser.id);
|
||||||
|
|
||||||
if (!credential) {
|
if (!credential) {
|
||||||
throw BadRequest('Invalid credentials')
|
throw BadRequest('Invalid credentials');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await this.tokensService.verifyHashedToken(credential.secret_data, data.password))) {
|
if (!(await this.tokensService.verifyHashedToken(credential.secret_data, data.password))) {
|
||||||
throw BadRequest('Invalid credentials')
|
throw BadRequest('Invalid credentials');
|
||||||
}
|
}
|
||||||
|
|
||||||
const totpCredentials = await this.credentialsRepository.findTOTPCredentialsByUserId(existingUser.id)
|
const totpCredentials = await this.credentialsRepository.findTOTPCredentialsByUserId(existingUser.id);
|
||||||
|
|
||||||
return await this.createUserSession(existingUser.id, req, totpCredentials)
|
return await this.createUserSession(existingUser.id, req, totpCredentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createUserSession(existingUserId: string, req: HonoRequest, totpCredentials: Credentials | undefined) {
|
async createUserSession(existingUserId: string, req: HonoRequest, totpCredentials: Credentials | undefined) {
|
||||||
const requestIpAddress = req.header('x-real-ip')
|
const requestIpAddress = req.header('x-real-ip');
|
||||||
const requestIpCountry = req.header('x-vercel-ip-country')
|
const requestIpCountry = req.header('x-vercel-ip-country');
|
||||||
return this.luciaService.lucia.createSession(existingUserId, {
|
return this.sessionsService.createSession(
|
||||||
ip_country: requestIpCountry || 'unknown',
|
this.sessionsService.generateSessionToken(),
|
||||||
ip_address: requestIpAddress || 'unknown',
|
existingUserId,
|
||||||
twoFactorAuthEnabled: !!totpCredentials && totpCredentials?.secret_data !== null && totpCredentials?.secret_data !== '',
|
requestIpCountry || 'unknown',
|
||||||
isTwoFactorAuthenticated: false,
|
requestIpAddress || 'unknown',
|
||||||
})
|
!!totpCredentials && totpCredentials?.secret_data !== null && totpCredentials?.secret_data !== '',
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new user and send a welcome email - or other onboarding process
|
// Create a new user and send a welcome email - or other onboarding process
|
||||||
private async handleNewUserRegistration(email: string) {
|
private async handleNewUserRegistration(email: string) {
|
||||||
const newUser = await this.usersRepository.create({ email, verified: true })
|
const newUser = await this.usersRepository.create({ email, verified: true });
|
||||||
this.mailerService.sendWelcome({ to: email, props: null })
|
this.mailerService.sendWelcome({ to: email, props: null });
|
||||||
// TODO: add whatever onboarding process or extra data you need here
|
// TODO: add whatever onboarding process or extra data you need here
|
||||||
return newUser
|
return newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch a valid request from the database, verify the token and burn the request if it is valid
|
// Fetch a valid request from the database, verify the token and burn the request if it is valid
|
||||||
|
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import { config } from '$lib/server/api/common/config'
|
|
||||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service'
|
|
||||||
import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle'
|
|
||||||
import { Lucia, TimeSpan } from 'lucia'
|
|
||||||
import { inject, injectable } from 'tsyringe'
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class LuciaService {
|
|
||||||
readonly lucia: Lucia
|
|
||||||
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {
|
|
||||||
const adapter = new DrizzlePostgreSQLAdapter(this.drizzle.db, this.drizzle.schema.sessionsTable, this.drizzle.schema.usersTable)
|
|
||||||
this.lucia = new Lucia(adapter, {
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
71
src/lib/server/api/services/sessions.service.ts
Normal file
71
src/lib/server/api/services/sessions.service.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { SessionsRepository } from '$lib/server/api/repositories/sessions.repository';
|
||||||
|
import { sha256 } from '@oslojs/crypto/sha2';
|
||||||
|
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
|
||||||
|
import { TimeSpan } from 'lucia';
|
||||||
|
import { inject, injectable } from 'tsyringe';
|
||||||
|
import type { Sessions, Users } from '../databases/postgres/tables';
|
||||||
|
|
||||||
|
export type SessionValidationResult = { session: Sessions; user: Users } | { session: null; user: null };
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SessionsService {
|
||||||
|
constructor(@inject(SessionsRepository) private readonly sessionsRepository: SessionsRepository) {}
|
||||||
|
|
||||||
|
generateSessionToken() {
|
||||||
|
const bytes = new Uint8Array(20);
|
||||||
|
crypto.getRandomValues(bytes);
|
||||||
|
return encodeBase32LowerCaseNoPadding(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSession(
|
||||||
|
token: string,
|
||||||
|
userId: string,
|
||||||
|
ipCountry: string,
|
||||||
|
ipAddress: string,
|
||||||
|
twoFactorAuthEnabled: boolean,
|
||||||
|
isTwoFactorAuthenticated: boolean,
|
||||||
|
) {
|
||||||
|
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||||
|
const session: Sessions = {
|
||||||
|
id: sessionId,
|
||||||
|
userId,
|
||||||
|
expiresAt: new Date(Date.now() + new TimeSpan(2, 'w').seconds()),
|
||||||
|
ipCountry,
|
||||||
|
ipAddress,
|
||||||
|
twoFactorAuthEnabled,
|
||||||
|
isTwoFactorAuthenticated,
|
||||||
|
};
|
||||||
|
await this.sessionsRepository.create(session);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateSessionToken(token: string): Promise<SessionValidationResult> {
|
||||||
|
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||||
|
const sessions = await this.sessionsRepository.findBySessionId(sessionId);
|
||||||
|
if (sessions.length < 1) {
|
||||||
|
return {
|
||||||
|
session: null,
|
||||||
|
user: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const { user, session } = sessions[0];
|
||||||
|
if (Date.now() >= session.expiresAt.getTime()) {
|
||||||
|
await this.sessionsRepository.deleteBySessionId(sessionId);
|
||||||
|
return {
|
||||||
|
session: null,
|
||||||
|
user: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Date.now() >= session.expiresAt.getTime() - new TimeSpan(1, 'w').seconds()) {
|
||||||
|
session.expiresAt = new Date(Date.now() + new TimeSpan(2, 'w').seconds());
|
||||||
|
await this.sessionsRepository.updateSessionExpiresAt(sessionId, session.expiresAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { session, user };
|
||||||
|
}
|
||||||
|
|
||||||
|
async invalidateSession(sessionId: string) {
|
||||||
|
await this.sessionsRepository.deleteBySessionId(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ import { CredentialsRepository } from '$lib/server/api/repositories/credentials.
|
||||||
import { decodeHex, encodeHexLowerCase } from '@oslojs/encoding';
|
import { decodeHex, encodeHexLowerCase } from '@oslojs/encoding';
|
||||||
import { verifyTOTP } from '@oslojs/otp';
|
import { verifyTOTP } from '@oslojs/otp';
|
||||||
import { inject, injectable } from 'tsyringe';
|
import { inject, injectable } from 'tsyringe';
|
||||||
import type { CredentialsType } from '../databases/tables';
|
import type { CredentialsType } from '../databases/postgres/tables';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class TotpService {
|
export class TotpService {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import type { SignupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto'
|
import type { OAuthUser } from '$lib/server/api/common/types/oauth';
|
||||||
import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository'
|
import type { SignupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto';
|
||||||
import { FederatedIdentityRepository } from '$lib/server/api/repositories/federated_identity.repository'
|
import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository';
|
||||||
import { WishlistsRepository } from '$lib/server/api/repositories/wishlists.repository'
|
import { FederatedIdentityRepository } from '$lib/server/api/repositories/federated_identity.repository';
|
||||||
import { TokensService } from '$lib/server/api/services/tokens.service'
|
import { WishlistsRepository } from '$lib/server/api/repositories/wishlists.repository';
|
||||||
import { UserRolesService } from '$lib/server/api/services/user_roles.service'
|
import { TokensService } from '$lib/server/api/services/tokens.service';
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { UserRolesService } from '$lib/server/api/services/user_roles.service';
|
||||||
import {CredentialsType, RoleName} from '../databases/tables'
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { type UpdateUser, UsersRepository } from '../repositories/users.repository'
|
import { CredentialsType, RoleName } from '../databases/postgres/tables';
|
||||||
import { CollectionsService } from './collections.service'
|
import { type UpdateUser, UsersRepository } from '../repositories/users.repository';
|
||||||
import { DrizzleService } from './drizzle.service'
|
import { CollectionsService } from './collections.service';
|
||||||
import { WishlistsService } from './wishlists.service'
|
import { DrizzleService } from './drizzle.service';
|
||||||
import type {OAuthUser} from "$lib/server/api/common/types/oauth";
|
import { WishlistsService } from './wishlists.service';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
|
|
@ -27,9 +27,9 @@ export class UsersService {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create(data: SignupUsernameEmailDto) {
|
async create(data: SignupUsernameEmailDto) {
|
||||||
const { firstName, lastName, email, username, password } = data
|
const { firstName, lastName, email, username, password } = data;
|
||||||
|
|
||||||
const hashedPassword = await this.tokenService.createHashedToken(password)
|
const hashedPassword = await this.tokenService.createHashedToken(password);
|
||||||
return await this.drizzleService.db.transaction(async (trx) => {
|
return await this.drizzleService.db.transaction(async (trx) => {
|
||||||
const createdUser = await this.usersRepository.create(
|
const createdUser = await this.usersRepository.create(
|
||||||
{
|
{
|
||||||
|
|
@ -39,10 +39,10 @@ export class UsersService {
|
||||||
username,
|
username,
|
||||||
},
|
},
|
||||||
trx,
|
trx,
|
||||||
)
|
);
|
||||||
|
|
||||||
if (!createdUser) {
|
if (!createdUser) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = await this.credentialsRepository.create(
|
const credentials = await this.credentialsRepository.create(
|
||||||
|
|
@ -52,18 +52,18 @@ export class UsersService {
|
||||||
secret_data: hashedPassword,
|
secret_data: hashedPassword,
|
||||||
},
|
},
|
||||||
trx,
|
trx,
|
||||||
)
|
);
|
||||||
|
|
||||||
if (!credentials) {
|
if (!credentials) {
|
||||||
await this.usersRepository.delete(createdUser.id)
|
await this.usersRepository.delete(createdUser.id);
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.userRolesService.addRoleToUser(createdUser.id, RoleName.USER, true, trx)
|
await this.userRolesService.addRoleToUser(createdUser.id, RoleName.USER, true, trx);
|
||||||
|
|
||||||
await this.wishlistsService.createEmptyNoName(createdUser.id, trx)
|
await this.wishlistsService.createEmptyNoName(createdUser.id, trx);
|
||||||
await this.collectionsService.createEmptyNoName(createdUser.id, trx)
|
await this.collectionsService.createEmptyNoName(createdUser.id, trx);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createOAuthUser(oAuthUser: OAuthUser, oauthProvider: string) {
|
async createOAuthUser(oAuthUser: OAuthUser, oauthProvider: string) {
|
||||||
|
|
@ -78,10 +78,10 @@ export class UsersService {
|
||||||
email_verified: oAuthUser.email_verified || false,
|
email_verified: oAuthUser.email_verified || false,
|
||||||
},
|
},
|
||||||
trx,
|
trx,
|
||||||
)
|
);
|
||||||
|
|
||||||
if (!createdUser) {
|
if (!createdUser) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.federatedIdentityRepository.create(
|
await this.federatedIdentityRepository.create(
|
||||||
|
|
@ -92,58 +92,58 @@ export class UsersService {
|
||||||
federated_username: oAuthUser.email || oAuthUser.username,
|
federated_username: oAuthUser.email || oAuthUser.username,
|
||||||
},
|
},
|
||||||
trx,
|
trx,
|
||||||
)
|
);
|
||||||
|
|
||||||
await this.userRolesService.addRoleToUser(createdUser.id, RoleName.USER, true, trx)
|
await this.userRolesService.addRoleToUser(createdUser.id, RoleName.USER, true, trx);
|
||||||
|
|
||||||
await this.wishlistsService.createEmptyNoName(createdUser.id, trx)
|
await this.wishlistsService.createEmptyNoName(createdUser.id, trx);
|
||||||
await this.collectionsService.createEmptyNoName(createdUser.id, trx)
|
await this.collectionsService.createEmptyNoName(createdUser.id, trx);
|
||||||
return createdUser
|
return createdUser;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUser(userId: string, data: UpdateUser) {
|
async updateUser(userId: string, data: UpdateUser) {
|
||||||
return this.usersRepository.update(userId, data)
|
return this.usersRepository.update(userId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByUsername(username: string) {
|
async findOneByUsername(username: string) {
|
||||||
return this.usersRepository.findOneByUsername(username)
|
return this.usersRepository.findOneByUsername(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByEmail(email: string) {
|
async findOneByEmail(email: string) {
|
||||||
return this.usersRepository.findOneByEmail(email)
|
return this.usersRepository.findOneByEmail(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneById(id: string) {
|
async findOneById(id: string) {
|
||||||
return this.usersRepository.findOneById(id)
|
return this.usersRepository.findOneById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updatePassword(userId: string, password: string) {
|
async updatePassword(userId: string, password: string) {
|
||||||
const hashedPassword = await this.tokenService.createHashedToken(password)
|
const hashedPassword = await this.tokenService.createHashedToken(password);
|
||||||
const currentCredentials = await this.credentialsRepository.findPasswordCredentialsByUserId(userId)
|
const currentCredentials = await this.credentialsRepository.findPasswordCredentialsByUserId(userId);
|
||||||
if (!currentCredentials) {
|
if (!currentCredentials) {
|
||||||
await this.credentialsRepository.create({
|
await this.credentialsRepository.create({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
type: CredentialsType.PASSWORD,
|
type: CredentialsType.PASSWORD,
|
||||||
secret_data: hashedPassword,
|
secret_data: hashedPassword,
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.credentialsRepository.update(currentCredentials.id, {
|
await this.credentialsRepository.update(currentCredentials.id, {
|
||||||
secret_data: hashedPassword,
|
secret_data: hashedPassword,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyPassword(userId: string, data: { password: string }) {
|
async verifyPassword(userId: string, data: { password: string }) {
|
||||||
const user = await this.usersRepository.findOneById(userId)
|
const user = await this.usersRepository.findOneById(userId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('User not found')
|
throw new Error('User not found');
|
||||||
}
|
}
|
||||||
const credential = await this.credentialsRepository.findOneByUserIdAndType(userId, CredentialsType.PASSWORD)
|
const credential = await this.credentialsRepository.findOneByUserIdAndType(userId, CredentialsType.PASSWORD);
|
||||||
if (!credential) {
|
if (!credential) {
|
||||||
throw new Error('Password credentials not found')
|
throw new Error('Password credentials not found');
|
||||||
}
|
}
|
||||||
const { password } = data
|
const { password } = data;
|
||||||
return this.tokenService.verifyHashedToken(credential.secret_data, password)
|
return this.tokenService.verifyHashedToken(credential.secret_data, password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,32 @@
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata';
|
||||||
import { IamService } from '$lib/server/api/services/iam.service'
|
import { IamService } from '$lib/server/api/services/iam.service';
|
||||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||||
import { UsersService } from '$lib/server/api/services/users.service'
|
import { UsersService } from '$lib/server/api/services/users.service';
|
||||||
import { faker } from '@faker-js/faker'
|
import { faker } from '@faker-js/faker';
|
||||||
import { container } from 'tsyringe'
|
import { container } from 'tsyringe';
|
||||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
describe('IamService', () => {
|
describe('IamService', () => {
|
||||||
let service: IamService
|
let service: IamService;
|
||||||
const luciaService = vi.mocked(LuciaService.prototype)
|
const luciaService = vi.mocked(SessionsService.prototype);
|
||||||
const userService = vi.mocked(UsersService.prototype)
|
const userService = vi.mocked(UsersService.prototype);
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
service = container
|
service = container
|
||||||
.register<LuciaService>(LuciaService, { useValue: luciaService })
|
.register<SessionsService>(SessionsService, { useValue: luciaService })
|
||||||
.register<UsersService>(UsersService, { useValue: userService })
|
.register<UsersService>(UsersService, { useValue: userService })
|
||||||
.resolve(IamService)
|
.resolve(IamService);
|
||||||
})
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetAllMocks()
|
vi.resetAllMocks();
|
||||||
})
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
vi.resetAllMocks()
|
vi.resetAllMocks();
|
||||||
})
|
});
|
||||||
|
|
||||||
const timeStampDate = new Date()
|
const timeStampDate = new Date();
|
||||||
const dbUser = {
|
const dbUser = {
|
||||||
id: faker.string.uuid(),
|
id: faker.string.uuid(),
|
||||||
cuid: 'ciglo1j8q0000t9j4xq8d6p5e',
|
cuid: 'ciglo1j8q0000t9j4xq8d6p5e',
|
||||||
|
|
@ -40,85 +40,85 @@ describe('IamService', () => {
|
||||||
theme: 'system',
|
theme: 'system',
|
||||||
createdAt: timeStampDate,
|
createdAt: timeStampDate,
|
||||||
updatedAt: timeStampDate,
|
updatedAt: timeStampDate,
|
||||||
}
|
};
|
||||||
|
|
||||||
describe('Update Profile', () => {
|
describe('Update Profile', () => {
|
||||||
it('should update user', async () => {
|
it('should update user', async () => {
|
||||||
userService.findOneById = vi.fn().mockResolvedValueOnce(dbUser)
|
userService.findOneById = vi.fn().mockResolvedValueOnce(dbUser);
|
||||||
userService.findOneByUsername = vi.fn().mockResolvedValue(undefined)
|
userService.findOneByUsername = vi.fn().mockResolvedValue(undefined);
|
||||||
userService.updateUser = vi.fn().mockResolvedValue(dbUser)
|
userService.updateUser = vi.fn().mockResolvedValue(dbUser);
|
||||||
|
|
||||||
const spy_userService_findOneById = vi.spyOn(userService, 'findOneById')
|
const spy_userService_findOneById = vi.spyOn(userService, 'findOneById');
|
||||||
const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername')
|
const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername');
|
||||||
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser')
|
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser');
|
||||||
await expect(
|
await expect(
|
||||||
service.updateProfile(faker.string.uuid(), {
|
service.updateProfile(faker.string.uuid(), {
|
||||||
username: faker.internet.userName(),
|
username: faker.internet.userName(),
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual(dbUser)
|
).resolves.toEqual(dbUser);
|
||||||
expect(spy_userService_findOneById).toBeCalledTimes(1)
|
expect(spy_userService_findOneById).toBeCalledTimes(1);
|
||||||
expect(spy_userService_findOneByUsername).toBeCalledTimes(1)
|
expect(spy_userService_findOneByUsername).toBeCalledTimes(1);
|
||||||
expect(spy_userService_updateUser).toBeCalledTimes(1)
|
expect(spy_userService_updateUser).toBeCalledTimes(1);
|
||||||
})
|
});
|
||||||
|
|
||||||
it('should error on no user found', async () => {
|
it('should error on no user found', async () => {
|
||||||
userService.findOneById = vi.fn().mockResolvedValueOnce(undefined)
|
userService.findOneById = vi.fn().mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
const spy_userService_findOneById = vi.spyOn(userService, 'findOneById')
|
const spy_userService_findOneById = vi.spyOn(userService, 'findOneById');
|
||||||
const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername')
|
const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername');
|
||||||
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser')
|
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser');
|
||||||
await expect(
|
await expect(
|
||||||
service.updateProfile(faker.string.uuid(), {
|
service.updateProfile(faker.string.uuid(), {
|
||||||
username: faker.internet.userName(),
|
username: faker.internet.userName(),
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({
|
).resolves.toEqual({
|
||||||
error: 'User not found',
|
error: 'User not found',
|
||||||
})
|
});
|
||||||
expect(spy_userService_findOneById).toBeCalledTimes(1)
|
expect(spy_userService_findOneById).toBeCalledTimes(1);
|
||||||
expect(spy_userService_findOneByUsername).toBeCalledTimes(0)
|
expect(spy_userService_findOneByUsername).toBeCalledTimes(0);
|
||||||
expect(spy_userService_updateUser).toBeCalledTimes(0)
|
expect(spy_userService_updateUser).toBeCalledTimes(0);
|
||||||
})
|
});
|
||||||
|
|
||||||
it('should error on duplicate username', async () => {
|
it('should error on duplicate username', async () => {
|
||||||
userService.findOneById = vi.fn().mockResolvedValueOnce(dbUser)
|
userService.findOneById = vi.fn().mockResolvedValueOnce(dbUser);
|
||||||
userService.findOneByUsername = vi.fn().mockResolvedValue({
|
userService.findOneByUsername = vi.fn().mockResolvedValue({
|
||||||
id: faker.string.uuid(),
|
id: faker.string.uuid(),
|
||||||
})
|
});
|
||||||
userService.updateUser = vi.fn().mockResolvedValue(dbUser)
|
userService.updateUser = vi.fn().mockResolvedValue(dbUser);
|
||||||
|
|
||||||
const spy_userService_findOneById = vi.spyOn(userService, 'findOneById')
|
const spy_userService_findOneById = vi.spyOn(userService, 'findOneById');
|
||||||
const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername')
|
const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername');
|
||||||
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser')
|
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser');
|
||||||
await expect(
|
await expect(
|
||||||
service.updateProfile(faker.string.uuid(), {
|
service.updateProfile(faker.string.uuid(), {
|
||||||
username: faker.internet.userName(),
|
username: faker.internet.userName(),
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({
|
).resolves.toEqual({
|
||||||
error: 'Username already in use',
|
error: 'Username already in use',
|
||||||
})
|
});
|
||||||
expect(spy_userService_findOneById).toBeCalledTimes(1)
|
expect(spy_userService_findOneById).toBeCalledTimes(1);
|
||||||
expect(spy_userService_findOneByUsername).toBeCalledTimes(1)
|
expect(spy_userService_findOneByUsername).toBeCalledTimes(1);
|
||||||
expect(spy_userService_updateUser).toBeCalledTimes(0)
|
expect(spy_userService_updateUser).toBeCalledTimes(0);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
it('should not error if the user id of new username is the current user id', async () => {
|
it('should not error if the user id of new username is the current user id', async () => {
|
||||||
userService.findOneById = vi.fn().mockResolvedValueOnce(dbUser)
|
userService.findOneById = vi.fn().mockResolvedValueOnce(dbUser);
|
||||||
userService.findOneByUsername = vi.fn().mockResolvedValue({
|
userService.findOneByUsername = vi.fn().mockResolvedValue({
|
||||||
id: dbUser.id,
|
id: dbUser.id,
|
||||||
})
|
});
|
||||||
userService.updateUser = vi.fn().mockResolvedValue(dbUser)
|
userService.updateUser = vi.fn().mockResolvedValue(dbUser);
|
||||||
|
|
||||||
const spy_userService_findOneById = vi.spyOn(userService, 'findOneById')
|
const spy_userService_findOneById = vi.spyOn(userService, 'findOneById');
|
||||||
const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername')
|
const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername');
|
||||||
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser')
|
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser');
|
||||||
await expect(
|
await expect(
|
||||||
service.updateProfile(dbUser.id, {
|
service.updateProfile(dbUser.id, {
|
||||||
username: dbUser.id,
|
username: dbUser.id,
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual(dbUser)
|
).resolves.toEqual(dbUser);
|
||||||
expect(spy_userService_findOneById).toBeCalledTimes(1)
|
expect(spy_userService_findOneById).toBeCalledTimes(1);
|
||||||
expect(spy_userService_findOneByUsername).toBeCalledTimes(1)
|
expect(spy_userService_findOneByUsername).toBeCalledTimes(1);
|
||||||
expect(spy_userService_updateUser).toBeCalledTimes(1)
|
expect(spy_userService_updateUser).toBeCalledTimes(1);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue