mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-01-13 09:12:44 +00:00
254 lines
5.6 KiB
Go
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
|
|
}
|