refactor: extract all db queries into their own class

This commit is contained in:
Nicolas Meienberger 2023-04-19 19:05:14 +02:00 committed by Nicolas Meienberger
parent f22e49f920
commit 06099c0ae4
8 changed files with 249 additions and 122 deletions

View file

@ -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 }) => {

View file

@ -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';

View file

@ -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';

View file

@ -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(),

View file

@ -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();
}
}

View file

@ -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];
}
}

View file

@ -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) => {

View file

@ -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');