fix(api): resolve Docker startup failures (secrets, Redis, Prisma)

- Pass BETTER_AUTH_SECRET through all 6 docker-compose files to API container
- Fix BullModule to parse VALKEY_URL instead of VALKEY_HOST/VALKEY_PORT,
  matching all other Redis consumers in the codebase
- Migrate Prisma encryption from removed $use() middleware to $extends()
  client extensions (Prisma 6.x compatibility), keeping extends PrismaClient
  pattern with only account and llmProviderInstance getters overridden

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 11:04:04 -06:00
parent 7b892d5197
commit 0ca3945061
11 changed files with 314 additions and 17 deletions

View File

@@ -1,8 +1,8 @@
import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from "@nestjs/common";
import { PrismaClient } from "@prisma/client";
import { VaultService } from "../vault/vault.service";
import { registerAccountEncryptionMiddleware } from "./account-encryption.middleware";
import { registerLlmEncryptionMiddleware } from "./llm-encryption.middleware";
import { createAccountEncryptionExtension } from "./account-encryption.extension";
import { createLlmEncryptionExtension } from "./llm-encryption.extension";
/**
* Prisma service that manages database connection lifecycle
@@ -11,11 +11,20 @@ import { registerLlmEncryptionMiddleware } from "./llm-encryption.middleware";
* IMPORTANT: VaultService is required (not optional) for encryption/decryption
* of sensitive Account tokens. It automatically falls back to AES-256-GCM when
* OpenBao is unavailable.
*
* Encryption is handled via Prisma Client Extensions ($extends) on the `account`
* and `llmProviderInstance` models. The extended client is stored in `_xClient`
* and only those two model getters are overridden — all other models use the
* base PrismaClient inheritance, preserving full type safety.
*/
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
private readonly logger = new Logger(PrismaService.name);
// Extended client with encryption hooks for account + llmProviderInstance
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _xClient: any = null;
constructor(private readonly vaultService: VaultService) {
super({
log: process.env.NODE_ENV === "development" ? ["query", "info", "warn", "error"] : ["error"],
@@ -30,20 +39,30 @@ export class PrismaService extends PrismaClient implements OnModuleInit, OnModul
await this.$connect();
this.logger.log("Database connection established");
// Register Account token encryption middleware
// VaultService provides OpenBao Transit encryption with AES-256-GCM fallback
registerAccountEncryptionMiddleware(this, this.vaultService);
this.logger.log("Account encryption middleware registered");
// Register LLM provider API key encryption middleware
registerLlmEncryptionMiddleware(this, this.vaultService);
this.logger.log("LLM encryption middleware registered");
// Register encryption extensions (replaces removed $use() middleware)
this._xClient = this.$extends(createAccountEncryptionExtension(this.vaultService)).$extends(
createLlmEncryptionExtension(this.vaultService)
);
this.logger.log("Encryption extensions registered (Account, LlmProviderInstance)");
} catch (error) {
this.logger.error("Failed to connect to database", error);
throw error;
}
}
// Override only the 2 models that need encryption hooks.
// All other models (user, task, workspace, etc.) use base PrismaClient via inheritance.
// Cast _xClient to PrismaClient to preserve the accessor return types for consumers.
override get account() {
return this._xClient ? (this._xClient as PrismaClient).account : super.account;
}
override get llmProviderInstance() {
if (this._xClient) return (this._xClient as PrismaClient).llmProviderInstance;
return super.llmProviderInstance;
}
/**
* Disconnect from database when NestJS module is destroyed
*/