Rename ISPmail models to use Virtual* prefix
- Domain -> VirtualDomain - User -> VirtualUser - Alias -> VirtualAlias This clarifies which tables are ISPmail tables vs IMC tables.
This commit is contained in:
parent
93922e4cad
commit
d5880d5096
8 changed files with 87 additions and 96 deletions
13
Makefile
13
Makefile
|
|
@ -32,18 +32,9 @@ build-backend: build-frontend
|
|||
# Development targets
|
||||
dev:
|
||||
@echo "Starting backend dev server (hot-reload enabled)..."
|
||||
cd $(BACKEND_DIR) && USE_EMBEDDED=false $(HOME)/go/bin/air > /tmp/imc-backend.log 2>&1 &
|
||||
@echo "Backend started, PID: $$(pgrep -f air)"
|
||||
sleep 3
|
||||
cd $(BACKEND_DIR) && USE_EMBEDDED=false $(HOME)/go/bin/air
|
||||
@echo "Starting frontend dev server..."
|
||||
cd $(FRONTEND_DIR) && bun run dev > /tmp/imc-frontend.log 2>&1 &
|
||||
@echo "Frontend started, PID: $$(pgrep -f 'vite')"
|
||||
@echo ""
|
||||
@echo "Logs:"
|
||||
@echo " Backend: tail -f /tmp/imc-backend.log"
|
||||
@echo " Frontend: tail -f /tmp/imc-frontend.log"
|
||||
@echo ""
|
||||
@echo "Stop with: pkill -f 'air|vite'"
|
||||
cd $(FRONTEND_DIR) && bun run dev
|
||||
|
||||
dev-frontend:
|
||||
@echo "Starting frontend dev server..."
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ func (h *DomainHandler) List(c *gin.Context) {
|
|||
domainStats := make([]db.DomainStats, len(domains))
|
||||
for i, d := range domains {
|
||||
var userCount, aliasCount int64
|
||||
h.db.Model(&db.User{}).Where("domain_id = ?", d.ID).Count(&userCount)
|
||||
h.db.Model(&db.Alias{}).Where("domain_id = ?", d.ID).Count(&aliasCount)
|
||||
h.db.Model(&db.VirtualUser{}).Where("domain_id = ?", d.ID).Count(&userCount)
|
||||
h.db.Model(&db.VirtualAlias{}).Where("domain_id = ?", d.ID).Count(&aliasCount)
|
||||
domainStats[i] = db.DomainStats{
|
||||
ID: d.ID,
|
||||
Name: d.Name,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ func (h *StatsHandler) Get(c *gin.Context) {
|
|||
|
||||
users, err := h.db.GetAllMailUsers()
|
||||
if err != nil {
|
||||
users = []db.User{}
|
||||
users = []db.VirtualUser{}
|
||||
}
|
||||
|
||||
aliases, err := h.db.GetAllAliases()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package db
|
||||
|
||||
func (d *DB) GetAllAliases() ([]AliasWithDomain, error) {
|
||||
var aliases []Alias
|
||||
var aliases []VirtualAlias
|
||||
if err := d.Preload("Domain").Find(&aliases).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -9,8 +9,8 @@ func (d *DB) GetAllAliases() ([]AliasWithDomain, error) {
|
|||
result := make([]AliasWithDomain, len(aliases))
|
||||
for i, a := range aliases {
|
||||
result[i] = AliasWithDomain{
|
||||
Alias: a,
|
||||
DomainName: "",
|
||||
VirtualAlias: a,
|
||||
DomainName: "",
|
||||
}
|
||||
if a.Domain != nil {
|
||||
result[i].DomainName = a.Domain.Name
|
||||
|
|
@ -23,35 +23,35 @@ func (d *DB) GetAllAliases() ([]AliasWithDomain, error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (d *DB) GetAliasesByDomain(domainID uint) ([]Alias, error) {
|
||||
var aliases []Alias
|
||||
func (d *DB) GetAliasesByDomain(domainID uint) ([]VirtualAlias, error) {
|
||||
var aliases []VirtualAlias
|
||||
if err := d.Where("domain_id = ?", domainID).Find(&aliases).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if aliases == nil {
|
||||
aliases = []Alias{}
|
||||
aliases = []VirtualAlias{}
|
||||
}
|
||||
return aliases, nil
|
||||
}
|
||||
|
||||
func (d *DB) GetAliasByID(id uint) (*Alias, error) {
|
||||
var alias Alias
|
||||
func (d *DB) GetAliasByID(id uint) (*VirtualAlias, error) {
|
||||
var alias VirtualAlias
|
||||
if err := d.Preload("Domain").First(&alias, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &alias, nil
|
||||
}
|
||||
|
||||
func (d *DB) GetAliasBySource(source string) (*Alias, error) {
|
||||
var alias Alias
|
||||
func (d *DB) GetAliasBySource(source string) (*VirtualAlias, error) {
|
||||
var alias VirtualAlias
|
||||
if err := d.Where("source = ?", source).First(&alias).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &alias, nil
|
||||
}
|
||||
|
||||
func (d *DB) CreateAlias(source, destination string) (*Alias, error) {
|
||||
alias := Alias{
|
||||
func (d *DB) CreateAlias(source, destination string) (*VirtualAlias, error) {
|
||||
alias := VirtualAlias{
|
||||
Source: source,
|
||||
Destination: destination,
|
||||
}
|
||||
|
|
@ -61,8 +61,8 @@ func (d *DB) CreateAlias(source, destination string) (*Alias, error) {
|
|||
return &alias, nil
|
||||
}
|
||||
|
||||
func (d *DB) CreateAliasInDomain(source, destination string, domainID uint) (*Alias, error) {
|
||||
alias := Alias{
|
||||
func (d *DB) CreateAliasInDomain(source, destination string, domainID uint) (*VirtualAlias, error) {
|
||||
alias := VirtualAlias{
|
||||
DomainID: domainID,
|
||||
Source: source,
|
||||
Destination: destination,
|
||||
|
|
@ -74,5 +74,5 @@ func (d *DB) CreateAliasInDomain(source, destination string, domainID uint) (*Al
|
|||
}
|
||||
|
||||
func (d *DB) DeleteAlias(id uint) error {
|
||||
return d.Delete(&Alias{}, id).Error
|
||||
return d.Delete(&VirtualAlias{}, id).Error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,46 +28,46 @@ type DB struct {
|
|||
// These tables are shared with the ISPmail system and must not be modified.
|
||||
// =============================================================================
|
||||
|
||||
// Domain represents a mail domain (e.g., "example.org").
|
||||
// VirtualDomain represents a mail domain (e.g., "example.org").
|
||||
// This corresponds to the existing ISPmail virtual_domains table.
|
||||
type Domain struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"` // Primary key, auto-increment
|
||||
Name string `gorm:"uniqueIndex;size:50;not null" json:"name"` // Domain name, must be unique
|
||||
CreatedAt time.Time `json:"createdAt"` // When the domain was added
|
||||
Users []User `gorm:"foreignKey:DomainID" json:"users,omitempty"` // Mail users in this domain
|
||||
Aliases []Alias `gorm:"foreignKey:DomainID" json:"aliases,omitempty"` // Aliases in this domain
|
||||
type VirtualDomain struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"` // Primary key, auto-increment
|
||||
Name string `gorm:"uniqueIndex;size:50;not null" json:"name"` // Domain name, must be unique
|
||||
CreatedAt time.Time `json:"createdAt"` // When the domain was added
|
||||
Users []VirtualUser `gorm:"foreignKey:DomainID" json:"users,omitempty"` // Mail users in this domain
|
||||
Aliases []VirtualAlias `gorm:"foreignKey:DomainID" json:"aliases,omitempty"` // Aliases in this domain
|
||||
}
|
||||
|
||||
// TableName tells GORM to use the existing ISPmail table name.
|
||||
func (Domain) TableName() string { return "virtual_domains" }
|
||||
func (VirtualDomain) TableName() string { return "virtual_domains" }
|
||||
|
||||
// User represents a mail user (e.g., "user@example.org").
|
||||
// VirtualUser represents a mail user (e.g., "user@example.org").
|
||||
// This corresponds to the existing ISPmail virtual_users table.
|
||||
type User struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"` // Primary key
|
||||
DomainID uint `gorm:"not null" json:"domainId"` // Foreign key to Domain
|
||||
Email string `gorm:"uniqueIndex;size:100;not null" json:"email"` // Full email address, must be unique
|
||||
Password string `gorm:"size:150;not null" json:"-"` // Mailbox password (bcrypt hash, - means exclude from JSON)
|
||||
Quota int64 `gorm:"default:0" json:"quota"` // Mailbox size limit in bytes (0 = default)
|
||||
Domain *Domain `gorm:"foreignKey:DomainID" json:"domain,omitempty"` // Associated domain
|
||||
type VirtualUser struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"` // Primary key
|
||||
DomainID uint `gorm:"not null" json:"domainId"` // Foreign key to VirtualDomain
|
||||
Email string `gorm:"uniqueIndex;size:100;not null" json:"email"` // Full email address, must be unique
|
||||
Password string `gorm:"size:150;not null" json:"-"` // Mailbox password (bcrypt hash, - means exclude from JSON)
|
||||
Quota int64 `gorm:"default:0" json:"quota"` // Mailbox size limit in bytes (0 = default)
|
||||
Domain *VirtualDomain `gorm:"foreignKey:DomainID" json:"domain,omitempty"` // Associated domain
|
||||
}
|
||||
|
||||
// TableName tells GORM to use the existing ISPmail table name.
|
||||
func (User) TableName() string { return "virtual_users" }
|
||||
func (VirtualUser) TableName() string { return "virtual_users" }
|
||||
|
||||
// Alias represents an email alias/forwarding rule.
|
||||
// VirtualAlias represents an email alias/forwarding rule.
|
||||
// Maps one email address (source) to another (destination).
|
||||
// This corresponds to the existing ISPmail virtual_aliases table.
|
||||
type Alias struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"` // Primary key
|
||||
DomainID uint `gorm:"not null" json:"domainId"` // Foreign key to Domain
|
||||
Source string `gorm:"size:100;not null" json:"source"` // Original email address (alias)
|
||||
Destination string `gorm:"size:100;not null" json:"destination"` // Forward-to email address
|
||||
Domain *Domain `gorm:"foreignKey:DomainID" json:"domain,omitempty"` // Associated domain
|
||||
type VirtualAlias struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"` // Primary key
|
||||
DomainID uint `gorm:"not null" json:"domainId"` // Foreign key to VirtualDomain
|
||||
Source string `gorm:"size:100;not null" json:"source"` // Original email address (alias)
|
||||
Destination string `gorm:"size:100;not null" json:"destination"` // Forward-to email address
|
||||
Domain *VirtualDomain `gorm:"foreignKey:DomainID" json:"domain,omitempty"` // Associated domain
|
||||
}
|
||||
|
||||
// TableName tells GORM to use the existing ISPmail table name.
|
||||
func (Alias) TableName() string { return "virtual_aliases" }
|
||||
func (VirtualAlias) TableName() string { return "virtual_aliases" }
|
||||
|
||||
// =============================================================================
|
||||
// IMC Application Models (tables created by this app)
|
||||
|
|
@ -90,11 +90,11 @@ func (ImcUser) TableName() string { return "imc_users" }
|
|||
// ImcUserDomain represents the many-to-many relationship between admin users and domains.
|
||||
// An admin user can have access to multiple domains, and a domain can be accessed by multiple users.
|
||||
type ImcUserDomain struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"` // Primary key
|
||||
UserID uint `gorm:"not null" json:"userId"` // Foreign key to ImcUser
|
||||
DomainID uint `gorm:"not null" json:"domainId"` // Foreign key to Domain
|
||||
CreatedAt time.Time `json:"createdAt"` // When access was granted
|
||||
Domain *Domain `gorm:"foreignKey:DomainID" json:"domain,omitempty"` // Associated domain (for preloading)
|
||||
ID uint `gorm:"primaryKey" json:"id"` // Primary key
|
||||
UserID uint `gorm:"not null" json:"userId"` // Foreign key to ImcUser
|
||||
DomainID uint `gorm:"not null" json:"domainId"` // Foreign key to VirtualDomain
|
||||
CreatedAt time.Time `json:"createdAt"` // When access was granted
|
||||
Domain *VirtualDomain `gorm:"foreignKey:DomainID" json:"domain,omitempty"` // Associated domain (for preloading)
|
||||
}
|
||||
|
||||
// TableName specifies the database table name for ImcUserDomain.
|
||||
|
|
@ -128,14 +128,14 @@ type DomainStats struct {
|
|||
|
||||
// AliasWithDomain combines an alias with its domain name for API responses.
|
||||
type AliasWithDomain struct {
|
||||
Alias // Embed Alias struct
|
||||
DomainName string `json:"domainName"` // Denormalized domain name for convenience
|
||||
VirtualAlias // Embed VirtualAlias struct
|
||||
DomainName string `json:"domainName"` // Denormalized domain name for convenience
|
||||
}
|
||||
|
||||
// UserWithDomain combines a user with their domain name for API responses.
|
||||
type UserWithDomain struct {
|
||||
User // Embed User struct
|
||||
DomainName string `json:"domainName"` // Denormalized domain name for convenience
|
||||
VirtualUser // Embed VirtualUser struct
|
||||
DomainName string `json:"domainName"` // Denormalized domain name for convenience
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package db
|
||||
|
||||
func (d *DB) GetAllDomains() ([]DomainStats, error) {
|
||||
var domains []Domain
|
||||
var domains []VirtualDomain
|
||||
if err := d.Order("name ASC").Find(&domains).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -9,8 +9,8 @@ func (d *DB) GetAllDomains() ([]DomainStats, error) {
|
|||
stats := make([]DomainStats, len(domains))
|
||||
for i, dom := range domains {
|
||||
var userCount, aliasCount int64
|
||||
d.Model(&User{}).Where("domain_id = ?", dom.ID).Count(&userCount)
|
||||
d.Model(&Alias{}).Where("domain_id = ?", dom.ID).Count(&aliasCount)
|
||||
d.Model(&VirtualUser{}).Where("domain_id = ?", dom.ID).Count(&userCount)
|
||||
d.Model(&VirtualAlias{}).Where("domain_id = ?", dom.ID).Count(&aliasCount)
|
||||
|
||||
stats[i] = DomainStats{
|
||||
ID: dom.ID,
|
||||
|
|
@ -26,24 +26,24 @@ func (d *DB) GetAllDomains() ([]DomainStats, error) {
|
|||
return stats, nil
|
||||
}
|
||||
|
||||
func (d *DB) GetDomainByID(id uint) (*Domain, error) {
|
||||
var domain Domain
|
||||
func (d *DB) GetDomainByID(id uint) (*VirtualDomain, error) {
|
||||
var domain VirtualDomain
|
||||
if err := d.Where("id = ?", id).First(&domain).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain, nil
|
||||
}
|
||||
|
||||
func (d *DB) GetDomainByName(name string) (*Domain, error) {
|
||||
var domain Domain
|
||||
func (d *DB) GetDomainByName(name string) (*VirtualDomain, error) {
|
||||
var domain VirtualDomain
|
||||
if err := d.Where("name = ?", name).First(&domain).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain, nil
|
||||
}
|
||||
|
||||
func (d *DB) CreateDomain(name string) (*Domain, error) {
|
||||
domain := Domain{Name: name}
|
||||
func (d *DB) CreateDomain(name string) (*VirtualDomain, error) {
|
||||
domain := VirtualDomain{Name: name}
|
||||
if err := d.Create(&domain).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -54,13 +54,13 @@ func (d *DB) DeleteDomain(id uint) error {
|
|||
tx := d.Begin()
|
||||
defer func() { tx.Rollback() }()
|
||||
|
||||
if err := tx.Where("domain_id = ?", id).Delete(&User{}).Error; err != nil {
|
||||
if err := tx.Where("domain_id = ?", id).Delete(&VirtualUser{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("domain_id = ?", id).Delete(&Alias{}).Error; err != nil {
|
||||
if err := tx.Where("domain_id = ?", id).Delete(&VirtualAlias{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Delete(&Domain{}, id).Error; err != nil {
|
||||
if err := tx.Delete(&VirtualDomain{}, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,10 +87,10 @@ func (d *DB) UpsertAdminUser(username, passwordHash string) error {
|
|||
// userID: the ID of the user.
|
||||
// isAdmin: whether the user has admin privileges.
|
||||
// Returns: a list of accessible domains.
|
||||
func (d *DB) GetUserAccessibleDomains(userID uint, isAdmin bool) ([]Domain, error) {
|
||||
func (d *DB) GetUserAccessibleDomains(userID uint, isAdmin bool) ([]VirtualDomain, error) {
|
||||
// Admins get access to all domains.
|
||||
if isAdmin {
|
||||
var domains []Domain
|
||||
var domains []VirtualDomain
|
||||
if err := d.Order("name ASC").Find(&domains).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -100,7 +100,7 @@ func (d *DB) GetUserAccessibleDomains(userID uint, isAdmin bool) ([]Domain, erro
|
|||
// Regular users query the imc_users2domains table.
|
||||
// We use raw SQL with Joins because GORM's association methods
|
||||
// don't handle our cross-database queries well.
|
||||
var domains []Domain
|
||||
var domains []VirtualDomain
|
||||
err := d.Table("imc_users2domains"). // Query from this table
|
||||
Select("virtual_domains.*"). // Select all columns from domains
|
||||
Joins("JOIN virtual_domains ON virtual_domains.id = imc_users2domains.domain_id"). // Join to get domain details
|
||||
|
|
@ -111,7 +111,7 @@ func (d *DB) GetUserAccessibleDomains(userID uint, isAdmin bool) ([]Domain, erro
|
|||
return nil, err
|
||||
}
|
||||
if domains == nil {
|
||||
domains = []Domain{} // Return empty slice, not nil
|
||||
domains = []VirtualDomain{} // Return empty slice, not nil
|
||||
}
|
||||
return domains, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +1,45 @@
|
|||
package db
|
||||
|
||||
func (d *DB) GetAllMailUsers() ([]User, error) {
|
||||
var users []User
|
||||
func (d *DB) GetAllMailUsers() ([]VirtualUser, error) {
|
||||
var users []VirtualUser
|
||||
if err := d.Preload("Domain").Find(&users).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if users == nil {
|
||||
users = []User{}
|
||||
users = []VirtualUser{}
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (d *DB) GetUsersByDomain(domainID uint) ([]User, error) {
|
||||
var users []User
|
||||
func (d *DB) GetUsersByDomain(domainID uint) ([]VirtualUser, error) {
|
||||
var users []VirtualUser
|
||||
if err := d.Where("domain_id = ?", domainID).Find(&users).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if users == nil {
|
||||
users = []User{}
|
||||
users = []VirtualUser{}
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (d *DB) GetUserByID(id uint) (*User, error) {
|
||||
var user User
|
||||
func (d *DB) GetUserByID(id uint) (*VirtualUser, error) {
|
||||
var user VirtualUser
|
||||
if err := d.Preload("Domain").First(&user, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (d *DB) GetUserByEmail(email string) (*User, error) {
|
||||
var user User
|
||||
func (d *DB) GetUserByEmail(email string) (*VirtualUser, error) {
|
||||
var user VirtualUser
|
||||
if err := d.Where("email = ?", email).First(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (d *DB) CreateUser(email, passwordHash string, quota int64) (*User, error) {
|
||||
user := User{
|
||||
func (d *DB) CreateUser(email, passwordHash string, quota int64) (*VirtualUser, error) {
|
||||
user := VirtualUser{
|
||||
Email: email,
|
||||
Password: passwordHash,
|
||||
Quota: quota,
|
||||
|
|
@ -50,8 +50,8 @@ func (d *DB) CreateUser(email, passwordHash string, quota int64) (*User, error)
|
|||
return &user, nil
|
||||
}
|
||||
|
||||
func (d *DB) CreateUserInDomain(email, passwordHash string, quota int64, domainID uint) (*User, error) {
|
||||
user := User{
|
||||
func (d *DB) CreateUserInDomain(email, passwordHash string, quota int64, domainID uint) (*VirtualUser, error) {
|
||||
user := VirtualUser{
|
||||
DomainID: domainID,
|
||||
Email: email,
|
||||
Password: passwordHash,
|
||||
|
|
@ -64,13 +64,13 @@ func (d *DB) CreateUserInDomain(email, passwordHash string, quota int64, domainI
|
|||
}
|
||||
|
||||
func (d *DB) UpdateUserPassword(id uint, passwordHash string) error {
|
||||
return d.Model(&User{}).Where("id = ?", id).Update("password", passwordHash).Error
|
||||
return d.Model(&VirtualUser{}).Where("id = ?", id).Update("password", passwordHash).Error
|
||||
}
|
||||
|
||||
func (d *DB) UpdateUserQuota(id uint, quota int64) error {
|
||||
return d.Model(&User{}).Where("id = ?", id).Update("quota", quota).Error
|
||||
return d.Model(&VirtualUser{}).Where("id = ?", id).Update("quota", quota).Error
|
||||
}
|
||||
|
||||
func (d *DB) DeleteUser(id uint) error {
|
||||
return d.Delete(&User{}, id).Error
|
||||
return d.Delete(&VirtualUser{}, id).Error
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue