Migrate GORM to generics API, simplify user-domain relationship, refactor models

This commit is contained in:
Christoph Haas 2026-03-28 17:21:00 +01:00
parent 19ce2224b8
commit 560b40503a
9 changed files with 192 additions and 117 deletions

View file

@ -80,8 +80,10 @@ type ImcUser struct {
Username string `gorm:"uniqueIndex;size:100;not null" json:"username"` // Login username
PasswordHash string `gorm:"size:255;not null" json:"-"` // Bcrypt hash of password (- means exclude from JSON)
Role string `gorm:"type:enum('admin','user');default:'user'" json:"role"` // Role: admin or regular user
DomainID uint `gorm:"not null" json:"domainId"` // Foreign key to VirtualDomain (primary domain this user manages)
Domain *VirtualDomain `gorm:"foreignKey:DomainID" json:"domain,omitempty"` // Associated domain
CreatedAt time.Time `json:"createdAt"` // When account was created
Domains []ImcUserDomain `gorm:"foreignKey:UserID" json:"domains,omitempty"` // Domains this user can access
Domains []ImcUserDomain `gorm:"foreignKey:UserID" json:"domains,omitempty"` // Additional domains this user can access (many-to-many)
}
// TableName specifies the database table name for ImcUser.
@ -104,7 +106,7 @@ func (ImcUserDomain) TableName() string { return "imc_users2domains" }
// Used to track failed login attempts and implement rate limiting.
type ImcLoginAttempt struct {
ID uint `gorm:"primaryKey" json:"id"` // Primary key
Email string `gorm:"size:100;not null" json:"email"` // Email/username that was used
Username string `gorm:"size:100;not null" json:"username"` // Username that was used
IPAddress string `gorm:"size:45;not null" json:"ipAddress"` // IP address of the requester (IPv6 compatible)
AttemptedAt time.Time `json:"attemptedAt"` // When the attempt occurred
Successful bool `gorm:"default:false" json:"successful"` // Whether the login succeeded
@ -132,12 +134,6 @@ type AliasWithDomain struct {
DomainName string `json:"domainName"` // Denormalized domain name for convenience
}
// UserWithDomain combines a user with their domain name for API responses.
type UserWithDomain struct {
VirtualUser // Embed VirtualUser struct
DomainName string `json:"domainName"` // Denormalized domain name for convenience
}
// =============================================================================
// Database Connection
// =============================================================================
@ -196,15 +192,18 @@ func Connect(cfg *config.Config) (*DB, error) {
func (d *DB) InitSchema() error {
// Create imc_users table for admin accounts.
// This table stores admin users who can log into the web interface.
// Each user is associated with one domain they can manage.
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',
domain_id INT UNSIGNED NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_username (username),
INDEX idx_role (role)
INDEX idx_role (role),
INDEX idx_domain_id (domain_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`
if err := d.Exec(imcUsersSQL).Error; err != nil {
@ -216,11 +215,11 @@ func (d *DB) InitSchema() error {
loginAttemptsSQL := `
CREATE TABLE IF NOT EXISTS imc_login_attempts (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(100) NOT NULL,
username 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_username_time (username, attempted_at),
INDEX idx_ip_time (ip_address, attempted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`
@ -228,8 +227,8 @@ func (d *DB) InitSchema() error {
return err
}
// Create imc_users2domains table for user-domain permissions.
// This implements many-to-many: a user can access multiple domains.
// Create imc_users2domains table for additional user-domain permissions (many-to-many).
// A user can have access to multiple domains beyond their primary domain.
users2DomainsSQL := `
CREATE TABLE IF NOT EXISTS imc_users2domains (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,