diff --git a/src/backend/database/repositories/factory.ts b/src/backend/database/repositories/factory.ts index 02f07aad..e74e92ee 100644 --- a/src/backend/database/repositories/factory.ts +++ b/src/backend/database/repositories/factory.ts @@ -1,6 +1,7 @@ import { DatabaseSaveTrigger } from "../../utils/database-save-trigger.js"; import { getDb, getSqlite } from "../db/index.js"; import type { DatabaseContext } from "./database-context.js"; +import { WebauthnCredentialRepository } from "./webauthn-credential-repository.js"; import { AlertRepository } from "./alert-repository.js"; import { ApiKeyRepository } from "./api-key-repository.js"; import { AuditLogRepository } from "./audit-log-repository.js"; @@ -68,6 +69,13 @@ export function getCurrentSettingValue(key: string): string | null { return row?.value ?? null; } +export function createCurrentWebauthnCredentialRepository(): WebauthnCredentialRepository { + return new WebauthnCredentialRepository( + createCurrentRepositoryContext(), + createCurrentRepositoryWriteHook("webauthn_credential_repository_write"), + ); +} + export function createCurrentAlertRepository(): AlertRepository { return new AlertRepository( createCurrentRepositoryContext(), diff --git a/src/backend/database/repositories/host-repository.ts b/src/backend/database/repositories/host-repository.ts index 62ebf843..0cb7a4db 100644 --- a/src/backend/database/repositories/host-repository.ts +++ b/src/backend/database/repositories/host-repository.ts @@ -78,6 +78,32 @@ export class HostRepository { return rows[0] ?? null; } + async findDecryptedByIdAs( + userId: string, + hostId: number, + ): Promise { + const row = await this.findById(hostId); + if (!row) return null; + + const userDataKey = DataCrypto.getUserDataKey(userId); + if (!userDataKey) return null; + + return DataCrypto.decryptRecord("ssh_data", row, userId, userDataKey); + } + + async listProxmoxEnabled(): Promise< + Pick[] + > { + return this.context.drizzle + .select({ + id: hosts.id, + userId: hosts.userId, + proxmoxConfig: hosts.proxmoxConfig, + }) + .from(hosts) + .where(eq(hosts.enableProxmox, true)); + } + async listByUserId(userId: string): Promise { return this.context.drizzle .select() diff --git a/src/backend/database/repositories/sso-provider-repository.ts b/src/backend/database/repositories/sso-provider-repository.ts index a6f2e430..403a81a0 100644 --- a/src/backend/database/repositories/sso-provider-repository.ts +++ b/src/backend/database/repositories/sso-provider-repository.ts @@ -35,6 +35,14 @@ export class SsoProviderRepository { .orderBy(asc(ssoProviders.displayOrder), asc(ssoProviders.id)); } + async listEnabled(): Promise { + return this.context.drizzle + .select() + .from(ssoProviders) + .where(eq(ssoProviders.enabled, true)) + .orderBy(asc(ssoProviders.displayOrder), asc(ssoProviders.id)); + } + async listAll(): Promise { return this.context.drizzle .select() diff --git a/src/backend/database/repositories/webauthn-credential-repository.ts b/src/backend/database/repositories/webauthn-credential-repository.ts new file mode 100644 index 00000000..f5dac86d --- /dev/null +++ b/src/backend/database/repositories/webauthn-credential-repository.ts @@ -0,0 +1,86 @@ +import { and, eq } from "drizzle-orm"; +import { webauthnCredentials } from "../db/schema.js"; +import type { DatabaseContext } from "./database-context.js"; + +export type WebauthnCredentialRecord = typeof webauthnCredentials.$inferSelect; +export type NewWebauthnCredentialRecord = + typeof webauthnCredentials.$inferInsert; + +export interface WebauthnCredentialAuthState { + counter: number; + backedUp: boolean; + deviceType: string | null; + lastUsedAt: string; +} + +export class WebauthnCredentialRepository { + constructor( + private readonly context: DatabaseContext, + private readonly onWrite?: () => void | Promise, + ) {} + + async listByUserId(userId: string): Promise { + return this.context.drizzle + .select() + .from(webauthnCredentials) + .where(eq(webauthnCredentials.userId, userId)); + } + + async findByCredentialId( + credentialId: string, + ): Promise { + const rows = await this.context.drizzle + .select() + .from(webauthnCredentials) + .where(eq(webauthnCredentials.credentialId, credentialId)) + .limit(1); + + return rows[0] ?? null; + } + + async create( + record: NewWebauthnCredentialRecord, + ): Promise { + const rows = await this.context.drizzle + .insert(webauthnCredentials) + .values(record) + .returning(); + + await this.afterWrite(); + return rows[0]; + } + + async updateAuthState( + id: string, + state: WebauthnCredentialAuthState, + ): Promise { + await this.context.drizzle + .update(webauthnCredentials) + .set(state) + .where(eq(webauthnCredentials.id, id)); + + await this.afterWrite(); + } + + async deleteForUser(userId: string, id: string): Promise { + const rows = await this.context.drizzle + .delete(webauthnCredentials) + .where( + and( + eq(webauthnCredentials.id, id), + eq(webauthnCredentials.userId, userId), + ), + ) + .returning({ id: webauthnCredentials.id }); + + if (rows.length > 0) { + await this.afterWrite(); + } + + return rows.length > 0; + } + + private async afterWrite(): Promise { + await this.onWrite?.(); + } +} diff --git a/src/backend/database/routes/proxmox.ts b/src/backend/database/routes/proxmox.ts index 49100e56..8f4618e6 100644 --- a/src/backend/database/routes/proxmox.ts +++ b/src/backend/database/routes/proxmox.ts @@ -1,10 +1,11 @@ import express from "express"; import { Client as SSHClient } from "ssh2"; -import { db, getDb, DatabaseSaveTrigger } from "../db/index.js"; -import { hosts, sshCredentials } from "../db/schema.js"; -import { eq, and } from "drizzle-orm"; import { logger } from "../../utils/logger.js"; -import { SimpleDBOps } from "../../utils/simple-db-ops.js"; +import { DataCrypto } from "../../utils/data-crypto.js"; +import { + createCurrentCredentialRepository, + createCurrentHostRepository, +} from "../repositories/factory.js"; import { AuthManager } from "../../utils/auth-manager.js"; import type { AuthenticatedRequest } from "../../../types/index.js"; import type { SSHHost } from "../../../types/index.js"; @@ -288,25 +289,24 @@ async function discoverProxmoxGuestsForHost( defaultCredentialId: number | null; config: ReturnType; }> { - if (!SimpleDBOps.isUserDataUnlocked(userId)) { + if (!DataCrypto.canUserAccessData(userId)) { const error = new Error("Session expired — please log in again"); (error as Error & { code?: string }).code = "SESSION_EXPIRED"; throw error; } - const hostResults = await SimpleDBOps.select( - getDb().select().from(hosts).where(eq(hosts.id, parsedHostId)), - "ssh_data", + const hostRecord = await createCurrentHostRepository().findDecryptedByIdAs( userId, + parsedHostId, ); - if (!hostResults.length) { + if (!hostRecord) { const error = new Error("Host not found"); (error as Error & { status?: number }).status = 404; throw error; } - const host = hostResults[0] as unknown as SSHHost; + const host = hostRecord as unknown as SSHHost; const proxmoxCfgRaw = parseJsonObject(host.proxmoxConfig); const config = parseProxmoxConfig(proxmoxCfgRaw); @@ -362,21 +362,13 @@ async function discoverProxmoxGuestsForHost( }); } } else { - const creds = await SimpleDBOps.select( - getDb() - .select() - .from(sshCredentials) - .where( - and( - eq(sshCredentials.id, host.credentialId as number), - eq(sshCredentials.userId, userId), - ), - ), - "ssh_credentials", - userId, - ); - if (creds.length > 0) { - const c = creds[0]; + const cred = + await createCurrentCredentialRepository().findDecryptedByIdForUser( + userId, + host.credentialId as number, + ); + if (cred) { + const c = cred; resolvedCredentials = { password: c.password as string | undefined, sshKey: (c.key || c.privateKey) as string | undefined, @@ -634,11 +626,10 @@ async function syncProxmoxHost( ); const now = new Date().toISOString(); - const existingHosts = await SimpleDBOps.select>( - db.select().from(hosts).where(eq(hosts.userId, userId)), - "ssh_data", - userId, - ); + const existingHosts = + (await createCurrentHostRepository().listDecryptedByUserId( + userId, + )) as unknown as Record[]; const existingBySource = new Map>(); for (const host of existingHosts) { const source = getProxmoxSource(host); @@ -712,12 +703,10 @@ async function syncProxmoxHost( }; if (existing) { - await SimpleDBOps.update( - hosts, - "ssh_data", - eq(hosts.id, existing.id as number), - update, + await createCurrentHostRepository().updateEncryptedForUser( userId, + existing.id as number, + update, ); result.updated++; continue; @@ -732,57 +721,51 @@ async function syncProxmoxHost( enableRdp: connectionType === "rdp", }); - await SimpleDBOps.insert( - hosts, - "ssh_data", - { - ...update, - userId, - createdAt: now, - pin: false, - authType: connectionType === "rdp" ? "password" : importAuth.authType, - credentialId: - connectionType === "ssh" ? importAuth.credentialId : null, - overrideCredentialUsername: importAuth.overrideCredentialUsername, - password: null, - key: null, - keyPassword: null, - keyType: null, - rdpUser: null, - rdpPassword: null, - rdpDomain: null, - rdpSecurity: null, - rdpIgnoreCert: 0, - rdpPort: connectionType === "rdp" ? 3389 : null, - vncUser: null, - vncPassword: null, - vncPort: null, - telnetUser: null, - telnetPassword: null, - telnetPort: null, - defaultPath: "/", - tunnelConnections: "[]", - jumpHosts: null, - quickActions: null, - statsConfig: null, - dockerConfig: null, - terminalConfig: null, - forceKeyboardInteractive: "false", - useSocks5: 0, - socks5Host: null, - socks5Port: null, - socks5Username: null, - socks5Password: null, - socks5ProxyChain: null, - portKnockSequence: null, - showTerminalInSidebar: 0, - showFileManagerInSidebar: 0, - showTunnelInSidebar: 0, - showDockerInSidebar: 0, - showServerStatsInSidebar: 0, - }, + await createCurrentHostRepository().createEncryptedForUser(userId, { + ...update, userId, - ); + createdAt: now, + pin: false, + authType: connectionType === "rdp" ? "password" : importAuth.authType, + credentialId: connectionType === "ssh" ? importAuth.credentialId : null, + overrideCredentialUsername: importAuth.overrideCredentialUsername, + password: null, + key: null, + keyPassword: null, + keyType: null, + rdpUser: null, + rdpPassword: null, + rdpDomain: null, + rdpSecurity: null, + rdpIgnoreCert: 0, + rdpPort: connectionType === "rdp" ? 3389 : null, + vncUser: null, + vncPassword: null, + vncPort: null, + telnetUser: null, + telnetPassword: null, + telnetPort: null, + defaultPath: "/", + tunnelConnections: "[]", + jumpHosts: null, + quickActions: null, + statsConfig: null, + dockerConfig: null, + terminalConfig: null, + forceKeyboardInteractive: "false", + useSocks5: 0, + socks5Host: null, + socks5Port: null, + socks5Username: null, + socks5Password: null, + socks5ProxyChain: null, + portKnockSequence: null, + showTerminalInSidebar: 0, + showFileManagerInSidebar: 0, + showTunnelInSidebar: 0, + showDockerInSidebar: 0, + showServerStatsInSidebar: 0, + }); result.created++; } @@ -793,10 +776,9 @@ async function syncProxmoxHost( const source = getProxmoxSource(existing); if (!source) continue; const missingSince = source.missingSince || now; - await SimpleDBOps.update( - hosts, - "ssh_data", - eq(hosts.id, existing.id as number), + await createCurrentHostRepository().updateEncryptedForUser( + userId, + existing.id as number, { tags: mergeTags(existing.tags, ["proxmox-missing"]), proxmoxConfig: JSON.stringify({ @@ -808,7 +790,6 @@ async function syncProxmoxHost( }), updatedAt: now, }, - userId, ); result.markedMissing++; } @@ -842,18 +823,13 @@ async function writeSyncStatus( hostId: number, patch: Record, ): Promise { - const rows = await db - .select({ proxmoxConfig: hosts.proxmoxConfig }) - .from(hosts) - .where(and(eq(hosts.id, hostId), eq(hosts.userId, userId))) - .limit(1); - if (!rows.length) return; - const config = parseJsonObject(rows[0].proxmoxConfig); - await db - .update(hosts) - .set({ proxmoxConfig: JSON.stringify({ ...config, ...patch }) }) - .where(and(eq(hosts.id, hostId), eq(hosts.userId, userId))); - DatabaseSaveTrigger.triggerSave("proxmox_sync_status"); + const hostRepository = createCurrentHostRepository(); + const row = await hostRepository.findByIdForUser(userId, hostId); + if (!row) return; + const config = parseJsonObject(row.proxmoxConfig); + await hostRepository.updateForUser(userId, hostId, { + proxmoxConfig: JSON.stringify({ ...config, ...patch }), + }); } router.post("/sync", authenticateJWT, requireDataAccess, async (req, res) => { @@ -886,22 +862,14 @@ router.post("/sync", authenticateJWT, requireDataAccess, async (req, res) => { async function runDueProxmoxAutoSyncs(): Promise { try { - const rows = await db - .select({ - id: hosts.id, - userId: hosts.userId, - enableProxmox: hosts.enableProxmox, - proxmoxConfig: hosts.proxmoxConfig, - }) - .from(hosts) - .where(eq(hosts.enableProxmox, true)); + const rows = await createCurrentHostRepository().listProxmoxEnabled(); const now = Date.now(); for (const row of rows) { const configRaw = parseJsonObject(row.proxmoxConfig); const config = parseProxmoxConfig(configRaw); if (!config.autoSyncEnabled) continue; - if (!SimpleDBOps.isUserDataUnlocked(row.userId)) continue; + if (!DataCrypto.canUserAccessData(row.userId)) continue; const lastSyncAt = typeof configRaw.lastSyncAt === "string" diff --git a/src/backend/database/routes/session-log-routes.ts b/src/backend/database/routes/session-log-routes.ts index bc723e2e..3873aa8f 100644 --- a/src/backend/database/routes/session-log-routes.ts +++ b/src/backend/database/routes/session-log-routes.ts @@ -6,8 +6,11 @@ import { apiLogger } from "../../utils/logger.js"; import { AuthManager } from "../../utils/auth-manager.js"; import type { Request, Response } from "express"; import { PermissionManager } from "../../utils/permission-manager.js"; -import { createCurrentSessionRecordingRepository } from "../repositories/factory.js"; -import { getDb } from "../db/index.js"; +import { + createCurrentSessionRecordingRepository, + createCurrentSettingsRepository, + getCurrentSettingValue, +} from "../repositories/factory.js"; const router = express.Router(); @@ -29,12 +32,10 @@ function getRetentionDays(): number { 10, ); try { - const row = getDb() - .$client.prepare( - "SELECT value FROM settings WHERE key = 'session_recording_retention_days'", - ) - .get() as { value?: string } | undefined; - const configured = parseInt(row?.value || "", 10); + const configured = parseInt( + getCurrentSettingValue("session_recording_retention_days") || "", + 10, + ); if (configured >= 1 && configured <= 3650) return configured; } catch { // use environment/default below @@ -164,11 +165,10 @@ router.put( .status(400) .json({ error: "Retention must be between 1 and 3650 days" }); } - getDb() - .$client.prepare( - "INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value", - ) - .run("session_recording_retention_days", String(retentionDays)); + await createCurrentSettingsRepository().upsert( + "session_recording_retention_days", + String(retentionDays), + ); void pruneOldLogs(); res.json({ retentionDays }); }, diff --git a/src/backend/database/routes/user-oidc-utils.ts b/src/backend/database/routes/user-oidc-utils.ts index aaaf1bfb..3b3e66e5 100644 --- a/src/backend/database/routes/user-oidc-utils.ts +++ b/src/backend/database/routes/user-oidc-utils.ts @@ -2,9 +2,6 @@ import { authLogger } from "../../utils/logger.js"; import type { SSOProviderType } from "../../../types/index.js"; import { DataCrypto } from "../../utils/data-crypto.js"; import { Agent } from "undici"; -import { eq } from "drizzle-orm"; -import { getDb } from "../db/index.js"; -import { ssoProviders } from "../db/schema.js"; import { createCurrentSettingsRepository, createCurrentSsoProviderRepository, @@ -426,10 +423,7 @@ export async function resolveProviderByIssuer(issuer: string): Promise<{ const target = normalizeIssuer(issuer); try { - const rows = await getDb() - .select() - .from(ssoProviders) - .where(eq(ssoProviders.enabled, true)); + const rows = await createCurrentSsoProviderRepository().listEnabled(); for (const row of rows) { if (!["oidc", "github", "google"].includes(row.type)) continue; let parsed: Record; diff --git a/src/backend/database/routes/user-webauthn-routes.ts b/src/backend/database/routes/user-webauthn-routes.ts index 6eff114b..f2d4c06e 100644 --- a/src/backend/database/routes/user-webauthn-routes.ts +++ b/src/backend/database/routes/user-webauthn-routes.ts @@ -12,7 +12,6 @@ import { verifyAuthenticationResponse, verifyRegistrationResponse, } from "@simplewebauthn/server"; -import { and, eq } from "drizzle-orm"; import { nanoid } from "nanoid"; import type { AuthenticatedRequest } from "../../../types/index.js"; import { AuthManager } from "../../utils/auth-manager.js"; @@ -21,8 +20,12 @@ import { generateDeviceFingerprint, parseUserAgent, } from "../../utils/user-agent-parser.js"; -import { db, saveMemoryDatabaseToFile } from "../db/index.js"; -import { users, webauthnCredentials } from "../db/schema.js"; +import { + createCurrentUserRepository, + createCurrentWebauthnCredentialRepository, + getCurrentSettingValue, +} from "../repositories/factory.js"; +import type { WebauthnCredentialRecord } from "../repositories/webauthn-credential-repository.js"; type UserVerification = "discouraged" | "preferred" | "required"; type NativeAppRequestChecker = (req: Request) => boolean; @@ -113,7 +116,7 @@ function parseTransports(value: string | null): AuthenticatorTransportFuture[] { } function getCredentialForVerification( - credential: typeof webauthnCredentials.$inferSelect, + credential: WebauthnCredentialRecord, ): WebAuthnCredential { return { id: credential.credentialId as Base64URLString, @@ -135,10 +138,8 @@ export function registerUserWebAuthnRoutes( return res.status(401).json({ error: "Authentication required" }); } - const credentials = await db - .select() - .from(webauthnCredentials) - .where(eq(webauthnCredentials.userId, userId)); + const credentials = + await createCurrentWebauthnCredentialRepository().listByUserId(userId); res.json({ credentials: credentials.map((credential) => ({ @@ -169,15 +170,13 @@ export function registerUserWebAuthnRoutes( }); } - const user = await db.select().from(users).where(eq(users.id, userId)); - if (!user.length) { + const user = await createCurrentUserRepository().findById(userId); + if (!user) { return res.status(404).json({ error: "User not found" }); } - const existing = await db - .select() - .from(webauthnCredentials) - .where(eq(webauthnCredentials.userId, userId)); + const existing = + await createCurrentWebauthnCredentialRepository().listByUserId(userId); const origin = getRequestOrigin(req); const rpID = getRpID(origin); @@ -189,8 +188,8 @@ export function registerUserWebAuthnRoutes( rpName: "Termix", rpID, userID: Buffer.from(userId, "utf8"), - userName: user[0].username, - userDisplayName: user[0].username, + userName: user.username, + userDisplayName: user.username, attestationType: "none", excludeCredentials: existing.map((credential) => ({ id: credential.credentialId as Base64URLString, @@ -259,7 +258,7 @@ export function registerUserWebAuthnRoutes( ? req.body.name.trim().slice(0, 80) : "Passkey"; - await db.insert(webauthnCredentials).values({ + await createCurrentWebauthnCredentialRepository().create({ id: nanoid(), userId, name, @@ -273,7 +272,6 @@ export function registerUserWebAuthnRoutes( createdAt: new Date().toISOString(), }); - await saveMemoryDatabaseToFile(); res.json({ success: true }); } catch (error) { authLogger.warn("WebAuthn registration failed", { @@ -301,19 +299,14 @@ export function registerUserWebAuthnRoutes( | undefined; if (username) { - const user = await db - .select() - .from(users) - .where(eq(users.username, username)); - if (!user.length) { + const user = await createCurrentUserRepository().findByUsername(username); + if (!user) { return res.status(404).json({ error: "No passkeys found" }); } - userId = user[0].id; - const credentials = await db - .select() - .from(webauthnCredentials) - .where(eq(webauthnCredentials.userId, userId)); + userId = user.id; + const credentials = + await createCurrentWebauthnCredentialRepository().listByUserId(userId); if (!credentials.length) { return res.status(404).json({ error: "No passkeys found" }); @@ -360,16 +353,15 @@ export function registerUserWebAuthnRoutes( return res.status(400).json({ error: "Invalid passkey response" }); } - const credentials = await db - .select() - .from(webauthnCredentials) - .where(eq(webauthnCredentials.credentialId, response.id)); + const credential = + await createCurrentWebauthnCredentialRepository().findByCredentialId( + response.id, + ); - if (!credentials.length) { + if (!credential) { return res.status(401).json({ error: "Passkey not recognized" }); } - const credential = credentials[0]; if (challenge.userId && challenge.userId !== credential.userId) { return res.status(401).json({ error: "Passkey not recognized" }); } @@ -391,15 +383,13 @@ export function registerUserWebAuthnRoutes( return res.status(401).json({ error: "Passkey authentication failed" }); } - const user = await db - .select() - .from(users) - .where(eq(users.id, credential.userId)); - if (!user.length) { + const userRecord = await createCurrentUserRepository().findById( + credential.userId, + ); + if (!userRecord) { return res.status(404).json({ error: "User not found" }); } - const userRecord = user[0]; const deviceInfo = parseUserAgent(req); const dataUnlocked = await authManager.authenticateWebAuthnUser( userRecord.id, @@ -413,15 +403,15 @@ export function registerUserWebAuthnRoutes( }); } - await db - .update(webauthnCredentials) - .set({ + await createCurrentWebauthnCredentialRepository().updateAuthState( + credential.id, + { counter: verification.authenticationInfo.newCounter, backedUp: verification.authenticationInfo.credentialBackedUp, deviceType: verification.authenticationInfo.credentialDeviceType, lastUsedAt: new Date().toISOString(), - }) - .where(eq(webauthnCredentials.id, credential.id)); + }, + ); if (userRecord.totpEnabled) { const deviceFingerprint = generateDeviceFingerprint(deviceInfo); @@ -431,7 +421,6 @@ export function registerUserWebAuthnRoutes( ); if (!isTrusted) { - await saveMemoryDatabaseToFile(); const tempToken = await authManager.generateJWTToken(userRecord.id, { pendingTOTP: true, expiresIn: "10m", @@ -451,15 +440,9 @@ export function registerUserWebAuthnRoutes( deviceInfo: deviceInfo.deviceInfo, }); - await saveMemoryDatabaseToFile(); - - const timeoutRow = db.$client - .prepare( - "SELECT value FROM settings WHERE key = 'session_timeout_hours'", - ) - .get() as { value: string } | undefined; - const timeoutHours = timeoutRow - ? parseInt(timeoutRow.value, 10) || 24 + const timeoutSetting = getCurrentSettingValue("session_timeout_hours"); + const timeoutHours = timeoutSetting + ? parseInt(timeoutSetting, 10) || 24 : 24; const maxAge = req.body?.rememberMe ? 30 * 24 * 60 * 60 * 1000 @@ -498,15 +481,10 @@ export function registerUserWebAuthnRoutes( const credentialId = String(req.params.credentialId); - await db - .delete(webauthnCredentials) - .where( - and( - eq(webauthnCredentials.id, credentialId), - eq(webauthnCredentials.userId, userId), - ), - ); - await saveMemoryDatabaseToFile(); + await createCurrentWebauthnCredentialRepository().deleteForUser( + userId, + credentialId, + ); res.json({ success: true }); }, diff --git a/src/backend/guacamole/guacamole-server.ts b/src/backend/guacamole/guacamole-server.ts index 27ca03e5..26dd1b73 100644 --- a/src/backend/guacamole/guacamole-server.ts +++ b/src/backend/guacamole/guacamole-server.ts @@ -5,8 +5,7 @@ import { getCurrentSettingValue } from "../database/repositories/factory.js"; import { resolveGuacdOptions } from "../utils/guacd-config.js"; import fs from "fs"; import path from "path"; -import { getDb } from "../database/db/index.js"; -import { sessionRecordings } from "../database/db/schema.js"; +import { createCurrentSessionRecordingRepository } from "../database/repositories/factory.js"; import type { GuacamoleRecordingMetadata } from "./token-service.js"; const tokenService = GuacamoleTokenService.getInstance(); @@ -63,21 +62,19 @@ async function persistGuacamoleRecording( const endedAt = new Date(); const startedAt = new Date(recording.startedAt); - await getDb() - .insert(sessionRecordings) - .values({ - hostId: recording.hostId, - userId: recording.userId, - startedAt: startedAt.toISOString(), - endedAt: endedAt.toISOString(), - duration: Math.max( - 0, - Math.floor((endedAt.getTime() - startedAt.getTime()) / 1000), - ), - recordingPath: resolvedPath, - protocol: recording.protocol, - format: "guacamole", - }); + await createCurrentSessionRecordingRepository().create({ + hostId: recording.hostId, + userId: recording.userId, + startedAt: startedAt.toISOString(), + endedAt: endedAt.toISOString(), + duration: Math.max( + 0, + Math.floor((endedAt.getTime() - startedAt.getTime()) / 1000), + ), + recordingPath: resolvedPath, + protocol: recording.protocol, + format: "guacamole", + }); } const websocketOptions = {