mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Merge pull request #32 from BradNut/hono-zod-openapi
OpenAPI, Update Drizzle, Remove Lucia Auth
This commit is contained in:
commit
eddb896378
132 changed files with 8821 additions and 2541 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(),
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
|
|||
65
package.json
65
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.47.1",
|
||||
"@sveltejs/adapter-auto": "^3.2.5",
|
||||
"@sveltejs/enhanced-img": "^0.3.8",
|
||||
"@sveltejs/kit": "^2.6.3",
|
||||
"@playwright/test": "^1.48.2",
|
||||
"@sveltejs/adapter-auto": "^3.3.1",
|
||||
"@sveltejs/enhanced-img": "^0.3.10",
|
||||
"@sveltejs/kit": "^2.8.0",
|
||||
"@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.2",
|
||||
"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",
|
||||
"typescript": "^5.6.2",
|
||||
"vite": "^5.4.8",
|
||||
"tslib": "^2.8.1",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.10",
|
||||
"vitest": "^1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
|
@ -83,6 +83,7 @@
|
|||
"@internationalized/date": "^3.5.6",
|
||||
"@lucia-auth/adapter-drizzle": "^1.1.0",
|
||||
"@lukeed/uuid": "^2.0.1",
|
||||
"@needle-di/core": "^0.8.4",
|
||||
"@neondatabase/serverless": "^0.9.5",
|
||||
"@node-rs/argon2": "^1.8.3",
|
||||
"@oslojs/crypto": "^1.0.1",
|
||||
|
|
@ -92,27 +93,27 @@
|
|||
"@oslojs/otp": "^1.0.0",
|
||||
"@oslojs/webauthn": "^1.0.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@scalar/hono-api-reference": "^0.5.152",
|
||||
"@sveltejs/adapter-node": "^5.2.5",
|
||||
"@sveltejs/adapter-vercel": "^5.4.5",
|
||||
"@scalar/hono-api-reference": "^0.5.159",
|
||||
"@sveltejs/adapter-node": "^5.2.9",
|
||||
"@sveltejs/adapter-vercel": "^5.4.7",
|
||||
"@types/feather-icons": "^4.29.4",
|
||||
"bits-ui": "^0.21.16",
|
||||
"boardgamegeekclient": "^1.9.1",
|
||||
"bullmq": "^5.17.1",
|
||||
"bullmq": "^5.25.3",
|
||||
"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.1",
|
||||
"drizzle-zod": "^0.5.1",
|
||||
"feather-icons": "^4.29.2",
|
||||
"formsnap": "^1.0.1",
|
||||
"handlebars": "^4.7.8",
|
||||
"hono": "^4.6.3",
|
||||
"hono": "^4.6.9",
|
||||
"hono-pino": "^0.3.0",
|
||||
"hono-rate-limiter": "^0.4.0",
|
||||
"hono-zod-openapi": "^0.2.0",
|
||||
"hono-zod-openapi": "^0.4.2",
|
||||
"html-entities": "^2.5.2",
|
||||
"iconify-icon": "^2.1.0",
|
||||
"ioredis": "^5.4.1",
|
||||
|
|
@ -120,23 +121,23 @@
|
|||
"just-kebab-case": "^4.2.0",
|
||||
"loader": "^2.1.1",
|
||||
"mode-watcher": "^0.4.1",
|
||||
"open-props": "^1.7.6",
|
||||
"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.0.9",
|
||||
"stoker": "^1.3.0",
|
||||
"svelte-lazy-loader": "^1.0.0",
|
||||
"svelte-sonner": "^0.3.28",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
774
pnpm-lock.yaml
774
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,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
12
src/lib/server/api/common/openapi/create-auth-route.ts
Normal file
12
src/lib/server/api/common/openapi/create-auth-route.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { type HonoOpenApiOperation, type HonoOpenApiRequestSchemas, defineOpenApiOperation } from "hono-zod-openapi";
|
||||
|
||||
export const taggedAuthRoute = <T extends HonoOpenApiRequestSchemas>(
|
||||
tag: string,
|
||||
doc: HonoOpenApiOperation<T>,
|
||||
) => {
|
||||
return defineOpenApiOperation({
|
||||
...doc,
|
||||
tags: [tag],
|
||||
security: [{ cookieAuth: [] }],
|
||||
});
|
||||
};
|
||||
15
src/lib/server/api/common/openapi/create-cookie-schema.ts
Normal file
15
src/lib/server/api/common/openapi/create-cookie-schema.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { authCookieSchema } from '$lib/server/api/common/openapi/schemas';
|
||||
import { z } from '@hono/zod-openapi';
|
||||
|
||||
export type ZodSchema = z.ZodUnion<never> | z.AnyZodObject | z.ZodArray<z.AnyZodObject>;
|
||||
type ZodString = z.ZodString;
|
||||
|
||||
export function createAuthCookieSchema() {
|
||||
return createCookieSchema(authCookieSchema);
|
||||
}
|
||||
|
||||
export function createCookieSchema<T extends ZodSchema>(schema: ZodString) {
|
||||
return z.object({
|
||||
cookie: schema,
|
||||
});
|
||||
}
|
||||
7
src/lib/server/api/common/openapi/cuidParamsSchema.ts
Normal file
7
src/lib/server/api/common/openapi/cuidParamsSchema.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { z } from '@hono/zod-openapi';
|
||||
|
||||
const cuidParamsSchema = z.object({
|
||||
cuid: z.string().cuid2(),
|
||||
});
|
||||
|
||||
export default cuidParamsSchema;
|
||||
3
src/lib/server/api/common/openapi/schemas.ts
Normal file
3
src/lib/server/api/common/openapi/schemas.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { z } from '@hono/zod-openapi';
|
||||
|
||||
export const authCookieSchema = z.string().regex(/^session=\w+$/);
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import type { Sessions } from '$lib/server/api/databases/postgres/tables';
|
||||
import type { Hono } from 'hono';
|
||||
import type { PinoLogger } from 'hono-pino';
|
||||
import type { Promisify, RateLimitInfo } from 'hono-rate-limiter';
|
||||
import type { Session, User } from 'lucia';
|
||||
import type { User } from 'lucia';
|
||||
|
||||
// export type AppOpenAPI = OpenAPIHono<AppBindings>;
|
||||
export type AppOpenAPI = Hono;
|
||||
export type AppOpenAPI = Hono<AppBindings>;
|
||||
|
||||
export type AppBindings = {
|
||||
Variables: {
|
||||
logger: PinoLogger;
|
||||
session: Session | null;
|
||||
session: Sessions | null;
|
||||
user: User | null;
|
||||
rateLimit: RateLimitInfo;
|
||||
rateLimitStore: {
|
||||
|
|
@ -22,7 +23,7 @@ export type AppBindings = {
|
|||
export type HonoTypes = {
|
||||
Variables: {
|
||||
logger: PinoLogger;
|
||||
session: Session | null;
|
||||
session: Sessions | null;
|
||||
user: User | null;
|
||||
rateLimit: RateLimitInfo;
|
||||
rateLimitStore: {
|
||||
|
|
|
|||
63
src/lib/server/api/common/utils/cookies.ts
Normal file
63
src/lib/server/api/common/utils/cookies.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { config } from '$lib/server/api/common/config';
|
||||
import env from '$lib/server/api/common/env';
|
||||
import type { Context } from 'hono';
|
||||
import { setCookie } from 'hono/cookie';
|
||||
import type { CookieOptions } from 'hono/utils/cookie';
|
||||
import { TimeSpan } from 'oslo';
|
||||
|
||||
export const cookieMaxAge = 60 * 60 * 24 * 30;
|
||||
export const cookieExpiresMilliseconds = new TimeSpan(2, 'w').milliseconds();
|
||||
export const cookieExpiresAt = new Date(Date.now() + cookieExpiresMilliseconds);
|
||||
export const halfCookieExpiresMilliseconds = cookieExpiresMilliseconds / 2;
|
||||
export const halfCookieExpiresAt = new Date(Date.now() + halfCookieExpiresMilliseconds);
|
||||
export const cookieName = 'session';
|
||||
|
||||
export type SessionCookie = {
|
||||
name: string;
|
||||
value: string;
|
||||
attributes: CookieOptions;
|
||||
};
|
||||
|
||||
export function createSessionTokenCookie(token: string, expiresAt: Date): SessionCookie {
|
||||
return {
|
||||
name: cookieName,
|
||||
value: token,
|
||||
attributes: {
|
||||
path: '/',
|
||||
maxAge: cookieMaxAge,
|
||||
domain: env.DOMAIN,
|
||||
sameSite: 'lax',
|
||||
secure: config.isProduction,
|
||||
httpOnly: true,
|
||||
expires: expiresAt,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createBlankSessionTokenCookie(): SessionCookie {
|
||||
return {
|
||||
name: cookieName,
|
||||
value: '',
|
||||
attributes: {
|
||||
path: '/',
|
||||
maxAge: 0,
|
||||
domain: env.DOMAIN,
|
||||
sameSite: 'lax',
|
||||
secure: config.isProduction,
|
||||
httpOnly: true,
|
||||
expires: new Date(0),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function setSessionCookie(c: Context, sessionCookie: SessionCookie) {
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
maxAge: sessionCookie.attributes?.maxAge,
|
||||
domain: sessionCookie.attributes.domain,
|
||||
sameSite: sessionCookie.attributes.sameSite as undefined,
|
||||
secure: sessionCookie.attributes.secure,
|
||||
httpOnly: sessionCookie.attributes.httpOnly,
|
||||
expires: sessionCookie.attributes.expires,
|
||||
});
|
||||
}
|
||||
|
|
@ -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,6 +1,4 @@
|
|||
// import type { AppOpenAPI } from '$lib/server/api/common/types/hono';
|
||||
import { apiReference } from '@scalar/hono-api-reference';
|
||||
import { Hono } from 'hono';
|
||||
|
||||
import type { AppOpenAPI } from '$lib/server/api/common/types/hono';
|
||||
// import { createOpenApiDocument } from 'hono-zod-openapi';
|
||||
|
|
@ -40,6 +38,19 @@ export default function configureOpenAPI(app: AppOpenAPI) {
|
|||
description: 'Bored Game API',
|
||||
version: packageJSON.version,
|
||||
},
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearerAuth: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
},
|
||||
cookieAuth: {
|
||||
type: 'apiKey',
|
||||
name: 'session',
|
||||
in: 'cookie',
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.get(
|
||||
|
|
|
|||
|
|
@ -1,32 +1,40 @@
|
|||
import 'reflect-metadata'
|
||||
import { Controller } from '$lib/server/api/common/types/controller'
|
||||
import { CollectionsService } from '$lib/server/api/services/collections.service'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
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 { allCollections, getCollectionByCUID, numberOfCollections } from '$lib/server/api/controllers/collection.routes';
|
||||
import { CollectionsService } from '$lib/server/api/services/collections.service';
|
||||
import { openApi } from 'hono-zod-openapi';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { requireAuth } from '../middleware/require-auth.middleware';
|
||||
|
||||
@injectable()
|
||||
export class CollectionController extends Controller {
|
||||
constructor(@inject(CollectionsService) private readonly collectionsService: CollectionsService) {
|
||||
super()
|
||||
super();
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/', requireAuth, async (c) => {
|
||||
const user = c.var.user
|
||||
const collections = await this.collectionsService.findAllByUserId(user.id)
|
||||
console.log('collections service', collections)
|
||||
return c.json({ collections })
|
||||
.get('/', requireAuth, openApi(allCollections), async (c) => {
|
||||
const user = c.var.user;
|
||||
const collections = await this.collectionsService.findAllByUserId(user.id);
|
||||
console.log('collections service', collections);
|
||||
return c.json({ collections }, StatusCodes.OK);
|
||||
})
|
||||
.get('/count', requireAuth, async (c) => {
|
||||
const user = c.var.user
|
||||
const collections = await this.collectionsService.findAllByUserIdWithDetails(user.id)
|
||||
return c.json({ collections })
|
||||
})
|
||||
.get('/:cuid', requireAuth, async (c) => {
|
||||
const cuid = c.req.param('cuid')
|
||||
const collection = await this.collectionsService.findOneByCuid(cuid)
|
||||
return c.json({ collection })
|
||||
.get('/count', requireAuth, openApi(numberOfCollections), async (c) => {
|
||||
const user = c.var.user;
|
||||
const collections = await this.collectionsService.findAllByUserIdWithDetails(user.id);
|
||||
return c.json({ count: collections?.length || 0 }, StatusCodes.OK);
|
||||
})
|
||||
.get('/:cuid', requireAuth, openApi(getCollectionByCUID), async (c) => {
|
||||
const cuid = c.req.param('cuid');
|
||||
const collection = await this.collectionsService.findOneByCuid(cuid);
|
||||
|
||||
if (!collection) {
|
||||
return c.json('Collection not found', StatusCodes.NOT_FOUND);
|
||||
}
|
||||
|
||||
return c.json({ collection }, StatusCodes.OK);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
69
src/lib/server/api/controllers/collection.routes.ts
Normal file
69
src/lib/server/api/controllers/collection.routes.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
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 { 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';
|
||||
|
||||
export const allCollections = taggedAuthRoute(tag, {
|
||||
responses: {
|
||||
[StatusCodes.OK]: {
|
||||
description: 'User profile',
|
||||
schema: selectCollectionSchema,
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.UNAUTHORIZED]: {
|
||||
description: 'Unauthorized',
|
||||
schema: createErrorSchema(unauthorizedSchema),
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const numberOfCollections = taggedAuthRoute(tag, {
|
||||
responses: {
|
||||
[StatusCodes.OK]: {
|
||||
description: 'User profile',
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.UNAUTHORIZED]: {
|
||||
description: 'Unauthorized',
|
||||
schema: createErrorSchema(unauthorizedSchema),
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const getCollectionByCUID = taggedAuthRoute(tag, {
|
||||
request: {
|
||||
param: {
|
||||
schema: cuidParamsSchema,
|
||||
example: { cuid: 'z6uiuc9qz82xjf5dexc5kr2d' },
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
[StatusCodes.OK]: {
|
||||
description: 'User profile',
|
||||
schema: selectCollectionSchema,
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.NOT_FOUND]: {
|
||||
description: 'The collection does not exist',
|
||||
schema: z.object({ message: z.string() }).openapi({
|
||||
example: {
|
||||
message: 'The collection does not exist',
|
||||
},
|
||||
}),
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.UNAUTHORIZED]: {
|
||||
description: 'Unauthorized',
|
||||
schema: createErrorSchema(unauthorizedSchema),
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { StatusCodes } from '$lib/constants/status-codes';
|
||||
import { Controller } from '$lib/server/api/common/types/controller';
|
||||
import { iam, updateProfile } from '$lib/server/api/controllers/iam.routes';
|
||||
import { createBlankSessionTokenCookie, setSessionCookie } 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';
|
||||
|
|
@ -8,19 +8,20 @@ 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';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { requireAuth } from '../middleware/require-auth.middleware';
|
||||
import { iam, logout, updateEmail, updatePassword, updateProfile, verifyPassword } from './iam.routes';
|
||||
|
||||
@injectable()
|
||||
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();
|
||||
}
|
||||
|
|
@ -42,69 +43,74 @@ export class IamController extends Controller {
|
|||
const { firstName, lastName, username } = c.req.valid('json');
|
||||
const updatedUser = await this.iamService.updateProfile(user.id, { firstName, lastName, username });
|
||||
if (!updatedUser) {
|
||||
return c.json('Username already in use', StatusCodes.BAD_REQUEST);
|
||||
return c.json('Username already in use', StatusCodes.UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
return c.json({ user: updatedUser }, StatusCodes.OK);
|
||||
},
|
||||
)
|
||||
.post('/verify/password', requireAuth, zValidator('json', verifyPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||
const user = c.var.user;
|
||||
const { password } = c.req.valid('json');
|
||||
const passwordVerified = await this.iamService.verifyPassword(user.id, { password });
|
||||
if (!passwordVerified) {
|
||||
console.log('Incorrect password');
|
||||
return c.json('Incorrect password', StatusCodes.BAD_REQUEST);
|
||||
}
|
||||
return c.json({}, StatusCodes.OK);
|
||||
})
|
||||
.put('/update/password', requireAuth, zValidator('json', changePasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||
const user = c.var.user;
|
||||
const { password, confirm_password } = c.req.valid('json');
|
||||
if (password !== confirm_password) {
|
||||
return c.json('Passwords do not match', StatusCodes.BAD_REQUEST);
|
||||
}
|
||||
try {
|
||||
await this.iamService.updatePassword(user.id, { password, confirm_password });
|
||||
await this.luciaService.lucia.invalidateUserSessions(user.id);
|
||||
await this.loginRequestService.createUserSession(user.id, c.req, undefined);
|
||||
const sessionCookie = this.luciaService.lucia.createBlankSessionCookie();
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
maxAge: sessionCookie.attributes.maxAge,
|
||||
domain: sessionCookie.attributes.domain,
|
||||
sameSite: sessionCookie.attributes.sameSite as any,
|
||||
secure: sessionCookie.attributes.secure,
|
||||
httpOnly: sessionCookie.attributes.httpOnly,
|
||||
expires: sessionCookie.attributes.expires,
|
||||
});
|
||||
return c.json({ status: 'success' });
|
||||
} catch (error) {
|
||||
console.error('Error updating password', error);
|
||||
return c.json('Error updating password', StatusCodes.BAD_REQUEST);
|
||||
}
|
||||
})
|
||||
.post('/update/email', requireAuth, zValidator('json', updateEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||
const user = c.var.user;
|
||||
const { email } = c.req.valid('json');
|
||||
const updatedUser = await this.iamService.updateEmail(user.id, { email });
|
||||
if (!updatedUser) {
|
||||
return c.json('Email already in use', StatusCodes.BAD_REQUEST);
|
||||
}
|
||||
return c.json({ user: updatedUser }, StatusCodes.OK);
|
||||
})
|
||||
.post('/logout', requireAuth, async (c) => {
|
||||
.post(
|
||||
'/verify/password',
|
||||
requireAuth,
|
||||
zValidator('json', verifyPasswordDto),
|
||||
openApi(verifyPassword),
|
||||
limiter({ limit: 10, minutes: 60 }),
|
||||
async (c) => {
|
||||
const user = c.var.user;
|
||||
const { password } = c.req.valid('json');
|
||||
const passwordVerified = await this.iamService.verifyPassword(user.id, { password });
|
||||
if (!passwordVerified) {
|
||||
console.log('Incorrect password');
|
||||
return c.json('Incorrect password', StatusCodes.FORBIDDEN);
|
||||
}
|
||||
return c.json({}, StatusCodes.OK);
|
||||
},
|
||||
)
|
||||
.put(
|
||||
'/update/password',
|
||||
requireAuth,
|
||||
openApi(updatePassword),
|
||||
zValidator('json', changePasswordDto),
|
||||
limiter({ limit: 10, minutes: 60 }),
|
||||
async (c) => {
|
||||
const user = c.var.user;
|
||||
const { password, confirm_password } = c.req.valid('json');
|
||||
if (password !== confirm_password) {
|
||||
return c.json('Passwords do not match', StatusCodes.UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
try {
|
||||
await this.iamService.updatePassword(user.id, { password, confirm_password });
|
||||
await this.sessionsService.invalidateSession(user.id);
|
||||
await this.loginRequestService.createUserSession(user.id, c.req, undefined);
|
||||
const sessionCookie = createBlankSessionTokenCookie();
|
||||
setSessionCookie(c, sessionCookie);
|
||||
return c.json({ status: 'success' });
|
||||
} catch (error) {
|
||||
console.error('Error updating password', error);
|
||||
return c.json('Error updating password', StatusCodes.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
},
|
||||
)
|
||||
.post(
|
||||
'/update/email',
|
||||
requireAuth,
|
||||
openApi(updateEmail),
|
||||
zValidator('json', updateEmailDto),
|
||||
limiter({ limit: 10, minutes: 60 }),
|
||||
async (c) => {
|
||||
const user = c.var.user;
|
||||
const { email } = c.req.valid('json');
|
||||
const updatedUser = await this.iamService.updateEmail(user.id, { email });
|
||||
if (!updatedUser) {
|
||||
return c.json('Cannot change email address', StatusCodes.FORBIDDEN);
|
||||
}
|
||||
return c.json({ user: updatedUser }, StatusCodes.OK);
|
||||
},
|
||||
)
|
||||
.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,
|
||||
});
|
||||
const sessionCookie = createBlankSessionTokenCookie();
|
||||
setSessionCookie(c, sessionCookie);
|
||||
return c.json({ status: 'success' });
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
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 { defineOpenApiOperation } from 'hono-zod-openapi';
|
||||
import { createErrorSchema } from 'stoker/openapi/schemas';
|
||||
import { taggedAuthRoute } from '../common/openapi/create-auth-route';
|
||||
import { changePasswordDto } from '../dtos/change-password.dto';
|
||||
import { verifyPasswordDto } from '../dtos/verify-password.dto';
|
||||
|
||||
const tags = ['IAM'];
|
||||
const tag = 'IAM';
|
||||
|
||||
export const iam = defineOpenApiOperation({
|
||||
tags,
|
||||
export const iam = taggedAuthRoute(tag, {
|
||||
responses: {
|
||||
[StatusCodes.OK]: {
|
||||
description: 'User profile',
|
||||
|
|
@ -23,8 +24,7 @@ export const iam = defineOpenApiOperation({
|
|||
},
|
||||
});
|
||||
|
||||
export const updateProfile = defineOpenApiOperation({
|
||||
tags,
|
||||
export const updateProfile = taggedAuthRoute(tag, {
|
||||
request: {
|
||||
json: updateProfileDto,
|
||||
},
|
||||
|
|
@ -34,11 +34,108 @@ export const updateProfile = defineOpenApiOperation({
|
|||
schema: selectUserSchema,
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.UNPROCESSABLE_ENTITY]: {
|
||||
[StatusCodes.BAD_REQUEST]: {
|
||||
description: 'The validation error(s)',
|
||||
schema: createErrorSchema(updateProfileDto),
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.UNPROCESSABLE_ENTITY]: {
|
||||
description: 'Username already in use',
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.UNAUTHORIZED]: {
|
||||
description: 'Unauthorized',
|
||||
schema: createErrorSchema(unauthorizedSchema),
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const verifyPassword = taggedAuthRoute(tag, {
|
||||
request: {
|
||||
json: verifyPasswordDto,
|
||||
},
|
||||
responses: {
|
||||
[StatusCodes.OK]: {
|
||||
description: 'Password verified',
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.BAD_REQUEST]: {
|
||||
description: 'The validation error(s)',
|
||||
schema: createErrorSchema(verifyPasswordDto),
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.UNPROCESSABLE_ENTITY]: {
|
||||
description: 'Incorrect password',
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.UNAUTHORIZED]: {
|
||||
description: 'Unauthorized',
|
||||
schema: createErrorSchema(unauthorizedSchema),
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const updatePassword = taggedAuthRoute(tag, {
|
||||
request: {
|
||||
json: changePasswordDto,
|
||||
},
|
||||
responses: {
|
||||
[StatusCodes.OK]: {
|
||||
description: 'Password updated',
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.BAD_REQUEST]: {
|
||||
description: 'The validation error(s)',
|
||||
schema: createErrorSchema(changePasswordDto),
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.UNAUTHORIZED]: {
|
||||
description: 'Unauthorized',
|
||||
schema: createErrorSchema(unauthorizedSchema),
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.FORBIDDEN]: {
|
||||
description: 'Incorrect password',
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.INTERNAL_SERVER_ERROR]: {
|
||||
description: 'Error updating password',
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const updateEmail = taggedAuthRoute(tag, {
|
||||
responses: {
|
||||
[StatusCodes.OK]: {
|
||||
description: 'Email updated',
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.BAD_REQUEST]: {
|
||||
description: 'The validation error(s)',
|
||||
schema: createErrorSchema(changePasswordDto),
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.UNAUTHORIZED]: {
|
||||
description: 'Unauthorized',
|
||||
schema: createErrorSchema(unauthorizedSchema),
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.FORBIDDEN]: {
|
||||
description: 'Cannot change email address',
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const logout = taggedAuthRoute(tag, {
|
||||
responses: {
|
||||
[StatusCodes.OK]: {
|
||||
description: 'Logged out',
|
||||
mediaType: 'application/json',
|
||||
},
|
||||
[StatusCodes.UNAUTHORIZED]: {
|
||||
description: 'Unauthorized',
|
||||
schema: createErrorSchema(unauthorizedSchema),
|
||||
|
|
|
|||
|
|
@ -1,42 +1,38 @@
|
|||
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 { inject, injectable } from 'tsyringe'
|
||||
import { limiter } from '../middleware/rate-limiter.middleware'
|
||||
import { LoginRequestsService } from '../services/loginrequest.service'
|
||||
import 'reflect-metadata';
|
||||
import { Controller } from '$lib/server/api/common/types/controller';
|
||||
import { cookieExpiresAt, createSessionTokenCookie, setSessionCookie } from '$lib/server/api/common/utils/cookies';
|
||||
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';
|
||||
|
||||
@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('/', 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 = createSessionTokenCookie(session.id, cookieExpiresAt);
|
||||
console.log('set cookie', sessionCookie);
|
||||
setSessionCookie(c, sessionCookie);
|
||||
return c.json({ message: 'ok' });
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
21
src/lib/server/api/controllers/login.routes.ts
Normal file
21
src/lib/server/api/controllers/login.routes.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { defineOpenApiOperation } from "hono-zod-openapi";
|
||||
import { StatusCodes } from '$lib/constants/status-codes';
|
||||
import { signinUsernameDto } from "../dtos/signin-username.dto";
|
||||
import { createErrorSchema } from "stoker/openapi/schemas";
|
||||
|
||||
export const signinUsername = defineOpenApiOperation({
|
||||
tags: ['Login'],
|
||||
summary: 'Sign in with username',
|
||||
description: 'Sign in with username',
|
||||
responses: {
|
||||
[StatusCodes.OK]: {
|
||||
description: 'Sign in with username',
|
||||
schema: signinUsernameDto,
|
||||
},
|
||||
[StatusCodes.UNPROCESSABLE_ENTITY]: {
|
||||
description: 'The validation error(s)',
|
||||
schema: createErrorSchema(signinUsernameDto),
|
||||
mediaType: 'application/json',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -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,96 +1,86 @@
|
|||
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 { cookieExpiresAt, createSessionTokenCookie, setSessionCookie } 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)
|
||||
|
||||
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' })
|
||||
const sessionToken = this.sessionsService.generateSessionToken();
|
||||
const session = await this.sessionsService.createSession(sessionToken, userId, '', '', false, false);
|
||||
const sessionCookie = createSessionTokenCookie(session.id, cookieExpiresAt);
|
||||
setSessionCookie(c, sessionCookie);
|
||||
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,51 +90,39 @@ 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 sessionToken = this.sessionsService.generateSessionToken();
|
||||
const session = await this.sessionsService.createSession(sessionToken, userId, '', '', false, false);
|
||||
const sessionCookie = createSessionTokenCookie(session.id, cookieExpiresAt);
|
||||
setSessionCookie(c, sessionCookie);
|
||||
|
||||
const session = await this.luciaService.lucia.createSession(userId, {})
|
||||
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id)
|
||||
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
maxAge:
|
||||
sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds()
|
||||
? sessionCookie.attributes.maxAge
|
||||
: new TimeSpan(2, 'w').seconds(),
|
||||
domain: sessionCookie.attributes.domain,
|
||||
sameSite: sessionCookie.attributes.sameSite as any,
|
||||
secure: sessionCookie.attributes.secure,
|
||||
httpOnly: sessionCookie.attributes.httpOnly,
|
||||
expires: sessionCookie.attributes.expires,
|
||||
})
|
||||
|
||||
return c.json({ message: 'ok' })
|
||||
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
|
||||
}
|
||||
]
|
||||
}
|
||||
20
src/lib/server/api/databases/postgres/migrate.ts
Normal file
20
src/lib/server/api/databases/postgres/migrate.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import 'dotenv/config';
|
||||
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
||||
import config from '../../../../../../drizzle.config';
|
||||
import env from '../../common/env';
|
||||
import { DrizzleService } from '../../services/drizzle.service';
|
||||
|
||||
const drizzleService = new DrizzleService();
|
||||
|
||||
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(drizzleService.db, { migrationsFolder: config.out });
|
||||
console.log('Migrations complete');
|
||||
|
||||
await drizzleService.dispose();
|
||||
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,18 @@
|
|||
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 { 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 +44,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 { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import * as schema from '../tables';
|
||||
import roles from './data/roles.json';
|
||||
|
||||
export default async function seed(db: NodePgDatabase<typeof schema>) {
|
||||
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 { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import { HashingService } from '../../../services/hashing.service';
|
||||
import * as schema from '../tables';
|
||||
import users from './data/users.json';
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ type JsonRole = {
|
|||
primary: boolean;
|
||||
};
|
||||
|
||||
export default async function seed(db: db) {
|
||||
export default async function seed(db: NodePgDatabase<typeof schema>) {
|
||||
const hashingService = new HashingService();
|
||||
const adminRole = await db.select().from(schema.rolesTable).where(eq(schema.rolesTable.name, 'admin'));
|
||||
const userRole = await db.select().from(schema.rolesTable).where(eq(schema.rolesTable.name, 'user'));
|
||||
|
|
@ -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],
|
||||
}),
|
||||
}))
|
||||
}));
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
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 { collection_items } from './collectionItems.table';
|
||||
import { usersTable } from './users.table';
|
||||
|
||||
export const collections = pgTable('collections', {
|
||||
id: uuid().primaryKey().defaultRandom(),
|
||||
cuid: text()
|
||||
.unique()
|
||||
.$defaultFn(() => cuid2()),
|
||||
user_id: uuid()
|
||||
.notNull()
|
||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
name: text().notNull().default('My Collection'),
|
||||
...timestamps,
|
||||
});
|
||||
|
||||
export const collection_relations = relations(collections, ({ one, many }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [collections.user_id],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
collection_items: many(collection_items),
|
||||
}));
|
||||
|
||||
export const selectCollectionSchema = createSelectSchema(collections);
|
||||
|
||||
export type Collections = InferSelectModel<typeof collections>;
|
||||
|
|
@ -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,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 { usersTable } from './users.table'
|
||||
import { collection_items } from './collectionItems.table'
|
||||
|
||||
export const collections = pgTable('collections', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
.unique()
|
||||
.$defaultFn(() => cuid2()),
|
||||
user_id: uuid('user_id')
|
||||
.notNull()
|
||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull().default('My Collection'),
|
||||
...timestamps,
|
||||
})
|
||||
|
||||
export const collection_relations = relations(collections, ({ one, many }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [collections.user_id],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
collection_items: many(collection_items),
|
||||
}))
|
||||
|
||||
export type Collections = InferSelectModel<typeof collections>
|
||||
|
|
@ -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,3 @@
|
|||
import 'reflect-metadata';
|
||||
import createApp from '$lib/server/api/common/create-app';
|
||||
import configureOpenAPI from '$lib/server/api/configure-open-api';
|
||||
import { CollectionController } from '$lib/server/api/controllers/collection.controller';
|
||||
|
|
@ -34,7 +33,6 @@ const routes = app
|
|||
.route('/mfa', container.resolve(MfaController).routes())
|
||||
.get('/', (c) => c.json({ message: 'Server is healthy' }));
|
||||
|
||||
// @ts-ignore - this is a workaround for https://github.com/paolostyle/hono-zod-openapi/issues/2
|
||||
configureOpenAPI(app);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -1,12 +1,22 @@
|
|||
import { LuciaService } from '$lib/server/api/services/lucia.service';
|
||||
import 'reflect-metadata';
|
||||
import {
|
||||
type SessionCookie,
|
||||
cookieExpiresAt,
|
||||
cookieName,
|
||||
createBlankSessionTokenCookie,
|
||||
createSessionTokenCookie,
|
||||
setSessionCookie,
|
||||
} 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 { parseCookies } from 'oslo/cookie';
|
||||
import { verifyRequestOrigin } from 'oslo/request';
|
||||
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') {
|
||||
|
|
@ -21,20 +31,22 @@ export const verifyOrigin: MiddlewareHandler<AppBindings> = createMiddleware(asy
|
|||
});
|
||||
|
||||
export const validateAuthSession: MiddlewareHandler<AppBindings> = createMiddleware(async (c, next) => {
|
||||
const sessionId = lucia.readSessionCookie(c.req.header('Cookie') ?? '');
|
||||
const cookies = parseCookies(c.req.header('Cookie') ?? '');
|
||||
const sessionId = cookies.get(cookieName) ?? null;
|
||||
if (!sessionId) {
|
||||
c.set('user', null);
|
||||
c.set('session', null);
|
||||
return next();
|
||||
}
|
||||
|
||||
const { session, user } = await lucia.validateSession(sessionId);
|
||||
if (session?.fresh) {
|
||||
c.header('Set-Cookie', lucia.createSessionCookie(session.id).serialize(), { append: true });
|
||||
}
|
||||
if (!session) {
|
||||
c.header('Set-Cookie', lucia.createBlankSessionCookie().serialize(), { append: true });
|
||||
const { session, user } = await sessionService.validateSessionToken(sessionId);
|
||||
let sessionCookie: SessionCookie;
|
||||
if (session !== null) {
|
||||
sessionCookie = createSessionTokenCookie(session.id, cookieExpiresAt);
|
||||
} else {
|
||||
sessionCookie = createBlankSessionTokenCookie();
|
||||
}
|
||||
setSessionCookie(c, sessionCookie);
|
||||
c.set('session', session);
|
||||
c.set('user', user);
|
||||
return next();
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import { Unauthorized } from '$lib/server/api/common/exceptions'
|
||||
import type { MiddlewareHandler } from 'hono'
|
||||
import { createMiddleware } from 'hono/factory'
|
||||
import type { Session, User } from 'lucia'
|
||||
import { Unauthorized } from '$lib/server/api/common/exceptions';
|
||||
import type { Sessions } from '$lib/server/api/databases/postgres/tables';
|
||||
import type { MiddlewareHandler } from 'hono';
|
||||
import { createMiddleware } from 'hono/factory';
|
||||
import type { User } from 'lucia';
|
||||
|
||||
export const requireAuth: MiddlewareHandler<{
|
||||
Variables: {
|
||||
session: Session
|
||||
user: User
|
||||
}
|
||||
session: Sessions;
|
||||
user: User;
|
||||
};
|
||||
}> = createMiddleware(async (c, next) => {
|
||||
const user = c.var.user
|
||||
if (!user) throw Unauthorized('You must be logged in to access this resource')
|
||||
return next()
|
||||
})
|
||||
const user = c.var.user;
|
||||
if (!user) throw Unauthorized('You must be logged in to access this resource');
|
||||
return next();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 pg 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,
|
||||
})
|
||||
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));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue