small fix, don't worry about it

This commit is contained in:
Tomas Dvorak
2026-04-10 12:06:01 +02:00
parent 954a1a1080
commit c6a99c7e21
214 changed files with 40237 additions and 2828 deletions
+226 -116
View File
@@ -1,8 +1,12 @@
package models
import (
"fmt"
"log"
"github.com/trackeep/backend/config"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// DB is the global database instance
@@ -13,120 +17,226 @@ func InitDB() {
DB = config.GetDB()
}
// AutoMigrate runs database migrations for all models
func AutoMigrate() {
db := config.GetDB()
// Auto migrate all models
db.AutoMigrate(
&User{},
&Tag{},
&Bookmark{},
&Task{},
&File{},
&Note{},
&TimeEntry{},
&FileAnalysis{},
&ChatSession{},
&ChatMessage{},
&LearningPath{},
&LearningModule{},
&ModuleResource{},
&Enrollment{},
&Progress{},
&Course{},
&LearningPathCourse{},
&CalendarEvent{},
&RecurrenceRule{},
&CalendarSettings{},
// Search models
&ContentEmbedding{},
&SavedSearch{},
&SavedSearchTag{},
&SearchAnalytics{},
&SearchSuggestion{},
// AI Feature models
&AISummary{},
&AITaskSuggestion{},
&UserAISettings{},
&UserSearchSettings{},
&UserUpdateSettings{},
&AITagSuggestion{},
&AIContentGeneration{},
&AICodeReview{},
&AILearningRecommendation{},
// Advanced AI Recommendation models
&AIRecommendation{},
&UserPreference{},
&RecommendationInteraction{},
// Integration models
&Integration{},
&SyncLog{},
&WebhookEvent{},
&GitHubAppInstallState{},
&GitHubAppInstallation{},
&GitHubRepoBackup{},
// Analytics models
&Analytics{},
&ProductivityMetrics{},
&LearningAnalytics{},
&ContentAnalytics{},
&GitHubAnalytics{},
&HabitAnalytics{},
&Goal{},
&Milestone{},
&AnalyticsReport{},
// Social features models
&Skill{},
&Project{},
&ProjectTag{},
&SocialLink{},
&Follow{},
// Team workspace models
&Team{},
&TeamMember{},
&TeamInvitation{},
&TeamProject{},
&TeamProjectTag{},
&TeamBookmark{},
&TeamNote{},
&TeamTask{},
&TeamFile{},
&TeamActivity{},
// Security models
&AuditLog{},
// Marketplace models
&MarketplaceItem{},
&MarketplaceTag{},
&MarketplaceReview{},
&MarketplacePurchase{},
&ContentShare{},
// Community models
&Challenge{},
&ChallengeParticipant{},
&ChallengeTeam{},
&ChallengeMilestone{},
&ChallengeMilestoneCompletion{},
&ChallengeResource{},
&ChallengeTag{},
&Mentorship{},
&MentorshipSession{},
&MentorshipReview{},
&MentorshipMilestone{},
&MentorshipRequest{},
// YouTube cache models
&YouTubeChannelCache{},
// Video bookmark models
&VideoBookmark{},
// Messaging models
&Conversation{},
&ConversationMember{},
&Message{},
&MessageAttachment{},
&MessageReference{},
&MessageSuggestion{},
&MessageReaction{},
&PasswordVaultItem{},
&PasswordVaultShare{},
)
func tableHasColumn(db *gorm.DB, tableName, columnName string) (bool, error) {
var count int64
err := db.Raw(
`SELECT count(*)
FROM information_schema.columns
WHERE table_schema = current_schema()
AND table_name = ?
AND column_name = ?`,
tableName,
columnName,
).Scan(&count).Error
return count > 0, err
}
func tableExists(db *gorm.DB, tableName string) (bool, error) {
var count int64
err := db.Raw(
`SELECT count(*)
FROM information_schema.tables
WHERE table_schema = current_schema()
AND table_name = ?`,
tableName,
).Scan(&count).Error
return count > 0, err
}
func repairLegacyBootstrapSchema(db *gorm.DB) error {
if db == nil {
return nil
}
usersTableExists, err := tableExists(db, "users")
if err != nil {
return err
}
if !usersTableExists {
return nil
}
hasLegacyPasswordHash, err := tableHasColumn(db, "users", "password_hash")
if err != nil {
return err
}
if !hasLegacyPasswordHash {
return nil
}
hasCurrentPasswordColumn, err := tableHasColumn(db, "users", "password")
if err != nil {
return err
}
if hasCurrentPasswordColumn {
return nil
}
var userCount int64
if err := db.Table("users").Count(&userCount).Error; err != nil {
return err
}
if userCount > 0 {
return fmt.Errorf("legacy bootstrap schema detected with %d existing users; manual migration is required", userCount)
}
log.Println("Legacy bootstrap schema detected with no users; dropping stale UUID-based tables before auto-migration")
return db.Exec(`DROP TABLE IF EXISTS
file_tags,
note_tags,
task_tags,
bookmark_tags,
audit_logs,
files,
notes,
tasks,
bookmarks,
tags,
users
CASCADE`).Error
}
// AutoMigrate runs database migrations for all models
func AutoMigrate() error {
db := config.GetDB()
if db == nil {
return fmt.Errorf("database not initialized")
}
if err := repairLegacyBootstrapSchema(db); err != nil {
return err
}
// The pgx simple-protocol path used by GORM's PostgreSQL migrator can fail
// schema introspection (`SELECT * ... LIMIT 1`) with "insufficient arguments".
// Running migrations with prepared statements enabled avoids that path and
// allows startup migrations to create missing production tables reliably.
migrationDB := db.Session(&gorm.Session{PrepareStmt: true})
models := []struct {
name string
model interface{}
}{
{name: "User", model: &User{}},
{name: "Tag", model: &Tag{}},
{name: "Bookmark", model: &Bookmark{}},
{name: "Task", model: &Task{}},
{name: "File", model: &File{}},
{name: "Note", model: &Note{}},
{name: "APIKey", model: &APIKey{}},
{name: "BrowserExtension", model: &BrowserExtension{}},
{name: "TimeEntry", model: &TimeEntry{}},
{name: "FileAnalysis", model: &FileAnalysis{}},
{name: "ChatSession", model: &ChatSession{}},
{name: "ChatMessage", model: &ChatMessage{}},
{name: "LearningPath", model: &LearningPath{}},
{name: "LearningModule", model: &LearningModule{}},
{name: "ModuleResource", model: &ModuleResource{}},
{name: "Enrollment", model: &Enrollment{}},
{name: "Progress", model: &Progress{}},
{name: "Course", model: &Course{}},
{name: "LearningPathCourse", model: &LearningPathCourse{}},
{name: "CalendarEvent", model: &CalendarEvent{}},
{name: "RecurrenceRule", model: &RecurrenceRule{}},
{name: "CalendarSettings", model: &CalendarSettings{}},
{name: "ContentEmbedding", model: &ContentEmbedding{}},
{name: "SavedSearch", model: &SavedSearch{}},
{name: "SavedSearchTag", model: &SavedSearchTag{}},
{name: "SearchAnalytics", model: &SearchAnalytics{}},
{name: "SearchSuggestion", model: &SearchSuggestion{}},
{name: "AISummary", model: &AISummary{}},
{name: "AITaskSuggestion", model: &AITaskSuggestion{}},
{name: "UserAISettings", model: &UserAISettings{}},
{name: "UserSearchSettings", model: &UserSearchSettings{}},
{name: "UserUpdateSettings", model: &UserUpdateSettings{}},
{name: "AITagSuggestion", model: &AITagSuggestion{}},
{name: "AIContentGeneration", model: &AIContentGeneration{}},
{name: "AICodeReview", model: &AICodeReview{}},
{name: "AILearningRecommendation", model: &AILearningRecommendation{}},
{name: "AIRecommendation", model: &AIRecommendation{}},
{name: "UserPreference", model: &UserPreference{}},
{name: "RecommendationInteraction", model: &RecommendationInteraction{}},
{name: "Integration", model: &Integration{}},
{name: "SyncLog", model: &SyncLog{}},
{name: "WebhookEvent", model: &WebhookEvent{}},
{name: "ControlServiceSession", model: &ControlServiceSession{}},
{name: "GitHubUserAuth", model: &GitHubUserAuth{}},
{name: "GitHubAppInstallState", model: &GitHubAppInstallState{}},
{name: "GitHubAppInstallation", model: &GitHubAppInstallation{}},
{name: "GitHubRepoBackup", model: &GitHubRepoBackup{}},
{name: "Analytics", model: &Analytics{}},
{name: "ProductivityMetrics", model: &ProductivityMetrics{}},
{name: "LearningAnalytics", model: &LearningAnalytics{}},
{name: "ContentAnalytics", model: &ContentAnalytics{}},
{name: "GitHubAnalytics", model: &GitHubAnalytics{}},
{name: "HabitAnalytics", model: &HabitAnalytics{}},
{name: "Goal", model: &Goal{}},
{name: "Milestone", model: &Milestone{}},
{name: "AnalyticsReport", model: &AnalyticsReport{}},
{name: "Skill", model: &Skill{}},
{name: "Project", model: &Project{}},
{name: "ProjectTag", model: &ProjectTag{}},
{name: "SocialLink", model: &SocialLink{}},
{name: "Follow", model: &Follow{}},
{name: "Team", model: &Team{}},
{name: "TeamMember", model: &TeamMember{}},
{name: "TeamInvitation", model: &TeamInvitation{}},
{name: "TeamProject", model: &TeamProject{}},
{name: "TeamProjectTag", model: &TeamProjectTag{}},
{name: "TeamBookmark", model: &TeamBookmark{}},
{name: "TeamNote", model: &TeamNote{}},
{name: "TeamTask", model: &TeamTask{}},
{name: "TeamFile", model: &TeamFile{}},
{name: "TeamActivity", model: &TeamActivity{}},
{name: "AuditLog", model: &AuditLog{}},
{name: "MarketplaceItem", model: &MarketplaceItem{}},
{name: "MarketplaceTag", model: &MarketplaceTag{}},
{name: "MarketplaceReview", model: &MarketplaceReview{}},
{name: "MarketplacePurchase", model: &MarketplacePurchase{}},
{name: "ContentShare", model: &ContentShare{}},
{name: "Challenge", model: &Challenge{}},
{name: "ChallengeParticipant", model: &ChallengeParticipant{}},
{name: "ChallengeTeam", model: &ChallengeTeam{}},
{name: "ChallengeMilestone", model: &ChallengeMilestone{}},
{name: "ChallengeMilestoneCompletion", model: &ChallengeMilestoneCompletion{}},
{name: "ChallengeResource", model: &ChallengeResource{}},
{name: "ChallengeTag", model: &ChallengeTag{}},
{name: "Mentorship", model: &Mentorship{}},
{name: "MentorshipSession", model: &MentorshipSession{}},
{name: "MentorshipReview", model: &MentorshipReview{}},
{name: "MentorshipMilestone", model: &MentorshipMilestone{}},
{name: "MentorshipRequest", model: &MentorshipRequest{}},
{name: "YouTubeChannelCache", model: &YouTubeChannelCache{}},
{name: "VideoBookmark", model: &VideoBookmark{}},
{name: "Conversation", model: &Conversation{}},
{name: "ConversationMember", model: &ConversationMember{}},
{name: "Message", model: &Message{}},
{name: "MessageAttachment", model: &MessageAttachment{}},
{name: "MessageReference", model: &MessageReference{}},
{name: "MessageSuggestion", model: &MessageSuggestion{}},
{name: "MessageReaction", model: &MessageReaction{}},
{name: "PasswordVaultItem", model: &PasswordVaultItem{}},
{name: "PasswordVaultShare", model: &PasswordVaultShare{}},
}
criticalModels := map[string]bool{
"User": true,
"ControlServiceSession": true,
"GitHubUserAuth": true,
"GitHubAppInstallState": true,
"GitHubAppInstallation": true,
"GitHubRepoBackup": true,
"AuditLog": true,
}
for _, entry := range models {
if err := migrationDB.Omit(clause.Associations).AutoMigrate(entry.model); err != nil {
if criticalModels[entry.name] {
return fmt.Errorf("auto-migrate %s: %w", entry.name, err)
}
log.Printf("Warning: skipping auto-migrate for %s: %v", entry.name, err)
}
}
return nil
}