package db import ( "gorm.io/gorm" "tercul/logger" "tercul/models" ) // RunMigrations runs all database migrations func RunMigrations(db *gorm.DB) error { logger.LogInfo("Running database migrations") // First, create all tables using GORM AutoMigrate if err := createTables(db); err != nil { logger.LogError("Failed to create tables", logger.F("error", err)) return err } // Then add indexes to improve query performance if err := addIndexes(db); err != nil { logger.LogError("Failed to add indexes", logger.F("error", err)) return err } logger.LogInfo("Database migrations completed successfully") return nil } // createTables creates all database tables using GORM AutoMigrate func createTables(db *gorm.DB) error { logger.LogInfo("Creating database tables") // Enable recommended extensions if err := db.Exec("CREATE EXTENSION IF NOT EXISTS pg_trgm").Error; err != nil { logger.LogError("Failed to enable pg_trgm extension", logger.F("error", err)) return err } // Create all models/tables err := db.AutoMigrate( // User-related models &models.User{}, &models.UserProfile{}, &models.UserSession{}, &models.PasswordReset{}, &models.EmailVerification{}, // Literary models &models.Work{}, &models.Translation{}, &models.Author{}, &models.Book{}, &models.Publisher{}, &models.Source{}, &models.Edition{}, &models.Series{}, &models.WorkSeries{}, // Organization models &models.Tag{}, &models.Category{}, // Interaction models &models.Comment{}, &models.Like{}, &models.Bookmark{}, &models.Collection{}, &models.Contribution{}, &models.InteractionEvent{}, // Location models &models.Country{}, &models.City{}, &models.Place{}, &models.Address{}, &models.Language{}, // Linguistic models &models.ReadabilityScore{}, &models.WritingStyle{}, &models.LinguisticLayer{}, &models.TextMetadata{}, &models.PoeticAnalysis{}, &models.Word{}, &models.Concept{}, &models.LanguageEntity{}, &models.TextBlock{}, &models.WordOccurrence{}, &models.EntityOccurrence{}, // Relationship models &models.Edge{}, &models.Embedding{}, &models.Media{}, &models.BookWork{}, &models.AuthorCountry{}, &models.WorkAuthor{}, &models.BookAuthor{}, // System models &models.Notification{}, &models.EditorialWorkflow{}, &models.Admin{}, &models.Vote{}, &models.Contributor{}, &models.HybridEntityWork{}, &models.ModerationFlag{}, &models.AuditLog{}, // Rights models &models.Copyright{}, &models.CopyrightClaim{}, &models.Monetization{}, &models.License{}, // Analytics models &models.WorkStats{}, &models.TranslationStats{}, &models.UserStats{}, &models.BookStats{}, &models.CollectionStats{}, &models.MediaStats{}, // Metadata models &models.LanguageAnalysis{}, &models.Gamification{}, &models.Stats{}, &models.SearchDocument{}, // Psychological models &models.Emotion{}, &models.Mood{}, &models.TopicCluster{}, ) if err != nil { return err } logger.LogInfo("Database tables created successfully") return nil } // addIndexes adds indexes to frequently queried columns func addIndexes(db *gorm.DB) error { // Work table indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_works_language ON works(language)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_works_title ON works(title)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_works_status ON works(status)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_works_slug ON works(slug)").Error; err != nil { return err } // Translation table indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_translations_work_id ON translations(work_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_translations_language ON translations(language)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_translations_translator_id ON translations(translator_id)").Error; err != nil { return err } if err := db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS ux_translations_entity_lang ON translations(translatable_type, translatable_id, language)").Error; err != nil { return err } // User table indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_users_role ON users(role)").Error; err != nil { return err } // Author table indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_authors_name ON authors(name)").Error; err != nil { return err } // Category table indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_categories_name ON categories(name)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_categories_slug ON categories(slug)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_categories_path ON categories(path)").Error; err != nil { return err } // Tag table indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_tags_name ON tags(name)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_tags_slug ON tags(slug)").Error; err != nil { return err } // Comment table indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_comments_user_id ON comments(user_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_comments_work_id ON comments(work_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_comments_translation_id ON comments(translation_id)").Error; err != nil { return err } // Like table indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_likes_user_id ON likes(user_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_likes_work_id ON likes(work_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_likes_translation_id ON likes(translation_id)").Error; err != nil { return err } // Bookmark table indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_bookmarks_user_id ON bookmarks(user_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_bookmarks_work_id ON bookmarks(work_id)").Error; err != nil { return err } // Collection table indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_collections_user_id ON collections(user_id)").Error; err != nil { return err } // Contribution table indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_contributions_user_id ON contributions(user_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_contributions_work_id ON contributions(work_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_contributions_status ON contributions(status)").Error; err != nil { return err } // Edge table indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_edges_source_table_id ON edges(source_table, source_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_edges_target_table_id ON edges(target_table, target_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_edges_relation ON edges(relation)").Error; err != nil { return err } // WorkAuthor unique pair and order index if err := db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS ux_work_authors_pair ON work_authors(work_id, author_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_work_authors_ordinal ON work_authors(ordinal)").Error; err != nil { return err } // BookAuthor unique pair and order index if err := db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS ux_book_authors_pair ON book_authors(book_id, author_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_book_authors_ordinal ON book_authors(ordinal)").Error; err != nil { return err } // InteractionEvent indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_interaction_events_target ON interaction_events(target_type, target_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_interaction_events_kind ON interaction_events(kind)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_interaction_events_user ON interaction_events(user_id)").Error; err != nil { return err } // SearchDocument indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_search_documents_entity ON search_documents(entity_type, entity_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_search_documents_lang ON search_documents(language_code)").Error; err != nil { return err } // Linguistic analysis indexes if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_text_metadata_work_id ON text_metadata(work_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_readability_scores_work_id ON readability_scores(work_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_language_analyses_work_id ON language_analyses(work_id)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_poetic_analyses_work_id ON poetic_analyses(work_id)").Error; err != nil { return err } // Timestamps indexes for frequently queried tables if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_works_created_at ON works(created_at)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_translations_created_at ON translations(created_at)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_comments_created_at ON comments(created_at)").Error; err != nil { return err } if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at)").Error; err != nil { return err } logger.LogInfo("Database indexes added successfully") return nil }