feat: migrate store layer to GORM with PostgreSQL support

- Migrate all store packages from raw database/sql to GORM ORM
- Add PostgreSQL support alongside SQLite
- Move EncryptedString type to crypto package for cleaner architecture
- Add automatic encryption/decryption for sensitive fields (API keys, secrets)
- Fix PostgreSQL AutoMigrate conflicts by skipping existing tables
- Fix duplicate /klines route registration
- Update tests to use GORM database connections
- Add database configuration support in config package
This commit is contained in:
tinkle-community
2026-01-01 19:32:49 +08:00
parent d547863ebb
commit 2d272bb7b8
32 changed files with 2573 additions and 3771 deletions

76
main.go
View File

@@ -36,30 +36,44 @@ func main() {
cfg := config.Get()
logger.Info("✅ Configuration loaded")
// Initialize database from environment variables
// DB_TYPE: sqlite (default) or postgres
// For SQLite: DB_PATH (default: data/data.db)
// For PostgreSQL: DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME, DB_SSLMODE
dbPath := os.Getenv("DB_PATH")
if dbPath == "" {
dbPath = "data/data.db"
// Initialize encryption service BEFORE database (so EncryptedString can decrypt on read)
logger.Info("🔐 Initializing encryption service...")
cryptoService, err := crypto.NewCryptoService()
if err != nil {
logger.Fatalf("❌ Failed to initialize encryption service: %v", err)
}
// For backward compatibility: command line arg overrides env var (SQLite only)
crypto.SetGlobalCryptoService(cryptoService)
logger.Info("✅ Encryption service initialized successfully")
// Initialize database from configuration
// For backward compatibility: command line arg overrides config (SQLite only)
if len(os.Args) > 1 {
dbPath = os.Args[1]
os.Setenv("DB_PATH", dbPath)
cfg.DBPath = os.Args[1]
}
// Ensure data directory exists (for SQLite)
if os.Getenv("DB_TYPE") == "" || os.Getenv("DB_TYPE") == "sqlite" {
if dir := filepath.Dir(dbPath); dir != "." {
if cfg.DBType == "sqlite" {
if dir := filepath.Dir(cfg.DBPath); dir != "." {
if err := os.MkdirAll(dir, 0755); err != nil {
logger.Errorf("Failed to create data directory: %v", err)
}
}
}
logger.Info("📋 Initializing database...")
st, err := store.NewFromEnv()
logger.Infof("📋 Initializing database (%s)...", cfg.DBType)
dbType := store.DBTypeSQLite
if cfg.DBType == "postgres" {
dbType = store.DBTypePostgres
}
st, err := store.NewWithConfig(store.DBConfig{
Type: dbType,
Path: cfg.DBPath,
Host: cfg.DBHost,
Port: cfg.DBPort,
User: cfg.DBUser,
Password: cfg.DBPassword,
DBName: cfg.DBName,
SSLMode: cfg.DBSSLMode,
})
if err != nil {
logger.Fatalf("❌ Failed to initialize database: %v", err)
}
@@ -69,40 +83,6 @@ func main() {
// Initialize installation ID for experience improvement (anonymous statistics)
initInstallationID(st)
// Initialize encryption service
logger.Info("🔐 Initializing encryption service...")
cryptoService, err := crypto.NewCryptoService()
if err != nil {
logger.Fatalf("❌ Failed to initialize encryption service: %v", err)
}
encryptFunc := func(plaintext string) string {
if plaintext == "" {
return plaintext
}
encrypted, err := cryptoService.EncryptForStorage(plaintext)
if err != nil {
logger.Warnf("⚠️ Encryption failed: %v", err)
return plaintext
}
return encrypted
}
decryptFunc := func(encrypted string) string {
if encrypted == "" {
return encrypted
}
if !cryptoService.IsEncryptedStorageValue(encrypted) {
return encrypted
}
decrypted, err := cryptoService.DecryptFromStorage(encrypted)
if err != nil {
logger.Warnf("⚠️ Decryption failed: %v", err)
return encrypted
}
return decrypted
}
st.SetCryptoFuncs(encryptFunc, decryptFunc)
logger.Info("✅ Encryption service initialized successfully")
// Set JWT secret
auth.SetJWTSecret(cfg.JWTSecret)
logger.Info("🔑 JWT secret configured")