Spaces:
Running
Running
restructure repo into production layout; add whatsapp-service, tests, ddl, docs, scripts; scrub hardcoded secrets
83d6851 | package instance_handler | |
| import ( | |
| "net/http" | |
| "time" | |
| "github.com/gin-gonic/gin" | |
| config "agentdeck-whatsapp-service/pkg/config" | |
| instance_model "agentdeck-whatsapp-service/pkg/instance/model" | |
| instance_service "agentdeck-whatsapp-service/pkg/instance/service" | |
| "agentdeck-whatsapp-service/pkg/utils" | |
| ) | |
| type InstanceHandler interface { | |
| Create(ctx *gin.Context) | |
| Connect(ctx *gin.Context) | |
| Reconnect(ctx *gin.Context) | |
| Disconnect(ctx *gin.Context) | |
| Logout(ctx *gin.Context) | |
| Delete(ctx *gin.Context) | |
| Status(ctx *gin.Context) | |
| Qr(ctx *gin.Context) | |
| All(ctx *gin.Context) | |
| Info(ctx *gin.Context) | |
| Pair(ctx *gin.Context) | |
| SetProxy(ctx *gin.Context) | |
| DeleteProxy(ctx *gin.Context) | |
| ForceReconnect(ctx *gin.Context) | |
| GetLogs(ctx *gin.Context) | |
| GetAdvancedSettings(ctx *gin.Context) | |
| UpdateAdvancedSettings(ctx *gin.Context) | |
| } | |
| type instanceHandler struct { | |
| config *config.Config | |
| instanceService instance_service.InstanceService | |
| } | |
| // Create a new instance | |
| // @Summary Create a new instance | |
| // @Description Creates a new instance with the provided data including optional advanced settings | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Param instance body instance_service.CreateStruct true "Instance data with optional advanced settings" | |
| // @Success 200 {object} gin.H "Instance created successfully" | |
| // @Failure 400 {object} gin.H "Error on validation" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/create [post] | |
| func (i *instanceHandler) Create(ctx *gin.Context) { | |
| var data *instance_service.CreateStruct | |
| err := ctx.ShouldBindJSON(&data) | |
| if err != nil { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| if data.Name == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "name is required"}) | |
| return | |
| } | |
| if data.Token == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "token is required"}) | |
| return | |
| } | |
| if data.Proxy != nil { | |
| if data.Proxy.Port == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "proxy port is required"}) | |
| return | |
| } | |
| if data.Proxy.Password == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "proxy password is required"}) | |
| return | |
| } | |
| if data.Proxy.Username == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "proxy username is required"}) | |
| return | |
| } | |
| if data.Proxy.Host == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "proxy host is required"}) | |
| return | |
| } | |
| } else { | |
| if i.config.ProxyHost != "" && i.config.ProxyPort != "" && i.config.ProxyUsername != "" && i.config.ProxyPassword != "" { | |
| data.Proxy = &instance_service.ProxyConfig{ | |
| Host: i.config.ProxyHost, | |
| Port: i.config.ProxyPort, | |
| Username: i.config.ProxyUsername, | |
| Password: i.config.ProxyPassword, | |
| } | |
| } | |
| } | |
| createdInstance, err := i.instanceService.Create(data) | |
| if err != nil { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success", "data": createdInstance}) | |
| } | |
| // Connect to instance | |
| // @Summary Connect to instance | |
| // @Description Connect to instance with the provided data | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Param instance body instance_service.ConnectStruct true "Instance data" | |
| // @Success 200 {object} gin.H "Instance connected successfully" | |
| // @Failure 400 {object} gin.H "Error on validation" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/connect [post] | |
| func (i *instanceHandler) Connect(ctx *gin.Context) { | |
| getInstance := ctx.MustGet("instance") | |
| instance, ok := getInstance.(*instance_model.Instance) | |
| if !ok { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": "instance not found"}) | |
| return | |
| } | |
| var data *instance_service.ConnectStruct | |
| err := ctx.ShouldBindBodyWithJSON(&data) | |
| if err != nil { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| instance, jid, eventString, err := i.instanceService.Connect(data, instance) | |
| if err != nil { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx.Set("instance", instance) | |
| responseData := gin.H{ | |
| "jid": jid, | |
| "webhookUrl": instance.Webhook, | |
| "eventString": eventString, | |
| } | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success", "data": responseData}) | |
| } | |
| // Reconnect to instance | |
| // @Summary Reconnect to instance | |
| // @Description Reconnect to instance | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Success 200 {object} gin.H "Instance reconnected successfully" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/reconnect [post] | |
| func (i *instanceHandler) Reconnect(ctx *gin.Context) { | |
| getInstance := ctx.MustGet("instance") | |
| instance, ok := getInstance.(*instance_model.Instance) | |
| if !ok { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": "instance not found"}) | |
| return | |
| } | |
| err := i.instanceService.Reconnect(instance) | |
| if err != nil { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success"}) | |
| } | |
| // Disconnect from instance | |
| // @Summary Disconnect from instance | |
| // @Description Disconnect from instance | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Success 200 {object} gin.H "Instance disconnected successfully" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/disconnect [post] | |
| func (i *instanceHandler) Disconnect(ctx *gin.Context) { | |
| getInstance := ctx.MustGet("instance") | |
| instance, ok := getInstance.(*instance_model.Instance) | |
| if !ok { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": "instance not found"}) | |
| return | |
| } | |
| updateInstance, err := i.instanceService.Disconnect(instance) | |
| if err != nil { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx.Set("instance", updateInstance) | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success"}) | |
| } | |
| // Logout from instance | |
| // @Summary Logout from instance | |
| // @Description Logout from instance | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Success 200 {object} gin.H "Instance logged out successfully" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/logout [delete] | |
| func (i *instanceHandler) Logout(ctx *gin.Context) { | |
| getInstance := ctx.MustGet("instance") | |
| instance, ok := getInstance.(*instance_model.Instance) | |
| if !ok { | |
| ctx.JSON(http.StatusNotFound, gin.H{"error": "instance not found"}) | |
| return | |
| } | |
| updateInstance, err := i.instanceService.Logout(instance) | |
| if err != nil { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx.Set("instance", updateInstance) | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success"}) | |
| } | |
| // Get instance status | |
| // @Summary Get instance status | |
| // @Description Get instance status | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Success 200 {object} gin.H "Instance status" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/status [get] | |
| func (i *instanceHandler) Status(ctx *gin.Context) { | |
| getInstance := ctx.MustGet("instance") | |
| instance, ok := getInstance.(*instance_model.Instance) | |
| if !ok { | |
| ctx.JSON(http.StatusNotFound, gin.H{"error": "instance not found"}) | |
| return | |
| } | |
| status, err := i.instanceService.Status(instance) | |
| if err != nil { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success", "data": status}) | |
| } | |
| // Get instance QR code | |
| // @Summary Get instance QR code | |
| // @Description Get instance QR code | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Success 200 {object} gin.H "Instance QR code" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/qr [get] | |
| func (i *instanceHandler) Qr(ctx *gin.Context) { | |
| getInstance := ctx.MustGet("instance") | |
| instance, ok := getInstance.(*instance_model.Instance) | |
| if !ok { | |
| ctx.JSON(http.StatusNotFound, gin.H{"error": "instance not found"}) | |
| return | |
| } | |
| qrcode, err := i.instanceService.GetQr(instance) | |
| if err != nil { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success", "data": qrcode}) | |
| } | |
| // Request pairing code | |
| // @Summary Request pairing code | |
| // @Description Request pairing code | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Param instance body instance_service.PairStruct true "Instance data" | |
| // @Success 200 {object} gin.H "Pairing code" | |
| // @Failure 400 {object} gin.H "Error on validation" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/pair [post] | |
| func (i *instanceHandler) Pair(ctx *gin.Context) { | |
| getInstance := ctx.MustGet("instance") | |
| instance, ok := getInstance.(*instance_model.Instance) | |
| if !ok { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": "instance not found"}) | |
| return | |
| } | |
| var data *instance_service.PairStruct | |
| err := ctx.ShouldBindBodyWithJSON(&data) | |
| if err != nil { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| if data.Phone == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "phone is required"}) | |
| return | |
| } | |
| pairingCode, err := i.instanceService.Pair(data, instance) | |
| if err != nil { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success", "data": pairingCode}) | |
| } | |
| // Get all instances | |
| // @Summary Get all instances | |
| // @Description Get all instances | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Success 200 {object} gin.H "All instances" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/all [get] | |
| func (i *instanceHandler) All(ctx *gin.Context) { | |
| instances, err := i.instanceService.GetAll() | |
| if err != nil { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success", "data": instances}) | |
| } | |
| // Get instance | |
| // @Summary Get instance | |
| // @Description Get instance | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Param instanceId path string true "Instance Id" | |
| // @Success 200 {object} gin.H "Instance" | |
| // @Failure 400 {object} gin.H "Error on validation" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/info/{instanceId} [get] | |
| func (i *instanceHandler) Info(ctx *gin.Context) { | |
| instanceId := ctx.Param("instanceId") | |
| if instanceId == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "instanceId is required"}) | |
| return | |
| } | |
| instance, err := i.instanceService.Info(instanceId) | |
| if err != nil { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success", "data": instance}) | |
| } | |
| // Delete instance | |
| // @Summary Delete instance | |
| // @Description Delete instance | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Param instanceId path string true "Instance Id" | |
| // @Success 200 {object} gin.H "Instance deleted successfully" | |
| // @Failure 400 {object} gin.H "Error on validation" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/delete/{instanceId} [delete] | |
| func (i *instanceHandler) Delete(ctx *gin.Context) { | |
| instanceId := ctx.Param("instanceId") | |
| if instanceId == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "instanceId is required"}) | |
| return | |
| } | |
| err := i.instanceService.Delete(instanceId) | |
| if err != nil { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success"}) | |
| } | |
| // Set proxy | |
| // @Summary Set proxy configuration | |
| // @Description Set proxy configuration for an instance | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Param instanceId path string true "Instance id" | |
| // @Param proxy body instance_service.SetProxyStruct true "Proxy configuration" | |
| // @Success 200 {object} gin.H "Proxy set successfully" | |
| // @Failure 400 {object} gin.H "Error on validation" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/proxy/{instanceId} [post] | |
| func (i *instanceHandler) SetProxy(ctx *gin.Context) { | |
| instanceId := ctx.Param("instanceId") | |
| if instanceId == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "instanceId is required"}) | |
| return | |
| } | |
| var data *instance_service.SetProxyStruct | |
| err := ctx.ShouldBindBodyWithJSON(&data) | |
| if err != nil { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| // Validate required fields | |
| if data.Host == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "host is required"}) | |
| return | |
| } | |
| if data.Port == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "port is required"}) | |
| return | |
| } | |
| err = i.instanceService.SetProxyFromStruct(instanceId, data) | |
| if err != nil { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| responseData := gin.H{ | |
| "protocol": utils.NormalizeProxyProtocol(data.Protocol, data.Port), | |
| "host": data.Host, | |
| "port": data.Port, | |
| "hasAuth": data.Username != "" && data.Password != "", | |
| } | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success", "data": responseData}) | |
| } | |
| // Delete proxy | |
| // @Summary Delete proxy | |
| // @Description Delete proxy | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Param instanceId path string true "Instance id" | |
| // @Success 200 {object} gin.H "Proxy deleted successfully" | |
| // @Failure 400 {object} gin.H "Error on validation" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/proxy/{instanceId} [delete] | |
| func (i *instanceHandler) DeleteProxy(ctx *gin.Context) { | |
| instanceId := ctx.Param("instanceId") | |
| if instanceId == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "id is required"}) | |
| return | |
| } | |
| err := i.instanceService.RemoveProxy(instanceId) | |
| if err != nil { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success"}) | |
| } | |
| // Force reconnect | |
| // @Summary Force reconnect | |
| // @Description Force reconnect | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Param instanceId path string true "Instance Id" | |
| // @Param instance body instance_service.ForceReconnectStruct true "Instance data" | |
| // @Success 200 {object} gin.H "Instance force reconnected successfully" | |
| // @Failure 400 {object} gin.H "Error on validation" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/forcereconnect/{instanceId} [post] | |
| func (i *instanceHandler) ForceReconnect(ctx *gin.Context) { | |
| instanceId := ctx.Param("instanceId") | |
| if instanceId == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "instanceId is required"}) | |
| return | |
| } | |
| var data *instance_service.ForceReconnectStruct | |
| err := ctx.ShouldBindBodyWithJSON(&data) | |
| if err != nil { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| var number string | |
| if data.Number == "" { | |
| ctx.JSON(http.StatusBadRequest, gin.H{"error": "number is required"}) | |
| return | |
| } | |
| number = data.Number | |
| err = i.instanceService.ForceReconnect(instanceId, number) | |
| if err != nil { | |
| ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx.JSON(http.StatusOK, gin.H{"message": "success"}) | |
| } | |
| type GetLogsQuery struct { | |
| StartDate string `form:"start_date"` | |
| EndDate string `form:"end_date"` | |
| Level string `form:"level"` | |
| Limit int `form:"limit"` | |
| } | |
| // GetLogs returns the log entries for an instance | |
| // @Summary Get instance logs | |
| // @Description Returns log entries for an instance, filterable by date range, level and limit | |
| // @Tags Instance | |
| // @Produce json | |
| // @Param instanceId path string true "Instance Id" | |
| // @Param start_date query string false "Start date (YYYY-MM-DD, defaults to 7 days ago)" | |
| // @Param end_date query string false "End date (YYYY-MM-DD, defaults to now)" | |
| // @Param level query string false "Log level filter" | |
| // @Param limit query int false "Max number of entries" | |
| // @Success 200 {object} gin.H "Logs" | |
| // @Failure 400 {object} gin.H "Error on validation" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/logs/{instanceId} [get] | |
| func (h *instanceHandler) GetLogs(c *gin.Context) { | |
| instanceId := c.Param("instanceId") | |
| var query GetLogsQuery | |
| if err := c.ShouldBindQuery(&query); err != nil { | |
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| // Converte as datas | |
| startDate, err := time.Parse("2006-01-02", query.StartDate) | |
| if err != nil { | |
| startDate = time.Now().AddDate(0, 0, -7) // Default: 7 dias atrás | |
| } | |
| endDate, err := time.Parse("2006-01-02", query.EndDate) | |
| if err != nil { | |
| endDate = time.Now() | |
| } | |
| // Ajusta o endDate para o final do dia | |
| endDate = time.Date(endDate.Year(), endDate.Month(), endDate.Day(), 23, 59, 59, 999999999, time.UTC) | |
| if query.Limit == 0 { | |
| query.Limit = 100 // Default: 100 registros | |
| } | |
| logs, err := h.instanceService.GetLogs(instanceId, startDate, endDate, query.Level, query.Limit) | |
| if err != nil { | |
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| c.JSON(http.StatusOK, logs) | |
| } | |
| // GetAdvancedSettings retrieves advanced settings for an instance | |
| // @Summary Get advanced settings | |
| // @Description Get advanced settings for a specific instance | |
| // @Tags Instance | |
| // @Produce json | |
| // @Param instanceId path string true "Instance ID" | |
| // @Success 200 {object} instance_model.AdvancedSettings "Advanced settings retrieved successfully" | |
| // @Failure 400 {object} gin.H "Invalid instance ID" | |
| // @Failure 404 {object} gin.H "Instance not found" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/{instanceId}/advanced-settings [get] | |
| func (h *instanceHandler) GetAdvancedSettings(c *gin.Context) { | |
| instanceId := c.Param("instanceId") | |
| if instanceId == "" { | |
| c.JSON(http.StatusBadRequest, gin.H{"error": "instanceId is required"}) | |
| return | |
| } | |
| settings, err := h.instanceService.GetAdvancedSettings(instanceId) | |
| if err != nil { | |
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| c.JSON(http.StatusOK, settings) | |
| } | |
| // UpdateAdvancedSettings updates advanced settings for an instance | |
| // @Summary Update advanced settings | |
| // @Description Update advanced settings for a specific instance | |
| // @Tags Instance | |
| // @Accept json | |
| // @Produce json | |
| // @Param instanceId path string true "Instance ID" | |
| // @Param settings body instance_model.AdvancedSettings true "Advanced settings data" | |
| // @Success 200 {object} gin.H "Advanced settings updated successfully" | |
| // @Failure 400 {object} gin.H "Invalid request data" | |
| // @Failure 404 {object} gin.H "Instance not found" | |
| // @Failure 500 {object} gin.H "Internal server error" | |
| // @Router /instance/{instanceId}/advanced-settings [put] | |
| func (h *instanceHandler) UpdateAdvancedSettings(c *gin.Context) { | |
| instanceId := c.Param("instanceId") | |
| if instanceId == "" { | |
| c.JSON(http.StatusBadRequest, gin.H{"error": "instanceId is required"}) | |
| return | |
| } | |
| var settings instance_model.AdvancedSettings | |
| if err := c.ShouldBindJSON(&settings); err != nil { | |
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| err := h.instanceService.UpdateAdvancedSettings(instanceId, &settings) | |
| if err != nil { | |
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| c.JSON(http.StatusOK, gin.H{ | |
| "message": "Advanced settings updated successfully", | |
| "settings": settings, | |
| }) | |
| } | |
| func NewInstanceHandler(instanceService instance_service.InstanceService, config *config.Config) InstanceHandler { | |
| return &instanceHandler{instanceService: instanceService, config: config} | |
| } | |