3x-ui/web/service/host.go
2026-01-09 15:36:14 +03:00

254 lines
5.6 KiB
Go

// Package service provides Host management service for multi-node mode.
package service
import (
"time"
"github.com/mhsanaei/3x-ui/v2/database"
"github.com/mhsanaei/3x-ui/v2/database/model"
"github.com/mhsanaei/3x-ui/v2/logger"
"github.com/mhsanaei/3x-ui/v2/util/common"
"gorm.io/gorm"
)
// HostService provides business logic for managing hosts.
type HostService struct{}
// GetHosts retrieves all hosts for a specific user.
func (s *HostService) GetHosts(userId int) ([]*model.Host, error) {
db := database.GetDB()
var hosts []*model.Host
err := db.Where("user_id = ?", userId).Find(&hosts).Error
if err != nil {
return nil, err
}
// Load inbound assignments for each host
for _, host := range hosts {
inboundIds, err := s.GetInboundIdsForHost(host.Id)
if err == nil {
host.InboundIds = inboundIds
}
}
return hosts, nil
}
// GetHost retrieves a host by ID.
func (s *HostService) GetHost(id int) (*model.Host, error) {
db := database.GetDB()
var host model.Host
err := db.First(&host, id).Error
if err != nil {
return nil, err
}
// Load inbound assignments
inboundIds, err := s.GetInboundIdsForHost(host.Id)
if err == nil {
host.InboundIds = inboundIds
}
return &host, nil
}
// GetInboundIdsForHost retrieves all inbound IDs assigned to a host.
func (s *HostService) GetInboundIdsForHost(hostId int) ([]int, error) {
db := database.GetDB()
var mappings []model.HostInboundMapping
err := db.Where("host_id = ?", hostId).Find(&mappings).Error
if err != nil {
return nil, err
}
inboundIds := make([]int, len(mappings))
for i, mapping := range mappings {
inboundIds[i] = mapping.InboundId
}
return inboundIds, nil
}
// GetHostForInbound retrieves the host assigned to an inbound (if any).
// Returns the first enabled host if multiple hosts are assigned.
func (s *HostService) GetHostForInbound(inboundId int) (*model.Host, error) {
db := database.GetDB()
var mapping model.HostInboundMapping
err := db.Where("inbound_id = ?", inboundId).First(&mapping).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil // No host assigned
}
return nil, err
}
var host model.Host
err = db.Where("id = ? AND enable = ?", mapping.HostId, true).First(&host).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil // Host disabled or not found
}
return nil, err
}
return &host, nil
}
// AddHost creates a new host.
func (s *HostService) AddHost(userId int, host *model.Host) error {
host.UserId = userId
// Set timestamps
now := time.Now().Unix()
if host.CreatedAt == 0 {
host.CreatedAt = now
}
host.UpdatedAt = now
db := database.GetDB()
tx := db.Begin()
var err error
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
err = tx.Create(host).Error
if err != nil {
return err
}
// Assign to inbounds if provided
if len(host.InboundIds) > 0 {
err = s.AssignHostToInbounds(tx, host.Id, host.InboundIds)
if err != nil {
return err
}
}
return nil
}
// UpdateHost updates an existing host.
func (s *HostService) UpdateHost(userId int, host *model.Host) error {
// Check if host exists and belongs to user
existing, err := s.GetHost(host.Id)
if err != nil {
return err
}
if existing.UserId != userId {
return common.NewError("Host not found or access denied")
}
// Update timestamp
host.UpdatedAt = time.Now().Unix()
db := database.GetDB()
tx := db.Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
// Update only provided fields
updates := make(map[string]interface{})
if host.Name != "" {
updates["name"] = host.Name
}
if host.Address != "" {
updates["address"] = host.Address
}
if host.Port > 0 {
updates["port"] = host.Port
}
if host.Protocol != "" {
updates["protocol"] = host.Protocol
}
if host.Remark != "" {
updates["remark"] = host.Remark
}
updates["enable"] = host.Enable
updates["updated_at"] = host.UpdatedAt
err = tx.Model(&model.Host{}).Where("id = ? AND user_id = ?", host.Id, userId).Updates(updates).Error
if err != nil {
return err
}
// Update inbound assignments if provided
if host.InboundIds != nil {
// Remove existing assignments
err = tx.Where("host_id = ?", host.Id).Delete(&model.HostInboundMapping{}).Error
if err != nil {
return err
}
// Add new assignments
if len(host.InboundIds) > 0 {
err = s.AssignHostToInbounds(tx, host.Id, host.InboundIds)
if err != nil {
return err
}
}
}
return nil
}
// DeleteHost deletes a host by ID.
func (s *HostService) DeleteHost(userId int, id int) error {
// Check if host exists and belongs to user
existing, err := s.GetHost(id)
if err != nil {
return err
}
if existing.UserId != userId {
return common.NewError("Host not found or access denied")
}
db := database.GetDB()
tx := db.Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
// Delete inbound mappings
err = tx.Where("host_id = ?", id).Delete(&model.HostInboundMapping{}).Error
if err != nil {
return err
}
// Delete host
err = tx.Where("id = ? AND user_id = ?", id, userId).Delete(&model.Host{}).Error
if err != nil {
return err
}
return nil
}
// AssignHostToInbounds assigns a host to multiple inbounds.
func (s *HostService) AssignHostToInbounds(tx *gorm.DB, hostId int, inboundIds []int) error {
for _, inboundId := range inboundIds {
mapping := &model.HostInboundMapping{
HostId: hostId,
InboundId: inboundId,
}
err := tx.Create(mapping).Error
if err != nil {
logger.Warningf("Failed to assign host %d to inbound %d: %v", hostId, inboundId, err)
// Continue with other assignments
}
}
return nil
}