diff --git a/src/server/db/index.ts b/src/server/db/index.ts index 4c19782f..318e449a 100644 --- a/src/server/db/index.ts +++ b/src/server/db/index.ts @@ -1,6 +1,7 @@ import { drizzle } from 'drizzle-orm/node-postgres'; import { Pool } from 'pg'; import { getConfig } from '../core/TipiConfig/TipiConfig'; +import * as schema from './schema'; const connectionString = `postgresql://${getConfig().postgresUsername}:${getConfig().postgresPassword}@${getConfig().postgresHost}:${getConfig().postgresPort}/${ getConfig().postgresDatabase @@ -10,4 +11,5 @@ const pool = new Pool({ connectionString, }); -export const db = drizzle(pool); +export const db = drizzle(pool, { schema }); +export type Database = typeof db; diff --git a/src/server/queries/apps/apps.queries.ts b/src/server/queries/apps/apps.queries.ts index e316980b..5a98df38 100644 --- a/src/server/queries/apps/apps.queries.ts +++ b/src/server/queries/apps/apps.queries.ts @@ -1,11 +1,11 @@ -import { NodePgDatabase } from 'drizzle-orm/node-postgres'; import { and, asc, eq, ne, notInArray } from 'drizzle-orm'; +import { Database } from '@/server/db'; import { appTable, NewApp, AppStatus } from '../../db/schema'; export class AppQueries { private db; - constructor(p: NodePgDatabase) { + constructor(p: Database) { this.db = p; } @@ -15,8 +15,7 @@ export class AppQueries { * @param {string} appId - The id of the app to return */ public async getApp(appId: string) { - const apps = await this.db.select().from(appTable).where(eq(appTable.id, appId)); - return apps[0]; + return this.db.query.appTable.findFirst({ where: eq(appTable.id, appId) }); } /** @@ -55,14 +54,14 @@ export class AppQueries { * @param {AppStatus} status - The status of the apps to return */ public async getAppsByStatus(status: AppStatus) { - return this.db.select().from(appTable).where(eq(appTable.status, status)).orderBy(asc(appTable.id)); + return this.db.query.appTable.findMany({ where: eq(appTable.status, status), orderBy: asc(appTable.id) }); } /** * Returns all apps installed sorted by id ascending */ public async getApps() { - return this.db.select().from(appTable).orderBy(asc(appTable.id)); + return this.db.query.appTable.findMany({ orderBy: asc(appTable.id) }); } /** @@ -72,10 +71,7 @@ export class AppQueries { * @param {string} id - The id of the app to exclude */ public async getAppsByDomain(domain: string, id: string) { - return this.db - .select() - .from(appTable) - .where(and(eq(appTable.domain, domain), eq(appTable.exposed, true), ne(appTable.id, id))); + return this.db.query.appTable.findMany({ where: and(eq(appTable.domain, domain), eq(appTable.exposed, true), ne(appTable.id, id)) }); } /** diff --git a/src/server/queries/auth/auth.queries.ts b/src/server/queries/auth/auth.queries.ts index 90c0bd77..e263ed95 100644 --- a/src/server/queries/auth/auth.queries.ts +++ b/src/server/queries/auth/auth.queries.ts @@ -1,11 +1,11 @@ -import { NodePgDatabase } from 'drizzle-orm/node-postgres'; -import { asc, eq } from 'drizzle-orm'; +import { eq } from 'drizzle-orm'; +import { Database } from '@/server/db'; import { userTable, NewUser } from '../../db/schema'; export class AuthQueries { private db; - constructor(p: NodePgDatabase) { + constructor(p: Database) { this.db = p; } @@ -15,8 +15,7 @@ export class AuthQueries { * @param {string} username - The username of the user to return */ public async getUserByUsername(username: string) { - const users = await this.db.select().from(userTable).where(eq(userTable.username, username.trim().toLowerCase())); - return users[0]; + return this.db.query.userTable.findFirst({ where: eq(userTable.username, username.trim().toLowerCase()) }); } /** @@ -25,12 +24,7 @@ export class AuthQueries { * @param {number} id - The id of the user to return */ public async getUserById(id: number) { - const users = await this.db - .select() - .from(userTable) - .where(eq(userTable.id, Number(id))); - - return users[0]; + return this.db.query.userTable.findFirst({ where: eq(userTable.id, Number(id)) }); } /** @@ -39,12 +33,7 @@ export class AuthQueries { * @param {number} id - The id of the user to return */ public async getUserDtoById(id: number) { - const users = await this.db - .select({ id: userTable.id, username: userTable.username, totpEnabled: userTable.totpEnabled, locale: userTable.locale }) - .from(userTable) - .where(eq(userTable.id, Number(id))); - - return users[0]; + return this.db.query.userTable.findFirst({ where: eq(userTable.id, Number(id)), columns: { id: true, username: true, totpEnabled: true, locale: true } }); } /** @@ -74,8 +63,7 @@ export class AuthQueries { * Returns the first operator found in the system */ public async getFirstOperator() { - const users = await this.db.select().from(userTable).where(eq(userTable.operator, true)).orderBy(asc(userTable.id)).limit(1); - return users[0]; + return this.db.query.userTable.findFirst({ where: eq(userTable.operator, true) }); } /** diff --git a/src/server/services/apps/apps.service.ts b/src/server/services/apps/apps.service.ts index 4ae31223..5b12cc06 100644 --- a/src/server/services/apps/apps.service.ts +++ b/src/server/services/apps/apps.service.ts @@ -1,8 +1,8 @@ import validator from 'validator'; -import { NodePgDatabase } from 'drizzle-orm/node-postgres'; import { App } from '@/server/db/schema'; import { AppQueries } from '@/server/queries/apps/apps.queries'; import { TranslatedError } from '@/server/utils/errors'; +import { Database } from '@/server/db'; import { checkAppRequirements, checkEnvFile, generateEnvFile, getAvailableApps, ensureAppFolder, AppInfo, getAppInfo, getUpdateInfo } from './apps.helpers'; import { getConfig } from '../../core/TipiConfig'; import { EventDispatcher } from '../../core/EventDispatcher'; @@ -25,7 +25,7 @@ const filterApps = (apps: AppInfo[]): AppInfo[] => apps.sort(sortApps).filter(fi export class AppServiceClass { private queries; - constructor(p: NodePgDatabase) { + constructor(p: Database) { this.queries = new AppQueries(p); } diff --git a/src/server/services/auth/auth.service.test.ts b/src/server/services/auth/auth.service.test.ts index 074a5fec..1d00fe67 100644 --- a/src/server/services/auth/auth.service.test.ts +++ b/src/server/services/auth/auth.service.test.ts @@ -4,7 +4,7 @@ import { faker } from '@faker-js/faker'; import { TotpAuthenticator } from '@/server/utils/totp'; import { generateSessionId } from '@/server/common/session.helpers'; import { fromAny, fromPartial } from '@total-typescript/shoehorn'; -import { mockInsert, mockSelect } from '@/server/tests/drizzle-helpers'; +import { mockInsert, mockQuery, mockSelect } from '@/server/tests/drizzle-helpers'; import { createDatabase, clearDatabase, closeDatabase, TestDatabase } from '@/server/tests/test-utils'; import { encrypt } from '../../utils/encryption'; import { setConfig } from '../../core/TipiConfig'; @@ -428,7 +428,7 @@ describe('Register', () => { // Arrange const req = {}; const email = faker.internet.email(); - const mockDatabase = { select: mockSelect([]), insert: mockInsert([]) }; + const mockDatabase = { select: mockSelect([]), insert: mockInsert([]), query: mockQuery(undefined) }; const newAuthService = new AuthServiceClass(fromAny(mockDatabase)); // Act & Assert diff --git a/src/server/services/auth/auth.service.ts b/src/server/services/auth/auth.service.ts index 2e721937..71e83785 100644 --- a/src/server/services/auth/auth.service.ts +++ b/src/server/services/auth/auth.service.ts @@ -1,12 +1,12 @@ import * as argon2 from 'argon2'; import validator from 'validator'; import { TotpAuthenticator } from '@/server/utils/totp'; -import { NodePgDatabase } from 'drizzle-orm/node-postgres'; import { AuthQueries } from '@/server/queries/auth/auth.queries'; import { Context } from '@/server/context'; import { TranslatedError } from '@/server/utils/errors'; import { Locales, getLocaleFromString } from '@/shared/internationalization/locales'; import { generateSessionId } from '@/server/common/session.helpers'; +import { Database } from '@/server/db'; import { getConfig } from '../../core/TipiConfig'; import TipiCache from '../../core/TipiCache'; import { fileExists, unlinkFile } from '../../common/fs.helpers'; @@ -21,7 +21,7 @@ type UsernamePasswordInput = { export class AuthServiceClass { private queries; - constructor(p: NodePgDatabase) { + constructor(p: Database) { this.queries = new AuthQueries(p); } diff --git a/src/server/tests/drizzle-helpers.ts b/src/server/tests/drizzle-helpers.ts index ad6729c0..0929f918 100644 --- a/src/server/tests/drizzle-helpers.ts +++ b/src/server/tests/drizzle-helpers.ts @@ -1,3 +1,5 @@ export const mockSelect = (returnValue: T) => jest.fn(() => ({ from: jest.fn(() => ({ where: jest.fn(() => returnValue) })) })); export const mockInsert = (returnValue: T) => jest.fn(() => ({ values: jest.fn(() => ({ returning: jest.fn(() => returnValue) })) })); + +export const mockQuery = (returnValue: T) => ({ userTable: { findFirst: jest.fn(() => returnValue) } }); diff --git a/src/server/tests/test-utils.ts b/src/server/tests/test-utils.ts index fc897220..583a1c0f 100644 --- a/src/server/tests/test-utils.ts +++ b/src/server/tests/test-utils.ts @@ -1,13 +1,14 @@ /* eslint-disable no-restricted-syntax */ import pg, { Pool } from 'pg'; -import { NodePgDatabase, drizzle } from 'drizzle-orm/node-postgres'; +import { drizzle } from 'drizzle-orm/node-postgres'; import { runPostgresMigrations } from '../run-migration'; import { getConfig } from '../core/TipiConfig'; -import { appTable, userTable } from '../db/schema'; +import * as schema from '../db/schema'; +import { Database } from '../db'; export type TestDatabase = { client: Pool; - db: NodePgDatabase; + db: Database; }; /** @@ -36,7 +37,7 @@ const createDatabase = async (testsuite: string): Promise => { connectionString: `postgresql://${getConfig().postgresUsername}:${getConfig().postgresPassword}@${getConfig().postgresHost}:${getConfig().postgresPort}/${testsuite}?connect_timeout=300`, }); - return { client, db: drizzle(client) }; + return { client, db: drizzle(client, { schema }) }; }; /** @@ -45,8 +46,8 @@ const createDatabase = async (testsuite: string): Promise => { * @param {TestDatabase} database - database to clear */ const clearDatabase = async (database: TestDatabase) => { - await database.db.delete(userTable); - await database.db.delete(appTable); + await database.db.delete(schema.userTable); + await database.db.delete(schema.appTable); }; const closeDatabase = async (database: TestDatabase) => {