mirror of
https://github.com/rishikanthc/Scriberr.git
synced 2026-06-30 15:57:01 +00:00
402 lines
11 KiB
Go
402 lines
11 KiB
Go
package database
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"scriberr/internal/models"
|
|
"scriberr/internal/transcription/scheduler"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type migrationStep func(*gorm.DB) error
|
|
|
|
var schemaSteps = map[int]migrationStep{
|
|
2: migrateStepV1ToV2,
|
|
3: migrateStepV2ToV3,
|
|
4: migrateStepV3ToV4,
|
|
5: migrateStepV4ToV5,
|
|
6: migrateStepV5ToV6,
|
|
7: migrateStepV6ToV7,
|
|
8: migrateStepV7ToV8,
|
|
9: migrateStepV8ToV9,
|
|
10: migrateStepV9ToV10,
|
|
11: migrateStepV10ToV11,
|
|
12: migrateStepV11ToV12,
|
|
13: migrateStepV12ToV13,
|
|
}
|
|
|
|
func runSchemaSteps(tx *gorm.DB, currentVersion int) error {
|
|
for version := currentVersion + 1; version <= latestSchemaVersion; version++ {
|
|
step := schemaSteps[version]
|
|
if step != nil {
|
|
if err := step(tx); err != nil {
|
|
return fmt.Errorf("apply schema migration v%d: %w", version, err)
|
|
}
|
|
}
|
|
if err := recordSchemaVersion(tx, version); err != nil {
|
|
return fmt.Errorf("record schema version %d: %w", version, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func migrateStepV1ToV2(tx *gorm.DB) error {
|
|
if err := ensureSingleDefaultPerUser(tx); err != nil {
|
|
return err
|
|
}
|
|
if err := backfillCompatibilityColumns(tx); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func migrateStepV2ToV3(tx *gorm.DB) error {
|
|
return nil
|
|
}
|
|
|
|
func migrateStepV3ToV4(tx *gorm.DB) error {
|
|
return nil
|
|
}
|
|
|
|
func migrateStepV4ToV5(tx *gorm.DB) error {
|
|
return nil
|
|
}
|
|
|
|
func migrateStepV5ToV6(tx *gorm.DB) error {
|
|
return nil
|
|
}
|
|
|
|
func migrateStepV6ToV7(tx *gorm.DB) error {
|
|
return nil
|
|
}
|
|
|
|
func migrateStepV7ToV8(tx *gorm.DB) error {
|
|
return nil
|
|
}
|
|
|
|
func migrateStepV8ToV9(tx *gorm.DB) error {
|
|
if err := tx.AutoMigrate(&models.RecordingSession{}); err != nil {
|
|
return err
|
|
}
|
|
return tx.Exec(`CREATE INDEX IF NOT EXISTS idx_recording_sessions_artifact_cleanup ON recording_sessions(status, temporary_artifacts_cleaned_at)`).Error
|
|
}
|
|
|
|
func migrateStepV9ToV10(tx *gorm.DB) error {
|
|
if err := tx.AutoMigrate(&models.UserSettings{}); err != nil {
|
|
return err
|
|
}
|
|
return backfillUserSettings(tx)
|
|
}
|
|
|
|
func migrateStepV10ToV11(tx *gorm.DB) error {
|
|
if err := tx.AutoMigrate(&models.SystemSetting{}); err != nil {
|
|
return err
|
|
}
|
|
return backfillSystemSettings(tx)
|
|
}
|
|
|
|
func migrateStepV11ToV12(tx *gorm.DB) error {
|
|
statements := []string{
|
|
`CREATE INDEX IF NOT EXISTS idx_transcriptions_queue_fifo ON transcriptions(status, queued_at, created_at, id)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_transcriptions_queue_user_status ON transcriptions(user_id, status, queued_at)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_transcriptions_queue_duration ON transcriptions(status, source_duration_ms, queued_at, created_at, id)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_transcriptions_user_status_updated ON transcriptions(user_id, status, updated_at DESC, id)`,
|
|
}
|
|
for _, statement := range statements {
|
|
if err := tx.Exec(statement).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func migrateStepV12ToV13(tx *gorm.DB) error {
|
|
if err := tx.AutoMigrate(&models.UserSettings{}); err != nil {
|
|
return err
|
|
}
|
|
if err := backfillUserSettings(tx); err != nil {
|
|
return err
|
|
}
|
|
statements := []string{
|
|
`CREATE INDEX IF NOT EXISTS idx_transcriptions_user_source_created ON transcriptions(user_id, source_file_hash, created_at DESC, id)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_transcriptions_user_files_created ON transcriptions(user_id, created_at DESC, id DESC) WHERE source_file_hash IS NOT NULL AND deleted_at IS NULL`,
|
|
}
|
|
for _, statement := range statements {
|
|
if err := tx.Exec(statement).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func backfillCompatibilityColumns(tx *gorm.DB) error {
|
|
if err := backfillUsers(tx); err != nil {
|
|
return err
|
|
}
|
|
if err := backfillAPIKeys(tx); err != nil {
|
|
return err
|
|
}
|
|
if err := backfillTranscriptionProfiles(tx); err != nil {
|
|
return err
|
|
}
|
|
if err := backfillTranscriptionJobs(tx); err != nil {
|
|
return err
|
|
}
|
|
if err := backfillTranscriptionExecutions(tx); err != nil {
|
|
return err
|
|
}
|
|
if err := backfillSummaryTemplates(tx); err != nil {
|
|
return err
|
|
}
|
|
if err := backfillLLMConfigs(tx); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func backfillUsers(tx *gorm.DB) error {
|
|
var rows []models.User
|
|
if err := tx.Find(&rows).Error; err != nil {
|
|
return err
|
|
}
|
|
for _, row := range rows {
|
|
if err := row.BeforeSave(tx); err != nil {
|
|
return err
|
|
}
|
|
updates := map[string]any{
|
|
"settings_json": row.SettingsJSON,
|
|
}
|
|
if err := withPreservedUpdatedAt(tx.Model(&models.User{}).Where("id = ?", row.ID), updates, row.UpdatedAt).
|
|
Updates(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := backfillUserSettings(tx); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func backfillUserSettings(tx *gorm.DB) error {
|
|
var users []models.User
|
|
if err := tx.Find(&users).Error; err != nil {
|
|
return err
|
|
}
|
|
for _, user := range users {
|
|
settings := models.UserSettings{
|
|
UserID: user.ID,
|
|
DefaultProfileID: user.DefaultProfileID,
|
|
AutoTranscriptionEnabled: user.AutoTranscriptionEnabled,
|
|
AutoRenameEnabled: user.AutoRenameEnabled,
|
|
SummaryDefaultModel: user.SummaryDefaultModel,
|
|
}
|
|
if settings.UserID == 0 {
|
|
continue
|
|
}
|
|
if !settings.AutoTranscriptionEnabled && user.SettingsJSON == "" {
|
|
settings.AutoTranscriptionEnabled = true
|
|
}
|
|
if !settings.AutoRenameEnabled && user.SettingsJSON == "" {
|
|
settings.AutoRenameEnabled = true
|
|
}
|
|
if err := tx.Where("user_id = ?", settings.UserID).Assign(settings).FirstOrCreate(&settings).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func backfillSystemSettings(tx *gorm.DB) error {
|
|
raw, err := scheduler.Marshal(scheduler.DefaultConfig())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
setting := models.SystemSetting{
|
|
Key: scheduler.SettingKey,
|
|
ValueJSON: raw,
|
|
}
|
|
return tx.Where("key = ?", setting.Key).FirstOrCreate(&setting).Error
|
|
}
|
|
|
|
func backfillAPIKeys(tx *gorm.DB) error {
|
|
var rows []models.APIKey
|
|
if err := tx.Find(&rows).Error; err != nil {
|
|
return err
|
|
}
|
|
for _, row := range rows {
|
|
if err := row.BeforeSave(tx); err != nil {
|
|
return err
|
|
}
|
|
updates := map[string]any{
|
|
"key_hash": row.KeyHash,
|
|
"key_prefix": row.KeyPrefix,
|
|
"metadata_json": row.MetadataJSON,
|
|
}
|
|
if err := withPreservedUpdatedAt(tx.Model(&models.APIKey{}).Where("id = ?", row.ID), updates, time.Time{}).
|
|
Updates(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func backfillTranscriptionProfiles(tx *gorm.DB) error {
|
|
var rows []models.TranscriptionProfile
|
|
if err := tx.Find(&rows).Error; err != nil {
|
|
return err
|
|
}
|
|
for _, row := range rows {
|
|
if err := row.BeforeSave(tx); err != nil {
|
|
return err
|
|
}
|
|
updates := map[string]any{
|
|
"user_id": row.UserID,
|
|
"model_name": row.ModelName,
|
|
"model_family": row.ModelFamily,
|
|
"language": row.Language,
|
|
"diarization_enabled": row.DiarizationEnabled,
|
|
"device": row.Device,
|
|
"compute_type": row.ComputeType,
|
|
"config_json": row.ConfigJSON,
|
|
"is_default": row.IsDefault,
|
|
}
|
|
if err := withPreservedUpdatedAt(tx.Model(&models.TranscriptionProfile{}).Where("id = ?", row.ID), updates, row.UpdatedAt).
|
|
Updates(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func backfillTranscriptionJobs(tx *gorm.DB) error {
|
|
var rows []models.TranscriptionJob
|
|
if err := tx.Find(&rows).Error; err != nil {
|
|
return err
|
|
}
|
|
for _, row := range rows {
|
|
if err := row.SyncColumnsForMigration(); err != nil {
|
|
return err
|
|
}
|
|
updates := map[string]any{
|
|
"user_id": row.UserID,
|
|
"source_file_path": row.AudioPath,
|
|
"source_file_name": row.SourceFileName,
|
|
"source_file_hash": row.SourceFileHash,
|
|
"source_duration_ms": row.SourceDurationMs,
|
|
"language": row.Language,
|
|
"transcript_text": row.Transcript,
|
|
"output_json_path": row.OutputJSONPath,
|
|
"output_srt_path": row.OutputSRTPath,
|
|
"output_vtt_path": row.OutputVTTPath,
|
|
"latest_execution_id": row.LatestExecutionID,
|
|
"last_error": row.ErrorMessage,
|
|
"metadata_json": row.MetadataJSON,
|
|
"completed_at": row.CompletedAt,
|
|
"status": row.Status,
|
|
"title": row.Title,
|
|
}
|
|
if err := withPreservedUpdatedAt(tx.Model(&models.TranscriptionJob{}).Where("id = ?", row.ID), updates, row.UpdatedAt).
|
|
Updates(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func backfillTranscriptionExecutions(tx *gorm.DB) error {
|
|
var rows []models.TranscriptionJobExecution
|
|
if err := tx.Find(&rows).Error; err != nil {
|
|
return err
|
|
}
|
|
for _, row := range rows {
|
|
if err := row.SyncColumnsForMigration(); err != nil {
|
|
return err
|
|
}
|
|
updates := map[string]any{
|
|
"user_id": row.UserID,
|
|
"execution_number": row.ExecutionNumber,
|
|
"trigger_type": row.TriggerType,
|
|
"status": row.Status,
|
|
"profile_id": row.ProfileID,
|
|
"model_name": row.ModelName,
|
|
"model_family": row.ModelFamily,
|
|
"provider": row.Provider,
|
|
"device": row.Device,
|
|
"compute_type": row.ComputeType,
|
|
"request_json": row.RequestJSON,
|
|
"config_json": row.ConfigJSON,
|
|
"started_at": row.StartedAt,
|
|
"completed_at": row.CompletedAt,
|
|
"failed_at": row.FailedAt,
|
|
"error_message": row.ErrorMessage,
|
|
"logs_path": row.LogsPath,
|
|
"output_json_path": row.OutputJSONPath,
|
|
}
|
|
if err := withPreservedUpdatedAt(tx.Model(&models.TranscriptionJobExecution{}).Where("id = ?", row.ID), updates, time.Time{}).
|
|
Updates(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func backfillSummaryTemplates(tx *gorm.DB) error {
|
|
var rows []models.SummaryTemplate
|
|
if err := tx.Find(&rows).Error; err != nil {
|
|
return err
|
|
}
|
|
for _, row := range rows {
|
|
if err := row.BeforeSave(tx); err != nil {
|
|
return err
|
|
}
|
|
updates := map[string]any{
|
|
"user_id": row.UserID,
|
|
"name": row.Name,
|
|
"prompt": row.Prompt,
|
|
"description": row.Description,
|
|
"config_json": row.ConfigJSON,
|
|
"is_default": row.IsDefault,
|
|
}
|
|
if err := withPreservedUpdatedAt(tx.Model(&models.SummaryTemplate{}).Where("id = ?", row.ID), updates, row.UpdatedAt).
|
|
Updates(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func backfillLLMConfigs(tx *gorm.DB) error {
|
|
var rows []models.LLMConfig
|
|
if err := tx.Find(&rows).Error; err != nil {
|
|
return err
|
|
}
|
|
for _, row := range rows {
|
|
if err := row.BeforeSave(tx); err != nil {
|
|
return err
|
|
}
|
|
updates := map[string]any{
|
|
"user_id": row.UserID,
|
|
"name": row.Name,
|
|
"provider": row.Provider,
|
|
"model_name": row.ModelName,
|
|
"base_url": row.BaseURL,
|
|
"config_json": row.ConfigJSON,
|
|
"is_default": row.IsDefault,
|
|
}
|
|
if err := withPreservedUpdatedAt(tx.Model(&models.LLMConfig{}).Where("id = ?", row.ID), updates, row.UpdatedAt).
|
|
Updates(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func withPreservedUpdatedAt(db *gorm.DB, updates map[string]any, updatedAt time.Time) *gorm.DB {
|
|
if !updatedAt.IsZero() {
|
|
updates["updated_at"] = updatedAt
|
|
}
|
|
return db.Session(&gorm.Session{SkipHooks: true}).Set("gorm:update_track_time", false)
|
|
}
|