Files
Scriberr/internal/database/database.go
Peter Somlo df5de714c4 fix: make transcription temp and output directories configurable
- Add TempDir field to Config struct to read TEMP_DIR env var
- Update NewUnifiedTranscriptionService to accept tempDir and outputDir parameters
- Remove hardcoded "data/temp" and "data/transcripts" paths from unified service
- Update NewUnifiedJobProcessor to pass directory paths from config
- Update main.go to use cfg.TempDir and cfg.TranscriptsDir
- Update all test files to use new function signatures
- Fix database.go to use directory from DATABASE_PATH instead of hardcoded "data/"
2026-01-07 12:18:26 -08:00

153 lines
4.3 KiB
Go

package database
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"time"
"scriberr/internal/models"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// DB is the global database instance
var DB *gorm.DB
// Initialize initializes the database connection with optimized settings
func Initialize(dbPath string) error {
var err error
// Create database directory if it doesn't exist
dbDir := filepath.Dir(dbPath)
if err := os.MkdirAll(dbDir, 0755); err != nil {
return fmt.Errorf("failed to create database directory: %v", err)
}
// SQLite connection string with performance optimizations
dsn := fmt.Sprintf("%s?"+
"_pragma=foreign_keys(1)&"+ // Enable foreign keys
"_pragma=journal_mode(WAL)&"+ // Use WAL mode for better concurrency
"_pragma=synchronous(NORMAL)&"+ // Balance between safety and performance
"_pragma=cache_size(-64000)&"+ // 64MB cache size
"_pragma=temp_store(MEMORY)&"+ // Store temp tables in memory
"_pragma=mmap_size(268435456)&"+ // 256MB mmap size
"_timeout=30000", // 30 second timeout
dbPath)
// Open database connection with optimized config
DB, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Warn), // Reduce logging overhead
CreateBatchSize: 100, // Optimize batch inserts
})
if err != nil {
return fmt.Errorf("failed to connect to database: %v", err)
}
// Get underlying sql.DB for connection pool configuration
sqlDB, err := DB.DB()
if err != nil {
return fmt.Errorf("failed to get underlying sql.DB: %v", err)
}
// Configure connection pool for optimal performance
sqlDB.SetMaxOpenConns(10) // SQLite generally works well with lower connection counts
sqlDB.SetMaxIdleConns(5) // Keep some connections idle
sqlDB.SetConnMaxLifetime(30 * time.Minute) // Reset connections every 30 minutes
sqlDB.SetConnMaxIdleTime(5 * time.Minute) // Close idle connections after 5 minutes
// Auto migrate the schema
if err := DB.AutoMigrate(
&models.TranscriptionJob{},
&models.TranscriptionJobExecution{},
&models.SpeakerMapping{},
&models.MultiTrackFile{},
&models.User{},
&models.APIKey{},
&models.TranscriptionProfile{},
&models.LLMConfig{},
&models.ChatSession{},
&models.ChatMessage{},
&models.SummaryTemplate{},
&models.SummarySetting{},
&models.Summary{},
&models.Note{},
&models.RefreshToken{},
); err != nil {
return fmt.Errorf("failed to auto migrate: %v", err)
}
// Cleanup duplicate speaker mappings before creating unique index (for backward compatibility)
// Keep the latest mapping for each (job_id, original_speaker) pair
cleanupQuery := `
DELETE FROM speaker_mappings
WHERE id NOT IN (
SELECT MAX(id)
FROM speaker_mappings
GROUP BY transcription_job_id, original_speaker
)
`
if err := DB.Exec(cleanupQuery).Error; err != nil {
// Log warning but continue, as table might not exist yet or query might fail for other reasons
// We don't want to block startup if this fails, but index creation might fail next.
fmt.Printf("Warning: Failed to cleanup duplicate speaker mappings: %v\n", err)
}
// Add unique constraint for speaker mappings (transcription_job_id + original_speaker)
if err := DB.Exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_speaker_mappings_unique ON speaker_mappings(transcription_job_id, original_speaker)").Error; err != nil {
return fmt.Errorf("failed to create unique constraint for speaker mappings: %v", err)
}
return nil
}
// Close closes the database connection gracefully
func Close() error {
if DB == nil {
return nil
}
sqlDB, err := DB.DB()
if err != nil {
return err
}
err = sqlDB.Close()
DB = nil // Set to nil after closing
return err
}
// HealthCheck performs a health check on the database connection
func HealthCheck() error {
if DB == nil {
return fmt.Errorf("database connection is nil")
}
sqlDB, err := DB.DB()
if err != nil {
return fmt.Errorf("failed to get underlying sql.DB: %v", err)
}
// Test the connection with a ping
if err := sqlDB.Ping(); err != nil {
return fmt.Errorf("database ping failed: %v", err)
}
return nil
}
// GetConnectionStats returns database connection pool statistics
func GetConnectionStats() sql.DBStats {
if DB == nil {
return sql.DBStats{}
}
sqlDB, err := DB.DB()
if err != nil {
return sql.DBStats{}
}
return sqlDB.Stats()
}