mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Refactoring to match simplifying done on the origin TaroStack.
This commit is contained in:
parent
16f00607b1
commit
3aa537f389
137 changed files with 2344 additions and 2405 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -7,6 +7,8 @@ node_modules
|
|||
.env.*
|
||||
*.xdp*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
.vercel
|
||||
.output
|
||||
.idea
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import 'dotenv/config';
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
import env from './src/env';
|
||||
import 'dotenv/config'
|
||||
import { defineConfig } from 'drizzle-kit'
|
||||
import env from './src/env'
|
||||
|
||||
export default defineConfig({
|
||||
dialect: 'postgresql',
|
||||
out: './src/lib/server/api/infrastructure/database/migrations',
|
||||
schema: './src/lib/server/api/infrastructure/database/tables/index.ts',
|
||||
out: './src/lib/server/api/databases/migrations',
|
||||
schema: './src/lib/server/api/databases/tables/drizzle.ts',
|
||||
dbCredentials: {
|
||||
host: env.DATABASE_HOST || 'localhost',
|
||||
port: Number(env.DATABASE_PORT) || 5432,
|
||||
|
|
@ -20,6 +20,6 @@ export default defineConfig({
|
|||
strict: true,
|
||||
migrations: {
|
||||
table: 'migrations',
|
||||
schema: 'public'
|
||||
}
|
||||
});
|
||||
schema: 'public',
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
import { error, json } from '@sveltejs/kit';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import {db} from '$lib/server/api/infrastructure/database';
|
||||
import { collection_items, usersTable } from '$db/schema';
|
||||
import { collection_items, usersTable } from '$db/schema'
|
||||
import { error, json } from '@sveltejs/kit'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { db } from '../../../../src/lib/server/api/packages/drizzle'
|
||||
|
||||
// Search a user's collection
|
||||
export async function GET({ url, locals, params }) {
|
||||
const searchParams = Object.fromEntries(url.searchParams);
|
||||
const q = searchParams?.q || '';
|
||||
const limit = Number.parseInt(searchParams?.limit) || 10;
|
||||
const skip = Number.parseInt(searchParams?.skip) || 0;
|
||||
const order = searchParams?.order || 'asc';
|
||||
const sort = searchParams?.sort || 'name';
|
||||
const collection_id = params.id;
|
||||
console.log('url', url);
|
||||
console.log('username', locals?.user?.id);
|
||||
const searchParams = Object.fromEntries(url.searchParams)
|
||||
const q = searchParams?.q || ''
|
||||
const limit = Number.parseInt(searchParams?.limit) || 10
|
||||
const skip = Number.parseInt(searchParams?.skip) || 0
|
||||
const order = searchParams?.order || 'asc'
|
||||
const sort = searchParams?.sort || 'name'
|
||||
const collection_id = params.id
|
||||
console.log('url', url)
|
||||
console.log('username', locals?.user?.id)
|
||||
|
||||
if (!locals.user) {
|
||||
error(401, { message: 'Unauthorized' });
|
||||
error(401, { message: 'Unauthorized' })
|
||||
}
|
||||
|
||||
const collection = await db.query.collections.findFirst({
|
||||
where: eq(usersTable.id, locals?.user?.id),
|
||||
});
|
||||
console.log('collection', collection);
|
||||
})
|
||||
console.log('collection', collection)
|
||||
|
||||
if (!collection) {
|
||||
console.log('Collection was not found');
|
||||
error(404, { message: 'Collection was not found' });
|
||||
console.log('Collection was not found')
|
||||
error(404, { message: 'Collection was not found' })
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -42,21 +42,20 @@ export async function GET({ url, locals, params }) {
|
|||
},
|
||||
},
|
||||
orderBy: (collection_items, { asc, desc }) => {
|
||||
const dbSort =
|
||||
sort === 'dateAdded' ? collection_items.created_at : collection_items.times_played;
|
||||
const dbSort = sort === 'dateAdded' ? collection_items.created_at : collection_items.times_played
|
||||
if (order === 'asc') {
|
||||
return asc(dbSort);
|
||||
return asc(dbSort)
|
||||
} else {
|
||||
return desc(dbSort);
|
||||
return desc(dbSort)
|
||||
}
|
||||
},
|
||||
offset: skip,
|
||||
limit,
|
||||
});
|
||||
})
|
||||
|
||||
return json(userCollectionItems);
|
||||
return json(userCollectionItems)
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
error(500, { message: 'Something went wrong' });
|
||||
console.error(e)
|
||||
error(500, { message: 'Something went wrong' })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
import { db } from '$lib/server/api/infrastructure/database';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { usersTable } from '$lib/server/api/infrastructure/database/tables';
|
||||
import { createPasswordResetToken } from '$lib/server/auth-utils.js';
|
||||
import { PUBLIC_SITE_URL } from '$env/static/public';
|
||||
import { PUBLIC_SITE_URL } from '$env/static/public'
|
||||
import { createPasswordResetToken } from '$lib/server/auth-utils.js'
|
||||
import { 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'
|
||||
|
||||
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 { password_reset_tokens } from '$lib/server/api/infrastructure/database/tables';
|
||||
import { isWithinExpirationDate } from 'oslo';
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { isWithinExpirationDate } from 'oslo'
|
||||
import { password_reset_tokens } from '../../../src/lib/server/api/databases/tables'
|
||||
// import { lucia } from '$lib/server/lucia';
|
||||
import {db} from '$lib/server/api/infrastructure/database';
|
||||
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(),
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
"scripts": {
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "tsx src/lib/server/api/infrastructure/database/migrate.ts",
|
||||
"db:seed": "tsx src/lib/server/api/infrastructure/database/seed.ts",
|
||||
"db:migrate": "tsx src/lib/server/api/databases/migrate.ts",
|
||||
"db:seed": "tsx src/lib/server/api/databases/seed.ts",
|
||||
"db:studio": "drizzle-kit studio --verbose",
|
||||
"dev": "NODE_OPTIONS=\"--inspect\" vite dev --host",
|
||||
"build": "vite build",
|
||||
|
|
|
|||
34
src/env.ts
34
src/env.ts
|
|
@ -1,13 +1,13 @@
|
|||
import { config } from 'dotenv';
|
||||
import { expand } from 'dotenv-expand';
|
||||
import { ZodError, z } from 'zod';
|
||||
import { config } from 'dotenv'
|
||||
import { expand } from 'dotenv-expand'
|
||||
import { ZodError, z } from 'zod'
|
||||
|
||||
const stringBoolean = z.coerce
|
||||
.string()
|
||||
.transform((val) => {
|
||||
return val === 'true';
|
||||
return val === 'true'
|
||||
})
|
||||
.default('false');
|
||||
.default('false')
|
||||
|
||||
const EnvSchema = z.object({
|
||||
ADMIN_USERNAME: z.string(),
|
||||
|
|
@ -20,6 +20,7 @@ const EnvSchema = z.object({
|
|||
DB_MIGRATING: stringBoolean,
|
||||
DB_SEEDING: stringBoolean,
|
||||
NODE_ENV: z.string().default('development'),
|
||||
ORIGIN: z.string(),
|
||||
PUBLIC_SITE_NAME: z.string(),
|
||||
PUBLIC_SITE_URL: z.string(),
|
||||
PUBLIC_UMAMI_DO_NOT_TRACK: z.string(),
|
||||
|
|
@ -27,26 +28,25 @@ const EnvSchema = z.object({
|
|||
PUBLIC_UMAMI_URL: z.string(),
|
||||
REDIS_URL: z.string(),
|
||||
TWO_FACTOR_TIMEOUT: z.coerce.number().default(300000),
|
||||
});
|
||||
})
|
||||
|
||||
export type EnvSchema = z.infer<typeof EnvSchema>;
|
||||
export type EnvSchema = z.infer<typeof EnvSchema>
|
||||
|
||||
expand(config());
|
||||
expand(config())
|
||||
|
||||
try {
|
||||
EnvSchema.parse(process.env);
|
||||
EnvSchema.parse(process.env)
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
let message = 'Missing required values in .env:\n';
|
||||
let message = 'Missing required values in .env:\n'
|
||||
for (const issue of error.issues) {
|
||||
message += `${issue.path[0]}\n`;
|
||||
message += `${issue.path[0]}\n`
|
||||
}
|
||||
const e = new Error(message);
|
||||
e.stack = '';
|
||||
throw e;
|
||||
} else {
|
||||
console.error(error);
|
||||
const e = new Error(message)
|
||||
e.stack = ''
|
||||
throw e
|
||||
}
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
export default EnvSchema.parse(process.env);
|
||||
export default EnvSchema.parse(process.env)
|
||||
|
|
|
|||
|
|
@ -1,19 +1,10 @@
|
|||
// import * as Sentry from '@sentry/sveltekit';
|
||||
import 'reflect-metadata'
|
||||
import { hc } from 'hono/client';
|
||||
import { sequence } from '@sveltejs/kit/hooks';
|
||||
import { redirect, type Handle } from '@sveltejs/kit';
|
||||
import type { ApiRoutes } from '$lib/server/api';
|
||||
import { parseApiResponse } from '$lib/utils/api';
|
||||
import { StatusCodes } from '$lib/constants/status-codes';
|
||||
|
||||
// TODO: Fix Sentry as it is not working on SvelteKit v2
|
||||
// Sentry.init({
|
||||
// dsn: 'https://742e43279df93a3c4a4a78c12eb1f879@o4506057768632320.ingest.sentry.io/4506057770401792',
|
||||
// tracesSampleRate: 1,
|
||||
// environment: dev ? 'development' : 'production',
|
||||
// enabled: !dev
|
||||
// });
|
||||
import { StatusCodes } from '$lib/constants/status-codes'
|
||||
import type { ApiRoutes } from '$lib/server/api'
|
||||
import { parseApiResponse } from '$lib/utils/api'
|
||||
import { type Handle, redirect } from '@sveltejs/kit'
|
||||
import { sequence } from '@sveltejs/kit/hooks'
|
||||
import { hc } from 'hono/client'
|
||||
|
||||
const apiClient: Handle = async ({ event, resolve }) => {
|
||||
/* ------------------------------ Register api ------------------------------ */
|
||||
|
|
@ -21,76 +12,31 @@ const apiClient: Handle = async ({ event, resolve }) => {
|
|||
fetch: event.fetch,
|
||||
headers: {
|
||||
'x-forwarded-for': event.url.host.includes('sveltekit-prerender') ? '127.0.0.1' : event.getClientAddress(),
|
||||
host: event.request.headers.get('host') || ''
|
||||
}
|
||||
});
|
||||
host: event.request.headers.get('host') || '',
|
||||
},
|
||||
})
|
||||
|
||||
/* ----------------------------- Auth functions ----------------------------- */
|
||||
async function getAuthedUser() {
|
||||
const { data } = await api.user.$get().then(parseApiResponse)
|
||||
return data?.user;
|
||||
return data?.user
|
||||
}
|
||||
|
||||
async function getAuthedUserOrThrow() {
|
||||
const { data } = await api.user.$get().then(parseApiResponse);
|
||||
if (!data || !data.user) throw redirect(StatusCodes.TEMPORARY_REDIRECT, '/');
|
||||
return data?.user;
|
||||
const { data } = await api.user.$get().then(parseApiResponse)
|
||||
if (!data || !data.user) throw redirect(StatusCodes.TEMPORARY_REDIRECT, '/')
|
||||
return data?.user
|
||||
}
|
||||
|
||||
/* ------------------------------ Set contexts ------------------------------ */
|
||||
event.locals.api = api;
|
||||
event.locals.parseApiResponse = parseApiResponse;
|
||||
event.locals.getAuthedUser = getAuthedUser;
|
||||
event.locals.getAuthedUserOrThrow = getAuthedUserOrThrow;
|
||||
event.locals.api = api
|
||||
event.locals.parseApiResponse = parseApiResponse
|
||||
event.locals.getAuthedUser = getAuthedUser
|
||||
event.locals.getAuthedUserOrThrow = getAuthedUserOrThrow
|
||||
|
||||
/* ----------------------------- Return response ---------------------------- */
|
||||
const response = await resolve(event);
|
||||
return response;
|
||||
};
|
||||
const response = await resolve(event)
|
||||
return response
|
||||
}
|
||||
|
||||
// export const authentication: Handle = async function ({ event, resolve }) {
|
||||
// event.locals.startTimer = Date.now();
|
||||
//
|
||||
// const ip = event.request.headers.get('x-forwarded-for') as string;
|
||||
// const country = event.request.headers.get('x-vercel-ip-country') as string;
|
||||
// event.locals.ip = dev ? '127.0.0.1' : ip; // || event.getClientAddress();
|
||||
// event.locals.country = dev ? 'us' : country;
|
||||
//
|
||||
// const sessionId = event.cookies.get(lucia.sessionCookieName);
|
||||
// if (!sessionId) {
|
||||
// event.locals.user = null;
|
||||
// event.locals.session = null;
|
||||
// return resolve(event);
|
||||
// }
|
||||
//
|
||||
// const { session, user } = await lucia.validateSession(sessionId);
|
||||
// if (session && session.fresh) {
|
||||
// const sessionCookie = lucia.createSessionCookie(session.id);
|
||||
// console.log('sessionCookie', JSON.stringify(sessionCookie, null, 2));
|
||||
// // sveltekit types deviates from the de-facto standard, you can use 'as any' too
|
||||
// event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
// path: '.',
|
||||
// ...sessionCookie.attributes,
|
||||
// });
|
||||
// }
|
||||
// console.log('session from hooks', JSON.stringify(session, null, 2));
|
||||
// if (!session) {
|
||||
// const sessionCookie = lucia.createBlankSessionCookie();
|
||||
// console.log('blank sessionCookie', JSON.stringify(sessionCookie, null, 2));
|
||||
// event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
// path: '.',
|
||||
// ...sessionCookie.attributes,
|
||||
// });
|
||||
// }
|
||||
// event.locals.user = user;
|
||||
// event.locals.session = session;
|
||||
//
|
||||
// return resolve(event);
|
||||
// };
|
||||
|
||||
export const handle: Handle = sequence(
|
||||
// Sentry.sentryHandle(),
|
||||
// authentication,
|
||||
apiClient
|
||||
);
|
||||
// export const handleError = Sentry.handleErrorWithSentry();
|
||||
export const handle: Handle = sequence(apiClient)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import { Hono } from 'hono'
|
||||
import type { BlankSchema } from 'hono/types'
|
||||
import type { HonoTypes } from '../../types'
|
||||
|
||||
export interface Controller {
|
||||
controller: Hono<HonoTypes, BlankSchema, '/'>
|
||||
routes(): any
|
||||
}
|
||||
4
src/lib/server/api/common/interfaces/email.interface.ts
Normal file
4
src/lib/server/api/common/interfaces/email.interface.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export interface Email {
|
||||
subject(): string
|
||||
html(): string
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import type { DatabaseProvider } from '$lib/server/api/providers/database.provider'
|
||||
|
||||
export interface Repository {
|
||||
trxHost(trx: DatabaseProvider): any
|
||||
}
|
||||
14
src/lib/server/api/common/utils/repository.utils.ts
Normal file
14
src/lib/server/api/common/utils/repository.utils.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
export const takeFirst = <T>(values: T[]): T | null => {
|
||||
if (values.length === 0) return null
|
||||
return values[0] as T
|
||||
}
|
||||
|
||||
export const takeFirstOrThrow = <T>(values: T[]): T => {
|
||||
if (values.length === 0)
|
||||
throw new HTTPException(404, {
|
||||
message: 'Resource not found',
|
||||
})
|
||||
return values[0] as T
|
||||
}
|
||||
29
src/lib/server/api/common/utils/table.utils.ts
Normal file
29
src/lib/server/api/common/utils/table.utils.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { timestamp } from 'drizzle-orm/pg-core'
|
||||
import { customType } from 'drizzle-orm/pg-core'
|
||||
|
||||
export const citext = customType<{ data: string }>({
|
||||
dataType() {
|
||||
return 'citext'
|
||||
},
|
||||
})
|
||||
|
||||
export const cuid2 = customType<{ data: string }>({
|
||||
dataType() {
|
||||
return 'text'
|
||||
},
|
||||
})
|
||||
|
||||
export const timestamps = {
|
||||
createdAt: timestamp('created_at', {
|
||||
mode: 'date',
|
||||
withTimezone: true,
|
||||
})
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
updatedAt: timestamp('updated_at', {
|
||||
mode: 'date',
|
||||
withTimezone: true,
|
||||
})
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
}
|
||||
|
|
@ -1,31 +1,29 @@
|
|||
import 'reflect-metadata';
|
||||
import { Hono } from 'hono';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { requireAuth } from "../middleware/auth.middleware";
|
||||
import type { HonoTypes } from '../types';
|
||||
import type { Controller } from '../interfaces/controller.interface';
|
||||
import {CollectionsService} from "$lib/server/api/services/collections.service";
|
||||
import 'reflect-metadata'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { CollectionsService } from '$lib/server/api/services/collections.service'
|
||||
import { Hono } from 'hono'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { requireAuth } from '../middleware/auth.middleware'
|
||||
import type { HonoTypes } from '../types'
|
||||
|
||||
@injectable()
|
||||
export class CollectionController implements Controller {
|
||||
controller = new Hono<HonoTypes>();
|
||||
controller = new Hono<HonoTypes>()
|
||||
|
||||
constructor(
|
||||
@inject(CollectionsService) private readonly collectionsService: CollectionsService,
|
||||
) { }
|
||||
constructor(@inject(CollectionsService) private readonly collectionsService: CollectionsService) {}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/', requireAuth, async (c) => {
|
||||
const user = c.var.user;
|
||||
const collections = await this.collectionsService.findAllByUserId(user.id);
|
||||
const user = c.var.user
|
||||
const collections = await this.collectionsService.findAllByUserId(user.id)
|
||||
console.log('collections service', collections)
|
||||
return c.json({ collections });
|
||||
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 });
|
||||
});
|
||||
const cuid = c.req.param('cuid')
|
||||
const collection = await this.collectionsService.findOneByCuid(cuid)
|
||||
return c.json({ collection })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import { Hono } from 'hono'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { setCookie } from 'hono/cookie'
|
||||
import { zValidator } from '@hono/zod-validator'
|
||||
import type { HonoTypes } from '../types'
|
||||
import { requireAuth } from '../middleware/auth.middleware'
|
||||
import type { Controller } from '$lib/server/api/interfaces/controller.interface'
|
||||
import { IamService } from '$lib/server/api/services/iam.service'
|
||||
import { LuciaProvider } from '$lib/server/api/providers'
|
||||
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware'
|
||||
import { updateProfileDto } from '$lib/dtos/update-profile.dto'
|
||||
import { updateEmailDto } from '$lib/dtos/update-email.dto'
|
||||
import { StatusCodes } from '$lib/constants/status-codes'
|
||||
import { verifyPasswordDto } from '$lib/dtos/verify-password.dto'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { updateEmailDto } from '$lib/server/api/dtos/update-email.dto'
|
||||
import { updateProfileDto } from '$lib/server/api/dtos/update-profile.dto'
|
||||
import { verifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto'
|
||||
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware'
|
||||
import { LuciaProvider } from '$lib/server/api/providers/lucia.provider'
|
||||
import { IamService } from '$lib/server/api/services/iam.service'
|
||||
import { zValidator } from '@hono/zod-validator'
|
||||
import { Hono } from 'hono'
|
||||
import { setCookie } from 'hono/cookie'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { requireAuth } from '../middleware/auth.middleware'
|
||||
import type { HonoTypes } from '../types'
|
||||
|
||||
@injectable()
|
||||
export class IamController implements Controller {
|
||||
|
|
|
|||
|
|
@ -1,43 +1,44 @@
|
|||
import 'reflect-metadata';
|
||||
import { Hono } from 'hono';
|
||||
import { setCookie } from 'hono/cookie';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { TimeSpan } from 'oslo';
|
||||
import type { HonoTypes } from '../types';
|
||||
import { limiter } from '../middleware/rate-limiter.middleware';
|
||||
import type { Controller } from '../interfaces/controller.interface';
|
||||
import { LoginRequestsService } from '../services/loginrequest.service';
|
||||
import { signinUsernameDto } from "$lib/dtos/signin-username.dto";
|
||||
import {LuciaProvider} from "$lib/server/api/providers";
|
||||
import 'reflect-metadata'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { signinUsernameDto } from '$lib/server/api/dtos/signin-username.dto'
|
||||
import { LuciaProvider } from '$lib/server/api/providers/lucia.provider'
|
||||
import { zValidator } from '@hono/zod-validator'
|
||||
import { Hono } from 'hono'
|
||||
import { setCookie } from 'hono/cookie'
|
||||
import { TimeSpan } from 'oslo'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { limiter } from '../middleware/rate-limiter.middleware'
|
||||
import { LoginRequestsService } from '../services/loginrequest.service'
|
||||
import type { HonoTypes } from '../types'
|
||||
|
||||
@injectable()
|
||||
export class LoginController implements Controller {
|
||||
controller = new Hono<HonoTypes>();
|
||||
controller = new Hono<HonoTypes>()
|
||||
|
||||
constructor(
|
||||
@inject(LoginRequestsService) private readonly loginRequestsService: LoginRequestsService,
|
||||
@inject(LuciaProvider) private lucia: LuciaProvider
|
||||
) { }
|
||||
@inject(LuciaProvider) private lucia: LuciaProvider,
|
||||
) {}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
.post('/', zValidator('json', signinUsernameDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||
const { username, password } = c.req.valid('json');
|
||||
const session = await this.loginRequestsService.verify({ username, password }, c.req);
|
||||
const sessionCookie = this.lucia.createSessionCookie(session.id);
|
||||
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('/', zValidator('json', signinUsernameDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||
const { username, password } = c.req.valid('json')
|
||||
const session = await this.loginRequestsService.verify({ username, password }, c.req)
|
||||
const sessionCookie = this.lucia.createSessionCookie(session.id)
|
||||
console.log('set cookie', sessionCookie)
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
maxAge:
|
||||
sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds()
|
||||
? sessionCookie.attributes.maxAge
|
||||
: new TimeSpan(2, 'w').seconds(),
|
||||
domain: sessionCookie.attributes.domain,
|
||||
sameSite: sessionCookie.attributes.sameSite as any,
|
||||
secure: sessionCookie.attributes.secure,
|
||||
httpOnly: sessionCookie.attributes.httpOnly,
|
||||
expires: sessionCookie.attributes.expires,
|
||||
})
|
||||
return c.json({ message: 'ok' })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import 'reflect-metadata'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { Hono } from 'hono'
|
||||
import { zValidator } from '@hono/zod-validator'
|
||||
import { requireAuth } from '../middleware/auth.middleware'
|
||||
import type { HonoTypes } from '../types'
|
||||
import type { Controller } from '$lib/server/api/interfaces/controller.interface'
|
||||
import { TotpService } from '$lib/server/api/services/totp.service'
|
||||
import { StatusCodes } from '$lib/constants/status-codes'
|
||||
import { verifyTotpDto } from '$lib/dtos/verify-totp.dto'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { verifyTotpDto } from '$lib/server/api/dtos/verify-totp.dto'
|
||||
import { TotpService } from '$lib/server/api/services/totp.service'
|
||||
import { zValidator } from '@hono/zod-validator'
|
||||
import { Hono } from 'hono'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { CredentialsType } from '../databases/tables'
|
||||
import { requireAuth } from '../middleware/auth.middleware'
|
||||
import { UsersService } from '../services/users.service'
|
||||
import { CredentialsType } from '$db/tables'
|
||||
import type { HonoTypes } from '../types'
|
||||
|
||||
@injectable()
|
||||
export class MfaController implements Controller {
|
||||
|
|
|
|||
|
|
@ -1,57 +1,58 @@
|
|||
import 'reflect-metadata';
|
||||
import { Hono } from 'hono';
|
||||
import { setCookie } from 'hono/cookie';
|
||||
import {inject, injectable} from 'tsyringe';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { TimeSpan } from 'oslo';
|
||||
import type { HonoTypes } from '../types';
|
||||
import type { Controller } from '../interfaces/controller.interface';
|
||||
import { signupUsernameEmailDto } from "$lib/dtos/signup-username-email.dto";
|
||||
import {limiter} from "$lib/server/api/middleware/rate-limiter.middleware";
|
||||
import {UsersService} from "$lib/server/api/services/users.service";
|
||||
import {LoginRequestsService} from "$lib/server/api/services/loginrequest.service";
|
||||
import {LuciaProvider} from "$lib/server/api/providers";
|
||||
import 'reflect-metadata'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { signupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto'
|
||||
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware'
|
||||
import { LuciaProvider } from '$lib/server/api/providers/lucia.provider'
|
||||
import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service'
|
||||
import { UsersService } from '$lib/server/api/services/users.service'
|
||||
import { zValidator } from '@hono/zod-validator'
|
||||
import { Hono } from 'hono'
|
||||
import { setCookie } from 'hono/cookie'
|
||||
import { TimeSpan } from 'oslo'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import type { HonoTypes } from '../types'
|
||||
|
||||
@injectable()
|
||||
export class SignupController implements Controller {
|
||||
controller = new Hono<HonoTypes>();
|
||||
controller = new Hono<HonoTypes>()
|
||||
|
||||
constructor(
|
||||
@inject(UsersService) private readonly usersService: UsersService,
|
||||
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
|
||||
@inject(LuciaProvider) private lucia: LuciaProvider
|
||||
) { }
|
||||
@inject(UsersService) private readonly usersService: UsersService,
|
||||
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
|
||||
@inject(LuciaProvider) private lucia: LuciaProvider,
|
||||
) {}
|
||||
|
||||
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);
|
||||
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)
|
||||
|
||||
if (existingUser) {
|
||||
return c.body("User already exists", 400);
|
||||
}
|
||||
if (existingUser) {
|
||||
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);
|
||||
}
|
||||
if (!user) {
|
||||
return c.body('Failed to create user', 500)
|
||||
}
|
||||
|
||||
const session = await this.loginRequestService.createUserSession(user.id, c.req, undefined);
|
||||
const sessionCookie = this.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' });
|
||||
});
|
||||
const session = await this.loginRequestService.createUserSession(user.id, c.req, undefined)
|
||||
const sessionCookie = this.lucia.createSessionCookie(session.id)
|
||||
console.log('set cookie', sessionCookie)
|
||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||
path: sessionCookie.attributes.path,
|
||||
maxAge:
|
||||
sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds()
|
||||
? sessionCookie.attributes.maxAge
|
||||
: new TimeSpan(2, 'w').seconds(),
|
||||
domain: sessionCookie.attributes.domain,
|
||||
sameSite: sessionCookie.attributes.sameSite as any,
|
||||
secure: sessionCookie.attributes.secure,
|
||||
httpOnly: sessionCookie.attributes.httpOnly,
|
||||
expires: sessionCookie.attributes.expires,
|
||||
})
|
||||
return c.json({ message: 'ok' })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,32 @@
|
|||
import 'reflect-metadata';
|
||||
import { Hono } from 'hono';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { requireAuth } from "../middleware/auth.middleware";
|
||||
import type { HonoTypes } from '../types';
|
||||
import type { Controller } from '../interfaces/controller.interface';
|
||||
import {UsersService} from "$lib/server/api/services/users.service";
|
||||
import 'reflect-metadata'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { UsersService } from '$lib/server/api/services/users.service'
|
||||
import { Hono } from 'hono'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { requireAuth } from '../middleware/auth.middleware'
|
||||
import type { HonoTypes } from '../types'
|
||||
|
||||
@injectable()
|
||||
export class UserController implements Controller {
|
||||
controller = new Hono<HonoTypes>();
|
||||
controller = new Hono<HonoTypes>()
|
||||
|
||||
constructor(
|
||||
@inject(UsersService) private readonly usersService: UsersService
|
||||
) { }
|
||||
constructor(@inject(UsersService) private readonly usersService: UsersService) {}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/', async (c) => {
|
||||
const user = c.var.user;
|
||||
return c.json({ user });
|
||||
const user = c.var.user
|
||||
return c.json({ user })
|
||||
})
|
||||
.get('/:id', requireAuth, async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const user = await this.usersService.findOneById(id);
|
||||
return c.json({ user });
|
||||
const id = c.req.param('id')
|
||||
const user = await this.usersService.findOneById(id)
|
||||
return c.json({ user })
|
||||
})
|
||||
.get('/username/:userName', requireAuth, async (c) => {
|
||||
const userName = c.req.param('userName');
|
||||
const user = await this.usersService.findOneByUsername(userName);
|
||||
return c.json({ user });
|
||||
});
|
||||
const userName = c.req.param('userName')
|
||||
const user = await this.usersService.findOneByUsername(userName)
|
||||
return c.json({ user })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,28 @@
|
|||
import 'reflect-metadata';
|
||||
import { Hono } from 'hono';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { requireAuth } from "../middleware/auth.middleware";
|
||||
import type { HonoTypes } from '../types';
|
||||
import type { Controller } from '../interfaces/controller.interface';
|
||||
import {WishlistsService} from "$lib/server/api/services/wishlists.service";
|
||||
import 'reflect-metadata'
|
||||
import type { Controller } from '$lib/server/api/common/interfaces/controller.interface'
|
||||
import { WishlistsService } from '$lib/server/api/services/wishlists.service'
|
||||
import { Hono } from 'hono'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { requireAuth } from '../middleware/auth.middleware'
|
||||
import type { HonoTypes } from '../types'
|
||||
|
||||
@injectable()
|
||||
export class WishlistController implements Controller {
|
||||
controller = new Hono<HonoTypes>();
|
||||
controller = new Hono<HonoTypes>()
|
||||
|
||||
constructor(
|
||||
@inject(WishlistsService) private readonly wishlistsService: WishlistsService
|
||||
) { }
|
||||
constructor(@inject(WishlistsService) private readonly wishlistsService: WishlistsService) {}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/', requireAuth, async (c) => {
|
||||
const user = c.var.user;
|
||||
const wishlists = await this.wishlistsService.findAllByUserId(user.id);
|
||||
return c.json({ wishlists });
|
||||
const user = c.var.user
|
||||
const wishlists = await this.wishlistsService.findAllByUserId(user.id)
|
||||
return c.json({ wishlists })
|
||||
})
|
||||
.get('/:cuid', requireAuth, async (c) => {
|
||||
const cuid = c.req.param('cuid')
|
||||
const wishlist = await this.wishlistsService.findOneByCuid(cuid)
|
||||
return c.json({ wishlist });
|
||||
});
|
||||
return c.json({ wishlist })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
src/lib/server/api/databases/migrate.ts
Normal file
26
src/lib/server/api/databases/migrate.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
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 '../../../../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: env.NODE_ENV === 'development' ? false : 'require',
|
||||
max: 1,
|
||||
})
|
||||
const db = drizzle(connection)
|
||||
|
||||
try {
|
||||
await migrate(db, { migrationsFolder: config.out! })
|
||||
console.log('Migrations complete')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
process.exit()
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
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/infrastructure/database/tables'
|
||||
|
||||
export const InsertCollectionSchema = createInsertSchema(collections, {
|
||||
name: (schema) => schema.name.trim()
|
||||
.min(3, { message: 'Must be at least 3 characters' })
|
||||
.max(64, { message: 'Must be less than 64 characters' }).optional(),
|
||||
name: (schema) =>
|
||||
schema.name.trim().min(3, { message: 'Must be at least 3 characters' }).max(64, { message: 'Must be less than 64 characters' }).optional(),
|
||||
}).omit({
|
||||
id: true,
|
||||
cuid: true,
|
||||
|
|
@ -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/infrastructure/database/tables'
|
||||
|
||||
export const InsertUserSchema = createInsertSchema(usersTable, {
|
||||
email: (schema) => schema.email.max(64).email().optional(),
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import { Table, getTableName, sql } from 'drizzle-orm';
|
||||
import env from '../../../../../env';
|
||||
import { db, pool } from './index';
|
||||
import * as schema from './tables';
|
||||
import * as seeds from './seeds';
|
||||
import { Table, getTableName, sql } from 'drizzle-orm'
|
||||
import env from '../../../../env'
|
||||
import { db, pool } from '../packages/drizzle'
|
||||
import * as seeds from './seeds'
|
||||
import * as schema from './tables'
|
||||
|
||||
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: db, 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 [
|
||||
|
|
@ -41,11 +41,11 @@ for (const table of [
|
|||
schema.wishlists,
|
||||
]) {
|
||||
// await db.delete(table); // clear tables without truncating / resetting ids
|
||||
await resetTable(db, table);
|
||||
await resetTable(db, table)
|
||||
}
|
||||
|
||||
await seeds.roles(db);
|
||||
await seeds.users(db);
|
||||
await seeds.roles(db)
|
||||
await seeds.users(db)
|
||||
|
||||
await pool.end();
|
||||
process.exit();
|
||||
await pool.end()
|
||||
process.exit()
|
||||
11
src/lib/server/api/databases/seeds/roles.ts
Normal file
11
src/lib/server/api/databases/seeds/roles.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import * as schema from '$lib/server/api/databases/tables'
|
||||
import { type db } from '$lib/server/api/packages/drizzle'
|
||||
import roles from './data/roles.json'
|
||||
|
||||
export default async function seed(db: db) {
|
||||
console.log('Creating roles ...')
|
||||
for (const role of roles) {
|
||||
await db.insert(schema.roles).values(role).onConflictDoNothing()
|
||||
}
|
||||
console.log('Roles created.')
|
||||
}
|
||||
|
|
@ -1,31 +1,31 @@
|
|||
import { eq } from 'drizzle-orm';
|
||||
import { Argon2id } from 'oslo/password';
|
||||
import { type db } from '$lib/server/api/infrastructure/database';
|
||||
import * as schema from '$lib/server/api/infrastructure/database/tables';
|
||||
import users from './data/users.json';
|
||||
import { config } from '../../../common/config';
|
||||
import * as schema from '$lib/server/api/databases/tables'
|
||||
import { type db } from '$lib/server/api/packages/drizzle'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { Argon2id } from 'oslo/password'
|
||||
import { config } from '../../configs/config'
|
||||
import users from './data/users.json'
|
||||
|
||||
type JsonUser = {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
id: string
|
||||
username: string
|
||||
email: string
|
||||
password: string
|
||||
roles: {
|
||||
name: string;
|
||||
primary: boolean;
|
||||
}[];
|
||||
};
|
||||
name: string
|
||||
primary: boolean
|
||||
}[]
|
||||
}
|
||||
|
||||
type JsonRole = {
|
||||
name: string;
|
||||
primary: boolean;
|
||||
};
|
||||
name: string
|
||||
primary: boolean
|
||||
}
|
||||
|
||||
export default async function seed(db: db) {
|
||||
const adminRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'admin'));
|
||||
const userRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'user'));
|
||||
const adminRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'admin'))
|
||||
const userRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'user'))
|
||||
|
||||
console.log('Admin Role: ', adminRole);
|
||||
console.log('Admin Role: ', adminRole)
|
||||
const adminUser = await db
|
||||
.insert(schema.usersTable)
|
||||
.values({
|
||||
|
|
@ -36,27 +36,19 @@ export default async function seed(db: db) {
|
|||
verified: true,
|
||||
})
|
||||
.returning()
|
||||
.onConflictDoNothing();
|
||||
.onConflictDoNothing()
|
||||
|
||||
console.log('Admin user created.', adminUser);
|
||||
console.log('Admin user created.', adminUser)
|
||||
|
||||
await db
|
||||
.insert(schema.credentialsTable)
|
||||
.values({
|
||||
user_id: adminUser[0].id,
|
||||
type: schema.CredentialsType.PASSWORD,
|
||||
secret_data: await new Argon2id().hash(`${config.ADMIN_PASSWORD}`),
|
||||
});
|
||||
await db.insert(schema.credentialsTable).values({
|
||||
user_id: adminUser[0].id,
|
||||
type: schema.CredentialsType.PASSWORD,
|
||||
secret_data: await new Argon2id().hash(`${config.ADMIN_PASSWORD}`),
|
||||
})
|
||||
|
||||
await db
|
||||
.insert(schema.collections)
|
||||
.values({ user_id: adminUser[0].id })
|
||||
.onConflictDoNothing();
|
||||
await db.insert(schema.collections).values({ user_id: adminUser[0].id }).onConflictDoNothing()
|
||||
|
||||
await db
|
||||
.insert(schema.wishlists)
|
||||
.values({ user_id: adminUser[0].id })
|
||||
.onConflictDoNothing();
|
||||
await db.insert(schema.wishlists).values({ user_id: adminUser[0].id }).onConflictDoNothing()
|
||||
|
||||
await db
|
||||
.insert(schema.user_roles)
|
||||
|
|
@ -65,9 +57,9 @@ export default async function seed(db: db) {
|
|||
role_id: adminRole[0].id,
|
||||
primary: true,
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
.onConflictDoNothing()
|
||||
|
||||
console.log('Admin user given admin role.');
|
||||
console.log('Admin user given admin role.')
|
||||
|
||||
await db
|
||||
.insert(schema.user_roles)
|
||||
|
|
@ -76,9 +68,9 @@ export default async function seed(db: db) {
|
|||
role_id: userRole[0].id,
|
||||
primary: false,
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
.onConflictDoNothing()
|
||||
|
||||
console.log('Admin user given user role.');
|
||||
console.log('Admin user given user role.')
|
||||
await Promise.all(
|
||||
users.map(async (user) => {
|
||||
const [insertedUser] = await db
|
||||
|
|
@ -86,27 +78,29 @@ export default async function seed(db: db) {
|
|||
.values({
|
||||
...user,
|
||||
})
|
||||
.returning();
|
||||
.returning()
|
||||
await db.insert(schema.credentialsTable).values({
|
||||
user_id: insertedUser?.id,
|
||||
type: schema.CredentialsType.PASSWORD,
|
||||
secret_data: await new Argon2id().hash(user.password),
|
||||
})
|
||||
await db.insert(schema.collections).values({ user_id: insertedUser?.id });
|
||||
await db.insert(schema.wishlists).values({ user_id: insertedUser?.id });
|
||||
await db.insert(schema.collections).values({ user_id: insertedUser?.id })
|
||||
await db.insert(schema.wishlists).values({ user_id: insertedUser?.id })
|
||||
await Promise.all(
|
||||
user.roles.map(async (role: JsonRole) => {
|
||||
const foundRole = await db.query.roles.findFirst({
|
||||
where: eq(schema.roles.name, role.name),
|
||||
});
|
||||
if (!foundRole) { throw new Error('Role not found'); };
|
||||
})
|
||||
if (!foundRole) {
|
||||
throw new Error('Role not found')
|
||||
}
|
||||
await db.insert(schema.user_roles).values({
|
||||
user_id: insertedUser?.id,
|
||||
role_id: foundRole?.id,
|
||||
primary: role?.primary,
|
||||
});
|
||||
})
|
||||
}),
|
||||
);
|
||||
)
|
||||
}),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import {categoriesToExternalIdsTable} from './categoriesToExternalIdsTable';
|
||||
import { categories_to_games_table } from './categoriesToGames';
|
||||
import { timestamps } from '../utils';
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { categoriesToExternalIdsTable } from './categoriesToExternalIdsTable'
|
||||
import { categories_to_games_table } from './categoriesToGames'
|
||||
|
||||
export const categoriesTable = pgTable('categories', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -13,11 +13,11 @@ export const categoriesTable = pgTable('categories', {
|
|||
name: text('name'),
|
||||
slug: text('slug'),
|
||||
...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,9 +1,9 @@
|
|||
import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { collections } from './collections';
|
||||
import {games} from './games';
|
||||
import { timestamps } from '../utils';
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { collections } from './collections'
|
||||
import { games } from './games'
|
||||
|
||||
export const collection_items = pgTable('collection_items', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -18,9 +18,9 @@ export const collection_items = pgTable('collection_items', {
|
|||
.references(() => games.id, { onDelete: 'cascade' }),
|
||||
times_played: integer('times_played').default(0),
|
||||
...timestamps,
|
||||
});
|
||||
})
|
||||
|
||||
export type CollectionItems = InferSelectModel<typeof collection_items>;
|
||||
export type CollectionItems = 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: [games.id],
|
||||
}),
|
||||
}));
|
||||
}))
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { usersTable } from './users.table';
|
||||
import { timestamps } from '../utils';
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export const collections = pgTable('collections', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -14,14 +14,13 @@ export const collections = pgTable('collections', {
|
|||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull().default('My Collection'),
|
||||
...timestamps,
|
||||
});
|
||||
})
|
||||
|
||||
export const collection_relations = relations(collections, ({ one }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [collections.user_id],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export type Collections = InferSelectModel<typeof collections>;
|
||||
}))
|
||||
|
||||
export type Collections = InferSelectModel<typeof collections>
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import { pgTable, text, uuid } from "drizzle-orm/pg-core";
|
||||
import { type InferSelectModel } from 'drizzle-orm';
|
||||
import { timestamps } from '../utils';
|
||||
import { usersTable } from "./users.table";
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { type InferSelectModel } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export enum CredentialsType {
|
||||
SECRET = 'secret',
|
||||
PASSWORD = 'password',
|
||||
TOTP = 'totp',
|
||||
HOTP = 'hotp'
|
||||
HOTP = 'hotp',
|
||||
}
|
||||
|
||||
export const credentialsTable = pgTable('credentials', {
|
||||
|
|
@ -17,7 +17,7 @@ export const credentialsTable = pgTable('credentials', {
|
|||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
type: text('type').notNull().default(CredentialsType.PASSWORD),
|
||||
secret_data: text('secret_data').notNull(),
|
||||
...timestamps
|
||||
});
|
||||
...timestamps,
|
||||
})
|
||||
|
||||
export type Credentials = InferSelectModel<typeof credentialsTable>;
|
||||
export type Credentials = InferSelectModel<typeof credentialsTable>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import {games} from './games';
|
||||
import { timestamps } from '../utils';
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { games } from './games'
|
||||
|
||||
export const expansions = pgTable('expansions', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -16,9 +16,9 @@ export const expansions = pgTable('expansions', {
|
|||
.notNull()
|
||||
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
|
||||
...timestamps,
|
||||
});
|
||||
})
|
||||
|
||||
export type Expansions = InferSelectModel<typeof expansions>;
|
||||
export type Expansions = InferSelectModel<typeof expansions>
|
||||
|
||||
export const expansion_relations = relations(expansions, ({ one }) => ({
|
||||
baseGame: one(games, {
|
||||
|
|
@ -29,4 +29,4 @@ export const expansion_relations = relations(expansions, ({ one }) => ({
|
|||
fields: [expansions.game_id],
|
||||
references: [games.id],
|
||||
}),
|
||||
}));
|
||||
}))
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { pgTable, text, uuid } from "drizzle-orm/pg-core";
|
||||
import { type InferSelectModel } from 'drizzle-orm';
|
||||
import { usersTable } from "./users.table";
|
||||
import { timestamps } from '../utils';
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { type InferSelectModel } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export const federatedIdentityTable = pgTable('federated_identity', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -11,7 +11,7 @@ export const federatedIdentityTable = pgTable('federated_identity', {
|
|||
identity_provider: text('identity_provider').notNull(),
|
||||
federated_user_id: text('federated_user_id').notNull(),
|
||||
federated_username: text('federated_username').notNull(),
|
||||
...timestamps
|
||||
});
|
||||
...timestamps,
|
||||
})
|
||||
|
||||
export type FederatedIdentity = InferSelectModel<typeof federatedIdentityTable>;
|
||||
export type FederatedIdentity = InferSelectModel<typeof federatedIdentityTable>
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { index, integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations, sql } from 'drizzle-orm';
|
||||
import {categories_to_games_table} from './categoriesToGames';
|
||||
import {gamesToExternalIds} from './gamesToExternalIds';
|
||||
import {mechanics_to_games} from './mechanicsToGames';
|
||||
import {publishers_to_games} from './publishersToGames';
|
||||
import { timestamps } from '../utils';
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
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 { categories_to_games_table } from './categoriesToGames'
|
||||
import { gamesToExternalIds } from './gamesToExternalIds'
|
||||
import { mechanics_to_games } from './mechanicsToGames'
|
||||
import { publishers_to_games } from './publishersToGames'
|
||||
|
||||
export const games = pgTable(
|
||||
'games',
|
||||
|
|
@ -39,13 +39,13 @@ export const games = pgTable(
|
|||
)`,
|
||||
),
|
||||
}),
|
||||
);
|
||||
)
|
||||
|
||||
export const gameRelations = relations(games, ({ many }) => ({
|
||||
categories_to_games: many(categories_to_games_table),
|
||||
mechanics_to_games: many(mechanics_to_games),
|
||||
publishers_to_games: many(publishers_to_games),
|
||||
gamesToExternalIds: many(gamesToExternalIds),
|
||||
}));
|
||||
}))
|
||||
|
||||
export type Games = InferSelectModel<typeof games>;
|
||||
export type Games = InferSelectModel<typeof games>
|
||||
23
src/lib/server/api/databases/tables/mechanics.ts
Normal file
23
src/lib/server/api/databases/tables/mechanics.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, 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,8 +1,8 @@
|
|||
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { usersTable } from './users.table';
|
||||
import { timestamps } from '../utils';
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export const password_reset_tokens = pgTable('password_reset_tokens', {
|
||||
id: text('id')
|
||||
|
|
@ -13,13 +13,13 @@ export const password_reset_tokens = pgTable('password_reset_tokens', {
|
|||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
expires_at: timestamp('expires_at'),
|
||||
...timestamps,
|
||||
});
|
||||
})
|
||||
|
||||
export type PasswordResetTokens = InferSelectModel<typeof password_reset_tokens>;
|
||||
export type PasswordResetTokens = InferSelectModel<typeof password_reset_tokens>
|
||||
|
||||
export const password_reset_token_relations = relations(password_reset_tokens, ({ one }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [password_reset_tokens.user_id],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
}))
|
||||
23
src/lib/server/api/databases/tables/publishers.ts
Normal file
23
src/lib/server/api/databases/tables/publishers.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, 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,7 +1,7 @@
|
|||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import type { InferSelectModel } from 'drizzle-orm';
|
||||
import { usersTable } from './users.table';
|
||||
import { timestamps } from '../utils';
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import type { InferSelectModel } from 'drizzle-orm'
|
||||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export const recoveryCodesTable = pgTable('recovery_codes', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -11,6 +11,6 @@ export const recoveryCodesTable = pgTable('recovery_codes', {
|
|||
code: text('code').notNull(),
|
||||
used: boolean('used').default(false),
|
||||
...timestamps,
|
||||
});
|
||||
})
|
||||
|
||||
export type RecoveryCodesTable = InferSelectModel<typeof recoveryCodesTable>;
|
||||
export type RecoveryCodesTable = InferSelectModel<typeof recoveryCodesTable>
|
||||
21
src/lib/server/api/databases/tables/roles.ts
Normal file
21
src/lib/server/api/databases/tables/roles.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { user_roles } from './userRoles'
|
||||
|
||||
export const roles = pgTable('roles', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
.unique()
|
||||
.$defaultFn(() => cuid2())
|
||||
.notNull(),
|
||||
name: text('name').unique().notNull(),
|
||||
...timestamps,
|
||||
})
|
||||
|
||||
export type Roles = InferSelectModel<typeof roles>
|
||||
|
||||
export const role_relations = relations(roles, ({ many }) => ({
|
||||
user_roles: many(user_roles),
|
||||
}))
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
||||
import { timestamps } from '../utils';
|
||||
import { usersTable } from './users.table';
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export const twoFactorTable = pgTable('two_factor', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -20,13 +20,13 @@ export const twoFactorTable = pgTable('two_factor', {
|
|||
.references(() => usersTable.id)
|
||||
.unique(),
|
||||
...timestamps,
|
||||
});
|
||||
})
|
||||
|
||||
export const emailVerificationsRelations = relations(twoFactorTable, ({ one }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [twoFactorTable.userId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
}))
|
||||
|
||||
export type TwoFactor = InferSelectModel<typeof twoFactorTable>;
|
||||
export type TwoFactor = InferSelectModel<typeof twoFactorTable>
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { usersTable } from './users.table';
|
||||
import {roles} from './roles';
|
||||
import { timestamps } from '../utils';
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { roles } from './roles'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export const user_roles = pgTable('user_roles', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -18,7 +18,7 @@ export const user_roles = pgTable('user_roles', {
|
|||
.references(() => roles.id, { onDelete: 'cascade' }),
|
||||
primary: boolean('primary').default(false),
|
||||
...timestamps,
|
||||
});
|
||||
})
|
||||
|
||||
export const user_role_relations = relations(user_roles, ({ one }) => ({
|
||||
role: one(roles, {
|
||||
|
|
@ -29,6 +29,6 @@ export const user_role_relations = relations(user_roles, ({ one }) => ({
|
|||
fields: [user_roles.user_id],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
}))
|
||||
|
||||
export type UserRoles = InferSelectModel<typeof user_roles>;
|
||||
export type UserRoles = InferSelectModel<typeof user_roles>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { timestamps } from '../utils'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { user_roles } from './userRoles'
|
||||
|
||||
export const usersTable = pgTable('users', {
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import {wishlists} from './wishlists';
|
||||
import {games} from './games';
|
||||
import { timestamps } from '../utils';
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { games } from './games'
|
||||
import { wishlists } from './wishlists'
|
||||
|
||||
export const wishlist_items = pgTable('wishlist_items', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -17,9 +17,9 @@ export const wishlist_items = pgTable('wishlist_items', {
|
|||
.notNull()
|
||||
.references(() => games.id, { onDelete: 'cascade' }),
|
||||
...timestamps,
|
||||
});
|
||||
})
|
||||
|
||||
export type WishlistItems = InferSelectModel<typeof wishlist_items>;
|
||||
export type WishlistItems = InferSelectModel<typeof wishlist_items>
|
||||
|
||||
export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({
|
||||
wishlist: one(wishlists, {
|
||||
|
|
@ -30,4 +30,4 @@ export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({
|
|||
fields: [wishlist_items.game_id],
|
||||
references: [games.id],
|
||||
}),
|
||||
}));
|
||||
}))
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { usersTable } from './users.table';
|
||||
import { timestamps } from '../utils';
|
||||
import { timestamps } from '$lib/server/api/common/utils/table.utils'
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2'
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { usersTable } from './users.table'
|
||||
|
||||
export const wishlists = pgTable('wishlists', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
|
@ -14,13 +14,13 @@ export const wishlists = pgTable('wishlists', {
|
|||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull().default('My Wishlist'),
|
||||
...timestamps,
|
||||
});
|
||||
})
|
||||
|
||||
export type Wishlists = InferSelectModel<typeof wishlists>;
|
||||
export type Wishlists = InferSelectModel<typeof wishlists>
|
||||
|
||||
export const wishlists_relations = relations(wishlists, ({ one }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [wishlists.user_id],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
}))
|
||||
1
src/lib/server/api/dtos/create-user-role.dto.ts
Normal file
1
src/lib/server/api/dtos/create-user-role.dto.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
5
src/lib/server/api/dtos/id-params.dto.ts
Normal file
5
src/lib/server/api/dtos/id-params.dto.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const IdParamsDto = z.object({
|
||||
id: z.trim().number(),
|
||||
});
|
||||
20
src/lib/server/api/dtos/register-emailpassword.dto.ts
Normal file
20
src/lib/server/api/dtos/register-emailpassword.dto.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { z } from "zod";
|
||||
import { refinePasswords } from "$lib/validations/account";
|
||||
|
||||
export const registerEmailPasswordDto = z.object({
|
||||
firstName: z.string().trim().optional(),
|
||||
lastName: z.string().trim().optional(),
|
||||
email: z.string().trim().max(64, { message: 'Email must be less than 64 characters' }).optional(),
|
||||
username: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, { message: 'Must be at least 3 characters' })
|
||||
.max(50, { message: 'Must be less than 50 characters' }),
|
||||
password: z.string({ required_error: 'Password is required' }).trim(),
|
||||
confirm_password: z.string({ required_error: 'Confirm Password is required' }).trim(),
|
||||
})
|
||||
.superRefine(({ confirm_password, password }, ctx) => {
|
||||
refinePasswords(confirm_password, password, ctx);
|
||||
});
|
||||
|
||||
export type RegisterEmailPasswordDto = z.infer<typeof registerEmailPasswordDto>;
|
||||
12
src/lib/server/api/dtos/signin-username.dto.ts
Normal file
12
src/lib/server/api/dtos/signin-username.dto.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const signinUsernameDto = z.object({
|
||||
username: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, { message: 'Must be at least 3 characters' })
|
||||
.max(50, { message: 'Must be less than 50 characters' }),
|
||||
password: z.string({ required_error: 'Password is required' }).trim(),
|
||||
});
|
||||
|
||||
export type SigninUsernameDto = z.infer<typeof signinUsernameDto>;
|
||||
24
src/lib/server/api/dtos/signup-username-email.dto.ts
Normal file
24
src/lib/server/api/dtos/signup-username-email.dto.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { z } from "zod";
|
||||
import { refinePasswords } from "$lib/validations/account";
|
||||
|
||||
export const signupUsernameEmailDto = z.object({
|
||||
firstName: z.string().trim().optional(),
|
||||
lastName: z.string().trim().optional(),
|
||||
email: z.string()
|
||||
.trim()
|
||||
.max(64, {message: 'Email must be less than 64 characters'})
|
||||
.email({message: 'Please enter a valid email'})
|
||||
.optional(),
|
||||
username: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, {message: 'Must be at least 3 characters'})
|
||||
.max(50, {message: 'Must be less than 50 characters'}),
|
||||
password: z.string({required_error: 'Password is required'}).trim(),
|
||||
confirm_password: z.string({required_error: 'Confirm Password is required'}).trim()
|
||||
})
|
||||
.superRefine(({ confirm_password, password }, ctx) => {
|
||||
return refinePasswords(confirm_password, password, ctx);
|
||||
});
|
||||
|
||||
export type SignupUsernameEmailDto = z.infer<typeof signupUsernameEmailDto>
|
||||
11
src/lib/server/api/dtos/update-email.dto.ts
Normal file
11
src/lib/server/api/dtos/update-email.dto.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const updateEmailDto = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(64, {message: 'Email must be less than 64 characters'})
|
||||
.email({message: 'Please enter a valid email'})
|
||||
});
|
||||
|
||||
export type UpdateEmailDto = z.infer<typeof updateEmailDto>;
|
||||
23
src/lib/server/api/dtos/update-profile.dto.ts
Normal file
23
src/lib/server/api/dtos/update-profile.dto.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const updateProfileDto = z.object({
|
||||
firstName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, {message: 'Must be at least 3 characters'})
|
||||
.max(50, {message: 'Must be less than 50 characters'})
|
||||
.optional(),
|
||||
lastName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, {message: 'Must be at least 3 characters'})
|
||||
.max(50, {message: 'Must be less than 50 characters'})
|
||||
.optional(),
|
||||
username: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, {message: 'Must be at least 3 characters'})
|
||||
.max(50, {message: 'Must be less than 50 characters'})
|
||||
});
|
||||
|
||||
export type UpdateProfileDto = z.infer<typeof updateProfileDto>;
|
||||
7
src/lib/server/api/dtos/verify-password.dto.ts
Normal file
7
src/lib/server/api/dtos/verify-password.dto.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { z } from 'zod'
|
||||
|
||||
export const verifyPasswordDto = z.object({
|
||||
password: z.string({ required_error: 'Password is required' }).trim(),
|
||||
})
|
||||
|
||||
export type VerifyPasswordDto = z.infer<typeof verifyPasswordDto>
|
||||
11
src/lib/server/api/dtos/verify-totp.dto.ts
Normal file
11
src/lib/server/api/dtos/verify-totp.dto.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const verifyTotpDto = z.object({
|
||||
code: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(6, { message: 'Must be at least 6 characters' })
|
||||
.max(6, { message: 'Must be less than 6 characters' }),
|
||||
});
|
||||
|
||||
export type VerifyTotpDto = z.infer<typeof verifyTotpDto>;
|
||||
|
|
@ -1,59 +1,64 @@
|
|||
import 'reflect-metadata'
|
||||
import { Hono } from 'hono';
|
||||
import { hc } from 'hono/client';
|
||||
import { cors } from 'hono/cors';
|
||||
import { logger } from 'hono/logger';
|
||||
import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware';
|
||||
import { config } from './common/config';
|
||||
import { container } from 'tsyringe';
|
||||
import { IamController } from './controllers/iam.controller';
|
||||
import { LoginController } from './controllers/login.controller';
|
||||
import { MfaController} from "$lib/server/api/controllers/mfa.controller";
|
||||
import {UserController} from "$lib/server/api/controllers/user.controller";
|
||||
import {SignupController} from "$lib/server/api/controllers/signup.controller";
|
||||
import {WishlistController} from "$lib/server/api/controllers/wishlist.controller";
|
||||
import {CollectionController} from "$lib/server/api/controllers/collection.controller";
|
||||
import { CollectionController } from '$lib/server/api/controllers/collection.controller'
|
||||
import { MfaController } from '$lib/server/api/controllers/mfa.controller'
|
||||
import { SignupController } from '$lib/server/api/controllers/signup.controller'
|
||||
import { UserController } from '$lib/server/api/controllers/user.controller'
|
||||
import { WishlistController } from '$lib/server/api/controllers/wishlist.controller'
|
||||
import { AuthCleanupJobs } from '$lib/server/api/jobs/auth-cleanup.job'
|
||||
import { Hono } from 'hono'
|
||||
import { hc } from 'hono/client'
|
||||
import { cors } from 'hono/cors'
|
||||
import { logger } from 'hono/logger'
|
||||
import { container } from 'tsyringe'
|
||||
import { config } from './configs/config'
|
||||
import { IamController } from './controllers/iam.controller'
|
||||
import { LoginController } from './controllers/login.controller'
|
||||
import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware'
|
||||
|
||||
/* ----------------------------------- Api ---------------------------------- */
|
||||
const app = new Hono().basePath('/api');
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* App */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export const app = new Hono().basePath('/api')
|
||||
|
||||
/* --------------------------- Global Middlewares --------------------------- */
|
||||
app.use(verifyOrigin).use(validateAuthSession);
|
||||
app.use(logger());
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Global Middlewares */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
app.use(verifyOrigin).use(validateAuthSession)
|
||||
app.use(logger())
|
||||
|
||||
app.use(
|
||||
'/*',
|
||||
cors({
|
||||
origin: [
|
||||
'http://localhost:5173',
|
||||
'http://localhost:80',
|
||||
'http://host.docker.internal:80',
|
||||
'http://host.docker.internal:5173'
|
||||
], // Replace with your allowed domains
|
||||
origin: ['http://localhost:5173', 'http://localhost:80', 'http://host.docker.internal:80', 'http://host.docker.internal:5173'], // Replace with your allowed domains
|
||||
|
||||
allowMethods: ['POST'],
|
||||
allowHeaders: ['Content-Type']
|
||||
allowHeaders: ['Content-Type'],
|
||||
// credentials: true, // If you need to send cookies or HTTP authentication
|
||||
})
|
||||
);
|
||||
}),
|
||||
)
|
||||
|
||||
/* --------------------------------- Routes --------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Routes */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
const routes = app
|
||||
.route('/me', container.resolve(IamController).routes())
|
||||
.route('/user', container.resolve(UserController).routes())
|
||||
.route('/login', container.resolve(LoginController).routes())
|
||||
.route('/signup', container.resolve(SignupController).routes())
|
||||
.route('/wishlists', container.resolve(WishlistController).routes())
|
||||
.route('/collections', container.resolve(CollectionController).routes())
|
||||
.route('/mfa', container.resolve(MfaController).routes())
|
||||
.get('/', (c) => c.json({ message: 'Server is healthy' }));
|
||||
.route('/me', container.resolve(IamController).routes())
|
||||
.route('/user', container.resolve(UserController).routes())
|
||||
.route('/login', container.resolve(LoginController).routes())
|
||||
.route('/signup', container.resolve(SignupController).routes())
|
||||
.route('/wishlists', container.resolve(WishlistController).routes())
|
||||
.route('/collections', container.resolve(CollectionController).routes())
|
||||
.route('/mfa', container.resolve(MfaController).routes())
|
||||
.get('/', (c) => c.json({ message: 'Server is healthy' }))
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Cron Jobs */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
container.resolve(AuthCleanupJobs).deleteStaleEmailVerificationRequests()
|
||||
container.resolve(AuthCleanupJobs).deleteStaleLoginRequests()
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Exports */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export type AppType = typeof routes;
|
||||
|
||||
export const rpc = hc<AppType>(config.ORIGIN);
|
||||
export type ApiClient = typeof rpc;
|
||||
export type ApiRoutes = typeof routes;
|
||||
export { app };
|
||||
export const rpc = hc<typeof routes>(config.ORIGIN)
|
||||
export type ApiClient = typeof rpc
|
||||
export type ApiRoutes = typeof routes
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
import 'dotenv/config';
|
||||
import postgres from 'postgres';
|
||||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import { migrate } from 'drizzle-orm/postgres-js/migrator';
|
||||
import env from '../../../../../env';
|
||||
import config from '../../../../../../drizzle.config';
|
||||
|
||||
const connection = postgres({
|
||||
host: env.DATABASE_HOST || 'localhost',
|
||||
port: env.DATABASE_PORT,
|
||||
user: env.DATABASE_USER || 'root',
|
||||
password: env.DATABASE_PASSWORD || '',
|
||||
database: env.DATABASE_DB || 'boredgame',
|
||||
ssl: env.NODE_ENV === 'development' ? false : 'require',
|
||||
max: 1,
|
||||
});
|
||||
const db = drizzle(connection);
|
||||
|
||||
try {
|
||||
await migrate(db, { migrationsFolder: config.out! });
|
||||
console.log('Migrations complete');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
process.exit();
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { type db } from '$lib/server/api/infrastructure/database';
|
||||
import * as schema from '$lib/server/api/infrastructure/database/tables';
|
||||
import roles from './data/roles.json';
|
||||
|
||||
export default async function seed(db: db) {
|
||||
console.log('Creating roles ...');
|
||||
for (const role of roles) {
|
||||
await db.insert(schema.roles).values(role).onConflictDoNothing();
|
||||
}
|
||||
console.log('Roles created.');
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import {mechanics_to_games} from './mechanicsToGames';
|
||||
import {mechanicsToExternalIds} from './mechanicsToExternalIds';
|
||||
import { timestamps } from '../utils';
|
||||
|
||||
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 { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import {publishers_to_games} from './publishersToGames';
|
||||
import {publishersToExternalIds} from './publishersToExternalIds';
|
||||
import { timestamps } from '../utils';
|
||||
|
||||
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,21 +0,0 @@
|
|||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import {user_roles} from './userRoles';
|
||||
import { timestamps } from '../utils';
|
||||
|
||||
export const roles = pgTable('roles', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
.unique()
|
||||
.$defaultFn(() => cuid2())
|
||||
.notNull(),
|
||||
name: text('name').unique().notNull(),
|
||||
...timestamps,
|
||||
});
|
||||
|
||||
export type Roles = InferSelectModel<typeof roles>;
|
||||
|
||||
export const role_relations = relations(roles, ({ many }) => ({
|
||||
user_roles: many(user_roles),
|
||||
}));
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
import { HTTPException } from 'hono/http-exception';
|
||||
import { timestamp, customType } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const citext = customType<{ data: string }>({
|
||||
dataType() {
|
||||
return 'citext';
|
||||
}
|
||||
});
|
||||
|
||||
export const cuid2 = customType<{ data: string }>({
|
||||
dataType() {
|
||||
return 'text';
|
||||
}
|
||||
});
|
||||
|
||||
export const takeFirst = <T>(values: T[]): T | null => {
|
||||
if (values.length === 0) return null;
|
||||
return values[0]!;
|
||||
};
|
||||
|
||||
export const takeFirstOrThrow = <T>(values: T[]): T => {
|
||||
if (values.length === 0)
|
||||
throw new HTTPException(404, {
|
||||
message: 'Resource not found'
|
||||
});
|
||||
return values[0]!;
|
||||
};
|
||||
|
||||
export const timestamps = {
|
||||
createdAt: timestamp('created_at', {
|
||||
mode: 'date',
|
||||
withTimezone: true
|
||||
})
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
updatedAt: timestamp('updated_at', {
|
||||
mode: 'date',
|
||||
withTimezone: true
|
||||
})
|
||||
.notNull()
|
||||
.defaultNow()
|
||||
};
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { Hono } from 'hono';
|
||||
import type { HonoTypes } from '../types';
|
||||
import type { BlankSchema } from 'hono/types';
|
||||
|
||||
export interface Controller {
|
||||
controller: Hono<HonoTypes, BlankSchema, '/'>;
|
||||
routes(): any;
|
||||
}
|
||||
42
src/lib/server/api/jobs/auth-cleanup.job.ts
Normal file
42
src/lib/server/api/jobs/auth-cleanup.job.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { inject, injectable } from 'tsyringe'
|
||||
import { JobsService } from '../services/jobs.service'
|
||||
|
||||
@injectable()
|
||||
export class AuthCleanupJobs {
|
||||
private queue
|
||||
|
||||
constructor(@inject(JobsService) private jobsService: JobsService) {
|
||||
/* ------------------------------ Create Queue ------------------------------ */
|
||||
this.queue = this.jobsService.createQueue('test')
|
||||
|
||||
/* ---------------------------- Register Workers ---------------------------- */
|
||||
this.worker().then((r) => console.log('auth-cleanup job worker started'))
|
||||
}
|
||||
|
||||
async deleteStaleEmailVerificationRequests() {
|
||||
await this.queue.add('delete_stale_email_verifiactions', null, {
|
||||
repeat: {
|
||||
pattern: '0 0 * * 0', // Runs once a week at midnight on Sunday
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async deleteStaleLoginRequests() {
|
||||
await this.queue.add('delete_stale_login_requests', null, {
|
||||
repeat: {
|
||||
pattern: '0 0 * * 0', // Runs once a week at midnight on Sunday
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private async worker() {
|
||||
return this.jobsService.createWorker(this.queue.name, async (job) => {
|
||||
if (job.name === 'delete_stale_email_verifiactions') {
|
||||
// delete stale email verifications
|
||||
}
|
||||
if (job.name === 'delete_stale_login_requests') {
|
||||
// delete stale email verifications
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import type { MiddlewareHandler } from 'hono'
|
||||
import { createMiddleware } from 'hono/factory'
|
||||
import type { HonoTypes } from '../types'
|
||||
import { lucia } from '../infrastructure/auth/lucia'
|
||||
import { verifyRequestOrigin } from 'oslo/request'
|
||||
import type { Session, User } from 'lucia'
|
||||
import { Unauthorized } from '../common/errors'
|
||||
import { verifyRequestOrigin } from 'oslo/request'
|
||||
import { Unauthorized } from '../common/exceptions'
|
||||
import { lucia } from '../packages/lucia'
|
||||
import type { HonoTypes } from '../types'
|
||||
|
||||
export const verifyOrigin: MiddlewareHandler<HonoTypes> = createMiddleware(async (c, next) => {
|
||||
if (c.req.method === 'GET') {
|
||||
|
|
|
|||
|
|
@ -1,32 +1,34 @@
|
|||
import { rateLimiter } from "hono-rate-limiter";
|
||||
import { RedisStore } from 'rate-limit-redis'
|
||||
import { rateLimiter } from 'hono-rate-limiter'
|
||||
import RedisClient from 'ioredis'
|
||||
import type { HonoTypes } from "../types";
|
||||
import { config } from "../common/config";
|
||||
import { RedisStore } from 'rate-limit-redis'
|
||||
import { config } from '../configs/config'
|
||||
import type { HonoTypes } from '../types'
|
||||
|
||||
const client = new RedisClient(config.REDIS_URL)
|
||||
|
||||
export function limiter({ limit, minutes, key = "" }: {
|
||||
limit: number;
|
||||
minutes: number;
|
||||
key?: string;
|
||||
export function limiter({
|
||||
limit,
|
||||
minutes,
|
||||
key = '',
|
||||
}: {
|
||||
limit: number
|
||||
minutes: number
|
||||
key?: string
|
||||
}) {
|
||||
return rateLimiter({
|
||||
windowMs: minutes * 60 * 1000, // every x minutes
|
||||
limit, // Limit each IP to 100 requests per `window` (here, per 15 minutes).
|
||||
standardHeaders: "draft-6", // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header
|
||||
keyGenerator: (c) => {
|
||||
const vars = c.var as HonoTypes['Variables'];
|
||||
const clientKey = vars.user?.id || c.req.header("x-forwarded-for");
|
||||
const pathKey = key || c.req.routePath;
|
||||
return `${clientKey}_${pathKey}`
|
||||
}, // Method to generate custom identifiers for clients.
|
||||
// Redis store configuration
|
||||
store: new RedisStore({
|
||||
// @ts-expect-error - Known issue: the `call` function is not present in @types/ioredis
|
||||
sendCommand: (...args: string[]) => client.call(...args),
|
||||
}) as any,
|
||||
})
|
||||
return rateLimiter({
|
||||
windowMs: minutes * 60 * 1000, // every x minutes
|
||||
limit, // Limit each IP to 100 requests per `window` (here, per 15 minutes).
|
||||
standardHeaders: 'draft-6', // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header
|
||||
keyGenerator: (c) => {
|
||||
const vars = c.var as HonoTypes['Variables']
|
||||
const clientKey = vars.user?.id || c.req.header('x-forwarded-for')
|
||||
const pathKey = key || c.req.routePath
|
||||
return `${clientKey}_${pathKey}`
|
||||
}, // Method to generate custom identifiers for clients.
|
||||
// Redis store configuration
|
||||
store: new RedisStore({
|
||||
// @ts-expect-error - Known issue: the `call` function is not present in @types/ioredis
|
||||
sendCommand: (...args: string[]) => client.call(...args),
|
||||
}) as any,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import pg from 'pg';
|
||||
import { config } from '../../common/config';
|
||||
import * as schema from './tables';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres'
|
||||
import pg from 'pg'
|
||||
import { config } from '../configs/config'
|
||||
import * as schema from '../databases/tables'
|
||||
|
||||
// create the connection
|
||||
export const pool = new pg.Pool({
|
||||
|
|
@ -12,11 +12,11 @@ export const pool = new pg.Pool({
|
|||
database: config.DATABASE_DB,
|
||||
ssl: config.DATABASE_HOST !== 'localhost',
|
||||
max: config.DB_MIGRATING || config.DB_SEEDING ? 1 : undefined,
|
||||
});
|
||||
})
|
||||
|
||||
export const db = drizzle(pool, {
|
||||
schema,
|
||||
logger: config.NODE_ENV === 'development',
|
||||
});
|
||||
})
|
||||
|
||||
export type db = typeof db;
|
||||
export type db = typeof db
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle'
|
||||
// lib/server/lucia.ts
|
||||
import { Lucia, TimeSpan } from 'lucia';
|
||||
import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle';
|
||||
import { db } from '../database';
|
||||
import { sessionsTable, usersTable } from '../database/tables';
|
||||
import { config } from '../../common/config';
|
||||
import { Lucia, TimeSpan } from 'lucia'
|
||||
import { config } from '../configs/config'
|
||||
import { sessionsTable, usersTable } from '../databases/tables'
|
||||
import { db } from './drizzle'
|
||||
|
||||
const adapter = new DrizzlePostgreSQLAdapter(db, sessionsTable, usersTable);
|
||||
const adapter = new DrizzlePostgreSQLAdapter(db, sessionsTable, usersTable)
|
||||
|
||||
export const lucia = new Lucia(adapter, {
|
||||
getSessionAttributes: (attributes) => {
|
||||
|
|
@ -14,7 +14,7 @@ export const lucia = new Lucia(adapter, {
|
|||
ipAddress: attributes.ip_address,
|
||||
isTwoFactorAuthEnabled: attributes.twoFactorAuthEnabled,
|
||||
isTwoFactorAuthenticated: attributes.isTwoFactorAuthenticated,
|
||||
};
|
||||
}
|
||||
},
|
||||
getUserAttributes: (attributes) => {
|
||||
return {
|
||||
|
|
@ -25,7 +25,7 @@ export const lucia = new Lucia(adapter, {
|
|||
lastName: attributes.last_name,
|
||||
mfa_enabled: attributes.mfa_enabled,
|
||||
theme: attributes.theme,
|
||||
};
|
||||
}
|
||||
},
|
||||
sessionExpiresIn: new TimeSpan(2, 'w'), // 2 weeks
|
||||
sessionCookie: {
|
||||
|
|
@ -38,26 +38,26 @@ export const lucia = new Lucia(adapter, {
|
|||
domain: config.domain,
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
declare module 'lucia' {
|
||||
interface Register {
|
||||
Lucia: typeof lucia;
|
||||
DatabaseUserAttributes: DatabaseUserAttributes;
|
||||
DatabaseSessionAttributes: DatabaseSessionAttributes;
|
||||
Lucia: typeof lucia
|
||||
DatabaseUserAttributes: DatabaseUserAttributes
|
||||
DatabaseSessionAttributes: DatabaseSessionAttributes
|
||||
}
|
||||
interface DatabaseSessionAttributes {
|
||||
ip_country: string;
|
||||
ip_address: string;
|
||||
twoFactorAuthEnabled: boolean;
|
||||
isTwoFactorAuthenticated: boolean;
|
||||
ip_country: string
|
||||
ip_address: string
|
||||
twoFactorAuthEnabled: boolean
|
||||
isTwoFactorAuthenticated: boolean
|
||||
}
|
||||
interface DatabaseUserAttributes {
|
||||
username: string;
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
mfa_enabled: boolean;
|
||||
theme: string;
|
||||
username: string
|
||||
email: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
mfa_enabled: boolean
|
||||
theme: string
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { container } from 'tsyringe';
|
||||
import { db } from '../infrastructure/database';
|
||||
import { container } from 'tsyringe'
|
||||
import { db } from '../packages/drizzle'
|
||||
|
||||
// Symbol
|
||||
export const DatabaseProvider = Symbol('DATABASE_TOKEN');
|
||||
export const DatabaseProvider = Symbol('DATABASE_TOKEN')
|
||||
|
||||
// Type
|
||||
export type DatabaseProvider = typeof db;
|
||||
export type DatabaseProvider = typeof db
|
||||
|
||||
// Register
|
||||
container.register<DatabaseProvider>(DatabaseProvider, { useValue: db });
|
||||
container.register<DatabaseProvider>(DatabaseProvider, { useValue: db })
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
export * from './database.provider';
|
||||
export * from './lucia.provider';
|
||||
export * from './redis.provider';
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { container } from 'tsyringe';
|
||||
import { lucia } from '../infrastructure/auth/lucia';
|
||||
import { container } from 'tsyringe'
|
||||
import { lucia } from '../packages/lucia'
|
||||
|
||||
// Symbol
|
||||
export const LuciaProvider = Symbol('LUCIA_PROVIDER');
|
||||
export const LuciaProvider = Symbol('LUCIA_PROVIDER')
|
||||
|
||||
// Type
|
||||
export type LuciaProvider = typeof lucia;
|
||||
export type LuciaProvider = typeof lucia
|
||||
|
||||
// Register
|
||||
container.register<LuciaProvider>(LuciaProvider, { useValue: lucia });
|
||||
container.register<LuciaProvider>(LuciaProvider, { useValue: lucia })
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import { container } from 'tsyringe';
|
||||
import RedisClient from 'ioredis'
|
||||
import { config } from '../common/config';
|
||||
import { container } from 'tsyringe'
|
||||
import { config } from '../configs/config'
|
||||
|
||||
// Symbol
|
||||
export const RedisProvider = Symbol('REDIS_TOKEN');
|
||||
|
||||
// Type
|
||||
export type RedisProvider = RedisClient;
|
||||
|
||||
// Register
|
||||
export const RedisProvider = Symbol('REDIS_TOKEN')
|
||||
export type RedisProvider = RedisClient
|
||||
container.register<RedisProvider>(RedisProvider, {
|
||||
useValue: new RedisClient(config.REDIS_URL)
|
||||
});
|
||||
useValue: new RedisClient(config.REDIS_URL, {
|
||||
maxRetriesPerRequest: null,
|
||||
}),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import {inject, injectable} from "tsyringe";
|
||||
import { eq, type InferInsertModel } from "drizzle-orm";
|
||||
import {DatabaseProvider} from "$lib/server/api/providers";
|
||||
import { collections } from "../infrastructure/database/tables";
|
||||
import { takeFirstOrThrow } from "../infrastructure/database/utils";
|
||||
import type { Repository } from '$lib/server/api/common/interfaces/repository.interface'
|
||||
import { takeFirstOrThrow } from '$lib/server/api/common/utils/repository.utils'
|
||||
import { DatabaseProvider } from '$lib/server/api/providers/database.provider'
|
||||
import { type InferInsertModel, eq } from 'drizzle-orm'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { collections } from '../databases/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(DatabaseProvider) private readonly db: DatabaseProvider) { }
|
||||
export class CollectionsRepository implements Repository {
|
||||
constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) {}
|
||||
|
||||
async findAll() {
|
||||
return this.db.query.collections.findMany();
|
||||
return this.db.query.collections.findMany()
|
||||
}
|
||||
|
||||
async findOneById(id: string) {
|
||||
|
|
@ -20,8 +21,8 @@ export class CollectionsRepository {
|
|||
where: eq(collections.id, id),
|
||||
columns: {
|
||||
cuid: true,
|
||||
name: true
|
||||
}
|
||||
name: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -30,8 +31,8 @@ export class CollectionsRepository {
|
|||
where: eq(collections.cuid, cuid),
|
||||
columns: {
|
||||
cuid: true,
|
||||
name: true
|
||||
}
|
||||
name: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -40,27 +41,26 @@ export class CollectionsRepository {
|
|||
where: eq(collections.user_id, userId),
|
||||
columns: {
|
||||
cuid: true,
|
||||
name: true
|
||||
}
|
||||
name: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async findAllByUserId(userId: string) {
|
||||
return this.db.query.collections.findMany({
|
||||
where: eq(collections.user_id, userId)
|
||||
where: eq(collections.user_id, userId),
|
||||
})
|
||||
}
|
||||
|
||||
async create(data: CreateCollection) {
|
||||
return this.db.insert(collections).values(data).returning().then(takeFirstOrThrow);
|
||||
return this.db.insert(collections).values(data).returning().then(takeFirstOrThrow)
|
||||
}
|
||||
|
||||
async update(id: string, data: UpdateCollection) {
|
||||
return this.db
|
||||
.update(collections)
|
||||
.set(data)
|
||||
.where(eq(collections.id, id))
|
||||
.returning()
|
||||
.then(takeFirstOrThrow);
|
||||
return this.db.update(collections).set(data).where(eq(collections.id, id)).returning().then(takeFirstOrThrow)
|
||||
}
|
||||
}
|
||||
|
||||
trxHost(trx: DatabaseProvider) {
|
||||
return new CollectionsRepository(trx)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import { and, eq, type InferInsertModel } from 'drizzle-orm'
|
||||
import { credentialsTable, CredentialsType } from '../infrastructure/database/tables/credentials.table'
|
||||
import { takeFirstOrThrow } from '../infrastructure/database/utils'
|
||||
import type { Repository } from '$lib/server/api/common/interfaces/repository.interface'
|
||||
import { CredentialsType, credentialsTable } from '$lib/server/api/databases/tables/credentials.table'
|
||||
import { DatabaseProvider } from '$lib/server/api/providers/database.provider'
|
||||
import { type InferInsertModel, and, eq } from 'drizzle-orm'
|
||||
import { inject, injectable } from 'tsyringe'
|
||||
import { DatabaseProvider } from '$lib/server/api/providers'
|
||||
import { takeFirstOrThrow } from '../common/utils/repository.utils'
|
||||
|
||||
export type CreateCredentials = InferInsertModel<typeof credentialsTable>
|
||||
export type UpdateCredentials = Partial<CreateCredentials>
|
||||
|
||||
@injectable()
|
||||
export class CredentialsRepository {
|
||||
export class CredentialsRepository implements Repository {
|
||||
constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) {}
|
||||
|
||||
async findOneByUserId(userId: string) {
|
||||
|
|
@ -66,4 +67,8 @@ export class CredentialsRepository {
|
|||
async deleteByUserIdAndType(userId: string, type: CredentialsType) {
|
||||
return this.db.delete(credentialsTable).where(and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, type)))
|
||||
}
|
||||
|
||||
trxHost(trx: DatabaseProvider) {
|
||||
return new CredentialsRepository(trx)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue