feat: add app settings table, encryption utility, and generic email service
Some checks failed
Build & Release / build (push) Failing after 35s
Some checks failed
Build & Release / build (push) Failing after 35s
- app_settings table with encrypted field support (AES-256-GCM, key from ENCRYPTION_KEY env) - SettingsService for transparent encrypt/decrypt on get/set - EmailService factory with Resend and SendGrid providers (SMTP stub) — provider config lives in app_settings - Seeds initial admin user and email settings from env vars on first startup if not already present - Migration 0039_app_settings.sql Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
41
packages/backend/src/services/settings.service.ts
Normal file
41
packages/backend/src/services/settings.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { eq } from 'drizzle-orm'
|
||||
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
|
||||
import { appSettings } from '../db/schema/settings.js'
|
||||
import { encrypt, decrypt } from '../utils/encryption.js'
|
||||
|
||||
export const SettingsService = {
|
||||
async get(db: PostgresJsDatabase<any>, key: string): Promise<string | null> {
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(appSettings)
|
||||
.where(eq(appSettings.key, key))
|
||||
.limit(1)
|
||||
|
||||
if (!row || row.value === null) return null
|
||||
if (row.isEncrypted && row.iv) return decrypt(row.value, row.iv)
|
||||
return row.value
|
||||
},
|
||||
|
||||
async set(db: PostgresJsDatabase<any>, key: string, value: string, encrypted = false): Promise<void> {
|
||||
let storedValue = value
|
||||
let iv: string | null = null
|
||||
|
||||
if (encrypted) {
|
||||
const result = encrypt(value)
|
||||
storedValue = result.ciphertext
|
||||
iv = result.iv
|
||||
}
|
||||
|
||||
await db
|
||||
.insert(appSettings)
|
||||
.values({ key, value: storedValue, isEncrypted: encrypted, iv, updatedAt: new Date() })
|
||||
.onConflictDoUpdate({
|
||||
target: appSettings.key,
|
||||
set: { value: storedValue, isEncrypted: encrypted, iv, updatedAt: new Date() },
|
||||
})
|
||||
},
|
||||
|
||||
async delete(db: PostgresJsDatabase<any>, key: string): Promise<void> {
|
||||
await db.delete(appSettings).where(eq(appSettings.key, key))
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user