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 env from './src/lib/server/api/common/env'
|
||||
import { defineConfig } from 'drizzle-kit'
|
||||
import 'dotenv/config';
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
import env from './src/lib/server/api/common/env';
|
||||
|
||||
export default defineConfig({
|
||||
dialect: 'postgresql',
|
||||
out: './src/lib/server/api/databases/migrations',
|
||||
schema: './src/lib/server/api/databases/tables/index.ts',
|
||||
out: './src/lib/server/api/databases/postgres/migrations',
|
||||
schema: './src/lib/server/api/databases/postgres/tables/index.ts',
|
||||
casing: 'snake_case',
|
||||
dbCredentials: {
|
||||
host: env.DATABASE_HOST || 'localhost',
|
||||
port: Number(env.DATABASE_PORT) || 5432,
|
||||
|
|
@ -22,4 +23,4 @@ export default defineConfig({
|
|||
table: 'migrations',
|
||||
schema: 'public',
|
||||
},
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
import { PUBLIC_SITE_URL } from '$env/static/public'
|
||||
import { createPasswordResetToken } from '$lib/server/auth-utils.js'
|
||||
import { error } from '@sveltejs/kit'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { usersTable } from '../../src/lib/server/api/databases/tables'
|
||||
import { db } from '../../src/lib/server/api/packages/drizzle'
|
||||
import { PUBLIC_SITE_URL } from '$env/static/public';
|
||||
import { createPasswordResetToken } from '$lib/server/auth-utils.js';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { usersTable } from '../../src/lib/server/api/databases/postgres/tables';
|
||||
import { db } from '../../src/lib/server/api/packages/drizzle';
|
||||
|
||||
export async function POST({ locals, request }) {
|
||||
const { email }: { email: string } = await request.json()
|
||||
const { email }: { email: string } = await request.json();
|
||||
|
||||
if (!locals.user) {
|
||||
error(401, { message: 'Unauthorized' })
|
||||
error(401, { message: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const user = await db.query.usersTable.findFirst({
|
||||
where: eq(usersTable.email, email),
|
||||
})
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
error(200, {
|
||||
message: 'Email sent! Please check your email for a link to reset your password.',
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const verificationToken = await createPasswordResetToken(user.id)
|
||||
const verificationLink = PUBLIC_SITE_URL + verificationToken
|
||||
const verificationToken = await createPasswordResetToken(user.id);
|
||||
const verificationLink = PUBLIC_SITE_URL + verificationToken;
|
||||
|
||||
// TODO: send email
|
||||
console.log('Verification link: ' + verificationLink)
|
||||
console.log('Verification link: ' + verificationLink);
|
||||
|
||||
return new Response(null, {
|
||||
status: 200,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
import { eq } from 'drizzle-orm'
|
||||
import { isWithinExpirationDate } from 'oslo'
|
||||
import { password_reset_tokens } from '../../../src/lib/server/api/databases/tables'
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { isWithinExpirationDate } from 'oslo';
|
||||
import { password_reset_tokens } from '../../../src/lib/server/api/databases/postgres/tables';
|
||||
// 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 }) {
|
||||
const { password } = await request.json()
|
||||
const { password } = await request.json();
|
||||
|
||||
if (typeof password !== 'string' || password.length < 8) {
|
||||
return new Response(null, {
|
||||
status: 400,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const verificationToken = params.token
|
||||
const verificationToken = params.token;
|
||||
|
||||
const token = await db.query.password_reset_tokens.findFirst({
|
||||
where: eq(password_reset_tokens.id, verificationToken),
|
||||
})
|
||||
});
|
||||
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, {
|
||||
status: 400,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (!token?.expires_at || !isWithinExpirationDate(token.expires_at)) {
|
||||
return new Response(null, {
|
||||
status: 400,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// await lucia.invalidateUserSessions(token.user_id);
|
||||
|
|
@ -44,5 +44,5 @@ export async function POST({ request, params }) {
|
|||
Location: '/',
|
||||
'Set-Cookie': sessionCookie.serialize(),
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
|
|||
58
package.json
58
package.json
|
|
@ -5,8 +5,8 @@
|
|||
"scripts": {
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "tsx src/lib/server/api/databases/migrate.ts",
|
||||
"db:seed": "tsx src/lib/server/api/databases/seed.ts",
|
||||
"db:migrate": "tsx src/lib/server/api/databases/postgres/migrate.ts",
|
||||
"db:seed": "tsx src/lib/server/api/databases/postgres/seed.ts",
|
||||
"db:studio": "drizzle-kit studio --verbose",
|
||||
"dev": "NODE_OPTIONS=\"--inspect\" vite dev --host",
|
||||
"build": "vite build",
|
||||
|
|
@ -27,20 +27,20 @@
|
|||
"@faker-js/faker": "^8.4.1",
|
||||
"@melt-ui/pp": "^0.3.2",
|
||||
"@melt-ui/svelte": "^0.83.0",
|
||||
"@playwright/test": "^1.48.0",
|
||||
"@sveltejs/adapter-auto": "^3.2.5",
|
||||
"@sveltejs/enhanced-img": "^0.3.9",
|
||||
"@sveltejs/kit": "^2.7.1",
|
||||
"@playwright/test": "^1.48.2",
|
||||
"@sveltejs/adapter-auto": "^3.3.1",
|
||||
"@sveltejs/enhanced-img": "^0.3.10",
|
||||
"@sveltejs/kit": "^2.7.5",
|
||||
"@sveltejs/vite-plugin-svelte": "4.0.0-next.7",
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/node": "^20.16.11",
|
||||
"@types/node": "^20.17.6",
|
||||
"@types/pg": "^8.11.10",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"arctic": "^1.9.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"drizzle-kit": "^0.23.2",
|
||||
"drizzle-kit": "^0.27.1",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "2.36.0-next.13",
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
"just-debounce-it": "^3.2.0",
|
||||
"lucia": "3.2.0",
|
||||
"lucide-svelte": "^0.408.0",
|
||||
"nodemailer": "^6.9.15",
|
||||
"nodemailer": "^6.9.16",
|
||||
"postcss": "^8.4.47",
|
||||
"postcss-import": "^16.1.0",
|
||||
"postcss-load-config": "^5.1.0",
|
||||
|
|
@ -57,18 +57,18 @@
|
|||
"prettier-plugin-svelte": "^3.2.7",
|
||||
"svelte": "5.0.0-next.175",
|
||||
"svelte-check": "^3.8.6",
|
||||
"svelte-headless-table": "^0.18.2",
|
||||
"svelte-headless-table": "^0.18.3",
|
||||
"svelte-meta-tags": "^3.1.4",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"svelte-sequential-preprocessor": "^2.0.2",
|
||||
"sveltekit-flash-message": "^2.4.4",
|
||||
"sveltekit-superforms": "^2.19.1",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"sveltekit-superforms": "^2.20.0",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.7.0",
|
||||
"tsx": "^4.19.1",
|
||||
"tslib": "^2.8.1",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.9",
|
||||
"vite": "^5.4.10",
|
||||
"vitest": "^1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
|
@ -92,27 +92,27 @@
|
|||
"@oslojs/otp": "^1.0.0",
|
||||
"@oslojs/webauthn": "^1.0.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@scalar/hono-api-reference": "^0.5.154",
|
||||
"@sveltejs/adapter-node": "^5.2.7",
|
||||
"@sveltejs/adapter-vercel": "^5.4.5",
|
||||
"@scalar/hono-api-reference": "^0.5.158",
|
||||
"@sveltejs/adapter-node": "^5.2.9",
|
||||
"@sveltejs/adapter-vercel": "^5.4.6",
|
||||
"@types/feather-icons": "^4.29.4",
|
||||
"bits-ui": "^0.21.16",
|
||||
"boardgamegeekclient": "^1.9.1",
|
||||
"bullmq": "^5.20.0",
|
||||
"bullmq": "^5.24.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cookie": "^0.6.0",
|
||||
"cookie": "^1.0.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv-expand": "^11.0.6",
|
||||
"drizzle-orm": "^0.32.2",
|
||||
"drizzle-orm": "^0.36.0",
|
||||
"drizzle-zod": "^0.5.1",
|
||||
"feather-icons": "^4.29.2",
|
||||
"formsnap": "^1.0.1",
|
||||
"handlebars": "^4.7.8",
|
||||
"hono": "^4.6.4",
|
||||
"hono": "^4.6.9",
|
||||
"hono-pino": "^0.3.0",
|
||||
"hono-rate-limiter": "^0.4.0",
|
||||
"hono-zod-openapi": "^0.3.0",
|
||||
"hono-zod-openapi": "^0.3.1",
|
||||
"html-entities": "^2.5.2",
|
||||
"iconify-icon": "^2.1.0",
|
||||
"ioredis": "^5.4.1",
|
||||
|
|
@ -122,21 +122,21 @@
|
|||
"mode-watcher": "^0.4.1",
|
||||
"open-props": "^1.7.7",
|
||||
"oslo": "^1.2.1",
|
||||
"pg": "^8.13.0",
|
||||
"pino": "^9.4.0",
|
||||
"pino-pretty": "^11.2.2",
|
||||
"postgres": "^3.4.4",
|
||||
"pg": "^8.13.1",
|
||||
"pino": "^9.5.0",
|
||||
"pino-pretty": "^11.3.0",
|
||||
"postgres": "^3.4.5",
|
||||
"qrcode": "^1.5.4",
|
||||
"radix-svelte": "^0.9.0",
|
||||
"rate-limit-redis": "^4.2.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"stoker": "^1.2.3",
|
||||
"stoker": "^1.3.0",
|
||||
"svelte-lazy-loader": "^1.0.0",
|
||||
"svelte-sonner": "^0.3.28",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"tailwind-variants": "^0.2.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"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,
|
||||
ssl: false, // env.DATABASE_HOST !== 'localhost',
|
||||
max: env.DB_MIGRATING || env.DB_SEEDING ? 1 : undefined,
|
||||
migrating: env.DB_MIGRATING,
|
||||
seeding: env.DB_SEEDING,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
export interface Config {
|
||||
isProduction: boolean
|
||||
domain: string
|
||||
api: ApiConfig
|
||||
isProduction: boolean;
|
||||
domain: string;
|
||||
api: ApiConfig;
|
||||
// storage: StorageConfig
|
||||
redis: RedisConfig
|
||||
postgres: PostgresConfig
|
||||
redis: RedisConfig;
|
||||
postgres: PostgresConfig;
|
||||
}
|
||||
|
||||
interface ApiConfig {
|
||||
origin: string
|
||||
origin: string;
|
||||
}
|
||||
|
||||
// interface StorageConfig {
|
||||
|
|
@ -19,15 +19,17 @@ interface ApiConfig {
|
|||
// }
|
||||
|
||||
interface RedisConfig {
|
||||
url: string
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface PostgresConfig {
|
||||
user: string
|
||||
password: string
|
||||
host: string
|
||||
port: number
|
||||
database: string
|
||||
ssl: boolean
|
||||
max: number | undefined
|
||||
user: string;
|
||||
password: string;
|
||||
host: string;
|
||||
port: number;
|
||||
database: string;
|
||||
ssl: boolean;
|
||||
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 { customType } from 'drizzle-orm/pg-core'
|
||||
import { timestamp } from 'drizzle-orm/pg-core';
|
||||
import { customType } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const citext = customType<{ data: string }>({
|
||||
dataType() {
|
||||
return 'citext'
|
||||
return 'citext';
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
export const cuid2 = customType<{ data: string }>({
|
||||
dataType() {
|
||||
return 'text'
|
||||
return 'text';
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
export const timestamps = {
|
||||
createdAt: timestamp('created_at', {
|
||||
|
|
@ -25,5 +25,6 @@ export const timestamps = {
|
|||
withTimezone: true,
|
||||
})
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
}
|
||||
.defaultNow()
|
||||
.$onUpdate(() => new Date()),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { StatusCodes } from '$lib/constants/status-codes';
|
||||
import { unauthorizedSchema } from '$lib/server/api/common/exceptions';
|
||||
import cuidParamsSchema from '$lib/server/api/common/openapi/cuidParamsSchema';
|
||||
import { selectCollectionSchema } from '$lib/server/api/databases/tables';
|
||||
import { z } from '@hono/zod-openapi';
|
||||
import { IdParamsSchema } from 'stoker/openapi/schemas';
|
||||
import { createErrorSchema } from 'stoker/openapi/schemas';
|
||||
import { taggedAuthRoute } from '../common/openapi/create-auth-route';
|
||||
import { selectCollectionSchema } from '../databases/postgres/tables';
|
||||
|
||||
const tag = 'Collection';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { StatusCodes } from '$lib/constants/status-codes';
|
||||
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 { updateEmailDto } from '$lib/server/api/dtos/update-email.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 { IamService } from '$lib/server/api/services/iam.service';
|
||||
import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service';
|
||||
import { LuciaService } from '$lib/server/api/services/lucia.service';
|
||||
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { openApi } from 'hono-zod-openapi';
|
||||
import { setCookie } from 'hono/cookie';
|
||||
|
|
@ -20,7 +21,7 @@ export class IamController extends Controller {
|
|||
constructor(
|
||||
@inject(IamService) private readonly iamService: IamService,
|
||||
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
|
||||
@inject(LuciaService) private luciaService: LuciaService,
|
||||
@inject(SessionsService) private sessionsService: SessionsService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
|
@ -78,18 +79,9 @@ export class IamController extends Controller {
|
|||
}
|
||||
try {
|
||||
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);
|
||||
const sessionCookie = this.luciaService.lucia.createBlankSessionCookie();
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
maxAge: sessionCookie.attributes.maxAge,
|
||||
domain: sessionCookie.attributes.domain,
|
||||
sameSite: sessionCookie.attributes.sameSite as any,
|
||||
secure: sessionCookie.attributes.secure,
|
||||
httpOnly: sessionCookie.attributes.httpOnly,
|
||||
expires: sessionCookie.attributes.expires,
|
||||
});
|
||||
deleteSessionTokenCookie(c);
|
||||
return c.json({ status: 'success' });
|
||||
} catch (error) {
|
||||
console.error('Error updating password', error);
|
||||
|
|
@ -116,16 +108,7 @@ export class IamController extends Controller {
|
|||
.post('/logout', requireAuth, openApi(logout), async (c) => {
|
||||
const sessionId = c.var.session.id;
|
||||
await this.iamService.logout(sessionId);
|
||||
const sessionCookie = this.luciaService.lucia.createBlankSessionCookie();
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
maxAge: sessionCookie.attributes.maxAge,
|
||||
domain: sessionCookie.attributes.domain,
|
||||
sameSite: sessionCookie.attributes.sameSite as any,
|
||||
secure: sessionCookie.attributes.secure,
|
||||
httpOnly: sessionCookie.attributes.httpOnly,
|
||||
expires: sessionCookie.attributes.expires,
|
||||
});
|
||||
deleteSessionTokenCookie(c);
|
||||
return c.json({ status: 'success' });
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { StatusCodes } from '$lib/constants/status-codes';
|
||||
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 { createErrorSchema } from 'stoker/openapi/schemas';
|
||||
import { taggedAuthRoute } from '../common/openapi/create-auth-route';
|
||||
|
|
|
|||
|
|
@ -1,44 +1,50 @@
|
|||
import 'reflect-metadata'
|
||||
import { Controller } from '$lib/server/api/common/types/controller'
|
||||
import { signinUsernameDto } from '$lib/server/api/dtos/signin-username.dto'
|
||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
||||
import { zValidator } from '@hono/zod-validator'
|
||||
import { setCookie } from 'hono/cookie'
|
||||
import { TimeSpan } from 'oslo'
|
||||
import 'reflect-metadata';
|
||||
import { Controller } from '$lib/server/api/common/types/controller';
|
||||
import { signinUsernameDto } from '$lib/server/api/dtos/signin-username.dto';
|
||||
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { openApi } from 'hono-zod-openapi';
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { limiter } from '../middleware/rate-limiter.middleware'
|
||||
import { LoginRequestsService } from '../services/loginrequest.service'
|
||||
import { signinUsername } from './login.routes'
|
||||
import { setCookie } from 'hono/cookie';
|
||||
import { TimeSpan } from 'oslo';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { limiter } from '../middleware/rate-limiter.middleware';
|
||||
import { LoginRequestsService } from '../services/loginrequest.service';
|
||||
import { signinUsername } from './login.routes';
|
||||
|
||||
@injectable()
|
||||
export class LoginController extends Controller {
|
||||
constructor(
|
||||
@inject(LoginRequestsService) private readonly loginRequestsService: LoginRequestsService,
|
||||
@inject(LuciaService) private luciaService: LuciaService,
|
||||
@inject(SessionsService) private luciaService: SessionsService,
|
||||
) {
|
||||
super()
|
||||
super();
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller.post('/', openApi(signinUsername), zValidator('json', signinUsernameDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||
const { username, password } = c.req.valid('json')
|
||||
const session = await this.loginRequestsService.verify({ username, password }, c.req)
|
||||
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id)
|
||||
console.log('set cookie', sessionCookie)
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
maxAge:
|
||||
sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds()
|
||||
? sessionCookie.attributes.maxAge
|
||||
: new TimeSpan(2, 'w').seconds(),
|
||||
domain: sessionCookie.attributes.domain,
|
||||
sameSite: sessionCookie.attributes.sameSite as any,
|
||||
secure: sessionCookie.attributes.secure,
|
||||
httpOnly: sessionCookie.attributes.httpOnly,
|
||||
expires: sessionCookie.attributes.expires,
|
||||
})
|
||||
return c.json({ message: 'ok' })
|
||||
})
|
||||
return this.controller.post(
|
||||
'/',
|
||||
openApi(signinUsername),
|
||||
zValidator('json', signinUsernameDto),
|
||||
limiter({ limit: 10, minutes: 60 }),
|
||||
async (c) => {
|
||||
const { username, password } = c.req.valid('json');
|
||||
const session = await this.loginRequestsService.verify({ username, password }, c.req);
|
||||
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id);
|
||||
console.log('set cookie', sessionCookie);
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
maxAge:
|
||||
sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds()
|
||||
? sessionCookie.attributes.maxAge
|
||||
: new TimeSpan(2, 'w').seconds(),
|
||||
domain: sessionCookie.attributes.domain,
|
||||
sameSite: sessionCookie.attributes.sameSite as any,
|
||||
secure: sessionCookie.attributes.secure,
|
||||
httpOnly: sessionCookie.attributes.httpOnly,
|
||||
expires: sessionCookie.attributes.expires,
|
||||
});
|
||||
return c.json({ message: 'ok' });
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import 'reflect-metadata'
|
||||
import { StatusCodes } from '$lib/constants/status-codes'
|
||||
import { Controller } from '$lib/server/api/common/types/controller'
|
||||
import { verifyTotpDto } from '$lib/server/api/dtos/verify-totp.dto'
|
||||
import { RecoveryCodesService } from '$lib/server/api/services/recovery-codes.service'
|
||||
import { TotpService } from '$lib/server/api/services/totp.service'
|
||||
import { UsersService } from '$lib/server/api/services/users.service'
|
||||
import { zValidator } from '@hono/zod-validator'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { CredentialsType } from '../databases/tables'
|
||||
import { requireAuth } from '../middleware/require-auth.middleware'
|
||||
import 'reflect-metadata';
|
||||
import { StatusCodes } from '$lib/constants/status-codes';
|
||||
import { Controller } from '$lib/server/api/common/types/controller';
|
||||
import { verifyTotpDto } from '$lib/server/api/dtos/verify-totp.dto';
|
||||
import { RecoveryCodesService } from '$lib/server/api/services/recovery-codes.service';
|
||||
import { TotpService } from '$lib/server/api/services/totp.service';
|
||||
import { UsersService } from '$lib/server/api/services/users.service';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { CredentialsType } from '../databases/postgres/tables';
|
||||
import { requireAuth } from '../middleware/require-auth.middleware';
|
||||
|
||||
@injectable()
|
||||
export class MfaController extends Controller {
|
||||
|
|
@ -17,59 +17,59 @@ export class MfaController extends Controller {
|
|||
@inject(TotpService) private readonly totpService: TotpService,
|
||||
@inject(UsersService) private readonly usersService: UsersService,
|
||||
) {
|
||||
super()
|
||||
super();
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/totp', requireAuth, async (c) => {
|
||||
const user = c.var.user
|
||||
const totpCredential = await this.totpService.findOneByUserId(user.id)
|
||||
return c.json({ totpCredential })
|
||||
const user = c.var.user;
|
||||
const totpCredential = await this.totpService.findOneByUserId(user.id);
|
||||
return c.json({ totpCredential });
|
||||
})
|
||||
.post('/totp', requireAuth, async (c) => {
|
||||
const user = c.var.user
|
||||
const totpCredential = await this.totpService.create(user.id)
|
||||
return c.json({ totpCredential })
|
||||
const user = c.var.user;
|
||||
const totpCredential = await this.totpService.create(user.id);
|
||||
return c.json({ totpCredential });
|
||||
})
|
||||
.delete('/totp', requireAuth, async (c) => {
|
||||
const user = c.var.user
|
||||
const user = c.var.user;
|
||||
try {
|
||||
await this.totpService.deleteOneByUserIdAndType(user.id, CredentialsType.TOTP)
|
||||
await this.recoveryCodesService.deleteAllRecoveryCodesByUserId(user.id)
|
||||
await this.usersService.updateUser(user.id, { mfa_enabled: false })
|
||||
console.log('TOTP deleted')
|
||||
return c.body(null, StatusCodes.NO_CONTENT)
|
||||
await this.totpService.deleteOneByUserIdAndType(user.id, CredentialsType.TOTP);
|
||||
await this.recoveryCodesService.deleteAllRecoveryCodesByUserId(user.id);
|
||||
await this.usersService.updateUser(user.id, { mfa_enabled: false });
|
||||
console.log('TOTP deleted');
|
||||
return c.body(null, StatusCodes.NO_CONTENT);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return c.status(StatusCodes.INTERNAL_SERVER_ERROR)
|
||||
console.error(e);
|
||||
return c.status(StatusCodes.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
})
|
||||
.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
|
||||
const existingCodes = await this.recoveryCodesService.findAllRecoveryCodesByUserId(user.id)
|
||||
const existingCodes = await this.recoveryCodesService.findAllRecoveryCodesByUserId(user.id);
|
||||
if (existingCodes && existingCodes.length > 0) {
|
||||
console.log('Recovery Codes found', existingCodes)
|
||||
return c.json({ recoveryCodes: existingCodes })
|
||||
console.log('Recovery Codes found', existingCodes);
|
||||
return c.json({ recoveryCodes: existingCodes });
|
||||
}
|
||||
const recoveryCodes = await this.recoveryCodesService.createRecoveryCodes(user.id)
|
||||
return c.json({ recoveryCodes })
|
||||
const recoveryCodes = await this.recoveryCodesService.createRecoveryCodes(user.id);
|
||||
return c.json({ recoveryCodes });
|
||||
})
|
||||
.post('/totp/verify', requireAuth, zValidator('json', verifyTotpDto), async (c) => {
|
||||
try {
|
||||
const user = c.var.user
|
||||
const { code } = c.req.valid('json')
|
||||
const verified = await this.totpService.verify(user.id, code)
|
||||
const user = c.var.user;
|
||||
const { code } = c.req.valid('json');
|
||||
const verified = await this.totpService.verify(user.id, code);
|
||||
if (verified) {
|
||||
await this.usersService.updateUser(user.id, { mfa_enabled: true })
|
||||
return c.json({}, StatusCodes.OK)
|
||||
await this.usersService.updateUser(user.id, { mfa_enabled: true });
|
||||
return c.json({}, StatusCodes.OK);
|
||||
}
|
||||
return c.json('Invalid code', StatusCodes.BAD_REQUEST)
|
||||
return c.json('Invalid code', StatusCodes.BAD_REQUEST);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return c.status(StatusCodes.INTERNAL_SERVER_ERROR)
|
||||
console.error(e);
|
||||
return c.status(StatusCodes.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,56 @@
|
|||
import 'reflect-metadata'
|
||||
import { Controller } from '$lib/server/api/common/types/controller'
|
||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
||||
import { OAuthService } from '$lib/server/api/services/oauth.service'
|
||||
import { github, google } from '$lib/server/auth'
|
||||
import { OAuth2RequestError } from 'arctic'
|
||||
import { getCookie, setCookie } from 'hono/cookie'
|
||||
import { TimeSpan } from 'oslo'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import type {OAuthUser} from "$lib/server/api/common/types/oauth";
|
||||
import 'reflect-metadata';
|
||||
import { Controller } from '$lib/server/api/common/types/controller';
|
||||
import type { OAuthUser } from '$lib/server/api/common/types/oauth';
|
||||
import { createSessionTokenCookie } from '$lib/server/api/common/utils/cookies';
|
||||
import { OAuthService } from '$lib/server/api/services/oauth.service';
|
||||
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||
import { github, google } from '$lib/server/auth';
|
||||
import { OAuth2RequestError } from 'arctic';
|
||||
import { getCookie, setCookie } from 'hono/cookie';
|
||||
import { TimeSpan } from 'oslo';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
|
||||
@injectable()
|
||||
export class OAuthController extends Controller {
|
||||
constructor(
|
||||
@inject(LuciaService) private luciaService: LuciaService,
|
||||
@inject(SessionsService) private sessionsService: SessionsService,
|
||||
@inject(OAuthService) private oauthService: OAuthService,
|
||||
) {
|
||||
super()
|
||||
super();
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/github', async (c) => {
|
||||
try {
|
||||
const code = c.req.query('code')?.toString() ?? null
|
||||
const state = c.req.query('state')?.toString() ?? null
|
||||
const storedState = getCookie(c).github_oauth_state ?? null
|
||||
const code = c.req.query('code')?.toString() ?? null;
|
||||
const state = c.req.query('state')?.toString() ?? null;
|
||||
const storedState = getCookie(c).github_oauth_state ?? null;
|
||||
|
||||
if (!code || !state || !storedState || state !== storedState) {
|
||||
return c.body(null, 400)
|
||||
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', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.accessToken}`,
|
||||
},
|
||||
})
|
||||
const githubUser: GitHubUser = await githubUserResponse.json()
|
||||
});
|
||||
const githubUser: GitHubUser = await githubUserResponse.json();
|
||||
|
||||
const oAuthUser: OAuthUser = {
|
||||
sub: `${githubUser.id}`,
|
||||
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 sessionCookie = this.luciaService.lucia.createSessionCookie(session.id)
|
||||
const sessionToken = this.sessionsService.generateSessionToken();
|
||||
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, {
|
||||
path: sessionCookie.attributes.path,
|
||||
|
|
@ -60,37 +63,37 @@ export class OAuthController extends Controller {
|
|||
secure: sessionCookie.attributes.secure,
|
||||
httpOnly: sessionCookie.attributes.httpOnly,
|
||||
expires: sessionCookie.attributes.expires,
|
||||
})
|
||||
});
|
||||
|
||||
return c.json({ message: 'ok' })
|
||||
return c.json({ message: 'ok' });
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
// the specific error message depends on the provider
|
||||
if (error instanceof OAuth2RequestError) {
|
||||
// invalid code
|
||||
return c.body(null, 400)
|
||||
return c.body(null, 400);
|
||||
}
|
||||
return c.body(null, 500)
|
||||
return c.body(null, 500);
|
||||
}
|
||||
})
|
||||
.get('/google', async (c) => {
|
||||
try {
|
||||
const code = c.req.query('code')?.toString() ?? null
|
||||
const state = c.req.query('state')?.toString() ?? null
|
||||
const storedState = getCookie(c).google_oauth_state ?? null
|
||||
const storedCodeVerifier = getCookie(c).google_oauth_code_verifier ?? null
|
||||
const code = c.req.query('code')?.toString() ?? null;
|
||||
const state = c.req.query('state')?.toString() ?? null;
|
||||
const storedState = getCookie(c).google_oauth_state ?? null;
|
||||
const storedCodeVerifier = getCookie(c).google_oauth_code_verifier ?? null;
|
||||
|
||||
if (!code || !storedState || !storedCodeVerifier || state !== storedState) {
|
||||
return c.body(null, 400)
|
||||
return c.body(null, 400);
|
||||
}
|
||||
|
||||
const tokens = await google.validateAuthorizationCode(code, storedCodeVerifier)
|
||||
const googleUserResponse = await fetch("https://openidconnect.googleapis.com/v1/userinfo", {
|
||||
const tokens = await google.validateAuthorizationCode(code, storedCodeVerifier);
|
||||
const googleUserResponse = await fetch('https://openidconnect.googleapis.com/v1/userinfo', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.accessToken}`,
|
||||
},
|
||||
})
|
||||
const googleUser: GoogleUser = await googleUserResponse.json()
|
||||
});
|
||||
const googleUser: GoogleUser = await googleUserResponse.json();
|
||||
|
||||
const oAuthUser: OAuthUser = {
|
||||
sub: googleUser.sub,
|
||||
|
|
@ -100,12 +103,12 @@ export class OAuthController extends Controller {
|
|||
username: googleUser.email,
|
||||
email: googleUser.email,
|
||||
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 sessionCookie = this.luciaService.lucia.createSessionCookie(session.id)
|
||||
const session = await this.luciaService.lucia.createSession(userId, {});
|
||||
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id);
|
||||
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
|
|
@ -118,33 +121,33 @@ export class OAuthController extends Controller {
|
|||
secure: sessionCookie.attributes.secure,
|
||||
httpOnly: sessionCookie.attributes.httpOnly,
|
||||
expires: sessionCookie.attributes.expires,
|
||||
})
|
||||
});
|
||||
|
||||
return c.json({ message: 'ok' })
|
||||
return c.json({ message: 'ok' });
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
// the specific error message depends on the provider
|
||||
if (error instanceof OAuth2RequestError) {
|
||||
// invalid code
|
||||
return c.body(null, 400)
|
||||
return c.body(null, 400);
|
||||
}
|
||||
return c.body(null, 500)
|
||||
return c.body(null, 500);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface GitHubUser {
|
||||
id: number
|
||||
login: string
|
||||
id: number;
|
||||
login: string;
|
||||
}
|
||||
|
||||
interface GoogleUser {
|
||||
sub: string
|
||||
name: string
|
||||
given_name: string
|
||||
family_name: string
|
||||
picture: string
|
||||
email: string
|
||||
email_verified: boolean
|
||||
sub: string;
|
||||
name: string;
|
||||
given_name: string;
|
||||
family_name: string;
|
||||
picture: string;
|
||||
email: string;
|
||||
email_verified: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,43 @@
|
|||
import 'reflect-metadata'
|
||||
import { Controller } from '$lib/server/api/common/types/controller'
|
||||
import { signupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto'
|
||||
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware'
|
||||
import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service'
|
||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
||||
import { UsersService } from '$lib/server/api/services/users.service'
|
||||
import { zValidator } from '@hono/zod-validator'
|
||||
import { setCookie } from 'hono/cookie'
|
||||
import { TimeSpan } from 'oslo'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import 'reflect-metadata';
|
||||
import { Controller } from '$lib/server/api/common/types/controller';
|
||||
import { signupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto';
|
||||
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware';
|
||||
import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service';
|
||||
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||
import { UsersService } from '$lib/server/api/services/users.service';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { setCookie } from 'hono/cookie';
|
||||
import { TimeSpan } from 'oslo';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
|
||||
@injectable()
|
||||
export class SignupController extends Controller {
|
||||
constructor(
|
||||
@inject(UsersService) private readonly usersService: UsersService,
|
||||
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
|
||||
@inject(LuciaService) private luciaService: LuciaService,
|
||||
@inject(SessionsService) private luciaService: SessionsService,
|
||||
) {
|
||||
super()
|
||||
super();
|
||||
}
|
||||
|
||||
routes() {
|
||||
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 existingUser = await this.usersService.findOneByUsername(username)
|
||||
const { firstName, lastName, email, username, password, confirm_password } = await c.req.valid('json');
|
||||
const existingUser = await this.usersService.findOneByUsername(username);
|
||||
|
||||
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) {
|
||||
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 sessionCookie = this.luciaService.lucia.createSessionCookie(session.id)
|
||||
console.log('set cookie', sessionCookie)
|
||||
const session = await this.loginRequestService.createUserSession(user.id, c.req, undefined);
|
||||
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id);
|
||||
console.log('set cookie', sessionCookie);
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
maxAge:
|
||||
|
|
@ -49,8 +49,8 @@ export class SignupController extends Controller {
|
|||
secure: sessionCookie.attributes.secure,
|
||||
httpOnly: sessionCookie.attributes.httpOnly,
|
||||
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 { createInsertSchema, createSelectSchema } from 'drizzle-zod'
|
||||
import type { z } from 'zod'
|
||||
import { collections } from '$lib/server/api/databases/postgres/tables';
|
||||
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
|
||||
import type { z } from 'zod';
|
||||
|
||||
export const InsertCollectionSchema = createInsertSchema(collections, {
|
||||
name: (schema) =>
|
||||
|
|
@ -10,15 +10,15 @@ export const InsertCollectionSchema = createInsertSchema(collections, {
|
|||
cuid: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
})
|
||||
});
|
||||
|
||||
export type InsertCollectionSchema = z.infer<typeof InsertCollectionSchema>
|
||||
export type InsertCollectionSchema = z.infer<typeof InsertCollectionSchema>;
|
||||
|
||||
export const SelectCollectionSchema = createSelectSchema(collections).omit({
|
||||
id: true,
|
||||
user_id: true,
|
||||
createdAt: 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 { createInsertSchema, createSelectSchema } from 'drizzle-zod'
|
||||
import type { z } from 'zod'
|
||||
import { usersTable } from '$lib/server/api/databases/postgres/tables';
|
||||
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
|
||||
import type { z } from 'zod';
|
||||
|
||||
export const InsertUserSchema = createInsertSchema(usersTable, {
|
||||
email: (schema) => schema.email.max(64).email().optional(),
|
||||
|
|
@ -15,10 +15,10 @@ export const InsertUserSchema = createInsertSchema(usersTable, {
|
|||
cuid: true,
|
||||
createdAt: 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 { type Table, getTableName, sql } from 'drizzle-orm'
|
||||
import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
|
||||
import env from '../common/env'
|
||||
import { DrizzleService } from '../services/drizzle.service'
|
||||
import * as seeds from './seeds'
|
||||
import * as schema from './tables'
|
||||
import 'reflect-metadata';
|
||||
import { type Table, getTableName, sql } from 'drizzle-orm';
|
||||
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import env from '../../common/env';
|
||||
import { DrizzleService } from '../../services/drizzle.service';
|
||||
import * as seeds from './seeds';
|
||||
import * as schema from './tables';
|
||||
|
||||
const drizzleService = new DrizzleService()
|
||||
const drizzleService = new DrizzleService();
|
||||
|
||||
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) {
|
||||
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 [
|
||||
|
|
@ -45,11 +45,11 @@ for (const table of [
|
|||
schema.wishlistsTable,
|
||||
]) {
|
||||
// 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.users(drizzleService.db)
|
||||
await seeds.roles(drizzleService.db);
|
||||
await seeds.users(drizzleService.db);
|
||||
|
||||
await drizzleService.dispose()
|
||||
process.exit()
|
||||
await drizzleService.dispose();
|
||||
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 type { db } from '../../packages/drizzle';
|
||||
import { HashingService } from '../../services/hashing.service';
|
||||
import type { db } from '../../../packages/drizzle';
|
||||
import { HashingService } from '../../../services/hashing.service';
|
||||
import * as schema from '../tables';
|
||||
import users from './data/users.json';
|
||||
|
||||
|
|
@ -1,23 +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 { categoriesToExternalIdsTable } from './categoriesToExternalIds.table'
|
||||
import { categories_to_games_table } from './categoriesToGames.table'
|
||||
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 { categoriesToExternalIdsTable } from './categoriesToExternalIds.table';
|
||||
import { categories_to_games_table } from './categoriesToGames.table';
|
||||
|
||||
export const categoriesTable = pgTable('categories', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
id: uuid().primaryKey().defaultRandom(),
|
||||
cuid: text()
|
||||
.unique()
|
||||
.$defaultFn(() => cuid2()),
|
||||
name: text('name'),
|
||||
slug: text('slug'),
|
||||
name: text(),
|
||||
slug: text(),
|
||||
...timestamps,
|
||||
})
|
||||
});
|
||||
|
||||
export type Categories = InferSelectModel<typeof categoriesTable>
|
||||
export type Categories = InferSelectModel<typeof categoriesTable>;
|
||||
|
||||
export const categories_relations = relations(categoriesTable, ({ many }) => ({
|
||||
categories_to_games: many(categories_to_games_table),
|
||||
categoriesToExternalIds: many(categoriesToExternalIdsTable),
|
||||
}))
|
||||
}));
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import { relations } from 'drizzle-orm'
|
||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
||||
import { categoriesTable } from './categories.table'
|
||||
import { externalIdsTable } from './externalIds.table'
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||
import { categoriesTable } from './categories.table';
|
||||
import { externalIdsTable } from './externalIds.table';
|
||||
|
||||
export const categoriesToExternalIdsTable = pgTable(
|
||||
'categories_to_external_ids',
|
||||
{
|
||||
categoryId: uuid('category_id')
|
||||
categoryId: uuid()
|
||||
.notNull()
|
||||
.references(() => categoriesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
externalId: uuid('external_id')
|
||||
externalId: uuid()
|
||||
.notNull()
|
||||
.references(() => externalIdsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
},
|
||||
|
|
@ -18,9 +18,9 @@ export const categoriesToExternalIdsTable = pgTable(
|
|||
categoriesToExternalIdsPkey: primaryKey({
|
||||
columns: [table.categoryId, table.externalId],
|
||||
}),
|
||||
}
|
||||
};
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
export const categoriesToExternalIdsRelations = relations(categoriesToExternalIdsTable, ({ one }) => ({
|
||||
category: one(categoriesTable, {
|
||||
|
|
@ -31,4 +31,4 @@ export const categoriesToExternalIdsRelations = relations(categoriesToExternalId
|
|||
fields: [categoriesToExternalIdsTable.externalId],
|
||||
references: [externalIdsTable.id],
|
||||
}),
|
||||
}))
|
||||
}));
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import { relations } from 'drizzle-orm'
|
||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
||||
import { gamesTable } from '././games.table'
|
||||
import { categoriesTable } from './categories.table'
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||
import { categoriesTable } from './categories.table';
|
||||
import { gamesTable } from './games.table';
|
||||
|
||||
export const categories_to_games_table = pgTable(
|
||||
'categories_to_games',
|
||||
{
|
||||
category_id: uuid('category_id')
|
||||
category_id: uuid()
|
||||
.notNull()
|
||||
.references(() => categoriesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
game_id: uuid('game_id')
|
||||
game_id: uuid()
|
||||
.notNull()
|
||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
},
|
||||
|
|
@ -18,9 +18,9 @@ export const categories_to_games_table = pgTable(
|
|||
categoriesToGamesPkey: primaryKey({
|
||||
columns: [table.category_id, table.game_id],
|
||||
}),
|
||||
}
|
||||
};
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
export const categories_to_games_relations = relations(categories_to_games_table, ({ one }) => ({
|
||||
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],
|
||||
references: [gamesTable.id],
|
||||
}),
|
||||
}))
|
||||
}));
|
||||
|
|
@ -1,26 +1,26 @@
|
|||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { gamesTable } from '././games.table'
|
||||
import { collections } from './collections.table'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { timestamps } from '../../../common/utils/table';
|
||||
import { collections } from './collections.table';
|
||||
import { gamesTable } from './games.table';
|
||||
|
||||
export const collection_items = pgTable('collection_items', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
id: uuid().primaryKey().defaultRandom(),
|
||||
cuid: text()
|
||||
.unique()
|
||||
.$defaultFn(() => cuid2()),
|
||||
collection_id: uuid('collection_id')
|
||||
collection_id: uuid()
|
||||
.notNull()
|
||||
.references(() => collections.id, { onDelete: 'cascade' }),
|
||||
game_id: uuid('game_id')
|
||||
game_id: uuid()
|
||||
.notNull()
|
||||
.references(() => gamesTable.id, { onDelete: 'cascade' }),
|
||||
times_played: integer('times_played').default(0),
|
||||
times_played: integer().default(0),
|
||||
...timestamps,
|
||||
})
|
||||
});
|
||||
|
||||
export type CollectionItemsTable = InferSelectModel<typeof collection_items>
|
||||
export type CollectionItemsTable = InferSelectModel<typeof collection_items>;
|
||||
|
||||
export const collection_item_relations = relations(collection_items, ({ one }) => ({
|
||||
collection: one(collections, {
|
||||
|
|
@ -31,4 +31,4 @@ export const collection_item_relations = relations(collection_items, ({ one }) =
|
|||
fields: [collection_items.game_id],
|
||||
references: [gamesTable.id],
|
||||
}),
|
||||
}))
|
||||
}));
|
||||
|
|
@ -2,19 +2,19 @@ import { createId as cuid2 } from '@paralleldrive/cuid2';
|
|||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createSelectSchema } from 'drizzle-zod';
|
||||
import { timestamps } from '../../common/utils/table';
|
||||
import { timestamps } from '../../../common/utils/table';
|
||||
import { collection_items } from './collectionItems.table';
|
||||
import { usersTable } from './users.table';
|
||||
|
||||
export const collections = pgTable('collections', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
id: uuid().primaryKey().defaultRandom(),
|
||||
cuid: text()
|
||||
.unique()
|
||||
.$defaultFn(() => cuid2()),
|
||||
user_id: uuid('user_id')
|
||||
user_id: uuid()
|
||||
.notNull()
|
||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull().default('My Collection'),
|
||||
name: text().notNull().default('My Collection'),
|
||||
...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 { 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'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { timestamps } from '../../../common/utils/table';
|
||||
import { gamesTable } from './games.table';
|
||||
|
||||
export const expansionsTable = pgTable('expansions', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
id: uuid().primaryKey().defaultRandom(),
|
||||
cuid: text()
|
||||
.unique()
|
||||
.$defaultFn(() => cuid2()),
|
||||
base_game_id: uuid('base_game_id')
|
||||
base_game_id: uuid()
|
||||
.notNull()
|
||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
game_id: uuid('game_id')
|
||||
game_id: uuid()
|
||||
.notNull()
|
||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
...timestamps,
|
||||
})
|
||||
});
|
||||
|
||||
export type Expansions = InferSelectModel<typeof expansionsTable>
|
||||
export type Expansions = InferSelectModel<typeof expansionsTable>;
|
||||
|
||||
export const expansion_relations = relations(expansionsTable, ({ one }) => ({
|
||||
baseGame: one(gamesTable, {
|
||||
|
|
@ -29,4 +29,4 @@ export const expansion_relations = relations(expansionsTable, ({ one }) => ({
|
|||
fields: [expansionsTable.game_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 { gamesTable } from '././games.table'
|
||||
import { externalIdsTable } from './externalIds.table'
|
||||
import { relations } from 'drizzle-orm'
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||
import { externalIdsTable } from './externalIds.table';
|
||||
import { gamesTable } from './games.table';
|
||||
|
||||
export const gamesToExternalIdsTable = pgTable(
|
||||
'games_to_external_ids',
|
||||
{
|
||||
gameId: uuid('game_id')
|
||||
gameId: uuid()
|
||||
.notNull()
|
||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
externalId: uuid('external_id')
|
||||
externalId: uuid()
|
||||
.notNull()
|
||||
.references(() => externalIdsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
},
|
||||
|
|
@ -18,9 +18,9 @@ export const gamesToExternalIdsTable = pgTable(
|
|||
gamesToExternalIdsPkey: primaryKey({
|
||||
columns: [table.gameId, table.externalId],
|
||||
}),
|
||||
}
|
||||
};
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
export const gamesToExternalIdsRelations = relations(gamesToExternalIdsTable, ({ one }) => ({
|
||||
game: one(gamesTable, {
|
||||
|
|
@ -31,4 +31,4 @@ export const gamesToExternalIdsRelations = relations(gamesToExternalIdsTable, ({
|
|||
fields: [gamesToExternalIdsTable.externalId],
|
||||
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 { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
||||
import { gamesTable } from '././games.table'
|
||||
import { mechanicsTable } from './mechanics.table'
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||
import { gamesTable } from './games.table';
|
||||
import { mechanicsTable } from './mechanics.table';
|
||||
|
||||
export const mechanics_to_games = pgTable(
|
||||
'mechanics_to_games',
|
||||
{
|
||||
mechanic_id: uuid('mechanic_id')
|
||||
mechanic_id: uuid()
|
||||
.notNull()
|
||||
.references(() => mechanicsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
game_id: uuid('game_id')
|
||||
game_id: uuid()
|
||||
.notNull()
|
||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
},
|
||||
|
|
@ -18,9 +18,9 @@ export const mechanics_to_games = pgTable(
|
|||
mechanicsToGamesPkey: primaryKey({
|
||||
columns: [table.mechanic_id, table.game_id],
|
||||
}),
|
||||
}
|
||||
};
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
export const mechanics_to_games_relations = relations(mechanics_to_games, ({ one }) => ({
|
||||
mechanic: one(mechanicsTable, {
|
||||
|
|
@ -31,4 +31,4 @@ export const mechanics_to_games_relations = relations(mechanics_to_games, ({ one
|
|||
fields: [mechanics_to_games.game_id],
|
||||
references: [gamesTable.id],
|
||||
}),
|
||||
}))
|
||||
}));
|
||||
|
|
@ -1,25 +1,25 @@
|
|||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { usersTable } from './users.table'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
||||
import { timestamps } from '../../../common/utils/table';
|
||||
import { usersTable } from './users.table';
|
||||
|
||||
export const password_reset_tokens = pgTable('password_reset_tokens', {
|
||||
id: text('id')
|
||||
id: text()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => cuid2()),
|
||||
user_id: uuid('user_id')
|
||||
user_id: uuid()
|
||||
.notNull()
|
||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
expires_at: timestamp('expires_at'),
|
||||
expires_at: timestamp(),
|
||||
...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 }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [password_reset_tokens.user_id],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}))
|
||||
}));
|
||||
|
|
@ -1,23 +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'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { timestamps } from '../../../common/utils/table';
|
||||
import { publishersToExternalIdsTable } from './publishersToExternalIds.table';
|
||||
import { publishers_to_games } from './publishersToGames.table';
|
||||
|
||||
export const publishersTable = pgTable('publishers', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
id: uuid().primaryKey().defaultRandom(),
|
||||
cuid: text()
|
||||
.unique()
|
||||
.$defaultFn(() => cuid2()),
|
||||
name: text('name'),
|
||||
slug: text('slug'),
|
||||
name: text(),
|
||||
slug: text(),
|
||||
...timestamps,
|
||||
})
|
||||
});
|
||||
|
||||
export type Publishers = InferSelectModel<typeof publishersTable>
|
||||
export type Publishers = InferSelectModel<typeof publishersTable>;
|
||||
|
||||
export const publishers_relations = relations(publishersTable, ({ many }) => ({
|
||||
publishersToGames: many(publishers_to_games),
|
||||
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 { externalIdsTable } from './externalIds.table'
|
||||
import { publishersTable } from './publishers.table'
|
||||
import { relations } from 'drizzle-orm'
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||
import { externalIdsTable } from './externalIds.table';
|
||||
import { publishersTable } from './publishers.table';
|
||||
|
||||
export const publishersToExternalIdsTable = pgTable(
|
||||
'publishers_to_external_ids',
|
||||
{
|
||||
publisherId: uuid('publisher_id')
|
||||
publisherId: uuid()
|
||||
.notNull()
|
||||
.references(() => publishersTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
externalId: uuid('external_id')
|
||||
externalId: uuid()
|
||||
.notNull()
|
||||
.references(() => externalIdsTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
},
|
||||
|
|
@ -18,9 +18,9 @@ export const publishersToExternalIdsTable = pgTable(
|
|||
publishersToExternalIdsPkey: primaryKey({
|
||||
columns: [table.publisherId, table.externalId],
|
||||
}),
|
||||
}
|
||||
};
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
export const publishersToExternalIdsRelations = relations(publishersToExternalIdsTable, ({ one }) => ({
|
||||
publisher: one(publishersTable, {
|
||||
|
|
@ -31,4 +31,4 @@ export const publishersToExternalIdsRelations = relations(publishersToExternalId
|
|||
fields: [publishersToExternalIdsTable.externalId],
|
||||
references: [externalIdsTable.id],
|
||||
}),
|
||||
}))
|
||||
}));
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import { relations } from 'drizzle-orm'
|
||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'
|
||||
import { gamesTable } from '././games.table'
|
||||
import { publishersTable } from './publishers.table'
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
|
||||
import { gamesTable } from './games.table';
|
||||
import { publishersTable } from './publishers.table';
|
||||
|
||||
export const publishers_to_games = pgTable(
|
||||
'publishers_to_games',
|
||||
{
|
||||
publisher_id: uuid('publisher_id')
|
||||
publisher_id: uuid()
|
||||
.notNull()
|
||||
.references(() => publishersTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
game_id: uuid('game_id')
|
||||
game_id: uuid()
|
||||
.notNull()
|
||||
.references(() => gamesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
},
|
||||
|
|
@ -18,9 +18,9 @@ export const publishers_to_games = pgTable(
|
|||
publishersToGamesPkey: primaryKey({
|
||||
columns: [table.publisher_id, table.game_id],
|
||||
}),
|
||||
}
|
||||
};
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
export const publishers_to_games_relations = relations(publishers_to_games, ({ one }) => ({
|
||||
publisher: one(publishersTable, {
|
||||
|
|
@ -31,4 +31,4 @@ export const publishers_to_games_relations = relations(publishers_to_games, ({ o
|
|||
fields: [publishers_to_games.game_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 { relations, type InferSelectModel } from 'drizzle-orm';
|
||||
import { cuid2 } from '../../../common/utils/table';
|
||||
import { usersTable } from './users.table';
|
||||
|
||||
export const sessionsTable = pgTable('sessions', {
|
||||
id: text('id').primaryKey(),
|
||||
userId: uuid('user_id')
|
||||
id: cuid2().primaryKey(),
|
||||
userId: uuid()
|
||||
.notNull()
|
||||
.references(() => usersTable.id),
|
||||
expiresAt: timestamp('expires_at', {
|
||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
expiresAt: timestamp({
|
||||
withTimezone: true,
|
||||
mode: 'date',
|
||||
}).notNull(),
|
||||
ipCountry: text('ip_country'),
|
||||
ipAddress: text('ip_address'),
|
||||
twoFactorAuthEnabled: boolean('two_factor_auth_enabled').default(false),
|
||||
isTwoFactorAuthenticated: boolean('is_two_factor_authenticated').default(false),
|
||||
ipCountry: text(),
|
||||
ipAddress: text(),
|
||||
twoFactorAuthEnabled: boolean().default(false),
|
||||
isTwoFactorAuthenticated: boolean().default(false),
|
||||
});
|
||||
|
||||
export const sessionsRelations = relations(sessionsTable, ({ one }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [sessionsTable.userId],
|
||||
references: [usersTable.id],
|
||||
})
|
||||
}),
|
||||
}));
|
||||
|
||||
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 { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { timestamps } from '../../common/utils/table'
|
||||
import { rolesTable } from './roles.table'
|
||||
import { usersTable } from './users.table'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { timestamps } from '../../../common/utils/table';
|
||||
import { rolesTable } from './roles.table';
|
||||
import { usersTable } from './users.table';
|
||||
|
||||
export const user_roles = pgTable('user_roles', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
id: uuid().primaryKey().defaultRandom(),
|
||||
cuid: text()
|
||||
.unique()
|
||||
.$defaultFn(() => cuid2()),
|
||||
user_id: uuid('user_id')
|
||||
user_id: uuid()
|
||||
.notNull()
|
||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
role_id: uuid('role_id')
|
||||
role_id: uuid()
|
||||
.notNull()
|
||||
.references(() => rolesTable.id, { onDelete: 'cascade' }),
|
||||
primary: boolean('primary').default(false),
|
||||
primary: boolean().default(false),
|
||||
...timestamps,
|
||||
})
|
||||
});
|
||||
|
||||
export const user_role_relations = relations(user_roles, ({ one }) => ({
|
||||
role: one(rolesTable, {
|
||||
|
|
@ -29,6 +29,6 @@ export const user_role_relations = relations(user_roles, ({ one }) => ({
|
|||
fields: [user_roles.user_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 { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createSelectSchema } from 'drizzle-zod';
|
||||
import { timestamps } from '../../common/utils/table';
|
||||
import { timestamps } from '../../../common/utils/table';
|
||||
import { user_roles } from './userRoles.table';
|
||||
|
||||
export const usersTable = pgTable('users', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
id: uuid().primaryKey().defaultRandom(),
|
||||
cuid: text()
|
||||
.unique()
|
||||
.$defaultFn(() => cuid2()),
|
||||
username: text('username').unique(),
|
||||
email: text('email').unique(),
|
||||
first_name: text('first_name'),
|
||||
last_name: text('last_name'),
|
||||
verified: boolean('verified').default(false),
|
||||
receive_email: boolean('receive_email').default(false),
|
||||
email_verified: boolean('email_verified').default(false),
|
||||
picture: text('picture'),
|
||||
mfa_enabled: boolean('mfa_enabled').notNull().default(false),
|
||||
theme: text('theme').default('system'),
|
||||
username: text().unique(),
|
||||
email: text().unique(),
|
||||
first_name: text(),
|
||||
last_name: text(),
|
||||
verified: boolean().default(false),
|
||||
receive_email: boolean().default(false),
|
||||
email_verified: boolean().default(false),
|
||||
picture: text(),
|
||||
mfa_enabled: boolean().notNull().default(false),
|
||||
theme: text().default('system'),
|
||||
...timestamps,
|
||||
});
|
||||
|
||||
|
|
@ -1,25 +1,25 @@
|
|||
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'
|
||||
import { wishlistsTable } from './wishlists.table'
|
||||
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';
|
||||
import { wishlistsTable } from './wishlists.table';
|
||||
|
||||
export const wishlist_items = pgTable('wishlist_items', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
id: uuid().primaryKey().defaultRandom(),
|
||||
cuid: text()
|
||||
.unique()
|
||||
.$defaultFn(() => cuid2()),
|
||||
wishlist_id: uuid('wishlist_id')
|
||||
wishlist_id: uuid()
|
||||
.notNull()
|
||||
.references(() => wishlistsTable.id, { onDelete: 'cascade' }),
|
||||
game_id: uuid('game_id')
|
||||
game_id: uuid()
|
||||
.notNull()
|
||||
.references(() => gamesTable.id, { onDelete: 'cascade' }),
|
||||
...timestamps,
|
||||
})
|
||||
});
|
||||
|
||||
export type WishlistItemsTable = InferSelectModel<typeof wishlist_items>
|
||||
export type WishlistItemsTable = InferSelectModel<typeof wishlist_items>;
|
||||
|
||||
export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({
|
||||
wishlist: one(wishlistsTable, {
|
||||
|
|
@ -30,4 +30,4 @@ export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({
|
|||
fields: [wishlist_items.game_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 { createMiddleware } from 'hono/factory';
|
||||
import { verifyRequestOrigin } from 'oslo/request';
|
||||
|
|
@ -6,7 +7,7 @@ import { container } from 'tsyringe';
|
|||
import type { AppBindings } from '../common/types/hono';
|
||||
|
||||
// resolve dependencies from the container
|
||||
const { lucia } = container.resolve(LuciaService);
|
||||
const sessionService = container.resolve(SessionsService);
|
||||
|
||||
export const verifyOrigin: MiddlewareHandler<AppBindings> = createMiddleware(async (c, next) => {
|
||||
if (c.req.method === 'GET') {
|
||||
|
|
@ -30,7 +31,7 @@ export const validateAuthSession: MiddlewareHandler<AppBindings> = createMiddlew
|
|||
|
||||
const { session, user } = await lucia.validateSession(sessionId);
|
||||
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) {
|
||||
c.header('Set-Cookie', lucia.createBlankSessionCookie().serialize(), { append: true });
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
import { drizzle } from 'drizzle-orm/node-postgres'
|
||||
import pg from 'pg'
|
||||
import { config } from '../common/config'
|
||||
import * as schema from '../databases/tables'
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { Pool } from 'pg';
|
||||
import { config } from '../common/config';
|
||||
import * as schema from '../databases/postgres/tables';
|
||||
|
||||
// create the connection
|
||||
export const pool = new pg.Pool({
|
||||
user: config.DATABASE_USER,
|
||||
password: config.DATABASE_PASSWORD,
|
||||
host: config.DATABASE_HOST,
|
||||
port: Number(config.DATABASE_PORT).valueOf(),
|
||||
database: config.DATABASE_DB,
|
||||
ssl: config.DATABASE_HOST !== 'localhost',
|
||||
max: config.DB_MIGRATING || config.DB_SEEDING ? 1 : undefined,
|
||||
})
|
||||
export const pool = new Pool({
|
||||
user: config.postgres.user,
|
||||
password: config.postgres.password,
|
||||
host: config.postgres.host,
|
||||
port: Number(config.postgres.port).valueOf(),
|
||||
database: config.postgres.database,
|
||||
ssl: config.postgres.host !== 'localhost',
|
||||
max: config.postgres.migrating || config.postgres.seeding ? 1 : undefined,
|
||||
});
|
||||
|
||||
export const db = drizzle(pool, {
|
||||
export const db = drizzle({
|
||||
client: pool,
|
||||
casing: 'snake_case',
|
||||
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 { DrizzleService } from '$lib/server/api/services/drizzle.service'
|
||||
import { type InferInsertModel, eq } from 'drizzle-orm'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { collections } from '../databases/tables'
|
||||
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 { collections } from '../databases/postgres/tables';
|
||||
|
||||
export type CreateCollection = InferInsertModel<typeof collections>
|
||||
export type UpdateCollection = Partial<CreateCollection>
|
||||
export type CreateCollection = InferInsertModel<typeof collections>;
|
||||
export type UpdateCollection = Partial<CreateCollection>;
|
||||
|
||||
@injectable()
|
||||
export class CollectionsRepository {
|
||||
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
|
||||
|
||||
async findAll(db = this.drizzle.db) {
|
||||
return db.query.collections.findMany()
|
||||
return db.query.collections.findMany();
|
||||
}
|
||||
|
||||
async findOneById(id: string, db = this.drizzle.db) {
|
||||
|
|
@ -22,7 +22,7 @@ export class CollectionsRepository {
|
|||
cuid: true,
|
||||
name: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findOneByCuid(cuid: string, db = this.drizzle.db) {
|
||||
|
|
@ -32,7 +32,7 @@ export class CollectionsRepository {
|
|||
cuid: true,
|
||||
name: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findOneByUserId(userId: string, db = this.drizzle.db) {
|
||||
|
|
@ -42,7 +42,7 @@ export class CollectionsRepository {
|
|||
cuid: true,
|
||||
name: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findAllByUserId(userId: string, db = this.drizzle.db) {
|
||||
|
|
@ -53,7 +53,7 @@ export class CollectionsRepository {
|
|||
name: true,
|
||||
createdAt: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findAllByUserIdWithDetails(userId: string, db = this.drizzle.db) {
|
||||
|
|
@ -70,14 +70,14 @@ export class CollectionsRepository {
|
|||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
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 { CredentialsType, credentialsTable } from '$lib/server/api/databases/tables/credentials.table'
|
||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service'
|
||||
import { type InferInsertModel, and, eq } from 'drizzle-orm'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { takeFirstOrThrow } from '../common/utils/repository'
|
||||
import 'reflect-metadata';
|
||||
import { CredentialsType, credentialsTable } from '$lib/server/api/databases/postgres/tables/credentials.table';
|
||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service';
|
||||
import { type InferInsertModel, and, eq } from 'drizzle-orm';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { takeFirstOrThrow } from '../common/utils/repository';
|
||||
|
||||
export type CreateCredentials = InferInsertModel<typeof credentialsTable>
|
||||
export type UpdateCredentials = Partial<CreateCredentials>
|
||||
export type DeleteCredentials = Pick<CreateCredentials, 'id'>
|
||||
export type CreateCredentials = InferInsertModel<typeof credentialsTable>;
|
||||
export type UpdateCredentials = Partial<CreateCredentials>;
|
||||
export type DeleteCredentials = Pick<CreateCredentials, 'id'>;
|
||||
|
||||
@injectable()
|
||||
export class CredentialsRepository {
|
||||
|
|
@ -16,56 +16,56 @@ export class CredentialsRepository {
|
|||
async findOneByUserId(userId: string, db = this.drizzle.db) {
|
||||
return db.query.credentialsTable.findFirst({
|
||||
where: eq(credentialsTable.user_id, userId),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findOneByUserIdAndType(userId: string, type: CredentialsType, db = this.drizzle.db) {
|
||||
return db.query.credentialsTable.findFirst({
|
||||
where: and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, type)),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findPasswordCredentialsByUserId(userId: string, db = this.drizzle.db) {
|
||||
return db.query.credentialsTable.findFirst({
|
||||
where: and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, CredentialsType.PASSWORD)),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findTOTPCredentialsByUserId(userId: string, db = this.drizzle.db) {
|
||||
return db.query.credentialsTable.findFirst({
|
||||
where: and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, CredentialsType.TOTP)),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findOneById(id: string, db = this.drizzle.db) {
|
||||
return db.query.credentialsTable.findFirst({
|
||||
where: eq(credentialsTable.id, id),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findOneByIdOrThrow(id: string, db = this.drizzle.db) {
|
||||
const credentials = await this.findOneById(id, db)
|
||||
if (!credentials) throw Error('Credentials not found')
|
||||
return credentials
|
||||
const credentials = await this.findOneById(id, db);
|
||||
if (!credentials) throw Error('Credentials not found');
|
||||
return credentials;
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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 { inject, injectable } from 'tsyringe'
|
||||
import { takeFirstOrThrow } from '../common/utils/repository'
|
||||
import { federatedIdentityTable } from '../databases/tables'
|
||||
import { DrizzleService } from '../services/drizzle.service'
|
||||
import { type InferInsertModel, and, eq } from 'drizzle-orm';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { takeFirstOrThrow } from '../common/utils/repository';
|
||||
import { federatedIdentityTable } from '../databases/postgres/tables';
|
||||
import { DrizzleService } from '../services/drizzle.service';
|
||||
|
||||
export type CreateFederatedIdentity = InferInsertModel<typeof federatedIdentityTable>
|
||||
export type CreateFederatedIdentity = InferInsertModel<typeof federatedIdentityTable>;
|
||||
|
||||
@injectable()
|
||||
export class FederatedIdentityRepository {
|
||||
|
|
@ -13,16 +13,16 @@ export class FederatedIdentityRepository {
|
|||
async findOneByUserIdAndProvider(userId: string, provider: string) {
|
||||
return this.drizzle.db.query.federatedIdentityTable.findFirst({
|
||||
where: and(eq(federatedIdentityTable.user_id, userId), eq(federatedIdentityTable.identity_provider, provider)),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findOneByFederatedUserIdAndProvider(federatedUserId: string, provider: string) {
|
||||
return this.drizzle.db.query.federatedIdentityTable.findFirst({
|
||||
where: and(eq(federatedIdentityTable.federated_user_id, federatedUserId), eq(federatedIdentityTable.identity_provider, provider)),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
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 { 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 { recoveryCodesTable } from '../databases/tables'
|
||||
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 { recoveryCodesTable } from '../databases/postgres/tables';
|
||||
|
||||
export type CreateRecoveryCodes = InferInsertModel<typeof recoveryCodesTable>
|
||||
export type CreateRecoveryCodes = InferInsertModel<typeof recoveryCodesTable>;
|
||||
|
||||
@injectable()
|
||||
export class RecoveryCodesRepository {
|
||||
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
|
||||
|
||||
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) {
|
||||
return db.query.recoveryCodesTable.findMany({
|
||||
where: eq(recoveryCodesTable.userId, userId),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
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 { type InferInsertModel, eq } from 'drizzle-orm'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { takeFirstOrThrow } from '../common/utils/repository'
|
||||
import { rolesTable } from '../databases/tables'
|
||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service';
|
||||
import { type InferInsertModel, eq } from 'drizzle-orm';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { takeFirstOrThrow } from '../common/utils/repository';
|
||||
import { rolesTable } from '../databases/postgres/tables';
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
export type CreateRole = InferInsertModel<typeof rolesTable>
|
||||
export type UpdateRole = Partial<CreateRole>
|
||||
export type CreateRole = InferInsertModel<typeof rolesTable>;
|
||||
export type UpdateRole = Partial<CreateRole>;
|
||||
|
||||
@injectable()
|
||||
export class RolesRepository {
|
||||
|
|
@ -30,40 +30,40 @@ export class RolesRepository {
|
|||
async findOneById(id: string, db = this.drizzle.db) {
|
||||
return db.query.rolesTable.findFirst({
|
||||
where: eq(rolesTable.id, id),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findOneByIdOrThrow(id: string, db = this.drizzle.db) {
|
||||
const role = await this.findOneById(id, db)
|
||||
if (!role) throw Error('Role not found')
|
||||
return role
|
||||
const role = await this.findOneById(id, db);
|
||||
if (!role) throw Error('Role not found');
|
||||
return role;
|
||||
}
|
||||
|
||||
async findAll(db = this.drizzle.db) {
|
||||
return db.query.rolesTable.findMany()
|
||||
return db.query.rolesTable.findMany();
|
||||
}
|
||||
|
||||
async findOneByName(name: string, db = this.drizzle.db) {
|
||||
return db.query.rolesTable.findFirst({
|
||||
where: eq(rolesTable.name, name),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findOneByNameOrThrow(name: string, db = this.drizzle.db) {
|
||||
const role = await this.findOneByName(name, db)
|
||||
if (!role) throw Error('Role not found')
|
||||
return role
|
||||
const role = await this.findOneByName(name, db);
|
||||
if (!role) throw Error('Role not found');
|
||||
return role;
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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 { type InferInsertModel, eq } from 'drizzle-orm'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { takeFirstOrThrow } from '../common/utils/repository'
|
||||
import { user_roles } from '../databases/tables'
|
||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service';
|
||||
import { type InferInsertModel, eq } from 'drizzle-orm';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { takeFirstOrThrow } from '../common/utils/repository';
|
||||
import { user_roles } from '../databases/postgres/tables';
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
export type CreateUserRole = InferInsertModel<typeof user_roles>
|
||||
export type UpdateUserRole = Partial<CreateUserRole>
|
||||
export type CreateUserRole = InferInsertModel<typeof user_roles>;
|
||||
export type UpdateUserRole = Partial<CreateUserRole>;
|
||||
|
||||
@injectable()
|
||||
export class UserRolesRepository {
|
||||
|
|
@ -30,26 +30,26 @@ export class UserRolesRepository {
|
|||
async findOneById(id: string, db = this.drizzle.db) {
|
||||
return db.query.user_roles.findFirst({
|
||||
where: eq(user_roles.id, id),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findOneByIdOrThrow(id: string, db = this.drizzle.db) {
|
||||
const userRole = await this.findOneById(id, db)
|
||||
if (!userRole) throw Error('User not found')
|
||||
return userRole
|
||||
const userRole = await this.findOneById(id, db);
|
||||
if (!userRole) throw Error('User not found');
|
||||
return userRole;
|
||||
}
|
||||
|
||||
async findAllByUserId(userId: string, db = this.drizzle.db) {
|
||||
return db.query.user_roles.findMany({
|
||||
where: eq(user_roles.user_id, userId),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
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 { type InferInsertModel, eq } from 'drizzle-orm';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import { DrizzleService } from '$lib/server/api/services/drizzle.service'
|
||||
import { type InferInsertModel, eq } from 'drizzle-orm'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { takeFirstOrThrow } from '../common/utils/repository'
|
||||
import { wishlistsTable } from '../databases/tables'
|
||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service';
|
||||
import { type InferInsertModel, eq } from 'drizzle-orm';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { takeFirstOrThrow } from '../common/utils/repository';
|
||||
import { wishlistsTable } from '../databases/postgres/tables';
|
||||
|
||||
export type CreateWishlist = InferInsertModel<typeof wishlistsTable>
|
||||
export type UpdateWishlist = Partial<CreateWishlist>
|
||||
export type CreateWishlist = InferInsertModel<typeof wishlistsTable>;
|
||||
export type UpdateWishlist = Partial<CreateWishlist>;
|
||||
|
||||
@injectable()
|
||||
export class WishlistsRepository {
|
||||
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
|
||||
|
||||
async findAll(db = this.drizzle.db) {
|
||||
return db.query.wishlistsTable.findMany()
|
||||
return db.query.wishlistsTable.findMany();
|
||||
}
|
||||
|
||||
async findOneById(id: string, db = this.drizzle.db) {
|
||||
|
|
@ -22,7 +22,7 @@ export class WishlistsRepository {
|
|||
cuid: true,
|
||||
name: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findOneByCuid(cuid: string, db = this.drizzle.db) {
|
||||
|
|
@ -32,7 +32,7 @@ export class WishlistsRepository {
|
|||
cuid: true,
|
||||
name: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findOneByUserId(userId: string, db = this.drizzle.db) {
|
||||
|
|
@ -42,7 +42,7 @@ export class WishlistsRepository {
|
|||
cuid: true,
|
||||
name: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async findAllByUserId(userId: string, db = this.drizzle.db) {
|
||||
|
|
@ -53,14 +53,14 @@ export class WishlistsRepository {
|
|||
name: true,
|
||||
createdAt: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
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 { type Disposable, injectable } from 'tsyringe';
|
||||
import { config } from '../common/config';
|
||||
import * as schema from '../databases/tables';
|
||||
import * as schema from '../databases/postgres/tables';
|
||||
|
||||
@injectable()
|
||||
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 { UpdateEmailDto } from '$lib/server/api/dtos/update-email.dto'
|
||||
import type { UpdateProfileDto } from '$lib/server/api/dtos/update-profile.dto'
|
||||
import type { VerifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto'
|
||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
||||
import { UsersService } from '$lib/server/api/services/users.service'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import type { ChangePasswordDto } from '$lib/server/api/dtos/change-password.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 { VerifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto';
|
||||
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||
import { UsersService } from '$lib/server/api/services/users.service';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { CredentialsType } from '../databases/postgres/tables';
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Service */
|
||||
|
|
@ -27,60 +27,60 @@ simple as possible. This makes the service easier to read, test and understand.
|
|||
@injectable()
|
||||
export class IamService {
|
||||
constructor(
|
||||
@inject(LuciaService) private luciaService: LuciaService,
|
||||
@inject(SessionsService) private luciaService: SessionsService,
|
||||
@inject(UsersService) private readonly usersService: UsersService,
|
||||
) {}
|
||||
|
||||
async logout(sessionId: string) {
|
||||
return this.luciaService.lucia.invalidateSession(sessionId)
|
||||
return this.luciaService.lucia.invalidateSession(sessionId);
|
||||
}
|
||||
|
||||
async updateProfile(userId: string, data: UpdateProfileDto) {
|
||||
const user = await this.usersService.findOneById(userId)
|
||||
const user = await this.usersService.findOneById(userId);
|
||||
if (!user) {
|
||||
return {
|
||||
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) {
|
||||
return {
|
||||
error: 'Username already in use',
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return this.usersService.updateUser(user.id, {
|
||||
first_name: data.firstName,
|
||||
last_name: data.lastName,
|
||||
username: data.username !== user.username ? data.username : user.username,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.usersService.updateUser(userId, {
|
||||
email,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async updatePassword(userId: string, data: ChangePasswordDto) {
|
||||
const { password } = data
|
||||
await this.usersService.updatePassword(userId, password)
|
||||
const { password } = data;
|
||||
await this.usersService.updatePassword(userId, password);
|
||||
}
|
||||
|
||||
async verifyPassword(userId: string, data: VerifyPasswordDto) {
|
||||
const user = await this.usersService.findOneById(userId)
|
||||
const user = await this.usersService.findOneById(userId);
|
||||
if (!user) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
const { password } = data
|
||||
return this.usersService.verifyPassword(userId, { password })
|
||||
const { password } = data;
|
||||
return this.usersService.verifyPassword(userId, { password });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import type { SigninUsernameDto } from '$lib/server/api/dtos/signin-username.dto'
|
||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
||||
import type { HonoRequest } from 'hono'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { BadRequest } from '../common/exceptions'
|
||||
import type { Credentials } from '../databases/tables'
|
||||
import { DatabaseProvider } from '../providers/database.provider'
|
||||
import { CredentialsRepository } from '../repositories/credentials.repository'
|
||||
import { UsersRepository } from '../repositories/users.repository'
|
||||
import { MailerService } from './mailer.service'
|
||||
import { TokensService } from './tokens.service'
|
||||
import type { SigninUsernameDto } from '$lib/server/api/dtos/signin-username.dto';
|
||||
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||
import type { HonoRequest } from 'hono';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { BadRequest } from '../common/exceptions';
|
||||
import type { Credentials } from '../databases/postgres/tables';
|
||||
import { DatabaseProvider } from '../providers/database.provider';
|
||||
import { CredentialsRepository } from '../repositories/credentials.repository';
|
||||
import { UsersRepository } from '../repositories/users.repository';
|
||||
import { MailerService } from './mailer.service';
|
||||
import { TokensService } from './tokens.service';
|
||||
|
||||
@injectable()
|
||||
export class LoginRequestsService {
|
||||
constructor(
|
||||
@inject(LuciaService) private luciaService: LuciaService,
|
||||
@inject(SessionsService) private sessionsService: SessionsService,
|
||||
@inject(DatabaseProvider) private readonly db: DatabaseProvider,
|
||||
@inject(TokensService) private readonly tokensService: TokensService,
|
||||
@inject(MailerService) private readonly mailerService: MailerService,
|
||||
|
|
@ -34,46 +34,48 @@ export class LoginRequestsService {
|
|||
// }
|
||||
|
||||
async verify(data: SigninUsernameDto, req: HonoRequest) {
|
||||
const requestIpAddress = req.header('x-real-ip')
|
||||
const requestIpCountry = req.header('x-vercel-ip-country')
|
||||
const existingUser = await this.usersRepository.findOneByUsername(data.username)
|
||||
const requestIpAddress = req.header('x-real-ip');
|
||||
const requestIpCountry = req.header('x-vercel-ip-country');
|
||||
const existingUser = await this.usersRepository.findOneByUsername(data.username);
|
||||
|
||||
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) {
|
||||
throw BadRequest('Invalid credentials')
|
||||
throw BadRequest('Invalid credentials');
|
||||
}
|
||||
|
||||
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) {
|
||||
const requestIpAddress = req.header('x-real-ip')
|
||||
const requestIpCountry = req.header('x-vercel-ip-country')
|
||||
return this.luciaService.lucia.createSession(existingUserId, {
|
||||
ip_country: requestIpCountry || 'unknown',
|
||||
ip_address: requestIpAddress || 'unknown',
|
||||
twoFactorAuthEnabled: !!totpCredentials && totpCredentials?.secret_data !== null && totpCredentials?.secret_data !== '',
|
||||
isTwoFactorAuthenticated: false,
|
||||
})
|
||||
const requestIpAddress = req.header('x-real-ip');
|
||||
const requestIpCountry = req.header('x-vercel-ip-country');
|
||||
return this.sessionsService.createSession(
|
||||
this.sessionsService.generateSessionToken(),
|
||||
existingUserId,
|
||||
requestIpCountry || 'unknown',
|
||||
requestIpAddress || 'unknown',
|
||||
!!totpCredentials && totpCredentials?.secret_data !== null && totpCredentials?.secret_data !== '',
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
// Create a new user and send a welcome email - or other onboarding process
|
||||
private async handleNewUserRegistration(email: string) {
|
||||
const newUser = await this.usersRepository.create({ email, verified: true })
|
||||
this.mailerService.sendWelcome({ to: email, props: null })
|
||||
const newUser = await this.usersRepository.create({ email, verified: true });
|
||||
this.mailerService.sendWelcome({ to: email, props: null });
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -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 { verifyTOTP } from '@oslojs/otp';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import type { CredentialsType } from '../databases/tables';
|
||||
import type { CredentialsType } from '../databases/postgres/tables';
|
||||
|
||||
@injectable()
|
||||
export class TotpService {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import type { SignupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto'
|
||||
import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository'
|
||||
import { FederatedIdentityRepository } from '$lib/server/api/repositories/federated_identity.repository'
|
||||
import { WishlistsRepository } from '$lib/server/api/repositories/wishlists.repository'
|
||||
import { TokensService } from '$lib/server/api/services/tokens.service'
|
||||
import { UserRolesService } from '$lib/server/api/services/user_roles.service'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import {CredentialsType, RoleName} from '../databases/tables'
|
||||
import { type UpdateUser, UsersRepository } from '../repositories/users.repository'
|
||||
import { CollectionsService } from './collections.service'
|
||||
import { DrizzleService } from './drizzle.service'
|
||||
import { WishlistsService } from './wishlists.service'
|
||||
import type {OAuthUser} from "$lib/server/api/common/types/oauth";
|
||||
import type { OAuthUser } from '$lib/server/api/common/types/oauth';
|
||||
import type { SignupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto';
|
||||
import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository';
|
||||
import { FederatedIdentityRepository } from '$lib/server/api/repositories/federated_identity.repository';
|
||||
import { WishlistsRepository } from '$lib/server/api/repositories/wishlists.repository';
|
||||
import { TokensService } from '$lib/server/api/services/tokens.service';
|
||||
import { UserRolesService } from '$lib/server/api/services/user_roles.service';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { CredentialsType, RoleName } from '../databases/postgres/tables';
|
||||
import { type UpdateUser, UsersRepository } from '../repositories/users.repository';
|
||||
import { CollectionsService } from './collections.service';
|
||||
import { DrizzleService } from './drizzle.service';
|
||||
import { WishlistsService } from './wishlists.service';
|
||||
|
||||
@injectable()
|
||||
export class UsersService {
|
||||
|
|
@ -27,9 +27,9 @@ export class UsersService {
|
|||
) {}
|
||||
|
||||
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) => {
|
||||
const createdUser = await this.usersRepository.create(
|
||||
{
|
||||
|
|
@ -39,10 +39,10 @@ export class UsersService {
|
|||
username,
|
||||
},
|
||||
trx,
|
||||
)
|
||||
);
|
||||
|
||||
if (!createdUser) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
const credentials = await this.credentialsRepository.create(
|
||||
|
|
@ -52,18 +52,18 @@ export class UsersService {
|
|||
secret_data: hashedPassword,
|
||||
},
|
||||
trx,
|
||||
)
|
||||
);
|
||||
|
||||
if (!credentials) {
|
||||
await this.usersRepository.delete(createdUser.id)
|
||||
return null
|
||||
await this.usersRepository.delete(createdUser.id);
|
||||
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.collectionsService.createEmptyNoName(createdUser.id, trx)
|
||||
})
|
||||
await this.wishlistsService.createEmptyNoName(createdUser.id, trx);
|
||||
await this.collectionsService.createEmptyNoName(createdUser.id, trx);
|
||||
});
|
||||
}
|
||||
|
||||
async createOAuthUser(oAuthUser: OAuthUser, oauthProvider: string) {
|
||||
|
|
@ -78,10 +78,10 @@ export class UsersService {
|
|||
email_verified: oAuthUser.email_verified || false,
|
||||
},
|
||||
trx,
|
||||
)
|
||||
);
|
||||
|
||||
if (!createdUser) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
await this.federatedIdentityRepository.create(
|
||||
|
|
@ -92,58 +92,58 @@ export class UsersService {
|
|||
federated_username: oAuthUser.email || oAuthUser.username,
|
||||
},
|
||||
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.collectionsService.createEmptyNoName(createdUser.id, trx)
|
||||
return createdUser
|
||||
})
|
||||
await this.wishlistsService.createEmptyNoName(createdUser.id, trx);
|
||||
await this.collectionsService.createEmptyNoName(createdUser.id, trx);
|
||||
return createdUser;
|
||||
});
|
||||
}
|
||||
|
||||
async updateUser(userId: string, data: UpdateUser) {
|
||||
return this.usersRepository.update(userId, data)
|
||||
return this.usersRepository.update(userId, data);
|
||||
}
|
||||
|
||||
async findOneByUsername(username: string) {
|
||||
return this.usersRepository.findOneByUsername(username)
|
||||
return this.usersRepository.findOneByUsername(username);
|
||||
}
|
||||
|
||||
async findOneByEmail(email: string) {
|
||||
return this.usersRepository.findOneByEmail(email)
|
||||
return this.usersRepository.findOneByEmail(email);
|
||||
}
|
||||
|
||||
async findOneById(id: string) {
|
||||
return this.usersRepository.findOneById(id)
|
||||
return this.usersRepository.findOneById(id);
|
||||
}
|
||||
|
||||
async updatePassword(userId: string, password: string) {
|
||||
const hashedPassword = await this.tokenService.createHashedToken(password)
|
||||
const currentCredentials = await this.credentialsRepository.findPasswordCredentialsByUserId(userId)
|
||||
const hashedPassword = await this.tokenService.createHashedToken(password);
|
||||
const currentCredentials = await this.credentialsRepository.findPasswordCredentialsByUserId(userId);
|
||||
if (!currentCredentials) {
|
||||
await this.credentialsRepository.create({
|
||||
user_id: userId,
|
||||
type: CredentialsType.PASSWORD,
|
||||
secret_data: hashedPassword,
|
||||
})
|
||||
});
|
||||
} else {
|
||||
await this.credentialsRepository.update(currentCredentials.id, {
|
||||
secret_data: hashedPassword,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async verifyPassword(userId: string, data: { password: string }) {
|
||||
const user = await this.usersRepository.findOneById(userId)
|
||||
const user = await this.usersRepository.findOneById(userId);
|
||||
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) {
|
||||
throw new Error('Password credentials not found')
|
||||
throw new Error('Password credentials not found');
|
||||
}
|
||||
const { password } = data
|
||||
return this.tokenService.verifyHashedToken(credential.secret_data, password)
|
||||
const { password } = data;
|
||||
return this.tokenService.verifyHashedToken(credential.secret_data, password);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
import 'reflect-metadata'
|
||||
import { IamService } from '$lib/server/api/services/iam.service'
|
||||
import { LuciaService } from '$lib/server/api/services/lucia.service'
|
||||
import { UsersService } from '$lib/server/api/services/users.service'
|
||||
import { faker } from '@faker-js/faker'
|
||||
import { container } from 'tsyringe'
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import 'reflect-metadata';
|
||||
import { IamService } from '$lib/server/api/services/iam.service';
|
||||
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||
import { UsersService } from '$lib/server/api/services/users.service';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { container } from 'tsyringe';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
describe('IamService', () => {
|
||||
let service: IamService
|
||||
const luciaService = vi.mocked(LuciaService.prototype)
|
||||
const userService = vi.mocked(UsersService.prototype)
|
||||
let service: IamService;
|
||||
const luciaService = vi.mocked(SessionsService.prototype);
|
||||
const userService = vi.mocked(UsersService.prototype);
|
||||
|
||||
beforeAll(() => {
|
||||
service = container
|
||||
.register<LuciaService>(LuciaService, { useValue: luciaService })
|
||||
.register<SessionsService>(SessionsService, { useValue: luciaService })
|
||||
.register<UsersService>(UsersService, { useValue: userService })
|
||||
.resolve(IamService)
|
||||
})
|
||||
.resolve(IamService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
})
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.resetAllMocks()
|
||||
})
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
const timeStampDate = new Date()
|
||||
const timeStampDate = new Date();
|
||||
const dbUser = {
|
||||
id: faker.string.uuid(),
|
||||
cuid: 'ciglo1j8q0000t9j4xq8d6p5e',
|
||||
|
|
@ -40,85 +40,85 @@ describe('IamService', () => {
|
|||
theme: 'system',
|
||||
createdAt: timeStampDate,
|
||||
updatedAt: timeStampDate,
|
||||
}
|
||||
};
|
||||
|
||||
describe('Update Profile', () => {
|
||||
it('should update user', async () => {
|
||||
userService.findOneById = vi.fn().mockResolvedValueOnce(dbUser)
|
||||
userService.findOneByUsername = vi.fn().mockResolvedValue(undefined)
|
||||
userService.updateUser = vi.fn().mockResolvedValue(dbUser)
|
||||
userService.findOneById = vi.fn().mockResolvedValueOnce(dbUser);
|
||||
userService.findOneByUsername = vi.fn().mockResolvedValue(undefined);
|
||||
userService.updateUser = vi.fn().mockResolvedValue(dbUser);
|
||||
|
||||
const spy_userService_findOneById = vi.spyOn(userService, 'findOneById')
|
||||
const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername')
|
||||
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser')
|
||||
const spy_userService_findOneById = vi.spyOn(userService, 'findOneById');
|
||||
const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername');
|
||||
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser');
|
||||
await expect(
|
||||
service.updateProfile(faker.string.uuid(), {
|
||||
username: faker.internet.userName(),
|
||||
}),
|
||||
).resolves.toEqual(dbUser)
|
||||
expect(spy_userService_findOneById).toBeCalledTimes(1)
|
||||
expect(spy_userService_findOneByUsername).toBeCalledTimes(1)
|
||||
expect(spy_userService_updateUser).toBeCalledTimes(1)
|
||||
})
|
||||
).resolves.toEqual(dbUser);
|
||||
expect(spy_userService_findOneById).toBeCalledTimes(1);
|
||||
expect(spy_userService_findOneByUsername).toBeCalledTimes(1);
|
||||
expect(spy_userService_updateUser).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
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_findOneByUsername = vi.spyOn(userService, 'findOneByUsername')
|
||||
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser')
|
||||
const spy_userService_findOneById = vi.spyOn(userService, 'findOneById');
|
||||
const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername');
|
||||
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser');
|
||||
await expect(
|
||||
service.updateProfile(faker.string.uuid(), {
|
||||
username: faker.internet.userName(),
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
error: 'User not found',
|
||||
})
|
||||
expect(spy_userService_findOneById).toBeCalledTimes(1)
|
||||
expect(spy_userService_findOneByUsername).toBeCalledTimes(0)
|
||||
expect(spy_userService_updateUser).toBeCalledTimes(0)
|
||||
})
|
||||
});
|
||||
expect(spy_userService_findOneById).toBeCalledTimes(1);
|
||||
expect(spy_userService_findOneByUsername).toBeCalledTimes(0);
|
||||
expect(spy_userService_updateUser).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should error on duplicate username', async () => {
|
||||
userService.findOneById = vi.fn().mockResolvedValueOnce(dbUser)
|
||||
userService.findOneById = vi.fn().mockResolvedValueOnce(dbUser);
|
||||
userService.findOneByUsername = vi.fn().mockResolvedValue({
|
||||
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_findOneByUsername = vi.spyOn(userService, 'findOneByUsername')
|
||||
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser')
|
||||
const spy_userService_findOneById = vi.spyOn(userService, 'findOneById');
|
||||
const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername');
|
||||
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser');
|
||||
await expect(
|
||||
service.updateProfile(faker.string.uuid(), {
|
||||
username: faker.internet.userName(),
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
error: 'Username already in use',
|
||||
})
|
||||
expect(spy_userService_findOneById).toBeCalledTimes(1)
|
||||
expect(spy_userService_findOneByUsername).toBeCalledTimes(1)
|
||||
expect(spy_userService_updateUser).toBeCalledTimes(0)
|
||||
})
|
||||
})
|
||||
});
|
||||
expect(spy_userService_findOneById).toBeCalledTimes(1);
|
||||
expect(spy_userService_findOneByUsername).toBeCalledTimes(1);
|
||||
expect(spy_userService_updateUser).toBeCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
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({
|
||||
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_findOneByUsername = vi.spyOn(userService, 'findOneByUsername')
|
||||
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser')
|
||||
const spy_userService_findOneById = vi.spyOn(userService, 'findOneById');
|
||||
const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername');
|
||||
const spy_userService_updateUser = vi.spyOn(userService, 'updateUser');
|
||||
await expect(
|
||||
service.updateProfile(dbUser.id, {
|
||||
username: dbUser.id,
|
||||
}),
|
||||
).resolves.toEqual(dbUser)
|
||||
expect(spy_userService_findOneById).toBeCalledTimes(1)
|
||||
expect(spy_userService_findOneByUsername).toBeCalledTimes(1)
|
||||
expect(spy_userService_updateUser).toBeCalledTimes(1)
|
||||
})
|
||||
})
|
||||
).resolves.toEqual(dbUser);
|
||||
expect(spy_userService_findOneById).toBeCalledTimes(1);
|
||||
expect(spy_userService_findOneByUsername).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