diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a75dd10bf..8f54c119b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,6 +59,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Build scripts + run: pnpm run build:scripts + - name: Run script to bump version & copy files run: pnpm run release id: version-bump diff --git a/apps/web/drizzle.config.ts b/apps/web/drizzle.config.ts index 4adb6790d..0d630172b 100644 --- a/apps/web/drizzle.config.ts +++ b/apps/web/drizzle.config.ts @@ -1,16 +1,12 @@ -import { POSTGRES_ENVS } from '@/database/client'; -import { defineConfig } from 'drizzle-kit'; +import { defineVitNodeDrizzleConfig } from '@vitnode/core/drizzle.config'; -const { url, ...rest } = POSTGRES_ENVS; +import { POSTGRES_URL, vitNodeApiConfig } from './src/vitnode.api.config'; -export default defineConfig({ - out: './src/database/migrations/', - // schema: ['./src/plugins/**/database/schema/*'], - schema: ['./src/database/schema/*'], +export default defineVitNodeDrizzleConfig({ + vitNodeApiConfig, + out: './migrations/', dialect: 'postgresql', - dbCredentials: process.env.POSTGRES_URL - ? { - url, - } - : rest, + dbCredentials: { + url: POSTGRES_URL, + }, }); diff --git a/apps/web/src/database/migrations/0000_funny_korath.sql b/apps/web/migrations/0000_wide_absorbing_man.sql similarity index 100% rename from apps/web/src/database/migrations/0000_funny_korath.sql rename to apps/web/migrations/0000_wide_absorbing_man.sql diff --git a/apps/web/src/database/migrations/meta/0000_snapshot.json b/apps/web/migrations/meta/0000_snapshot.json similarity index 99% rename from apps/web/src/database/migrations/meta/0000_snapshot.json rename to apps/web/migrations/meta/0000_snapshot.json index 1ffbde22d..45a2d4ced 100644 --- a/apps/web/src/database/migrations/meta/0000_snapshot.json +++ b/apps/web/migrations/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "fd378528-22f1-4e30-9108-4e06020776ba", + "id": "b2e3a2e7-f9c3-449a-92a9-8010647fe22e", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", diff --git a/apps/web/src/database/migrations/meta/_journal.json b/apps/web/migrations/meta/_journal.json similarity index 67% rename from apps/web/src/database/migrations/meta/_journal.json rename to apps/web/migrations/meta/_journal.json index 3295cfd2a..eab4a41d6 100644 --- a/apps/web/src/database/migrations/meta/_journal.json +++ b/apps/web/migrations/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "7", - "when": 1747245244787, - "tag": "0000_funny_korath", + "when": 1748527899355, + "tag": "0000_wide_absorbing_man", "breakpoints": true } ] diff --git a/apps/web/src/app/[locale]/(main)/(plugins)/(vitnode-blog)/blog/page.tsx b/apps/web/src/app/[locale]/(main)/(plugins)/(vitnode-blog)/blog/page.tsx index c3458bc27..ec516ab95 100644 --- a/apps/web/src/app/[locale]/(main)/(plugins)/(vitnode-blog)/blog/page.tsx +++ b/apps/web/src/app/[locale]/(main)/(plugins)/(vitnode-blog)/blog/page.tsx @@ -1,6 +1,7 @@ +import { Link } from '@vitnode/core/lib/navigation'; + import { Test } from '@vitnode/blog/views/test'; import { TestClient } from '@vitnode/blog/views/test/client'; -import { Link } from '@vitnode/core/lib/navigation'; export default function Page() { return ( diff --git a/apps/web/src/app/[locale]/(main)/(plugins)/(vitnode-core)/login/page.tsx b/apps/web/src/app/[locale]/(main)/(plugins)/(vitnode-core)/login/page.tsx index 291f8fbe6..62a57e214 100644 --- a/apps/web/src/app/[locale]/(main)/(plugins)/(vitnode-core)/login/page.tsx +++ b/apps/web/src/app/[locale]/(main)/(plugins)/(vitnode-core)/login/page.tsx @@ -1,8 +1,9 @@ import type { Metadata } from 'next/dist/types'; -import { SignInView } from '@vitnode/core/views/auth/sign-in/sign-in-view'; import { getTranslations } from 'next-intl/server'; +import { SignInView } from '@vitnode/core/views/auth/sign-in/sign-in-view'; + export const generateMetadata = async ({ locale, }: { diff --git a/apps/web/src/app/[locale]/(main)/(plugins)/(vitnode-core)/register/page.tsx b/apps/web/src/app/[locale]/(main)/(plugins)/(vitnode-core)/register/page.tsx index fa13dda37..65e261b4a 100644 --- a/apps/web/src/app/[locale]/(main)/(plugins)/(vitnode-core)/register/page.tsx +++ b/apps/web/src/app/[locale]/(main)/(plugins)/(vitnode-core)/register/page.tsx @@ -1,8 +1,9 @@ import type { Metadata } from 'next/dist/types'; -import { SignUpView } from '@vitnode/core/views/auth/sign-up/sign-up-view'; import { getTranslations } from 'next-intl/server'; +import { SignUpView } from '@vitnode/core/views/auth/sign-up/sign-up-view'; + export const generateMetadata = async ({ locale, }: { diff --git a/apps/web/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/core/page.tsx b/apps/web/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/core/page.tsx new file mode 100644 index 000000000..dd1ff7e3d --- /dev/null +++ b/apps/web/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/core/page.tsx @@ -0,0 +1,5 @@ +import { DashboardAdminView } from '@vitnode/core/views/admin/views/core/dashboard/dashboard-admin-view'; + +export default function Page() { + return ; +} diff --git a/apps/web/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/test/page.tsx b/apps/web/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/core/test/page.tsx similarity index 100% rename from apps/web/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/test/page.tsx rename to apps/web/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/core/test/page.tsx diff --git a/apps/web/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/users/page.tsx b/apps/web/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/core/users/page.tsx similarity index 100% rename from apps/web/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/users/page.tsx rename to apps/web/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/core/users/page.tsx diff --git a/apps/web/src/database/client.ts b/apps/web/src/database/client.ts deleted file mode 100644 index ca02a67e9..000000000 --- a/apps/web/src/database/client.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as dotenv from 'dotenv'; -import { drizzle } from 'drizzle-orm/postgres-js'; -import { join } from 'path'; - -dotenv.config({ - path: join(process.cwd(), '..', '..', '.env'), -}); - -export const POSTGRES_ENVS = { - url: - process.env.POSTGRES_URL ?? 'postgresql://root:root@localhost:5432/vitnode', - host: process.env.POSTGRES_HOST ?? 'localhost', - port: process.env.POSTGRES_PORT ? +process.env.POSTGRES_PORT : 5432, - user: process.env.POSTGRES_USER ?? 'root', - password: process.env.POSTGRES_PASSWORD ?? 'root', - database: process.env.POSTGRES_NAME ?? 'vitnode', - ssl: process.env.POSTGRES_SSL ? process.env.POSTGRES_SSL === 'true' : false, -}; - -export const dbClient = drizzle({ - connection: POSTGRES_ENVS.url, - casing: 'camelCase', -}); diff --git a/apps/web/src/vitnode.api.config.ts b/apps/web/src/vitnode.api.config.ts index e977708b5..03fa90108 100644 --- a/apps/web/src/vitnode.api.config.ts +++ b/apps/web/src/vitnode.api.config.ts @@ -4,9 +4,25 @@ import { DiscordSSOApiPlugin } from '@vitnode/core/api/plugins/sso/discord'; import { FacebookSSOApiPlugin } from '@vitnode/core/api/plugins/sso/facebook'; import { GoogleSSOApiPlugin } from '@vitnode/core/api/plugins/sso/google'; import { buildApiConfig } from '@vitnode/core/vitnode.config'; +import { drizzle } from 'drizzle-orm/postgres-js'; + +// import * as dotenv from 'dotenv'; +// import { drizzle } from 'drizzle-orm/postgres-js'; +// import { join } from 'path'; + +// dotenv.config({ +// path: join(process.cwd(), '..', '..', '.env'), +// }); + +export const POSTGRES_URL = + process.env.POSTGRES_URL ?? 'postgresql://root:root@localhost:5432/vitnode'; export const vitNodeApiConfig = buildApiConfig({ plugins: [blogApiPlugin()], + dbProvider: drizzle({ + connection: POSTGRES_URL, + casing: 'camelCase', + }), emailProvider: NodemailerEmailPlugin({ from: process.env.NODE_MAILER_FROM, host: process.env.NODE_MAILER_HOST, diff --git a/bump-version.mjs b/bump-version.mjs index 6398edd1d..6935ab7c5 100644 --- a/bump-version.mjs +++ b/bump-version.mjs @@ -365,6 +365,10 @@ class FileCopyManager { from: path.join(sourcePath, '.gitignore'), to: path.join(destPath, '.gitignore'), }, + { + from: path.join(sourcePath, 'drizzle.config.ts'), + to: path.join(destPath, 'drizzle.config.ts'), + }, ]; for (const { from, to } of files) { diff --git a/packages/create-vitnode-app/README.md b/packages/create-vitnode-app/README.md new file mode 100644 index 000000000..06be4926c --- /dev/null +++ b/packages/create-vitnode-app/README.md @@ -0,0 +1,38 @@ +# (VitNode) Create App + +This package is a CLI tool to create a new VitNode app quickly. + +Script based on [Create Next App](https://nextjs.org/). + +

+
+ + + + + VitNode Logo + + +
+
+

+ +## Usage + +```bash +npx create-vitnode-app@latest +``` + +or + +```bash +pnpm create vitnode-app@latest +``` + +## Options + +| Option | Description | +| ------------------- | ---------------------------------------------------------- | +| `--package-manager` | Specify the package manager to use. Support `npm`, `pnpm`. | +| `--eslint` | Initialize with eslint config. | +| `--skip-install` | Skip installing packages after initializing the project. | diff --git a/packages/vitnode/scripts/prepare-database.ts b/packages/vitnode/scripts/prepare-database.ts index 765539044..2f079c952 100644 --- a/packages/vitnode/scripts/prepare-database.ts +++ b/packages/vitnode/scripts/prepare-database.ts @@ -1,17 +1,28 @@ /* eslint-disable no-console */ -import { dbClient } from '@/database/client.js'; -import { core_admin_permissions } from '@/database/schema/admins.js'; -import { - core_languages, - core_languages_words, -} from '@/database/schema/languages.js'; -import { core_moderators_permissions } from '@/database/schema/moderators.js'; -import { core_roles } from '@/database/schema/roles.js'; +import { core_admin_permissions } from '@/database/admins.js'; +import { core_languages, core_languages_words } from '@/database/languages.js'; +import { core_moderators_permissions } from '@/database/moderators.js'; +import { core_roles } from '@/database/roles.js'; +import * as dotenv from 'dotenv'; import { count } from 'drizzle-orm'; +import { drizzle } from 'drizzle-orm/postgres-js'; +import { join } from 'path'; import { runInteractiveShellCommand } from './run-interactive-shell-command.js'; +dotenv.config({ + path: join(process.cwd(), '..', '..', '.env'), +}); + +console.log(join(process.cwd(), '..', '..', '.env')); + +const dbClient = drizzle({ + connection: + process.env.POSTGRES_URL ?? 'postgresql://root:root@localhost:5432/vitnode', + casing: 'camelCase', +}); + export const generateDatabaseMigrations = async () => { try { await runInteractiveShellCommand('npm', ['run', 'drizzle-kit', 'up']); diff --git a/packages/vitnode/scripts/prepare/prepare-files.ts b/packages/vitnode/scripts/prepare/prepare-files.ts index c4ef96fdb..3f7d88e66 100644 --- a/packages/vitnode/scripts/prepare/prepare-files.ts +++ b/packages/vitnode/scripts/prepare/prepare-files.ts @@ -1,51 +1,13 @@ /* eslint-disable no-console */ -import { existsSync } from 'fs'; -import { cp, mkdir } from 'fs/promises'; -import { join } from 'path'; -import { dirname } from 'path'; -import { fileURLToPath } from 'url'; import { preparePlugins } from './prepare-plugins'; -const __dirname = dirname(fileURLToPath(import.meta.url)); - -const copyDatabase = async (pluginsPath: string) => { - const databasePath = join(__dirname, '..', '..', 'src', 'database'); - if (!existsSync(databasePath)) { - console.log( - `⛔️ Database not found in 'src/database' directory. "${databasePath}"`, - ); - process.exit(1); - } - const destinationPath = join(process.cwd(), 'src', 'database'); - if (!existsSync(destinationPath)) { - await mkdir(destinationPath, { recursive: true }); - } - - // Copy files - await cp(databasePath, destinationPath, { - recursive: true, - filter: src => !src.endsWith('client.ts'), - }); - - // Copy client.ts if it doesn't exist - if (!existsSync(join(destinationPath, 'client.ts'))) { - await cp( - join(databasePath, 'client.ts'), - join(destinationPath, 'client.ts'), - ); - } -}; - export const prepareFiles = async ({ - pluginsPath, initMessage, }: { initMessage: string; - pluginsPath: string; }) => { console.log(`${initMessage} Preparing files...`); - await copyDatabase(pluginsPath); await preparePlugins(); console.log(`${initMessage} \x1b[32mFiles prepared successfully.\x1b[0m`); process.exit(0); diff --git a/packages/vitnode/scripts/scripts.ts b/packages/vitnode/scripts/scripts.ts index f7ea7230f..8f0ff8b66 100644 --- a/packages/vitnode/scripts/scripts.ts +++ b/packages/vitnode/scripts/scripts.ts @@ -1,24 +1,11 @@ #!/usr/bin/env node /* eslint-disable no-console */ -import { existsSync } from 'fs'; -import { join } from 'path'; import { processPlugin } from './plugin.js'; import { prepareDatabase } from './prepare-database.js'; import { prepareFiles } from './prepare/prepare-files.js'; const initMessage = '\x1b[34m[VitNode]\x1b[0m'; -const getPluginsPath = () => { - const pluginsPath = join(process.cwd(), 'src', 'plugins'); - if (!existsSync(pluginsPath)) { - console.log( - `⛔️ Plugins not found in 'src/plugins' directory. "${pluginsPath}"`, - ); - process.exit(1); - } - - return pluginsPath; -}; const command = process.argv[2]; const flag = process.argv[3]; @@ -35,7 +22,7 @@ switch (command) { break; case 'prepare': - void prepareFiles({ pluginsPath: getPluginsPath(), initMessage }); + void prepareFiles({ initMessage }); break; default: diff --git a/packages/vitnode/src/api/config.ts b/packages/vitnode/src/api/config.ts index 7e3e3fa03..efb17649d 100644 --- a/packages/vitnode/src/api/config.ts +++ b/packages/vitnode/src/api/config.ts @@ -60,9 +60,16 @@ export function VitNodeAPI({ emailProvider: vitNodeApiConfig.emailProvider, metadata: vitNodeConfig.metadata, authorization: vitNodeApiConfig.authorization, + dbProvider: vitNodeApiConfig.dbProvider, }), ); - app.use('/*/admin/*', globalAdminMiddleware()); + app.use(async (c, next) => { + if (c.req.path.includes('/admin/')) { + return globalAdminMiddleware()(c, next); + } + + return next(); + }); app.onError(error => { if (error instanceof HTTPException) { diff --git a/packages/vitnode/src/api/lib/get-user-ip.ts b/packages/vitnode/src/api/lib/get-user-ip.ts index 1dde2b24b..70ac8ea9e 100644 --- a/packages/vitnode/src/api/lib/get-user-ip.ts +++ b/packages/vitnode/src/api/lib/get-user-ip.ts @@ -1,7 +1,8 @@ -import type { HonoRequest } from 'hono'; +import type { Context, Input } from 'hono'; +import type { Env } from 'hono'; -export const getUserIp = (req: HonoRequest): string => { - const ip: string = req.header('x-forwarded-for')?.toString() ?? '0.0.0.0'; +export function getUserIp(c: Context): string { + const ip: string = c.req.header('x-forwarded-for')?.toString() ?? '0.0.0.0'; if (ip === '0.0.0.0') { // eslint-disable-next-line no-console @@ -11,4 +12,4 @@ export const getUserIp = (req: HonoRequest): string => { } return ip; -}; +} diff --git a/packages/vitnode/src/api/lib/with-pagination.ts b/packages/vitnode/src/api/lib/with-pagination.ts index 5c360ad8f..11f1d33ab 100644 --- a/packages/vitnode/src/api/lib/with-pagination.ts +++ b/packages/vitnode/src/api/lib/with-pagination.ts @@ -5,8 +5,8 @@ import type { PgTableWithColumns, TableConfig, } from 'drizzle-orm/pg-core'; +import type { Context, Env, Input } from 'hono'; -import { dbClient } from '@/database/client'; import { z } from '@hono/zod-openapi'; import { and, asc, count, desc, gt, lt } from 'drizzle-orm'; @@ -14,6 +14,7 @@ export async function withPagination< QueryMin extends Record, T extends TableConfig, Primary extends ColumnBaseConfig<'number', string>, + E extends Env, >({ query, table, @@ -21,7 +22,9 @@ export async function withPagination< where: whereFromParams, primaryCursor, orderBy: orderByFromParams, + c, }: { + c: Context; orderBy: { column: PgColumn; order: 'asc' | 'desc'; @@ -103,7 +106,8 @@ export async function withPagination< } // Get total count - const [{ count: totalCount }] = await dbClient + const [{ count: totalCount }] = await c + .get('db') .select({ count: count() }) .from(table as PgTable) .where(whereFromParams); diff --git a/packages/vitnode/src/api/middlewares/global/global.ts b/packages/vitnode/src/api/middlewares/global/global.ts index 56b299209..10fc37418 100644 --- a/packages/vitnode/src/api/middlewares/global/global.ts +++ b/packages/vitnode/src/api/middlewares/global/global.ts @@ -42,6 +42,7 @@ declare module 'hono' { title: string; }; }; + db: Pick['dbProvider']; deviceId: number; user: null | { avatarColor: string; @@ -62,7 +63,8 @@ export const globalMiddleware = ({ authorization, metadata, emailProvider, -}: Pick & + dbProvider, +}: Pick & Pick) => { return async (c: Context, next: Next) => { c.set('core', { @@ -82,6 +84,7 @@ export const globalMiddleware = ({ cookieSecure: authorization?.cookieSecure ?? true, }, }); + c.set('db', dbProvider); const deviceId = await new DeviceModel(c).getDeviceId(); c.set('deviceId', deviceId); diff --git a/packages/vitnode/src/api/models/device.ts b/packages/vitnode/src/api/models/device.ts index 447d64810..fe6393c12 100644 --- a/packages/vitnode/src/api/models/device.ts +++ b/packages/vitnode/src/api/models/device.ts @@ -1,7 +1,6 @@ import type { Context, Env, Input } from 'hono'; -import { dbClient } from '@/database/client'; -import { core_sessions_known_devices } from '@/database/schema/sessions'; +import { core_sessions_known_devices } from '@/database/sessions'; import { CONFIG } from '@/lib/config'; import { eq } from 'drizzle-orm'; import { getCookie, setCookie } from 'hono/cookie'; @@ -15,10 +14,11 @@ export class DeviceModel { protected readonly c: Context; private async createDevice() { - const [device] = await dbClient + const [device] = await this.c + .get('db') .insert(core_sessions_known_devices) .values({ - ipAddress: getUserIp(this.c.req), + ipAddress: getUserIp(this.c), userAgent: this.getUserAgent(), }) .returning({ id: core_sessions_known_devices.id }); @@ -56,7 +56,8 @@ export class DeviceModel { try { if (deviceIdFromCookie) { - const [device] = await dbClient + const [device] = await this.c + .get('db') .select({ id: core_sessions_known_devices.id, }) @@ -67,10 +68,11 @@ export class DeviceModel { return await this.createDevice(); } - await dbClient + await this.c + .get('db') .update(core_sessions_known_devices) .set({ - ipAddress: getUserIp(this.c.req), + ipAddress: getUserIp(this.c), userAgent: this.getUserAgent(), }) .where(eq(core_sessions_known_devices.id, deviceIdFromCookie)); diff --git a/packages/vitnode/src/api/models/session-admin.ts b/packages/vitnode/src/api/models/session-admin.ts index bfeaea669..c768c8963 100644 --- a/packages/vitnode/src/api/models/session-admin.ts +++ b/packages/vitnode/src/api/models/session-admin.ts @@ -1,10 +1,6 @@ import type { Context, Env, Input } from 'hono'; -import { dbClient } from '@/database/client'; -import { - core_admin_permissions, - core_admin_sessions, -} from '@/database/schema/admins'; +import { core_admin_permissions, core_admin_sessions } from '@/database/admins'; import { CONFIG } from '@/lib/config'; import { and, eq, gt, or } from 'drizzle-orm'; import { deleteCookie, getCookie, setCookie } from 'hono/cookie'; @@ -19,10 +15,11 @@ export class SessionAdminModel { protected readonly c: Context; async checkIfUserIsAdmin(userId: number) { - const user = await new UserModel().getUserById(userId); + const user = await new UserModel().getUserById({ id: userId, c: this.c }); if (!user) return false; - const [permission] = await dbClient + const [permission] = await this.c + .get('db') .select() .from(core_admin_permissions) .where( @@ -48,14 +45,17 @@ export class SessionAdminModel { .join(''); const deviceId = this.c.get('deviceId'); - await dbClient.insert(core_admin_sessions).values({ - token, - userId, - expiresAt: new Date( - Date.now() + this.c.get('core').authorization.adminCookieExpires, - ), - deviceId, - }); + await this.c + .get('db') + .insert(core_admin_sessions) + .values({ + token, + userId, + expiresAt: new Date( + Date.now() + this.c.get('core').authorization.adminCookieExpires, + ), + deviceId, + }); setCookie(this.c, this.c.get('core').authorization.adminCookieName, token, { httpOnly: true, @@ -78,7 +78,8 @@ export class SessionAdminModel { ); if (!token) return; - await dbClient + await this.c + .get('db') .delete(core_admin_sessions) .where(eq(core_admin_sessions.token, token)); deleteCookie(this.c, this.c.get('core').authorization.adminCookieName, { @@ -93,7 +94,8 @@ export class SessionAdminModel { const deviceId = this.c.get('deviceId'); if (!deviceId) return null; - const [session] = await dbClient + const [session] = await this.c + .get('db') .select({ token: core_admin_sessions.token, userId: core_admin_sessions.userId, @@ -109,7 +111,10 @@ export class SessionAdminModel { .limit(1); if (!session) return null; - const user = await new UserModel().getUserById(session.userId); + const user = await new UserModel().getUserById({ + id: session.userId, + c: this.c, + }); if (!user) return null; return user; diff --git a/packages/vitnode/src/api/models/session.ts b/packages/vitnode/src/api/models/session.ts index 2a34d635a..13eb48e7b 100644 --- a/packages/vitnode/src/api/models/session.ts +++ b/packages/vitnode/src/api/models/session.ts @@ -1,7 +1,6 @@ import type { Context, Env, Input } from 'hono'; -import { dbClient } from '@/database/client'; -import { core_sessions } from '@/database/schema/sessions'; +import { core_sessions } from '@/database/sessions'; import { CONFIG } from '@/lib/config'; import { and, eq, gt } from 'drizzle-orm'; import { deleteCookie, getCookie, setCookie } from 'hono/cookie'; @@ -23,14 +22,17 @@ export class SessionModel { .join(''); const deviceId = this.c.get('deviceId'); - await dbClient.insert(core_sessions).values({ - token, - userId, - expiresAt: new Date( - Date.now() + this.c.get('core').authorization.cookie_expires, - ), - deviceId, - }); + await this.c + .get('db') + .insert(core_sessions) + .values({ + token, + userId, + expiresAt: new Date( + Date.now() + this.c.get('core').authorization.cookie_expires, + ), + deviceId, + }); setCookie(this.c, this.c.get('core').authorization.cookieName, token, { httpOnly: true, @@ -55,7 +57,10 @@ export class SessionModel { ); if (!token) return; - await dbClient.delete(core_sessions).where(eq(core_sessions.token, token)); + await this.c + .get('db') + .delete(core_sessions) + .where(eq(core_sessions.token, token)); deleteCookie(this.c, this.c.get('core').authorization.cookieName); } @@ -68,7 +73,8 @@ export class SessionModel { const deviceId = this.c.get('deviceId'); if (!deviceId) return null; - const [session] = await dbClient + const [session] = await this.c + .get('db') .select({ token: core_sessions.token, userId: core_sessions.userId, @@ -86,7 +92,10 @@ export class SessionModel { if (!session || session.token !== token) { return null; } - const user = await new UserModel().getUserById(session.userId); + const user = await new UserModel().getUserById({ + id: session.userId, + c: this.c, + }); if (!user) return null; return user; diff --git a/packages/vitnode/src/api/models/sso.ts b/packages/vitnode/src/api/models/sso.ts index 09c53d130..2c8a15c42 100644 --- a/packages/vitnode/src/api/models/sso.ts +++ b/packages/vitnode/src/api/models/sso.ts @@ -1,7 +1,6 @@ import type { Context, Env, Input } from 'hono'; -import { dbClient } from '@/database/client'; -import { core_users, core_users_sso } from '@/database/schema/users'; +import { core_users, core_users_sso } from '@/database/users'; import { CONFIG } from '@/lib/config'; import { removeSpecialCharacters } from '@/lib/special-characters'; import crypto from 'crypto'; @@ -56,9 +55,9 @@ export class SSOModel { newsletter: false, hashedPassword: undefined, }, - c.req, + c, ); - await dbClient.insert(core_users_sso).values({ + await c.get('db').insert(core_users_sso).values({ userId: data.id, providerId: providerId, providerAccountId: user.id, @@ -87,7 +86,7 @@ export class SSOModel { const ssoToken = await provider.fetchToken(code); const userFromSSO = await provider.fetchUser(ssoToken); - return await dbClient.transaction(async tx => { + return await this.c.get('db').transaction(async tx => { const [dataSSOFromDb] = await tx .select({ userId: core_users_sso.userId, diff --git a/packages/vitnode/src/api/models/user/get-user-by-id.ts b/packages/vitnode/src/api/models/user/get-user-by-id.ts index 8befccb8b..b2db3cb6c 100644 --- a/packages/vitnode/src/api/models/user/get-user-by-id.ts +++ b/packages/vitnode/src/api/models/user/get-user-by-id.ts @@ -1,9 +1,17 @@ -import { dbClient } from '@/database/client'; -import { core_users } from '@/database/schema/users'; +import type { Context, Env, Input } from 'hono'; + +import { core_users } from '@/database/users'; import { eq } from 'drizzle-orm'; -export const getUserById = async (id: number) => { - const [user] = await dbClient +export async function getUserById({ + id, + c, +}: { + c: Context; + id: number; +}) { + const [user] = await c + .get('db') .select({ id: core_users.id, email: core_users.email, @@ -22,4 +30,4 @@ export const getUserById = async (id: number) => { if (!user) return null; return user; -}; +} diff --git a/packages/vitnode/src/api/models/user/sign-in-with-passwords.ts b/packages/vitnode/src/api/models/user/sign-in-with-passwords.ts index 93ae7f9bf..55f95f4e6 100644 --- a/packages/vitnode/src/api/models/user/sign-in-with-passwords.ts +++ b/packages/vitnode/src/api/models/user/sign-in-with-passwords.ts @@ -1,5 +1,7 @@ -import { dbClient } from '@/database/client'; -import { core_users } from '@/database/schema/users'; +import type { Context, Input } from 'hono'; +import type { Env } from 'hono'; + +import { core_users } from '@/database/users'; import { eq } from 'drizzle-orm'; import { HTTPException } from 'hono/http-exception'; @@ -8,11 +10,14 @@ import { PasswordModel } from '../password'; export const signInWithPassword = async ({ email, password, + c, }: { + c: Context; email: string; password: string; }) => { - const [user] = await dbClient + const [user] = await c + .get('db') .select({ id: core_users.id, email: core_users.email, diff --git a/packages/vitnode/src/api/models/user/sign-up.ts b/packages/vitnode/src/api/models/user/sign-up.ts index 5c81881d0..af2d72980 100644 --- a/packages/vitnode/src/api/models/user/sign-up.ts +++ b/packages/vitnode/src/api/models/user/sign-up.ts @@ -1,25 +1,28 @@ -import type { HonoRequest } from 'hono'; +import type { Context, Env, Input } from 'hono'; import { getUserIp } from '@/api/lib/get-user-ip'; import { generateAvatarColor } from '@/api/modules/users/avatar-color'; -import { dbClient } from '@/database/client'; -import { core_roles } from '@/database/schema/roles'; -import { core_users } from '@/database/schema/users'; +import { core_roles } from '@/database/roles'; +import { core_users } from '@/database/users'; import { removeSpecialCharacters } from '@/lib/special-characters'; import { and, count, eq, or } from 'drizzle-orm'; import { HTTPException } from 'hono/http-exception'; -const getDefaultData = async (): Promise<{ +const getDefaultData = async ( + c: Context, +): Promise<{ emailVerified: boolean; roleId: number; }> => { - const [countUsers] = await dbClient + const [countUsers] = await c + .get('db') .select({ count: count() }) .from(core_users); // If no users, return root group if (countUsers.count === 0) { - const [defaultRole] = await dbClient + const [defaultRole] = await c + .get('db') .select({ id: core_roles.id, }) @@ -39,7 +42,8 @@ const getDefaultData = async (): Promise<{ }; } - const [defaultRole] = await dbClient + const [defaultRole] = await c + .get('db') .select({ id: core_roles.id, }) @@ -72,10 +76,11 @@ export const signUp = async ( name: string; newsletter?: boolean; }, - req: HonoRequest, + c: Context, ) => { const convertToNameSEO = removeSpecialCharacters(name); - const checkIfUserExist = await dbClient + const checkIfUserExist = await c + .get('db') .select({ email: core_users.email, name_code: core_users.nameCode, @@ -103,8 +108,9 @@ export const signUp = async ( }); } - const { roleId, emailVerified } = await getDefaultData(); - const [data] = await dbClient + const { roleId, emailVerified } = await getDefaultData(c); + const [data] = await c + .get('db') .insert(core_users) .values({ email, @@ -116,7 +122,7 @@ export const signUp = async ( avatarColor: generateAvatarColor(name), roleId, emailVerified, - ipAddress: getUserIp(req), + ipAddress: getUserIp(c), // TODO: Handle language // language: await this.getLanguage(req), }) diff --git a/packages/vitnode/src/api/modules/admin/users/routes/list.route.ts b/packages/vitnode/src/api/modules/admin/users/routes/list.route.ts index 3efa998f9..360c5c76b 100644 --- a/packages/vitnode/src/api/modules/admin/users/routes/list.route.ts +++ b/packages/vitnode/src/api/modules/admin/users/routes/list.route.ts @@ -4,8 +4,7 @@ import { zodPaginationPageInfo, zodPaginationQuery, } from '@/api/lib/with-pagination'; -import { dbClient } from '@/database/client'; -import { core_users } from '@/database/schema/users'; +import { core_users } from '@/database/users'; import { z } from '@hono/zod-openapi'; export const listUsersAdminRoute = buildRoute({ @@ -56,7 +55,8 @@ export const listUsersAdminRoute = buildRoute({ }, primaryCursor: core_users.id, query: async ({ limit, where, orderBy }) => - await dbClient + await c + .get('db') .select({ id: core_users.id, name: core_users.name, @@ -81,6 +81,7 @@ export const listUsersAdminRoute = buildRoute({ : core_users.createdAt, order: query.order ?? 'desc', }, + c, }); return c.json(data); diff --git a/packages/vitnode/src/api/modules/admin/users/routes/users.route.ts b/packages/vitnode/src/api/modules/admin/users/routes/users.route.ts index 72545e7c0..518a27856 100644 --- a/packages/vitnode/src/api/modules/admin/users/routes/users.route.ts +++ b/packages/vitnode/src/api/modules/admin/users/routes/users.route.ts @@ -4,8 +4,7 @@ import { zodPaginationPageInfo, zodPaginationQuery, } from '@/api/lib/with-pagination'; -import { dbClient } from '@/database/client'; -import { core_users } from '@/database/schema/users'; +import { core_users } from '@/database/users'; import { z } from '@hono/zod-openapi'; export const usersAdminRoute = buildRoute({ @@ -59,7 +58,8 @@ export const usersAdminRoute = buildRoute({ }, primaryCursor: core_users.id, query: async ({ limit, where, orderBy }) => - await dbClient + await c + .get('db') .select({ id: core_users.id, name: core_users.name, @@ -84,6 +84,7 @@ export const usersAdminRoute = buildRoute({ : core_users.createdAt, order: query.order ?? 'desc', }, + c, }); return c.json(data); diff --git a/packages/vitnode/src/api/modules/middleware/test.ts b/packages/vitnode/src/api/modules/middleware/test.ts index b2f65987e..526830be2 100644 --- a/packages/vitnode/src/api/modules/middleware/test.ts +++ b/packages/vitnode/src/api/modules/middleware/test.ts @@ -1,6 +1,5 @@ import { buildRoute } from '@/api/lib/route'; -import { dbClient } from '@/database/client'; -import { core_test } from '@/database/schema/test'; +import { core_test } from '@/database/test'; import { z } from 'zod'; import { @@ -43,7 +42,8 @@ export const routeTestMiddleware = buildRoute({ }, primaryCursor: core_test.id, query: async ({ limit, where, orderBy }) => - await dbClient + await c + .get('db') .select() .from(core_test) .where(where) @@ -54,6 +54,7 @@ export const routeTestMiddleware = buildRoute({ column: query.orderBy ? core_test[query.orderBy] : core_test.createdAt, order: query.order ?? 'desc', }, + c, }); return c.json(data); diff --git a/packages/vitnode/src/api/modules/users/routes/sign-in.route.ts b/packages/vitnode/src/api/modules/users/routes/sign-in.route.ts index 85b49b1df..a739d6322 100644 --- a/packages/vitnode/src/api/modules/users/routes/sign-in.route.ts +++ b/packages/vitnode/src/api/modules/users/routes/sign-in.route.ts @@ -49,7 +49,11 @@ export const signInRoute = buildRoute({ }, handler: async c => { const { password, isAdmin, email } = c.req.valid('json'); - const data = await new UserModel().signInWithPassword({ password, email }); + const data = await new UserModel().signInWithPassword({ + password, + email, + c, + }); if (isAdmin) { const { token } = await new SessionAdminModel(c).createSessionByUserId( diff --git a/packages/vitnode/src/api/modules/users/routes/sign-up.route.ts b/packages/vitnode/src/api/modules/users/routes/sign-up.route.ts index cad27f562..a68aee5ec 100644 --- a/packages/vitnode/src/api/modules/users/routes/sign-up.route.ts +++ b/packages/vitnode/src/api/modules/users/routes/sign-up.route.ts @@ -55,7 +55,7 @@ export const signUpRoute = buildRoute({ ); const data = await new UserModel().signUp( { ...c.req.valid('json'), hashedPassword }, - c.req, + c, ); return c.json({ id: data.id }); diff --git a/packages/vitnode/src/app_admin/core/page.tsx b/packages/vitnode/src/app_admin/core/page.tsx new file mode 100644 index 000000000..331370fe7 --- /dev/null +++ b/packages/vitnode/src/app_admin/core/page.tsx @@ -0,0 +1,5 @@ +import { DashboardAdminView } from '../../views/admin/views/core/dashboard/dashboard-admin-view'; + +export default function Page() { + return ; +} diff --git a/packages/vitnode/src/app_admin/core/test/page.tsx b/packages/vitnode/src/app_admin/core/test/page.tsx new file mode 100644 index 000000000..0f9a918b8 --- /dev/null +++ b/packages/vitnode/src/app_admin/core/test/page.tsx @@ -0,0 +1,5 @@ +import { TestView } from '../../../views/admin/views/core/test'; + +export default function Page() { + return ; +} diff --git a/packages/vitnode/src/app_admin/users/page.tsx b/packages/vitnode/src/app_admin/core/users/page.tsx similarity index 59% rename from packages/vitnode/src/app_admin/users/page.tsx rename to packages/vitnode/src/app_admin/core/users/page.tsx index f73488f26..7610deca4 100644 --- a/packages/vitnode/src/app_admin/users/page.tsx +++ b/packages/vitnode/src/app_admin/core/users/page.tsx @@ -1,4 +1,4 @@ -import { UsersAdminView } from '../../views/admin/views/core/users/users-admin-view'; +import { UsersAdminView } from '../../../views/admin/views/core/users/users-admin-view'; export default function Page( props: React.ComponentProps, diff --git a/packages/vitnode/src/app_admin/page.tsx b/packages/vitnode/src/app_admin/page.tsx deleted file mode 100644 index 0ba052e39..000000000 --- a/packages/vitnode/src/app_admin/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DashboardAdminView } from '../views/admin/views/core/dashboard/dashboard-admin-view'; - -export default function Page() { - return ; -} diff --git a/packages/vitnode/src/app_admin/test/page.tsx b/packages/vitnode/src/app_admin/test/page.tsx deleted file mode 100644 index 537c4e896..000000000 --- a/packages/vitnode/src/app_admin/test/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { TestView } from '../../views/admin/views/core/test'; - -export default function Page() { - return ; -} diff --git a/packages/vitnode/src/components/form/fields/combobox.tsx b/packages/vitnode/src/components/form/fields/combobox.tsx index 1ddc3c085..57a44bab1 100644 --- a/packages/vitnode/src/components/form/fields/combobox.tsx +++ b/packages/vitnode/src/components/form/fields/combobox.tsx @@ -31,7 +31,8 @@ export function AutoFormCombobox({ field, description, shape, - placeholder, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + placeholder: _, className, labels = [], ...props diff --git a/apps/web/src/database/schema/admins.ts b/packages/vitnode/src/database/admins.ts similarity index 100% rename from apps/web/src/database/schema/admins.ts rename to packages/vitnode/src/database/admins.ts diff --git a/packages/vitnode/src/database/client.ts b/packages/vitnode/src/database/client.ts deleted file mode 100644 index 62ff46b9f..000000000 --- a/packages/vitnode/src/database/client.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as dotenv from 'dotenv'; -import { drizzle } from 'drizzle-orm/postgres-js'; -import { join } from 'path'; - -dotenv.config({ - path: join(process.cwd(), '..', '..', '.env'), -}); - -export const POSTGRES_ENVS = { - url: - process.env.POSTGRES_URL ?? 'postgresql://root:root@localhost:5432/vitnode', - host: process.env.POSTGRES_HOST ?? 'localhost', - port: process.env.POSTGRES_PORT ? +process.env.POSTGRES_PORT : 5432, - user: process.env.POSTGRES_USER ?? 'root', - password: process.env.POSTGRES_PASSWORD ?? 'root', - database: process.env.POSTGRES_NAME ?? 'vitnode', - ssl: process.env.POSTGRES_SSL ? process.env.POSTGRES_SSL === 'true' : false, -}; - -export const dbClient = drizzle({ - connection: process.env.POSTGRES_URL ? POSTGRES_ENVS.url : POSTGRES_ENVS, - casing: 'camelCase', -}); diff --git a/apps/web/src/database/schema/languages.ts b/packages/vitnode/src/database/languages.ts similarity index 100% rename from apps/web/src/database/schema/languages.ts rename to packages/vitnode/src/database/languages.ts diff --git a/apps/web/src/database/schema/moderators.ts b/packages/vitnode/src/database/moderators.ts similarity index 100% rename from apps/web/src/database/schema/moderators.ts rename to packages/vitnode/src/database/moderators.ts diff --git a/apps/web/src/database/schema/roles.ts b/packages/vitnode/src/database/roles.ts similarity index 100% rename from apps/web/src/database/schema/roles.ts rename to packages/vitnode/src/database/roles.ts diff --git a/packages/vitnode/src/database/schema/admins.ts b/packages/vitnode/src/database/schema/admins.ts deleted file mode 100644 index 2773083c6..000000000 --- a/packages/vitnode/src/database/schema/admins.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { relations } from 'drizzle-orm'; -import { index, pgTable } from 'drizzle-orm/pg-core'; - -import { core_roles } from './roles'; -import { core_sessions_known_devices } from './sessions'; -import { core_users } from './users'; - -export const core_admin_permissions = pgTable( - 'core_admin_permissions', - t => ({ - id: t.serial().primaryKey(), - roleId: t.integer().references(() => core_roles.id, { - onDelete: 'cascade', - }), - userId: t.integer().references(() => core_users.id, { - onDelete: 'cascade', - }), - createdAt: t.timestamp().notNull().defaultNow(), - updatedAt: t - .timestamp() - .notNull() - .$onUpdate(() => new Date()), - protected: t.boolean().notNull().default(false), - // data: t.jsonb().$type<{ permissions: PermissionsStaffArgs[] }>().default({ - // permissions: [], - // }), - }), - t => [ - index('core_admin_permissions_role_id_idx').on(t.roleId), - index('core_admin_permissions_user_id_idx').on(t.userId), - ], -).enableRLS(); - -export const core_admin_permissions_relations = relations( - core_admin_permissions, - ({ one }) => ({ - group: one(core_roles, { - fields: [core_admin_permissions.roleId], - references: [core_roles.id], - }), - user: one(core_users, { - fields: [core_admin_permissions.userId], - references: [core_users.id], - }), - }), -); - -export const core_admin_sessions = pgTable( - 'core_admin_sessions', - t => ({ - id: t.serial().primaryKey(), - token: t.varchar({ length: 255 }).notNull().unique(), - userId: t - .integer() - .notNull() - .references(() => core_users.id, { - onDelete: 'cascade', - }), - createdAt: t.timestamp().notNull().defaultNow(), - lastSeen: t.timestamp().notNull().defaultNow(), - expiresAt: t.timestamp().notNull(), - deviceId: t - .integer() - .references(() => core_sessions_known_devices.id, { - onDelete: 'cascade', - }) - .notNull(), - }), - t => [ - index('core_admin_sessions_token_idx').on(t.token), - index('core_admin_sessions_user_id_idx').on(t.userId), - ], -).enableRLS(); - -export const core_admin_sessions_relations = relations( - core_admin_sessions, - ({ one }) => ({ - user: one(core_users, { - fields: [core_admin_sessions.userId], - references: [core_users.id], - }), - device: one(core_sessions_known_devices, { - fields: [core_admin_sessions.deviceId], - references: [core_sessions_known_devices.id], - }), - }), -); diff --git a/packages/vitnode/src/database/schema/languages.ts b/packages/vitnode/src/database/schema/languages.ts deleted file mode 100644 index f7dd32998..000000000 --- a/packages/vitnode/src/database/schema/languages.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { relations } from 'drizzle-orm'; -import { index, pgTable } from 'drizzle-orm/pg-core'; - -export const core_languages = pgTable( - 'core_languages', - t => ({ - id: t.serial().primaryKey(), - code: t.varchar({ length: 32 }).notNull().unique(), - name: t.varchar({ length: 255 }).notNull(), - timezone: t.varchar({ length: 255 }).notNull().default('UTC'), - protected: t.boolean().notNull().default(false), - default: t.boolean().notNull().default(false), - enabled: t.boolean().notNull().default(true), - createdAt: t.timestamp().notNull().defaultNow(), - updatedAt: t - .timestamp() - .notNull() - .$onUpdate(() => new Date()), - time24: t.boolean().notNull().default(false), - }), - t => [ - index('core_languages_code_idx').on(t.code), - index('core_languages_name_idx').on(t.name), - ], -).enableRLS(); - -export const core_languages_words = pgTable( - 'core_languages_words', - t => ({ - id: t.serial().primaryKey(), - languageCode: t - .varchar() - .notNull() - .references(() => core_languages.code, { - onDelete: 'cascade', - }), - pluginCode: t.varchar({ length: 50 }).notNull(), - itemId: t.integer().notNull(), - value: t.text().notNull(), - tableName: t.varchar({ length: 255 }).notNull(), - variable: t.varchar({ length: 255 }).notNull(), - }), - t => [index('core_languages_words_lang_code_idx').on(t.languageCode)], -).enableRLS(); - -export const core_languages_words_relations = relations( - core_languages_words, - ({ one }) => ({ - language: one(core_languages, { - fields: [core_languages_words.languageCode], - references: [core_languages.code], - }), - }), -); diff --git a/packages/vitnode/src/database/schema/moderators.ts b/packages/vitnode/src/database/schema/moderators.ts deleted file mode 100644 index 5337c5ca4..000000000 --- a/packages/vitnode/src/database/schema/moderators.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { relations } from 'drizzle-orm'; -import { index, pgTable } from 'drizzle-orm/pg-core'; - -import { core_roles } from './roles'; -import { core_users } from './users'; - -export const core_moderators_permissions = pgTable( - 'core_moderators_permissions', - t => ({ - id: t.serial().primaryKey(), - roleId: t.integer().references(() => core_roles.id, { - onDelete: 'cascade', - }), - userId: t.integer().references(() => core_users.id, { - onDelete: 'cascade', - }), - createdAt: t.timestamp().notNull().defaultNow(), - updatedAt: t - .timestamp() - .notNull() - .$onUpdate(() => new Date()), - protected: t.boolean().notNull().default(false), - }), - t => [ - index('core_moderators_permissions_role_id_idx').on(t.roleId), - index('core_moderators_permissions_user_id_idx').on(t.userId), - ], -).enableRLS(); - -export const core_moderators_permissions_relations = relations( - core_moderators_permissions, - ({ one }) => ({ - group: one(core_roles, { - fields: [core_moderators_permissions.roleId], - references: [core_roles.id], - }), - user: one(core_users, { - fields: [core_moderators_permissions.userId], - references: [core_users.id], - }), - }), -); diff --git a/packages/vitnode/src/database/schema/roles.ts b/packages/vitnode/src/database/schema/roles.ts deleted file mode 100644 index 50a71d510..000000000 --- a/packages/vitnode/src/database/schema/roles.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { pgTable } from 'drizzle-orm/pg-core'; - -export const core_roles = pgTable('core_roles', t => ({ - id: t.serial().primaryKey(), - createdAt: t.timestamp().notNull().defaultNow(), - updatedAt: t - .timestamp() - .notNull() - .$onUpdate(() => new Date()), - protected: t.boolean().notNull().default(false), - default: t.boolean().notNull().default(false), - root: t.boolean().notNull().default(false), - guest: t.boolean().notNull().default(false), - color: t.varchar({ length: 19 }), -})).enableRLS(); diff --git a/packages/vitnode/src/database/schema/sessions.ts b/packages/vitnode/src/database/schema/sessions.ts deleted file mode 100644 index 5506b2f4c..000000000 --- a/packages/vitnode/src/database/schema/sessions.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { relations } from 'drizzle-orm'; -import { index, pgTable } from 'drizzle-orm/pg-core'; - -import { core_users } from './users'; - -export const core_sessions = pgTable( - 'core_sessions', - t => ({ - id: t.serial().primaryKey(), - token: t.varchar({ length: 255 }).notNull().unique(), - userId: t - .integer() - .notNull() - .references(() => core_users.id, { - onDelete: 'cascade', - }), - createdAt: t.timestamp().notNull().defaultNow(), - expiresAt: t.timestamp().notNull(), - deviceId: t - .integer() - .references(() => core_sessions_known_devices.id, { - onDelete: 'cascade', - }) - .notNull(), - }), - t => [index('core_sessions_user_id_idx').on(t.userId)], -).enableRLS(); - -export const core_sessions_relations = relations(core_sessions, ({ one }) => ({ - user: one(core_users, { - fields: [core_sessions.userId], - references: [core_users.id], - }), - device: one(core_sessions_known_devices, { - fields: [core_sessions.deviceId], - references: [core_sessions_known_devices.id], - }), -})); - -export const core_sessions_known_devices = pgTable( - 'core_sessions_known_devices', - t => ({ - id: t.serial().primaryKey(), - ipAddress: t.varchar({ length: 40 }).notNull(), - userAgent: t.text().notNull(), - lastSeen: t.timestamp().notNull().defaultNow(), - }), - t => [index('core_sessions_known_devices_ip_address_idx').on(t.ipAddress)], -).enableRLS(); - -export const core_sessions_known_devices_relations = relations( - core_sessions_known_devices, - ({ one }) => ({ - session: one(core_sessions, { - fields: [core_sessions_known_devices.id], - references: [core_sessions.deviceId], - }), - }), -); diff --git a/packages/vitnode/src/database/schema/test.ts b/packages/vitnode/src/database/schema/test.ts deleted file mode 100644 index 0cbdbd526..000000000 --- a/packages/vitnode/src/database/schema/test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { pgTable } from 'drizzle-orm/pg-core'; - -export const core_test = pgTable('core_test', t => ({ - id: t.serial().primaryKey(), - createdAt: t.timestamp().notNull().defaultNow(), - text: t.text().notNull(), -})).enableRLS(); diff --git a/packages/vitnode/src/database/schema/users.ts b/packages/vitnode/src/database/schema/users.ts deleted file mode 100644 index 3c134fbf8..000000000 --- a/packages/vitnode/src/database/schema/users.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { relations } from 'drizzle-orm'; -import { index, pgTable } from 'drizzle-orm/pg-core'; - -import { core_languages } from './languages'; -import { core_roles } from './roles'; - -export const core_users = pgTable( - 'core_users', - t => ({ - id: t.serial().primaryKey(), - nameCode: t.varchar({ length: 255 }).notNull().unique(), - name: t.varchar({ length: 255 }).notNull().unique(), - email: t.varchar({ length: 255 }).notNull().unique(), - password: t.varchar(), - createdAt: t.timestamp().notNull().defaultNow(), - newsletter: t.boolean().notNull().default(false), - avatarColor: t.varchar({ length: 6 }).notNull(), - emailVerified: t.boolean().notNull().default(false), - roleId: t - .integer() - .references(() => core_roles.id) - .notNull(), - birthday: t.timestamp(), - ipAddress: t.varchar({ length: 40 }).notNull(), - language: t - .varchar({ length: 32 }) - .notNull() - .default('en') - .references(() => core_languages.code, { - onDelete: 'set default', - }), - }), - t => [ - index('core_users_name_code_idx').on(t.nameCode), - index('core_users_name_idx').on(t.name), - index('core_users_email_idx').on(t.email), - ], -).enableRLS(); - -export const core_users_relations = relations(core_users, ({ one, many }) => ({ - group: one(core_roles, { - fields: [core_users.roleId], - references: [core_roles.id], - }), - language: one(core_languages, { - fields: [core_users.language], - references: [core_languages.code], - }), - confirm_email: one(core_users_confirm_emails, { - fields: [core_users.id], - references: [core_users_confirm_emails.userId], - }), - sso: many(core_users_sso), - forgot_password: one(core_users_forgot_password, { - fields: [core_users.id], - references: [core_users_forgot_password.userId], - }), -})); - -export const core_users_sso = pgTable( - 'core_users_sso', - t => ({ - userId: t - .integer() - .references(() => core_users.id, { - onDelete: 'cascade', - }) - .notNull(), - providerId: t.varchar({ length: 255 }).notNull(), - providerAccountId: t.varchar({ length: 255 }).notNull(), - createdAt: t.timestamp().notNull().defaultNow(), - updatedAt: t - .timestamp() - .notNull() - .$onUpdate(() => new Date()), - }), - t => [index('core_users_sso_user_id_idx').on(t.userId)], -).enableRLS(); - -export const core_users_sso_relations = relations( - core_users_sso, - ({ one }) => ({ - user: one(core_users, { - fields: [core_users_sso.userId], - references: [core_users.id], - }), - }), -); - -export const core_users_confirm_emails = pgTable( - 'core_users_confirm_emails', - t => ({ - id: t.serial().primaryKey(), - userId: t - .integer() - .references(() => core_users.id, { - onDelete: 'cascade', - }) - .notNull(), - token: t.varchar({ length: 100 }).notNull().unique(), - createdAt: t.timestamp().notNull().defaultNow(), - expires: t.timestamp().notNull(), - }), -).enableRLS(); - -export const core_users_confirm_emails_relations = relations( - core_users_confirm_emails, - ({ one }) => ({ - user: one(core_users, { - fields: [core_users_confirm_emails.userId], - references: [core_users.id], - }), - }), -); - -export const core_users_forgot_password = pgTable( - 'core_users_forgot_password', - t => ({ - id: t.serial().primaryKey(), - userId: t - .integer() - .references(() => core_users.id, { - onDelete: 'cascade', - }) - .notNull() - .unique(), - token: t.varchar({ length: 100 }).notNull().unique(), - ip_address: t.varchar({ length: 40 }).notNull(), - createdAt: t.timestamp().notNull().defaultNow(), - expiresAt: t.timestamp().notNull(), - }), -).enableRLS(); - -export const core_users_forgot_password_relations = relations( - core_users_forgot_password, - ({ one }) => ({ - user: one(core_users, { - fields: [core_users_forgot_password.userId], - references: [core_users.id], - }), - }), -); diff --git a/apps/web/src/database/schema/sessions.ts b/packages/vitnode/src/database/sessions.ts similarity index 100% rename from apps/web/src/database/schema/sessions.ts rename to packages/vitnode/src/database/sessions.ts diff --git a/apps/web/src/database/schema/test.ts b/packages/vitnode/src/database/test.ts similarity index 100% rename from apps/web/src/database/schema/test.ts rename to packages/vitnode/src/database/test.ts diff --git a/apps/web/src/database/schema/users.ts b/packages/vitnode/src/database/users.ts similarity index 100% rename from apps/web/src/database/schema/users.ts rename to packages/vitnode/src/database/users.ts diff --git a/packages/vitnode/src/drizzle.config.ts b/packages/vitnode/src/drizzle.config.ts new file mode 100644 index 000000000..04521fe7e --- /dev/null +++ b/packages/vitnode/src/drizzle.config.ts @@ -0,0 +1,39 @@ +import type { Config } from 'drizzle-kit'; + +import { defineConfig } from 'drizzle-kit'; +import { join } from 'path'; + +import type { VitNodeApiConfig } from './vitnode.config'; + +export const defineVitNodeDrizzleConfig = ({ + vitNodeApiConfig, + ...args +}: Config & { + vitNodeApiConfig: VitNodeApiConfig; +}) => { + const pluginNames = vitNodeApiConfig.plugins.map(plugin => plugin.name); + + const pluginPaths = ['@vitnode/core', ...pluginNames].map(pluginName => { + const pluginPath = join( + process.cwd(), + 'node_modules', + pluginName, + 'src', + 'database', + ); + + return pluginPath; + }); + + return defineConfig({ + ...args, + schema: [ + ...(Array.isArray(args.schema) + ? args.schema + : args.schema + ? [args.schema] + : []), + ...pluginPaths, + ], + }); +}; diff --git a/packages/vitnode/src/vitnode.config.ts b/packages/vitnode/src/vitnode.config.ts index 3269aa68a..d4ff34c3c 100644 --- a/packages/vitnode/src/vitnode.config.ts +++ b/packages/vitnode/src/vitnode.config.ts @@ -1,3 +1,4 @@ +import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'; import type { ThemeProvider } from 'next-themes'; import type { BuildPluginApiReturn } from './api/lib/plugin'; @@ -35,6 +36,7 @@ export interface VitNodeApiConfig { deviceCookieName?: string; ssoPlugins?: SSOApiPlugin[]; }; + dbProvider: PostgresJsDatabase; emailProvider?: EmailApiPlugin; plugins: BuildPluginApiReturn[]; }