package db import ( "fmt" "time" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "github.com/imc-vibe/backend/internal/config" ) type DB struct { *gorm.DB } type Domain struct { ID uint `gorm:"primaryKey" json:"id"` Name string `gorm:"uniqueIndex;size:50;not null" json:"name"` CreatedAt time.Time `json:"createdAt"` Users []User `gorm:"foreignKey:DomainID" json:"users,omitempty"` Aliases []Alias `gorm:"foreignKey:DomainID" json:"aliases,omitempty"` } func (Domain) TableName() string { return "virtual_domains" } type User struct { ID uint `gorm:"primaryKey" json:"id"` DomainID uint `gorm:"not null" json:"domainId"` Email string `gorm:"uniqueIndex;size:100;not null" json:"email"` Password string `gorm:"size:150;not null" json:"-"` Quota int64 `gorm:"default:0" json:"quota"` Domain *Domain `gorm:"foreignKey:DomainID" json:"domain,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty"` } func (User) TableName() string { return "virtual_users" } type Alias struct { ID uint `gorm:"primaryKey" json:"id"` DomainID uint `gorm:"not null" json:"domainId"` Source string `gorm:"size:100;not null" json:"source"` Destination string `gorm:"size:100;not null" json:"destination"` Domain *Domain `gorm:"foreignKey:DomainID" json:"domain,omitempty"` } func (Alias) TableName() string { return "virtual_aliases" } type ImcUser struct { ID uint `gorm:"primaryKey" json:"id"` Username string `gorm:"uniqueIndex;size:100;not null" json:"username"` PasswordHash string `gorm:"size:255;not null" json:"-"` Role string `gorm:"type:enum('admin','user');default:'user'" json:"role"` CreatedAt time.Time `json:"createdAt"` Domains []ImcUserDomain `gorm:"foreignKey:UserID" json:"domains,omitempty"` } func (ImcUser) TableName() string { return "imc_users" } type ImcUserDomain struct { ID uint `gorm:"primaryKey" json:"id"` UserID uint `gorm:"not null" json:"userId"` DomainID uint `gorm:"not null" json:"domainId"` CreatedAt time.Time `json:"createdAt"` Domain *Domain `gorm:"foreignKey:DomainID" json:"domain,omitempty"` } func (ImcUserDomain) TableName() string { return "imc_users2domains" } type ImcLoginAttempt struct { ID uint `gorm:"primaryKey" json:"id"` Email string `gorm:"size:100;not null" json:"email"` IPAddress string `gorm:"size:45;not null" json:"ipAddress"` AttemptedAt time.Time `json:"attemptedAt"` Successful bool `gorm:"default:false" json:"successful"` } func (ImcLoginAttempt) TableName() string { return "imc_login_attempts" } type PasswordResetToken struct { ID uint `gorm:"primaryKey" json:"id"` UserID uint `gorm:"not null" json:"userId"` Token string `gorm:"size:64;not null;uniqueIndex" json:"token"` ExpiresAt time.Time `gorm:"not null" json:"expiresAt"` Used bool `gorm:"default:false" json:"used"` CreatedAt time.Time `json:"createdAt"` } func (PasswordResetToken) TableName() string { return "imc_password_reset_tokens" } type DomainStats struct { ID uint `json:"id"` Name string `json:"name"` UserCount int64 `json:"userCount"` AliasCount int64 `json:"aliasCount"` } type AliasWithDomain struct { Alias DomainName string `json:"domainName"` } type UserWithDomain struct { User DomainName string `json:"domainName"` } func Connect(cfg *config.Config) (*DB, error) { dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&charset=utf8mb4", cfg.DBUser, cfg.DBPassword, cfg.DBHost, cfg.DBPort, cfg.DBName) db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { return nil, fmt.Errorf("failed to connect to database: %w", err) } sqlDB, err := db.DB() if err != nil { return nil, err } sqlDB.SetMaxOpenConns(25) sqlDB.SetMaxIdleConns(5) sqlDB.SetConnMaxLifetime(5 * time.Minute) return &DB{db}, nil } func (d *DB) InitSchema() error { imcUsersSQL := ` CREATE TABLE IF NOT EXISTS imc_users ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, username VARCHAR(100) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, role ENUM('admin','user') DEFAULT 'user', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_username (username), INDEX idx_role (role) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ` if err := d.Exec(imcUsersSQL).Error; err != nil { return err } loginAttemptsSQL := ` CREATE TABLE IF NOT EXISTS imc_login_attempts ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, email VARCHAR(100) NOT NULL, ip_address VARCHAR(45) NOT NULL, attempted_at DATETIME DEFAULT CURRENT_TIMESTAMP, successful BOOLEAN DEFAULT FALSE, INDEX idx_email_time (email, attempted_at), INDEX idx_ip_time (ip_address, attempted_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ` if err := d.Exec(loginAttemptsSQL).Error; err != nil { return err } users2DomainsSQL := ` CREATE TABLE IF NOT EXISTS imc_users2domains ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id INT UNSIGNED NOT NULL, domain_id INT UNSIGNED NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY unique_user_domain (user_id, domain_id), INDEX idx_user_id (user_id), INDEX idx_domain_id (domain_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ` if err := d.Exec(users2DomainsSQL).Error; err != nil { return err } resetTokensSQL := ` CREATE TABLE IF NOT EXISTS imc_password_reset_tokens ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id INT UNSIGNED NOT NULL, token VARCHAR(64) NOT NULL UNIQUE, expires_at DATETIME NOT NULL, used BOOLEAN DEFAULT FALSE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_token (token), INDEX idx_user_id (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ` return d.Exec(resetTokensSQL).Error }