Browse Source

refactor: extract all db queries into their own class

Nicolas Meienberger 2 years ago
parent
commit
06099c0ae4

+ 1 - 1
src/client/components/AppStatus/AppStatus.tsx

@@ -1,7 +1,7 @@
 import clsx from 'clsx';
 import React from 'react';
 import { Tooltip } from 'react-tooltip';
-import { type AppStatus as AppStatusEnum } from '@/server/db/schema';
+import type { AppStatus as AppStatusEnum } from '@/server/db/schema';
 import styles from './AppStatus.module.scss';
 
 export const AppStatus: React.FC<{ status: AppStatusEnum; lite?: boolean }> = ({ status, lite }) => {

+ 1 - 1
src/client/components/AppTile/AppTile.tsx

@@ -2,7 +2,7 @@ import Link from 'next/link';
 import React from 'react';
 import { IconDownload } from '@tabler/icons-react';
 import { Tooltip } from 'react-tooltip';
-import { AppStatus as AppStatusEnum } from '@/server/db/schema';
+import type { AppStatus as AppStatusEnum } from '@/server/db/schema';
 import { AppStatus } from '../AppStatus';
 import { AppLogo } from '../AppLogo/AppLogo';
 import { limitText } from '../../modules/AppStore/helpers/table.helpers';

+ 1 - 1
src/client/mocks/fixtures/app.fixtures.ts

@@ -1,5 +1,5 @@
 import { faker } from '@faker-js/faker';
-import { AppStatus } from '@/server/db/schema';
+import type { AppStatus } from '@/server/db/schema';
 import { APP_CATEGORIES } from '../../../server/services/apps/apps.types';
 import { App, AppCategory, AppInfo, AppWithInfo } from '../../core/types';
 

+ 1 - 0
src/server/db/schema.ts

@@ -26,6 +26,7 @@ export const userTable = pgTable('user', {
   salt: text('salt'),
 });
 export type User = InferModel<typeof userTable>;
+export type NewUser = InferModel<typeof userTable, 'insert'>;
 
 export const update = pgTable('update', {
   id: serial('id').notNull(),

+ 90 - 0
src/server/queries/apps/apps.queries.ts

@@ -0,0 +1,90 @@
+import { NodePgDatabase } from 'drizzle-orm/node-postgres';
+import { and, asc, eq, ne, notInArray } from 'drizzle-orm';
+import { appTable, NewApp, AppStatus } from '../../db/schema';
+
+export class AppQueries {
+  private db;
+
+  constructor(p: NodePgDatabase) {
+    this.db = p;
+  }
+
+  /**
+   * Given an app id, return the app
+   *
+   * @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];
+  }
+
+  /**
+   * Given an app id, update the app with the given data
+   *
+   * @param {string} appId - The id of the app to update
+   * @param {Partial<NewApp>} data - The data to update the app with
+   */
+  public async updateApp(appId: string, data: Partial<NewApp>) {
+    const updatedApps = await this.db.update(appTable).set(data).where(eq(appTable.id, appId)).returning();
+    return updatedApps[0];
+  }
+
+  /**
+   * Given an app id, delete the app
+   *
+   * @param {string} appId - The id of the app to delete
+   */
+  public async deleteApp(appId: string) {
+    await this.db.delete(appTable).where(eq(appTable.id, appId));
+  }
+
+  /**
+   * Given app data, creates a new app
+   *
+   * @param {NewApp} data - The data to create the app with
+   */
+  public async createApp(data: NewApp) {
+    const newApps = await this.db.insert(appTable).values(data).returning();
+    return newApps[0];
+  }
+
+  /**
+   * Returns all apps installed with the given status sorted by id ascending
+   *
+   * @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));
+  }
+
+  /**
+   * Returns all apps installed sorted by id ascending
+   */
+  public async getApps() {
+    return this.db.select().from(appTable).orderBy(asc(appTable.id));
+  }
+
+  /**
+   * Given a domain, return all apps that have this domain, are exposed and not the given id
+   *
+   * @param {string} domain - The domain to search for
+   * @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)));
+  }
+
+  /**
+   * Given an array of app status, update all apps that have a status not in the array with new values
+   *
+   * @param {AppStatus[]} statuses - The statuses to exclude from the update
+   * @param {Partial<NewApp>} data - The data to update the apps with
+   */
+  public async updateAppsByStatusNotIn(statuses: AppStatus[], data: Partial<NewApp>) {
+    return this.db.update(appTable).set(data).where(notInArray(appTable.status, statuses)).returning();
+  }
+}

+ 90 - 0
src/server/queries/auth/auth.queries.ts

@@ -0,0 +1,90 @@
+import { NodePgDatabase } from 'drizzle-orm/node-postgres';
+import { asc, eq } from 'drizzle-orm';
+import { userTable, NewUser } from '../../db/schema';
+
+export class AuthQueries {
+  private db;
+
+  constructor(p: NodePgDatabase) {
+    this.db = p;
+  }
+
+  /**
+   * Given a username, return the user associated to it
+   *
+   * @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];
+  }
+
+  /**
+   * Given a userId, return the user associated to it
+   *
+   * @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];
+  }
+
+  /**
+   * Given a userId, return the user associated to it with only the id, username, and totpEnabled fields
+   *
+   * @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 })
+      .from(userTable)
+      .where(eq(userTable.id, Number(id)));
+
+    return users[0];
+  }
+
+  /**
+   * Given a userId, update the user with the given data
+   *
+   * @param {number} id - The id of the user to update
+   * @param {Partial<NewUser>} data - The data to update the user with
+   */
+  public async updateUser(id: number, data: Partial<NewUser>) {
+    const updatedUsers = await this.db
+      .update(userTable)
+      .set(data)
+      .where(eq(userTable.id, Number(id)))
+      .returning();
+
+    return updatedUsers[0];
+  }
+
+  /**
+   * Returns all operators registered in the system
+   */
+  public async getOperators() {
+    return this.db.select().from(userTable).where(eq(userTable.operator, true));
+  }
+
+  /**
+   * 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];
+  }
+
+  /**
+   * Given user data, creates a new user
+   *
+   * @param {NewUser} data - The data to create the user with
+   */
+  public async createUser(data: NewUser) {
+    const newUsers = await this.db.insert(userTable).values(data).returning();
+    return newUsers[0];
+  }
+}

+ 43 - 83
src/server/services/apps/apps.service.ts

@@ -1,7 +1,7 @@
 import validator from 'validator';
 import { NodePgDatabase } from 'drizzle-orm/node-postgres';
-import { appTable, App } from '@/server/db/schema';
-import { and, asc, eq, ne, notInArray } from 'drizzle-orm';
+import { App } from '@/server/db/schema';
+import { AppQueries } from '@/server/queries/apps/apps.queries';
 import { checkAppRequirements, checkEnvFile, generateEnvFile, getAvailableApps, ensureAppFolder, AppInfo, getAppInfo, getUpdateInfo } from './apps.helpers';
 import { getConfig } from '../../core/TipiConfig';
 import { EventDispatcher } from '../../core/EventDispatcher';
@@ -22,10 +22,10 @@ const filterApp = (app: AppInfo): boolean => {
 const filterApps = (apps: AppInfo[]): AppInfo[] => apps.sort(sortApps).filter(filterApp);
 
 export class AppServiceClass {
-  private db;
+  private queries;
 
   constructor(p: NodePgDatabase) {
-    this.db = p;
+    this.queries = new AppQueries(p);
   }
 
   /**
@@ -33,17 +33,12 @@ export class AppServiceClass {
    *  It finds all the running apps and starts them by regenerating the env file, checking the env file and dispatching the start event.
    *  If the start event is successful, the app's status is updated to 'running', otherwise, it is updated to 'stopped'
    *  If there is an error while starting the app, it logs the error and updates the app's status to 'stopped'.
-   *
-   *  @returns {Promise<void>} - A promise that resolves when all apps are started.
    */
   public async startAllApps() {
-    const apps = await this.db.select().from(appTable).where(eq(appTable.status, 'running')).orderBy(asc(appTable.id));
+    const apps = await this.queries.getAppsByStatus('running');
 
     // Update all apps with status different than running or stopped to stopped
-    await this.db
-      .update(appTable)
-      .set({ status: 'stopped' })
-      .where(notInArray(appTable.status, ['running', 'stopped']));
+    await this.queries.updateAppsByStatusNotIn(['running', 'stopped', 'missing'], { status: 'stopped' });
 
     await Promise.all(
       apps.map(async (app) => {
@@ -53,17 +48,17 @@ export class AppServiceClass {
           generateEnvFile(app);
           checkEnvFile(app.id);
 
-          await this.db.update(appTable).set({ status: 'starting' }).where(eq(appTable.id, app.id));
+          await this.queries.updateApp(app.id, { status: 'starting' });
 
           EventDispatcher.dispatchEventAsync('app', ['start', app.id]).then(({ success }) => {
             if (success) {
-              this.db.update(appTable).set({ status: 'running' }).where(eq(appTable.id, app.id)).execute();
+              this.queries.updateApp(app.id, { status: 'running' });
             } else {
-              this.db.update(appTable).set({ status: 'stopped' }).where(eq(appTable.id, app.id)).execute();
+              this.queries.updateApp(app.id, { status: 'stopped' });
             }
           });
         } catch (e) {
-          await this.db.update(appTable).set({ status: 'stopped' }).where(eq(appTable.id, app.id));
+          await this.queries.updateApp(app.id, { status: 'stopped' });
           Logger.error(e);
         }
       }),
@@ -75,13 +70,10 @@ export class AppServiceClass {
    * It updates the app's status in the database to 'starting' and 'running' if the start process is successful, otherwise it updates the status to 'stopped'.
    *
    * @param {string} appName - The name of the app to start
-   * @returns {Promise<App | null>} - Returns a promise that resolves with the updated app information.
    * @throws {Error} - If the app is not found or the start process fails.
    */
   public startApp = async (appName: string) => {
-    const apps = await this.db.select().from(appTable).where(eq(appTable.id, appName));
-    const app = apps[0];
-
+    const app = await this.queries.getApp(appName);
     if (!app) {
       throw new Error(`App ${appName} not found`);
     }
@@ -91,18 +83,18 @@ export class AppServiceClass {
     generateEnvFile(app);
     checkEnvFile(appName);
 
-    await this.db.update(appTable).set({ status: 'starting' }).where(eq(appTable.id, appName));
+    await this.queries.updateApp(appName, { status: 'starting' });
     const { success, stdout } = await EventDispatcher.dispatchEventAsync('app', ['start', app.id]);
 
     if (success) {
-      await this.db.update(appTable).set({ status: 'running' }).where(eq(appTable.id, appName));
+      await this.queries.updateApp(appName, { status: 'running' });
     } else {
-      await this.db.update(appTable).set({ status: 'stopped' }).where(eq(appTable.id, appName));
+      await this.queries.updateApp(appName, { status: 'stopped' });
       throw new Error(`App ${appName} failed to start\nstdout: ${stdout}`);
     }
 
-    const updateApps = await this.db.select().from(appTable).where(eq(appTable.id, appName));
-    return updateApps[0];
+    const updatedApp = await this.queries.getApp(appName);
+    return updatedApp;
   };
 
   /**
@@ -112,11 +104,9 @@ export class AppServiceClass {
    * @param {Record<string, string>} form - The form data submitted by the user
    * @param {boolean} [exposed] - A flag indicating if the app will be exposed to the internet
    * @param {string} [domain] - The domain name to expose the app to the internet, required if exposed is true
-   * @returns {Promise<App | null>} Returns a promise that resolves to the installed app object
    */
   public installApp = async (id: string, form: Record<string, string>, exposed?: boolean, domain?: string) => {
-    const apps = await this.db.select().from(appTable).where(eq(appTable.id, id));
-    const app = apps[0];
+    const app = await this.queries.getApp(id);
 
     if (app) {
       await this.startApp(id);
@@ -150,21 +140,14 @@ export class AppServiceClass {
       }
 
       if (exposed && domain) {
-        const appsWithSameDomain = await this.db
-          .select()
-          .from(appTable)
-          .where(and(eq(appTable.domain, domain), eq(appTable.exposed, true)));
+        const appsWithSameDomain = await this.queries.getAppsByDomain(domain, id);
 
         if (appsWithSameDomain.length > 0) {
           throw new Error(`Domain ${domain} already in use by app ${appsWithSameDomain[0]?.id}`);
         }
       }
 
-      const newApps = await this.db
-        .insert(appTable)
-        .values({ id, status: 'installing', config: form, version: appInfo.tipi_version, exposed: exposed || false, domain: domain || null })
-        .returning();
-      const newApp = newApps[0];
+      const newApp = await this.queries.createApp({ id, status: 'installing', config: form, version: appInfo.tipi_version, exposed: exposed || false, domain: domain || null });
 
       if (newApp) {
         // Create env file
@@ -175,19 +158,17 @@ export class AppServiceClass {
       const { success, stdout } = await EventDispatcher.dispatchEventAsync('app', ['install', id]);
 
       if (!success) {
-        await this.db.delete(appTable).where(eq(appTable.id, id));
+        await this.queries.deleteApp(id);
         throw new Error(`App ${id} failed to install\nstdout: ${stdout}`);
       }
     }
 
-    const updatedApp = await this.db.update(appTable).set({ status: 'running' }).where(eq(appTable.id, id)).returning();
-    return updatedApp[0];
+    const updatedApp = await this.queries.updateApp(id, { status: 'running' });
+    return updatedApp;
   };
 
   /**
    * Lists available apps
-   *
-   * @returns {Promise<{apps: Array<AppInfo>, total: number }>} An object containing list of apps and total number of apps
    */
   public static listApps = async () => {
     const apps = await getAvailableApps();
@@ -203,7 +184,6 @@ export class AppServiceClass {
    * @param {object} form - The new configuration of the app.
    * @param {boolean} [exposed=false] - If the app should be exposed or not.
    * @param {string} [domain] - The domain for the app if exposed is true.
-   * @returns {Promise<App | null>} The updated app
    */
   public updateAppConfig = async (id: string, form: Record<string, string>, exposed?: boolean, domain?: string) => {
     if (exposed && !domain) {
@@ -214,8 +194,7 @@ export class AppServiceClass {
       throw new Error(`Domain ${domain} is not valid`);
     }
 
-    const apps = await this.db.select().from(appTable).where(eq(appTable.id, id));
-    const app = apps[0];
+    const app = await this.queries.getApp(id);
 
     if (!app) {
       throw new Error(`App ${id} not found`);
@@ -236,23 +215,14 @@ export class AppServiceClass {
     }
 
     if (exposed && domain) {
-      const appsWithSameDomain = await this.db
-        .select()
-        .from(appTable)
-        .where(and(eq(appTable.domain, domain), eq(appTable.exposed, true), ne(appTable.id, id)));
+      const appsWithSameDomain = await this.queries.getAppsByDomain(domain, id);
 
       if (appsWithSameDomain.length > 0) {
         throw new Error(`Domain ${domain} already in use by app ${appsWithSameDomain[0]?.id}`);
       }
     }
 
-    const updateApps = await this.db
-      .update(appTable)
-      .set({ exposed: exposed || false, domain: domain || null, config: form })
-      .where(eq(appTable.id, id))
-      .returning();
-
-    const updatedApp = updateApps[0];
+    const updatedApp = await this.queries.updateApp(id, { exposed: exposed || false, domain: domain || null, config: form });
 
     if (updatedApp) {
       generateEnvFile(updatedApp);
@@ -265,12 +235,10 @@ export class AppServiceClass {
    * Stops a running application by its id
    *
    * @param {string} id - The id of the application to stop
-   * @returns {Promise<App>} - The stopped application
    * @throws {Error} - If the app cannot be found or if stopping the app failed
    */
   public stopApp = async (id: string) => {
-    const apps = await this.db.select().from(appTable).where(eq(appTable.id, id));
-    const app = apps[0];
+    const app = await this.queries.getApp(id);
 
     if (!app) {
       throw new Error(`App ${id} not found`);
@@ -280,31 +248,29 @@ export class AppServiceClass {
     generateEnvFile(app);
 
     // Run script
-    await this.db.update(appTable).set({ status: 'stopping' }).where(eq(appTable.id, id));
+    await this.queries.updateApp(id, { status: 'stopping' });
 
     const { success, stdout } = await EventDispatcher.dispatchEventAsync('app', ['stop', id]);
 
     if (success) {
-      await this.db.update(appTable).set({ status: 'stopped' }).where(eq(appTable.id, id));
+      await this.queries.updateApp(id, { status: 'stopped' });
     } else {
-      await this.db.update(appTable).set({ status: 'running' }).where(eq(appTable.id, id));
+      await this.queries.updateApp(id, { status: 'running' });
       throw new Error(`App ${id} failed to stop\nstdout: ${stdout}`);
     }
 
-    const updatedApps = await this.db.update(appTable).set({ status: 'stopped' }).where(eq(appTable.id, id)).returning();
-    return updatedApps[0];
+    const updatedApp = await this.queries.getApp(id);
+    return updatedApp;
   };
 
   /**
    * Uninstalls an app by stopping it, running the app's `uninstall` script, and removing its data
    *
    * @param {string} id - The id of the app to uninstall
-   * @returns {Promise<{id: string, status: string, config: object}>} - An object containing the id of the uninstalled app, the status of the app ('missing'), and the config object
    * @throws {Error} - If the app is not found or if the app's `uninstall` script fails
    */
   public uninstallApp = async (id: string) => {
-    const apps = await this.db.select().from(appTable).where(eq(appTable.id, id));
-    const app = apps[0];
+    const app = await this.queries.getApp(id);
 
     if (!app) {
       throw new Error(`App ${id} not found`);
@@ -316,16 +282,16 @@ export class AppServiceClass {
     ensureAppFolder(id);
     generateEnvFile(app);
 
-    await this.db.update(appTable).set({ status: 'uninstalling' }).where(eq(appTable.id, id));
+    await this.queries.updateApp(id, { status: 'uninstalling' });
 
     const { success, stdout } = await EventDispatcher.dispatchEventAsync('app', ['uninstall', id]);
 
     if (!success) {
-      await this.db.update(appTable).set({ status: 'stopped' }).where(eq(appTable.id, id));
+      await this.queries.updateApp(id, { status: 'stopped' });
       throw new Error(`App ${id} failed to uninstall\nstdout: ${stdout}`);
     }
 
-    await this.db.delete(appTable).where(eq(appTable.id, id));
+    await this.queries.deleteApp(id);
 
     return { id, status: 'missing', config: {} };
   };
@@ -334,11 +300,9 @@ export class AppServiceClass {
    * Returns the app with the provided id. If the app is not found, it returns a default app object
    *
    * @param {string} id - The id of the app to retrieve
-   * @returns {Promise<App>} - The app object
    */
   public getApp = async (id: string) => {
-    const apps = await this.db.select().from(appTable).where(eq(appTable.id, id));
-    let app = apps[0];
+    let app = await this.queries.getApp(id);
     const info = getAppInfo(id, app?.status);
     const updateInfo = getUpdateInfo(id);
 
@@ -357,12 +321,10 @@ export class AppServiceClass {
    * Updates an app with the specified ID
    *
    * @param {string} id - ID of the app to update
-   * @returns {Promise<App>} - An object representing the updated app
    * @throws {Error} - If the app is not found or if the update process fails.
    */
   public updateApp = async (id: string) => {
-    const apps = await this.db.select().from(appTable).where(eq(appTable.id, id));
-    const app = apps[0];
+    const app = await this.queries.getApp(id);
 
     if (!app) {
       throw new Error(`App ${id} not found`);
@@ -371,30 +333,28 @@ export class AppServiceClass {
     ensureAppFolder(id);
     generateEnvFile(app);
 
-    await this.db.update(appTable).set({ status: 'updating' }).where(eq(appTable.id, id));
+    await this.queries.updateApp(id, { status: 'updating' });
 
     const { success, stdout } = await EventDispatcher.dispatchEventAsync('app', ['update', id]);
 
     if (success) {
       const appInfo = getAppInfo(app.id, app.status);
 
-      await this.db.update(appTable).set({ status: 'running', version: appInfo?.tipi_version }).where(eq(appTable.id, id));
+      await this.queries.updateApp(id, { status: 'running', version: appInfo?.tipi_version });
     } else {
-      await this.db.update(appTable).set({ status: 'stopped' }).where(eq(appTable.id, id));
+      await this.queries.updateApp(id, { status: 'stopped' });
       throw new Error(`App ${id} failed to update\nstdout: ${stdout}`);
     }
 
-    const updatedApps = await this.db.update(appTable).set({ status: 'stopped' }).where(eq(appTable.id, id)).returning();
-    return updatedApps[0];
+    const updatedApp = await this.queries.updateApp(id, { status: 'stopped' });
+    return updatedApp;
   };
 
   /**
    * Returns a list of all installed apps
-   *
-   * @returns {Promise<App[]>} - An array of app objects
    */
   public installedApps = async () => {
-    const apps = await this.db.select().from(appTable).orderBy(asc(appTable.id));
+    const apps = await this.queries.getApps();
 
     return apps
       .map((app) => {

+ 22 - 36
src/server/services/auth/auth.service.ts

@@ -3,9 +3,8 @@ import jwt from 'jsonwebtoken';
 import validator from 'validator';
 import { TotpAuthenticator } from '@/server/utils/totp';
 import { generateSessionId } from '@/server/common/get-server-auth-session';
-import { eq } from 'drizzle-orm';
 import { NodePgDatabase } from 'drizzle-orm/node-postgres';
-import { userTable } from '@/server/db/schema';
+import { AuthQueries } from '@/server/queries/auth/auth.queries';
 import { getConfig } from '../../core/TipiConfig';
 import TipiCache from '../../core/TipiCache';
 import { fileExists, unlinkFile } from '../../common/fs.helpers';
@@ -21,10 +20,10 @@ type TokenResponse = {
 };
 
 export class AuthServiceClass {
-  private db;
+  private queries;
 
   constructor(p: NodePgDatabase) {
-    this.db = p;
+    this.queries = new AuthQueries(p);
   }
 
   /**
@@ -35,9 +34,7 @@ export class AuthServiceClass {
    */
   public login = async (input: UsernamePasswordInput) => {
     const { password, username } = input;
-
-    const users = await this.db.select().from(userTable).where(eq(userTable.username, username.trim().toLowerCase()));
-    const user = users[0];
+    const user = await this.queries.getUserByUsername(username);
 
     if (!user) {
       throw new Error('User not found');
@@ -80,11 +77,7 @@ export class AuthServiceClass {
       throw new Error('TOTP session not found');
     }
 
-    const users = await this.db
-      .select()
-      .from(userTable)
-      .where(eq(userTable.id, Number(userId)));
-    const user = users[0];
+    const user = await this.queries.getUserById(Number(userId));
 
     if (!user) {
       throw new Error('User not found');
@@ -124,8 +117,7 @@ export class AuthServiceClass {
 
     const { userId, password } = params;
 
-    const users = await this.db.select().from(userTable).where(eq(userTable.id, userId));
-    const user = users[0];
+    const user = await this.queries.getUserById(userId);
 
     if (!user) {
       throw new Error('User not found');
@@ -149,7 +141,7 @@ export class AuthServiceClass {
 
     const encryptedTotpSecret = encrypt(newTotpSecret, salt);
 
-    await this.db.update(userTable).set({ totpSecret: encryptedTotpSecret, salt }).where(eq(userTable.id, userId));
+    await this.queries.updateUser(userId, { totpSecret: encryptedTotpSecret, salt });
 
     const uri = TotpAuthenticator.keyuri(user.username, 'Runtipi', newTotpSecret);
 
@@ -162,8 +154,7 @@ export class AuthServiceClass {
     }
 
     const { userId, totpCode } = params;
-    const users = await this.db.select().from(userTable).where(eq(userTable.id, userId));
-    const user = users[0];
+    const user = await this.queries.getUserById(userId);
 
     if (!user) {
       throw new Error('User not found');
@@ -180,7 +171,7 @@ export class AuthServiceClass {
       throw new Error('Invalid TOTP code');
     }
 
-    await this.db.update(userTable).set({ totpEnabled: true }).where(eq(userTable.id, userId));
+    await this.queries.updateUser(userId, { totpEnabled: true });
 
     return true;
   };
@@ -188,8 +179,7 @@ export class AuthServiceClass {
   public disableTotp = async (params: { userId: number; password: string }) => {
     const { userId, password } = params;
 
-    const users = await this.db.select().from(userTable).where(eq(userTable.id, userId));
-    const user = users[0];
+    const user = await this.queries.getUserById(userId);
 
     if (!user) {
       throw new Error('User not found');
@@ -204,7 +194,7 @@ export class AuthServiceClass {
       throw new Error('Invalid password');
     }
 
-    await this.db.update(userTable).set({ totpEnabled: false, totpSecret: null }).where(eq(userTable.id, userId));
+    await this.queries.updateUser(userId, { totpEnabled: false, totpSecret: null });
 
     return true;
   };
@@ -217,9 +207,9 @@ export class AuthServiceClass {
    * @throws {Error} - If the email or password is missing, the email is invalid or the user already exists
    */
   public register = async (input: UsernamePasswordInput) => {
-    const operator = await this.db.select().from(userTable).where(eq(userTable.operator, true));
+    const operators = await this.queries.getOperators();
 
-    if (operator.length > 0) {
+    if (operators.length > 0) {
       throw new Error('There is already an admin user. Please login to create a new user from the admin panel.');
     }
 
@@ -234,8 +224,7 @@ export class AuthServiceClass {
       throw new Error('Invalid username');
     }
 
-    const users = await this.db.select().from(userTable).where(eq(userTable.username, email));
-    const user = users[0];
+    const user = await this.queries.getUserByUsername(email);
 
     if (user) {
       throw new Error('User already exists');
@@ -243,8 +232,7 @@ export class AuthServiceClass {
 
     const hash = await argon2.hash(password);
 
-    const newUsers = await this.db.insert(userTable).values({ username: email, password: hash, operator: true }).returning();
-    const newUser = newUsers[0];
+    const newUser = await this.queries.createUser({ username: email, password: hash, operator: true });
 
     if (!newUser) {
       throw new Error('Error creating user');
@@ -267,8 +255,7 @@ export class AuthServiceClass {
   public me = async (userId: number | undefined) => {
     if (!userId) return null;
 
-    const users = await this.db.select({ id: userTable.id, username: userTable.username, totpEnabled: userTable.totpEnabled }).from(userTable).where(eq(userTable.id, userId));
-    const user = users[0];
+    const user = await this.queries.getUserDtoById(userId);
 
     if (!user) return null;
 
@@ -317,7 +304,7 @@ export class AuthServiceClass {
    * @returns {Promise<boolean>} - A boolean indicating if the system is configured or not
    */
   public isConfigured = async (): Promise<boolean> => {
-    const operators = await this.db.select().from(userTable).where(eq(userTable.operator, true));
+    const operators = await this.queries.getOperators();
 
     return operators.length > 0;
   };
@@ -337,15 +324,15 @@ export class AuthServiceClass {
 
     const { newPassword } = params;
 
-    const users = await this.db.select().from(userTable).where(eq(userTable.operator, true));
-    const user = users[0];
+    const user = await this.queries.getFirstOperator();
 
     if (!user) {
       throw new Error('Operator user not found');
     }
 
     const hash = await argon2.hash(newPassword);
-    await this.db.update(userTable).set({ password: hash, totpEnabled: false, totpSecret: null }).where(eq(userTable.id, user.id));
+
+    await this.queries.updateUser(user.id, { password: hash, totpEnabled: false, totpSecret: null });
 
     await unlinkFile(`/runtipi/state/password-change-request`);
 
@@ -388,8 +375,7 @@ export class AuthServiceClass {
 
     const { currentPassword, newPassword, userId } = params;
 
-    const users = await this.db.select().from(userTable).where(eq(userTable.id, userId));
-    const user = users[0];
+    const user = await this.queries.getUserById(userId);
 
     if (!user) {
       throw new Error('User not found');
@@ -406,7 +392,7 @@ export class AuthServiceClass {
     }
 
     const hash = await argon2.hash(newPassword);
-    await this.db.update(userTable).set({ password: hash }).where(eq(userTable.id, user.id));
+    await this.queries.updateUser(user.id, { password: hash });
 
     await TipiCache.delByValue(userId.toString(), 'auth');