fix: port assignment bulk update
This commit is contained in:
parent
689505fa6a
commit
0c25a3a941
|
|
@ -13,9 +13,9 @@ import (
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
ucManager manager.UsecaseManager
|
ucManager manager.UsecaseManager
|
||||||
engine *gin.Engine
|
engine *gin.Engine
|
||||||
host string
|
host string
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer() *Server {
|
func NewServer() *Server {
|
||||||
|
|
@ -41,63 +41,59 @@ func NewServer() *Server {
|
||||||
|
|
||||||
return &Server{
|
return &Server{
|
||||||
ucManager: ucManager,
|
ucManager: ucManager,
|
||||||
engine: engine,
|
engine: engine,
|
||||||
host: host,
|
host: host,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) setupController() {
|
func (s *Server) setupController() {
|
||||||
|
|
||||||
s.engine.Static("/uploads", "./uploads")
|
s.engine.Static("/uploads", "./uploads")
|
||||||
|
|
||||||
rg := s.engine.Group("/api/v1")
|
rg := s.engine.Group("/api/v1")
|
||||||
|
|
||||||
// Add bypass controller FIRST - before any auth middleware
|
// Add bypass controller FIRST - before any auth middleware
|
||||||
controller.NewBypassController(s.ucManager.NewUserUsecase(), rg).Route()
|
controller.NewBypassController(s.ucManager.NewUserUsecase(), rg).Route()
|
||||||
|
|
||||||
// Add auth controller (public endpoints)
|
// Add auth controller (public endpoints)
|
||||||
controller.NewAuthController(s.ucManager.NewAuthUsecase(), rg).Route()
|
controller.NewAuthController(s.ucManager.NewAuthUsecase(), rg).Route()
|
||||||
|
|
||||||
publicRegistration := rg.Group("/user-registration")
|
publicRegistration := rg.Group("/user-registration")
|
||||||
{
|
{
|
||||||
publicRegistration.POST("/register", controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), rg).RegisterUser)
|
publicRegistration.POST("/register", controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), rg).RegisterUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add user registration controller (public endpoints)
|
||||||
|
// controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), rg).Route()
|
||||||
// Add user registration controller (public endpoints)
|
|
||||||
// controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), rg).Route()
|
|
||||||
|
|
||||||
// Now apply auth middleware to protected routes
|
// Now apply auth middleware to protected routes
|
||||||
protected := rg.Use(middleware.ConditionalAuthMiddleware(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(), s.cfg)).(*gin.RouterGroup)
|
protected := rg.Use(middleware.ConditionalAuthMiddleware(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(), s.cfg)).(*gin.RouterGroup)
|
||||||
{
|
{
|
||||||
protectedRegistration := protected.Group("/user-registration")
|
protectedRegistration := protected.Group("/user-registration")
|
||||||
{
|
{
|
||||||
protectedRegistration.GET("/pending", middleware.RequireAdminRole(), controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), protected).GetPendingUsers)
|
protectedRegistration.GET("/pending", middleware.RequireAdminRole(), controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), protected).GetPendingUsers)
|
||||||
protectedRegistration.PUT("/approve/:id", middleware.RequireAdminRole(), controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), protected).ApproveUser)
|
protectedRegistration.PUT("/approve/:id", middleware.RequireAdminRole(), controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), protected).ApproveUser)
|
||||||
protectedRegistration.PUT("/reject/:id", middleware.RequireAdminRole(), controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), protected).RejectUser)
|
protectedRegistration.PUT("/reject/:id", middleware.RequireAdminRole(), controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), protected).RejectUser)
|
||||||
}
|
}
|
||||||
controller.NewUserRoleManagementController(s.ucManager.NewUserUsecase(), protected).Route()
|
controller.NewUserRoleManagementController(s.ucManager.NewUserUsecase(), protected).Route()
|
||||||
controller.NewUsersController(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(), protected).Route()
|
controller.NewUsersController(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(), protected).Route()
|
||||||
controller.NewCountAssetsController(s.ucManager.NewCountAssetsUsecase(), protected).Route()
|
controller.NewCountAssetsController(s.ucManager.NewCountAssetsUsecase(), protected).Route()
|
||||||
controller.NewDeviceController(s.ucManager.NewDeviceUsecase(), protected, s.cfg).Route()
|
controller.NewDeviceController(s.ucManager.NewDeviceUsecase(), protected, s.cfg).Route()
|
||||||
controller.NewBackboneController(s.ucManager.NewBackboneUsecase(), protected, s.cfg).Route()
|
controller.NewBackboneController(s.ucManager.NewBackboneUsecase(), protected, s.cfg).Route()
|
||||||
controller.NewFishboneController(s.ucManager.NewFishboneUsecase(), protected, s.cfg).Route()
|
controller.NewFishboneController(s.ucManager.NewFishboneUsecase(), protected, s.cfg).Route()
|
||||||
controller.NewTowerController(s.ucManager.NewTowerUsecase(), protected, s.cfg).Route()
|
controller.NewTowerController(s.ucManager.NewTowerUsecase(), protected, s.cfg).Route()
|
||||||
controller.NewDevicePortController(s.ucManager.NewDevicePortUsecase(), protected).Route()
|
controller.NewDevicePortController(s.ucManager.NewDevicePortUsecase(), protected).Route()
|
||||||
controller.NewActivityLogController(s.ucManager.NewActivityLogUsecase(), protected, s.cfg).Route()
|
controller.NewActivityLogController(s.ucManager.NewActivityLogUsecase(), protected, s.cfg).Route()
|
||||||
controller.NewDeviceInspectionController(s.ucManager.NewDeviceInspectionUsecase(), protected).Route()
|
controller.NewDeviceInspectionController(s.ucManager.NewDeviceInspectionUsecase(), protected).Route()
|
||||||
controller.NewNearestDeviceController(s.ucManager.NewNearestDeviceUsecase(), protected, s.cfg).Route()
|
controller.NewNearestDeviceController(s.ucManager.NewNearestDeviceUsecase(), protected, s.cfg).Route()
|
||||||
controller.NewDeviceDetailsController(s.ucManager.NewDeviceDetailsUsecase(), protected, s.cfg).Route()
|
controller.NewDeviceDetailsController(s.ucManager.NewDeviceDetailsUsecase(), protected, s.cfg).Route()
|
||||||
controller.NewOLTController(s.ucManager.NewOLTUsecase(), protected).Route()
|
controller.NewOLTController(s.ucManager.NewOLTUsecase(), protected).Route()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (s *Server) Run() {
|
func (s *Server) Run() {
|
||||||
s.setupController()
|
s.setupController()
|
||||||
if err := s.engine.Run(s.host); err != nil {
|
if err := s.engine.Run(s.host); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -15,40 +15,44 @@ type InfraManager interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type infraManager struct {
|
type infraManager struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *infraManager) openConn() error {
|
func (im *infraManager) openConn() error {
|
||||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s TimeZone=Asia/Shanghai",
|
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s TimeZone=Asia/Shanghai",
|
||||||
im.cfg.DBHost, im.cfg.DBUser, im.cfg.DBPass, im.cfg.DBName, im.cfg.DBPort)
|
im.cfg.DBHost, im.cfg.DBUser, im.cfg.DBPass, im.cfg.DBName, im.cfg.DBPort)
|
||||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = im.autoMigrate(db)
|
// Configure GORM to disable foreign key constraint during migration
|
||||||
if err != nil {
|
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||||
return fmt.Errorf("failed to migrate database schema: %w", err)
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
}
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
im.db = db
|
err = im.autoMigrate(db)
|
||||||
return nil
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to migrate database schema: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
im.db = db
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *infraManager) autoMigrate(db *gorm.DB) error {
|
func (im *infraManager) autoMigrate(db *gorm.DB) error {
|
||||||
return db.AutoMigrate(
|
return db.AutoMigrate(
|
||||||
&entity.Role{},
|
&entity.Role{},
|
||||||
&entity.User{},
|
&entity.User{},
|
||||||
&entity.Device{},
|
&entity.Device{},
|
||||||
&entity.Backbone{},
|
&entity.Tower{},
|
||||||
&entity.Fishbone{},
|
&entity.OLT{},
|
||||||
&entity.Tower{},
|
&entity.DevicePort{},
|
||||||
&entity.DevicePort{},
|
&entity.Backbone{},
|
||||||
&entity.CountAssets{},
|
&entity.Fishbone{},
|
||||||
&entity.ActivityLog{},
|
&entity.CountAssets{},
|
||||||
&entity.OLT{},
|
&entity.ActivityLog{},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInfraManager(cfg *config.Config) (InfraManager, error) {
|
func NewInfraManager(cfg *config.Config) (InfraManager, error) {
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,67 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CORSMiddleware() gin.HandlerFunc {
|
func CORSMiddleware() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
origin := c.Request.Header.Get("Origin")
|
origin := c.Request.Header.Get("Origin")
|
||||||
|
|
||||||
// Define allowed origins (add your React app's URL)
|
// Define allowed origins (add your React app's URL)
|
||||||
allowedOrigins := []string{
|
allowedOrigins := []string{
|
||||||
"http://localhost:3000",
|
"http://localhost:3000",
|
||||||
"http://localhost:3001",
|
"http://localhost:3001",
|
||||||
"http://127.0.0.1:3000",
|
"http://localhost:5173", // Vite development server
|
||||||
"http://127.0.0.1:5173",
|
"http://127.0.0.1:3000",
|
||||||
"http://103.110.8.103:80",// Add production URL
|
"http://127.0.0.1:5173",
|
||||||
"http://103.110.8.103",
|
"http://103.110.8.103:80", // Add production URL
|
||||||
|
"http://103.110.8.103",
|
||||||
|
"http://nam.winteraccess.id",
|
||||||
|
"https://nam.winteraccess.id",
|
||||||
|
}
|
||||||
|
|
||||||
"http://nam.winteraccess.id",
|
// Check if origin is in allowed list
|
||||||
|
isAllowed := false
|
||||||
|
for _, allowed := range allowedOrigins {
|
||||||
|
if origin == allowed {
|
||||||
|
isAllowed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
// For development, also allow localhost variations
|
||||||
|
if strings.Contains(origin, "localhost") || strings.Contains(origin, "127.0.0.1") {
|
||||||
|
isAllowed = true
|
||||||
|
}
|
||||||
|
|
||||||
// Check if origin is in allowed list
|
if origin != "" && isAllowed {
|
||||||
isAllowed := false
|
// For allowed origins, set specific origin and enable credentials
|
||||||
for _, allowed := range allowedOrigins {
|
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||||
if origin == allowed {
|
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||||
isAllowed = true
|
} else if origin == "" {
|
||||||
break
|
// For requests without origin (direct API calls, mobile apps, etc.)
|
||||||
}
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
}
|
// Don't set credentials for wildcard
|
||||||
|
} else {
|
||||||
|
// For disallowed origins, still set basic CORS but no credentials
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
}
|
||||||
|
|
||||||
// For development, also allow localhost variations
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE, PATCH")
|
||||||
if strings.Contains(origin, "localhost") || strings.Contains(origin, "127.0.0.1") {
|
c.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, accept, origin, Cache-Control, X-Requested-With")
|
||||||
isAllowed = true
|
c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Type")
|
||||||
}
|
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
|
||||||
|
|
||||||
if origin != "" && isAllowed {
|
// Handle preflight requests
|
||||||
// For allowed origins, set specific origin and enable credentials
|
if c.Request.Method == http.MethodOptions {
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
c.AbortWithStatus(http.StatusNoContent)
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
return
|
||||||
} else if origin == "" {
|
}
|
||||||
// For requests without origin (direct API calls, mobile apps, etc.)
|
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
// Don't set credentials for wildcard
|
|
||||||
} else {
|
|
||||||
// For disallowed origins, still set basic CORS but no credentials
|
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE, PATCH")
|
c.Next()
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, accept, origin, Cache-Control, X-Requested-With")
|
}
|
||||||
c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Type")
|
|
||||||
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
|
|
||||||
|
|
||||||
// Handle preflight requests
|
|
||||||
if c.Request.Method == http.MethodOptions {
|
|
||||||
c.AbortWithStatus(http.StatusNoContent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,47 +7,49 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeviceDetailsDTO struct {
|
type DeviceDetailsDTO struct {
|
||||||
DeviceCode string `json:"device_code" validate:"required"`
|
DeviceCode string `json:"device_code" validate:"required"`
|
||||||
DeviceType string `json:"device_type" validate:"required,oneof=OTB ODP"`
|
DeviceType string `json:"device_type" validate:"required,oneof=OTB ODP"`
|
||||||
Longitude float64 `json:"longitude" validate:"required"`
|
Longitude float64 `json:"longitude" validate:"required"`
|
||||||
Latitude float64 `json:"latitude" validate:"required"`
|
Latitude float64 `json:"latitude" validate:"required"`
|
||||||
PortAmount int `json:"port_amount" validate:"required,min=1,max=100"`
|
PortAmount int `json:"port_amount" validate:"required,min=1,max=100"`
|
||||||
Status string `json:"status" validate:"required,oneof=active inactive maintenance"`
|
Status string `json:"status" validate:"required,oneof=active inactive maintenance"`
|
||||||
Region *string `json:"region,omitempty" validate:"omitempty,min=3"`
|
Region *string `json:"region,omitempty" validate:"omitempty,min=3"`
|
||||||
Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
|
Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
|
||||||
City *string `json:"city,omitempty" validate:"omitempty,min=3"`
|
City *string `json:"city,omitempty" validate:"omitempty,min=3"`
|
||||||
District *string `json:"district,omitempty" validate:"omitempty,min=3"`
|
District *string `json:"district,omitempty" validate:"omitempty,min=3"`
|
||||||
TowerID *uuid.UUID `json:"tower_id,omitempty"`
|
TowerID *uuid.UUID `json:"tower_id,omitempty"`
|
||||||
OLTID *uuid.UUID `json:"olt_id,omitempty"`
|
OLTID *uuid.UUID `json:"olt_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateDeviceDetailsDTO struct {
|
type UpdateDeviceDetailsDTO struct {
|
||||||
DeviceCode *string `json:"device_code,omitempty" validate:"omitempty,min=3"`
|
DeviceCode *string `json:"device_code,omitempty" validate:"omitempty,min=3"`
|
||||||
DeviceType *string `json:"device_type,omitempty" validate:"omitempty,oneof=OTB ODP CLOSURE"`
|
DeviceType *string `json:"device_type,omitempty" validate:"omitempty,oneof=OTB ODP CLOSURE"`
|
||||||
Longitude *float64 `json:"longitude,omitempty" validate:"omitempty,longitude"`
|
Longitude *float64 `json:"longitude,omitempty" validate:"omitempty,longitude"`
|
||||||
Latitude *float64 `json:"latitude,omitempty" validate:"omitempty,latitude"`
|
Latitude *float64 `json:"latitude,omitempty" validate:"omitempty,latitude"`
|
||||||
PortAmount *int `json:"port_amount,omitempty" validate:"omitempty,min=0,max=100"`
|
PortAmount *int `json:"port_amount,omitempty" validate:"omitempty,min=0,max=100"`
|
||||||
Status *string `json:"status,omitempty" validate:"omitempty,oneof=active inactive maintenance"`
|
Status *string `json:"status,omitempty" validate:"omitempty,oneof=active inactive maintenance"`
|
||||||
Region *string `json:"region,omitempty" validate:"omitempty,min=3"`
|
Region *string `json:"region,omitempty" validate:"omitempty,min=3"`
|
||||||
Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
|
Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
|
||||||
City *string `json:"city,omitempty" validate:"omitempty,min=3"`
|
City *string `json:"city,omitempty" validate:"omitempty,min=3"`
|
||||||
District *string `json:"district,omitempty" validate:"omitempty,min=3"`
|
District *string `json:"district,omitempty" validate:"omitempty,min=3"`
|
||||||
TowerID *uuid.UUID `json:"tower_id,omitempty"`
|
TowerID *uuid.UUID `json:"tower_id,omitempty"`
|
||||||
OLTID *uuid.UUID `json:"olt_id,omitempty"`
|
OLTID *uuid.UUID `json:"olt_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AssignMultipleCustomersDTO struct {
|
type AssignMultipleCustomersDTO struct {
|
||||||
CustomerName string `json:"customer_name" binding:"required"`
|
CustomerName string `json:"customer_name" binding:"required"`
|
||||||
PortNumber *int `json:"port_number,omitempty"`
|
PortNumber *int `json:"port_number,omitempty"`
|
||||||
|
IsOccupied *bool `json:"is_occupied,omitempty"`
|
||||||
Status *entity.PortStatus `json:"status,omitempty"` // Add status field
|
Status *entity.PortStatus `json:"status,omitempty"` // Add status field
|
||||||
Bandwidth *string `json:"bandwidth,omitempty"` // Add bandwidth field
|
Bandwidth *string `json:"bandwidth,omitempty"` // Add bandwidth field
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateCustomerByPortDTO struct {
|
type UpdateCustomerByPortDTO struct {
|
||||||
PortNumber int `json:"port_number,omitempty"`
|
PortNumber int `json:"port_number,omitempty"`
|
||||||
NewCustomerName *string `json:"new_customer_name,omitempty"`
|
NewCustomerName *string `json:"new_customer_name,omitempty"`
|
||||||
Status *entity.PortStatus `json:"status,omitempty"` // Add status field
|
IsOccupied *bool `json:"is_occupied,omitempty"`
|
||||||
Bandwidth *string `json:"bandwidth,omitempty"` // Add bandwidth field
|
Status *entity.PortStatus `json:"status,omitempty"` // Add status field
|
||||||
|
Bandwidth *string `json:"bandwidth,omitempty"` // Add bandwidth field
|
||||||
}
|
}
|
||||||
|
|
||||||
type BulkUpdateCustomersByPortDTO struct {
|
type BulkUpdateCustomersByPortDTO struct {
|
||||||
|
|
|
||||||
|
|
@ -7,34 +7,34 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type DevicePort struct {
|
type DevicePort struct {
|
||||||
DeviceID uuid.UUID `json:"device_id"`
|
DeviceID uuid.UUID `json:"device_id"`
|
||||||
PortNumber int `json:"port_number"`
|
PortNumber int `json:"port_number"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateDevicePort struct {
|
type UpdateDevicePort struct {
|
||||||
DeviceID *uuid.UUID `json:"device_id,omitempty" validate:"omitempty,min=3"`
|
DeviceID *uuid.UUID `json:"device_id,omitempty" validate:"omitempty,min=3"`
|
||||||
PortNumber *int `json:"port_number,omitempty" validate:"omitempty,min=1"`
|
PortNumber *int `json:"port_number,omitempty" validate:"omitempty,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type AssignCustomerToPortDTO struct {
|
type AssignCustomerToPortDTO struct {
|
||||||
CustomerName string `json:"customer_name" binding:"required"`
|
CustomerName string `json:"customer_name" binding:"required"`
|
||||||
PortNumber *int `json:"port_number,omitempty"` // Optional, will auto-assign if not provided
|
PortNumber *int `json:"port_number,omitempty"` // Optional, will auto-assign if not provided
|
||||||
Status *entity.PortStatus `json:"status,omitempty"` // Add status field
|
Status *entity.PortStatus `json:"status,omitempty"` // Add status field
|
||||||
Bandwidth *string `json:"bandwidth,omitempty"` // Add bandwidth field
|
Bandwidth *string `json:"bandwidth,omitempty"` // Add bandwidth field
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdatePortUsageDTO struct {
|
type UpdatePortUsageDTO struct {
|
||||||
PortUsed int `json:"port_used" binding:"required,min=0"`
|
PortUsed int `json:"port_used" binding:"required,min=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PortAssignmentDTO struct {
|
type PortAssignmentDTO struct {
|
||||||
PortNumber int `json:"port_number" binding:"required,min=1"`
|
PortNumber int `json:"port_number" binding:"required,min=1"`
|
||||||
CustomerName *string `json:"customer_name,omitempty"`
|
CustomerName *string `json:"customer_name,omitempty"`
|
||||||
Status *entity.PortStatus `json:"status,omitempty" validate:"omitempty,portstatus"`
|
IsOccupied *bool `json:"is_occupied,omitempty"`
|
||||||
Bandwidth *string `json:"bandwidth,omitempty" validate:"omitempty,min=1"`
|
Status *entity.PortStatus `json:"status,omitempty" validate:"omitempty,portstatus"`
|
||||||
|
Bandwidth *string `json:"bandwidth,omitempty" validate:"omitempty,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdatePortAssignmentsDTO struct {
|
type UpdatePortAssignmentsDTO struct {
|
||||||
PortAssignments []PortAssignmentDTO `json:"port_assignments" binding:"required"`
|
PortAssignments []PortAssignmentDTO `json:"port_assignments" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
@ -13,287 +13,306 @@ import (
|
||||||
type PortStatus string
|
type PortStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PortStatusOn PortStatus = "on"
|
PortStatusOn PortStatus = "on"
|
||||||
PortStatusOff PortStatus = "off"
|
PortStatusOff PortStatus = "off"
|
||||||
PortStatusDyingGasp PortStatus = "dyingGasp"
|
PortStatusDyingGasp PortStatus = "dyingGasp"
|
||||||
PortStatusLOS PortStatus = "los"
|
PortStatusLOS PortStatus = "los"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StringSlice []string
|
type StringSlice []string
|
||||||
|
|
||||||
func (s StringSlice) Value() (driver.Value, error) {
|
func (s StringSlice) Value() (driver.Value, error) {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return "[]", nil
|
return "[]", nil
|
||||||
}
|
}
|
||||||
return json.Marshal(s)
|
return json.Marshal(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StringSlice) Scan(value interface{}) error {
|
func (s *StringSlice) Scan(value interface{}) error {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
*s = StringSlice{}
|
*s = StringSlice{}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytes []byte
|
var bytes []byte
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case []byte: // []byte and []uint8 are the same type in Go
|
case []byte: // []byte and []uint8 are the same type in Go
|
||||||
bytes = v
|
bytes = v
|
||||||
case string:
|
case string:
|
||||||
bytes = []byte(v)
|
bytes = []byte(v)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("cannot scan %T into StringSlice", value)
|
return fmt.Errorf("cannot scan %T into StringSlice", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle empty string case
|
// Handle empty string case
|
||||||
if len(bytes) == 0 {
|
if len(bytes) == 0 {
|
||||||
*s = StringSlice{}
|
*s = StringSlice{}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Unmarshal(bytes, s)
|
return json.Unmarshal(bytes, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PortAssignment struct {
|
type PortAssignment struct {
|
||||||
PortNumber int `json:"port_number"`
|
PortNumber int `json:"port_number"`
|
||||||
CustomerName *string `json:"customer_name"` // Nullable for empty ports
|
CustomerName *string `json:"customer_name"` // Nullable for empty ports
|
||||||
Status PortStatus `json:"status"` // Add status field
|
IsOccupied bool `json:"is_occupied"` // Explicitly store occupation status
|
||||||
Bandwidth *string `json:"bandwidth"` // Add bandwidth field (nullable)
|
Status PortStatus `json:"status"` // Add status field
|
||||||
|
Bandwidth *string `json:"bandwidth"` // Add bandwidth field (nullable)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PortAssignmentResponse struct {
|
type PortAssignmentResponse struct {
|
||||||
PortNumber int `json:"port_number"`
|
PortNumber int `json:"port_number"`
|
||||||
CustomerName *string `json:"customer_name"`
|
CustomerName *string `json:"customer_name"`
|
||||||
IsOccupied bool `json:"is_occupied"`
|
IsOccupied bool `json:"is_occupied"`
|
||||||
Status PortStatus `json:"status"` // Add status field
|
Status PortStatus `json:"status"` // Add status field
|
||||||
Bandwidth *string `json:"bandwidth"` // Add bandwidth field
|
Bandwidth *string `json:"bandwidth"` // Add bandwidth field
|
||||||
}
|
}
|
||||||
|
|
||||||
type PortAssignments []PortAssignment
|
type PortAssignments []PortAssignment
|
||||||
|
|
||||||
func (p PortAssignments) Value() (driver.Value, error) {
|
func (p PortAssignments) Value() (driver.Value, error) {
|
||||||
if len(p) == 0 {
|
if len(p) == 0 {
|
||||||
return "[]", nil
|
return "[]", nil
|
||||||
}
|
}
|
||||||
return json.Marshal(p)
|
return json.Marshal(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PortAssignments) Scan(value interface{}) error {
|
func (p *PortAssignments) Scan(value interface{}) error {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
*p = PortAssignments{}
|
*p = PortAssignments{}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytes []byte
|
var bytes []byte
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case []byte:
|
case []byte:
|
||||||
bytes = v
|
bytes = v
|
||||||
case string:
|
case string:
|
||||||
bytes = []byte(v)
|
bytes = []byte(v)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("cannot scan %T into PortAssignments", value)
|
return fmt.Errorf("cannot scan %T into PortAssignments", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(bytes) == 0 {
|
if len(bytes) == 0 {
|
||||||
*p = PortAssignments{}
|
*p = PortAssignments{}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Unmarshal(bytes, p)
|
return json.Unmarshal(bytes, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DevicePort struct {
|
type DevicePort struct {
|
||||||
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||||
DeviceID uuid.UUID `json:"device_id" gorm:"type:uuid;not null"`
|
DeviceID uuid.UUID `json:"device_id" gorm:"type:uuid;not null"`
|
||||||
PortUsed int `json:"port_used"`
|
PortUsed int `json:"port_used"`
|
||||||
PortAvailable int `json:"port_available"`
|
PortAvailable int `json:"port_available"`
|
||||||
CustomerCount int `json:"customer_count"`
|
CustomerCount int `json:"customer_count"`
|
||||||
CustomerNames StringSlice `json:"customer_names" gorm:"type:jsonb"`
|
CustomerNames StringSlice `json:"customer_names" gorm:"type:jsonb"`
|
||||||
PortAssignments PortAssignments `json:"port_assignments" gorm:"type:jsonb"`
|
PortAssignments PortAssignments `json:"port_assignments" gorm:"type:jsonb"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
// Relationships
|
// Relationships
|
||||||
Device Device `json:"device" gorm:"foreignKey:DeviceID"`
|
Device Device `json:"device" gorm:"foreignKey:DeviceID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to get customer names with port numbers
|
// Helper method to get customer names with port numbers
|
||||||
func (dp *DevicePort) GetCustomerNamesWithPorts() []string {
|
func (dp *DevicePort) GetCustomerNamesWithPorts() []string {
|
||||||
if dp.Device.DeviceType == "OTB" {
|
if dp.Device.DeviceType == "OTB" {
|
||||||
return []string{} // OTB devices do not have port assignments
|
return []string{} // OTB devices do not have port assignments
|
||||||
}
|
}
|
||||||
var result []string
|
var result []string
|
||||||
for _, assignment := range dp.PortAssignments {
|
for _, assignment := range dp.PortAssignments {
|
||||||
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||||
result = append(result, fmt.Sprintf("%d: %s", assignment.PortNumber, *assignment.CustomerName))
|
result = append(result, fmt.Sprintf("%d: %s", assignment.PortNumber, *assignment.CustomerName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If no port assignments, fall back to customer names
|
// If no port assignments, fall back to customer names
|
||||||
if len(result) == 0 {
|
if len(result) == 0 {
|
||||||
for i, name := range dp.CustomerNames {
|
for i, name := range dp.CustomerNames {
|
||||||
if name != "" {
|
if name != "" {
|
||||||
result = append(result, fmt.Sprintf("%d: %s", i+1, name))
|
result = append(result, fmt.Sprintf("%d: %s", i+1, name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to get only customer names
|
// Helper method to get only customer names
|
||||||
func (dp *DevicePort) GetCustomerNames() []string {
|
func (dp *DevicePort) GetCustomerNames() []string {
|
||||||
if dp.Device.DeviceType == "OTB" {
|
if dp.Device.DeviceType == "OTB" {
|
||||||
return []string{} // OTB devices do not have port assignments
|
return []string{} // OTB devices do not have port assignments
|
||||||
}
|
}
|
||||||
var result []string
|
var result []string
|
||||||
for _, assignment := range dp.PortAssignments {
|
for _, assignment := range dp.PortAssignments {
|
||||||
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||||
result = append(result, *assignment.CustomerName)
|
result = append(result, *assignment.CustomerName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If no port assignments, fall back to customer names
|
// If no port assignments, fall back to customer names
|
||||||
if len(result) == 0 {
|
if len(result) == 0 {
|
||||||
for _, name := range dp.CustomerNames {
|
for _, name := range dp.CustomerNames {
|
||||||
if name != "" {
|
if name != "" {
|
||||||
result = append(result, name)
|
result = append(result, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dp *DevicePort) GetPortAssignmentsWithDetails() []PortAssignmentResponse {
|
func (dp *DevicePort) GetPortAssignmentsWithDetails() []PortAssignmentResponse {
|
||||||
// If port_used is 0, return empty array
|
// If port_used is 0, return empty array
|
||||||
|
|
||||||
if dp.Device.DeviceType == "OTB" {
|
if dp.Device.DeviceType == "OTB" {
|
||||||
return []PortAssignmentResponse{}
|
return []PortAssignmentResponse{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dp.PortUsed == 0 {
|
if dp.PortUsed == 0 {
|
||||||
return []PortAssignmentResponse{}
|
return []PortAssignmentResponse{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var result []PortAssignmentResponse
|
var result []PortAssignmentResponse
|
||||||
|
|
||||||
// Step 1: Create a map of all port assignments
|
// Step 1: Create a map of all port assignments
|
||||||
portMap := make(map[int]*PortAssignment)
|
portMap := make(map[int]*PortAssignment)
|
||||||
maxPort := 0
|
maxPort := 0
|
||||||
|
|
||||||
if len(dp.PortAssignments) > 0 {
|
if len(dp.PortAssignments) > 0 {
|
||||||
for _, assignment := range dp.PortAssignments {
|
for _, assignment := range dp.PortAssignments {
|
||||||
portMap[assignment.PortNumber] = &assignment
|
portMap[assignment.PortNumber] = &assignment
|
||||||
if assignment.PortNumber > maxPort {
|
if assignment.PortNumber > maxPort {
|
||||||
maxPort = assignment.PortNumber
|
maxPort = assignment.PortNumber
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback: use CustomerNames array if PortAssignments is empty
|
// Fallback: use CustomerNames array if PortAssignments is empty
|
||||||
for i, name := range dp.CustomerNames {
|
for i, name := range dp.CustomerNames {
|
||||||
portNum := i + 1
|
portNum := i + 1
|
||||||
if name != "" || portNum <= dp.PortUsed {
|
if name != "" || portNum <= dp.PortUsed {
|
||||||
var customerName *string
|
var customerName *string
|
||||||
if name != "" {
|
if name != "" {
|
||||||
customerName = &name
|
customerName = &name
|
||||||
}
|
}
|
||||||
assignment := PortAssignment{
|
assignment := PortAssignment{
|
||||||
PortNumber: portNum,
|
PortNumber: portNum,
|
||||||
CustomerName: customerName,
|
CustomerName: customerName,
|
||||||
Status: PortStatusOn, // Default status
|
Status: PortStatusOn, // Default status
|
||||||
Bandwidth: nil, // Default bandwidth
|
Bandwidth: nil, // Default bandwidth
|
||||||
}
|
}
|
||||||
portMap[portNum] = &assignment
|
portMap[portNum] = &assignment
|
||||||
if portNum > maxPort {
|
if portNum > maxPort {
|
||||||
maxPort = portNum
|
maxPort = portNum
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure maxPort is at least equal to port_used
|
// Ensure maxPort is at least equal to port_used
|
||||||
if maxPort < dp.PortUsed {
|
if maxPort < dp.PortUsed {
|
||||||
maxPort = dp.PortUsed
|
maxPort = dp.PortUsed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Determine which ports should be occupied
|
// Step 2: Determine which ports should be occupied
|
||||||
portsWithCustomers := make([]int, 0)
|
portsWithCustomers := make([]int, 0)
|
||||||
for portNum, assignment := range portMap {
|
for portNum, assignment := range portMap {
|
||||||
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||||
portsWithCustomers = append(portsWithCustomers, portNum)
|
portsWithCustomers = append(portsWithCustomers, portNum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort to get them in order
|
// Sort to get them in order
|
||||||
sort.Ints(portsWithCustomers)
|
sort.Ints(portsWithCustomers)
|
||||||
|
// Step 3: Determine occupied ports - USE EXPLICIT IsOccupied FIELD
|
||||||
|
occupiedPorts := make(map[int]bool)
|
||||||
|
|
||||||
// Step 3: Determine occupied ports
|
// For modern approach: use the explicitly stored IsOccupied field
|
||||||
occupiedPorts := make(map[int]bool)
|
for portNum, assignment := range portMap {
|
||||||
|
// Use the explicitly stored IsOccupied value
|
||||||
|
// If not set, fallback to checking customer name presence
|
||||||
|
isOccupied := assignment.IsOccupied
|
||||||
|
if !isOccupied && assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||||
|
isOccupied = true
|
||||||
|
}
|
||||||
|
|
||||||
// All ports with customers are occupied
|
occupiedPorts[portNum] = isOccupied
|
||||||
for _, portNum := range portsWithCustomers {
|
}
|
||||||
occupiedPorts[portNum] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill remaining slots to reach port_used
|
// Legacy fallback: if we don't have enough explicit port assignments,
|
||||||
occupiedCount := len(portsWithCustomers)
|
// use the old logic only for ports that don't have explicit assignment data
|
||||||
if occupiedCount < dp.PortUsed {
|
currentOccupiedCount := 0
|
||||||
// Need to occupy more ports (ports without customer names but still occupied)
|
for _, isOccupied := range occupiedPorts {
|
||||||
for i := 1; i <= maxPort && occupiedCount < dp.PortUsed; i++ {
|
if isOccupied {
|
||||||
if !occupiedPorts[i] {
|
currentOccupiedCount++
|
||||||
occupiedPorts[i] = true
|
}
|
||||||
occupiedCount++
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4: Create the result for ALL ports up to maxPort
|
// Only apply legacy logic if we have fewer occupied ports than port_used
|
||||||
for i := 1; i <= maxPort; i++ {
|
// and only for ports that don't have explicit assignment data
|
||||||
var customerName *string
|
if currentOccupiedCount < dp.PortUsed {
|
||||||
var status PortStatus = PortStatusOff // Default status for unoccupied ports
|
for i := 1; i <= maxPort && currentOccupiedCount < dp.PortUsed; i++ {
|
||||||
var bandwidth *string
|
if _, hasExplicitData := portMap[i]; !hasExplicitData {
|
||||||
|
if !occupiedPorts[i] {
|
||||||
|
occupiedPorts[i] = true
|
||||||
|
currentOccupiedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if assignment := portMap[i]; assignment != nil {
|
// Step 4: Create the result for ALL ports up to maxPort
|
||||||
customerName = assignment.CustomerName
|
for i := 1; i <= maxPort; i++ {
|
||||||
status = assignment.Status
|
var customerName *string
|
||||||
bandwidth = assignment.Bandwidth
|
var status PortStatus = PortStatusOff // Default status for unoccupied ports
|
||||||
} else if occupiedPorts[i] {
|
var bandwidth *string
|
||||||
// Port is occupied but no assignment data - use defaults
|
|
||||||
status = PortStatusOn
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, PortAssignmentResponse{
|
if assignment := portMap[i]; assignment != nil {
|
||||||
PortNumber: i,
|
customerName = assignment.CustomerName
|
||||||
CustomerName: customerName,
|
status = assignment.Status
|
||||||
IsOccupied: occupiedPorts[i],
|
bandwidth = assignment.Bandwidth
|
||||||
Status: status,
|
}
|
||||||
Bandwidth: bandwidth,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
// Override status for legacy occupied ports (those without explicit assignment)
|
||||||
|
if occupiedPorts[i] && portMap[i] == nil {
|
||||||
|
status = PortStatusOn
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, PortAssignmentResponse{
|
||||||
|
PortNumber: i,
|
||||||
|
CustomerName: customerName,
|
||||||
|
IsOccupied: occupiedPorts[i],
|
||||||
|
Status: status,
|
||||||
|
Bandwidth: bandwidth,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
func (dp *DevicePort) GetCustomerNamesOnly() []string {
|
func (dp *DevicePort) GetCustomerNamesOnly() []string {
|
||||||
if dp.Device.DeviceType == "OTB" {
|
if dp.Device.DeviceType == "OTB" {
|
||||||
return []string{} // OTB devices do not have port assignments
|
return []string{} // OTB devices do not have port assignments
|
||||||
}
|
}
|
||||||
var result []string
|
var result []string
|
||||||
|
|
||||||
// If we have PortAssignments data, use it
|
// If we have PortAssignments data, use it
|
||||||
if len(dp.PortAssignments) > 0 {
|
if len(dp.PortAssignments) > 0 {
|
||||||
for _, assignment := range dp.PortAssignments {
|
for _, assignment := range dp.PortAssignments {
|
||||||
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||||
result = append(result, *assignment.CustomerName)
|
result = append(result, *assignment.CustomerName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback: use CustomerNames directly
|
// Fallback: use CustomerNames directly
|
||||||
for _, name := range dp.CustomerNames {
|
for _, name := range dp.CustomerNames {
|
||||||
if name != "" {
|
if name != "" {
|
||||||
result = append(result, name)
|
result = append(result, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (DevicePort) TableName() string {
|
func (DevicePort) TableName() string {
|
||||||
return "device_ports"
|
return "device_ports"
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue