Files
Termix/src/backend/utils/simple-db-ops.ts

183 lines
4.7 KiB
TypeScript

import { getDb, DatabaseSaveTrigger } from "../database/db/index.js";
import { DataCrypto } from "./data-crypto.js";
import { databaseLogger } from "./logger.js";
import type { SQLiteTable } from "drizzle-orm/sqlite-core";
type TableName = "users" | "ssh_data" | "ssh_credentials";
/**
* SimpleDBOps - Simplified encrypted database operations
*
* Linus-style simplification:
* - Remove all complex abstraction layers
* - Direct CRUD operations
* - Automatic encryption/decryption
* - No special case handling
*/
class SimpleDBOps {
/**
* Insert encrypted record
*/
static async insert<T extends Record<string, any>>(
table: SQLiteTable<any>,
tableName: TableName,
data: T,
userId: string,
): Promise<T> {
// Get user data key once and reuse throughout operation
const userDataKey = DataCrypto.validateUserAccess(userId);
// Generate consistent temporary ID for encryption context if record has no ID
const tempId = data.id || `temp-${userId}-${Date.now()}`;
const dataWithTempId = { ...data, id: tempId };
// Encrypt data using the locked key - recordId will be stored in encrypted fields
const encryptedData = DataCrypto.encryptRecord(tableName, dataWithTempId, userId, userDataKey);
// Remove temp ID if it was generated, let database assign real ID
if (!data.id) {
delete encryptedData.id;
}
// Insert into database
const result = await getDb().insert(table).values(encryptedData).returning();
// Trigger database save after insert
DatabaseSaveTrigger.triggerSave(`insert_${tableName}`);
// Decrypt return result using the same key - FieldCrypto will use stored recordId
const decryptedResult = DataCrypto.decryptRecord(
tableName,
result[0],
userId,
userDataKey
);
return decryptedResult as T;
}
/**
* Query multiple records
*/
static async select<T extends Record<string, any>>(
query: any,
tableName: TableName,
userId: string,
): Promise<T[]> {
// Get user data key once and reuse throughout operation
const userDataKey = DataCrypto.validateUserAccess(userId);
// Execute query
const results = await query;
// Decrypt results using locked key
const decryptedResults = DataCrypto.decryptRecords(
tableName,
results,
userId,
userDataKey
);
return decryptedResults;
}
/**
* Query single record
*/
static async selectOne<T extends Record<string, any>>(
query: any,
tableName: TableName,
userId: string,
): Promise<T | undefined> {
// Get user data key once and reuse throughout operation
const userDataKey = DataCrypto.validateUserAccess(userId);
// Execute query
const result = await query;
if (!result) return undefined;
// Decrypt results using locked key
const decryptedResult = DataCrypto.decryptRecord(
tableName,
result,
userId,
userDataKey
);
return decryptedResult;
}
/**
* Update record
*/
static async update<T extends Record<string, any>>(
table: SQLiteTable<any>,
tableName: TableName,
where: any,
data: Partial<T>,
userId: string,
): Promise<T[]> {
// Get user data key once and reuse throughout operation
const userDataKey = DataCrypto.validateUserAccess(userId);
// Encrypt update data using the locked key
const encryptedData = DataCrypto.encryptRecord(tableName, data, userId, userDataKey);
// Execute update
const result = await getDb()
.update(table)
.set(encryptedData)
.where(where)
.returning();
// Trigger database save after update
DatabaseSaveTrigger.triggerSave(`update_${tableName}`);
// Decrypt return data using the same key
const decryptedResults = DataCrypto.decryptRecords(
tableName,
result,
userId,
userDataKey
);
return decryptedResults as T[];
}
/**
* Delete record
*/
static async delete(
table: SQLiteTable<any>,
tableName: TableName,
where: any,
userId: string,
): Promise<any[]> {
const result = await getDb().delete(table).where(where).returning();
// Trigger database save after delete
DatabaseSaveTrigger.triggerSave(`delete_${tableName}`);
return result;
}
/**
* Health check
*/
static async healthCheck(userId: string): Promise<boolean> {
return DataCrypto.canUserAccessData(userId);
}
/**
* Special method: return encrypted data (for auto-start scenarios)
* No decryption, return data in encrypted state directly
*/
static async selectEncrypted(query: any, tableName: TableName): Promise<any[]> {
// Execute query directly, no decryption
const results = await query;
return results;
}
}
export { SimpleDBOps, type TableName };