mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 00:31:35 +00:00
- Core Go application with GraphQL API using gqlgen - Comprehensive data models for literary works, authors, translations - Repository pattern with caching layer - Authentication and authorization system - Linguistics analysis capabilities with multiple adapters - Vector search integration with Weaviate - Docker containerization support - Python data migration and analysis scripts - Clean architecture with proper separation of concerns - Production-ready configuration and middleware - Proper .gitignore excluding vendor/, database files, and build artifacts
332 lines
10 KiB
Go
332 lines
10 KiB
Go
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
|
|
}
|