package api import ( "fmt" dbclient "github.com/TicketsBot/GoPanel/database" "github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/database" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "strconv" "strings" ) type integrationUpdateBody struct { Name string `json:"name" validate:"required,min=1,max=32"` Description string `json:"description" validate:"required,min=1,max=255"` ImageUrl *string `json:"image_url" validate:"omitempty,url,max=255,startswith=https://"` PrivacyPolicyUrl *string `json:"privacy_policy_url" validate:"omitempty,url,max=255,startswith=https://"` Method string `json:"http_method" validate:"required,oneof=GET POST"` WebhookUrl string `json:"webhook_url" validate:"required,webhook,max=255,startsnotwith=https://discord.com,startsnotwith=https://discord.gg"` ValidationUrl *string `json:"validation_url" validate:"omitempty,url,max=255,startsnotwith=https://discord.com,startsnotwith=https://discord.gg"` Secrets []struct { Id int `json:"id" validate:"omitempty,min=1"` Name string `json:"name" validate:"required,min=1,max=32,excludesall=% "` Description *string `json:"description" validate:"omitempty,max=255"` } `json:"secrets" validate:"dive,omitempty,min=0,max=5"` Headers []struct { Id int `json:"id" validate:"omitempty,min=1"` Name string `json:"name" validate:"required,min=1,max=32,excludes= "` Value string `json:"value" validate:"required,min=1,max=255"` } `json:"headers" validate:"dive,omitempty,min=0,max=5"` Placeholders []struct { Id int `json:"id" validate:"omitempty,min=1"` Placeholder string `json:"name" validate:"required,min=1,max=32,excludesall=% "` JsonPath string `json:"json_path" validate:"required,min=1,max=255"` } `json:"placeholders" validate:"dive,omitempty,min=0,max=15"` } func UpdateIntegrationHandler(ctx *gin.Context) { userId := ctx.Keys["userid"].(uint64) integrationId, err := strconv.Atoi(ctx.Param("integrationid")) if err != nil { ctx.JSON(400, utils.ErrorStr("Invalid integration ID")) return } var data integrationUpdateBody if err := ctx.BindJSON(&data); err != nil { ctx.JSON(400, utils.ErrorJson(err)) return } if err := validate.Struct(data); err != nil { validationErrors, ok := err.(validator.ValidationErrors) if !ok { ctx.JSON(500, utils.ErrorStr("An error occurred while validating the integration")) return } formatted := "Your input contained the following errors:" for _, validationError := range validationErrors { formatted += fmt.Sprintf("\n%s", validationError.Error()) } formatted = strings.TrimSuffix(formatted, "\n") ctx.JSON(400, utils.ErrorStr(formatted)) return } integration, ok, err := dbclient.Client.CustomIntegrations.Get(ctx, integrationId) if err != nil { ctx.JSON(500, utils.ErrorJson(err)) return } if !ok { ctx.JSON(404, utils.ErrorStr("Integration not found")) return } if integration.OwnerId != userId { ctx.JSON(403, utils.ErrorStr("You do not own this integration")) return } if data.ValidationUrl != nil { sameHost, err := isSameValidationUrlHost(data.WebhookUrl, *data.ValidationUrl) if err != nil { ctx.JSON(500, utils.ErrorJson(err)) return } if !sameHost { ctx.JSON(400, utils.ErrorStr("Validation URL must be on the same host as the webhook URL")) return } } // Update integration metadata err = dbclient.Client.CustomIntegrations.Update(ctx, database.CustomIntegration{ Id: integration.Id, OwnerId: integration.OwnerId, HttpMethod: data.Method, WebhookUrl: data.WebhookUrl, ValidationUrl: data.ValidationUrl, Name: data.Name, Description: data.Description, ImageUrl: data.ImageUrl, PrivacyPolicyUrl: data.PrivacyPolicyUrl, Public: integration.Public, Approved: integration.Approved, }) if err != nil { ctx.JSON(500, utils.ErrorJson(err)) return } // Store secrets if !data.updateSecrets(ctx, integration.Id) { return } // Store headers if !data.updateHeaders(ctx, integration.Id) { return } // Store placeholders if !data.updatePlaceholders(ctx, integration.Id) { return } ctx.JSON(200, integration) } func (b *integrationUpdateBody) updatePlaceholders(ctx *gin.Context, integrationId int) bool { // Verify IDs are valid for the integration existingPlaceholders, err := dbclient.Client.CustomIntegrationPlaceholders.GetByIntegration(ctx, integrationId) if err != nil { ctx.JSON(500, utils.ErrorJson(err)) return false } for _, placeholder := range b.Placeholders { if placeholder.Id != 0 { isValid := false inner: for _, existingPlaceholder := range existingPlaceholders { if existingPlaceholder.Id == placeholder.Id { if existingPlaceholder.IntegrationId == integrationId { isValid = true break inner } else { ctx.JSON(400, utils.ErrorStr("Integration ID mismatch for placeholders")) return false } } } if !isValid { ctx.JSON(400, utils.ErrorStr("Integration ID mismatch for placeholders")) return false } } } placeholders := make([]database.CustomIntegrationPlaceholder, len(b.Placeholders)) for i, placeholder := range b.Placeholders { placeholders[i] = database.CustomIntegrationPlaceholder{ Id: placeholder.Id, Name: placeholder.Placeholder, JsonPath: placeholder.JsonPath, } } if _, err := dbclient.Client.CustomIntegrationPlaceholders.Set(ctx, integrationId, placeholders); err != nil { ctx.JSON(500, utils.ErrorJson(err)) return false } return true } func (b *integrationUpdateBody) updateHeaders(ctx *gin.Context, integrationId int) bool { // Verify IDs are valid for the integration existingHeaders, err := dbclient.Client.CustomIntegrationHeaders.GetByIntegration(ctx, integrationId) if err != nil { ctx.JSON(500, utils.ErrorJson(err)) return false } for _, header := range b.Headers { if header.Id != 0 { isValid := false inner: for _, existingHeader := range existingHeaders { if existingHeader.Id == header.Id { if existingHeader.IntegrationId == integrationId { isValid = true break inner } else { ctx.JSON(400, utils.ErrorStr("Integration ID mismatch for headers")) return false } } } if !isValid { ctx.JSON(400, utils.ErrorStr("Integration ID mismatch for headers")) return false } } } headers := make([]database.CustomIntegrationHeader, len(b.Headers)) for i, header := range b.Headers { headers[i] = database.CustomIntegrationHeader{ Id: header.Id, Name: header.Name, Value: header.Value, } } if _, err := dbclient.Client.CustomIntegrationHeaders.CreateOrUpdate(ctx, integrationId, headers); err != nil { ctx.JSON(500, utils.ErrorJson(err)) return false } return true } func (b *integrationUpdateBody) updateSecrets(ctx *gin.Context, integrationId int) bool { // Verify IDs are valid for the integration existingSecrets, err := dbclient.Client.CustomIntegrationSecrets.GetByIntegration(ctx, integrationId) if err != nil { ctx.JSON(500, utils.ErrorJson(err)) return false } for _, secret := range b.Secrets { if secret.Id != 0 { isValid := false inner: for _, existingSecret := range existingSecrets { if existingSecret.Id == secret.Id { if existingSecret.IntegrationId == integrationId { isValid = true break inner } else { ctx.JSON(400, utils.ErrorStr("Integration ID mismatch for secrets")) return false } } } if !isValid { ctx.JSON(400, utils.ErrorStr("Integration ID mismatch for secrets")) return false } } } secrets := make([]database.CustomIntegrationSecret, len(b.Secrets)) for i, secret := range b.Secrets { secrets[i] = database.CustomIntegrationSecret{ Id: secret.Id, Name: secret.Name, Description: secret.Description, } } if _, err := dbclient.Client.CustomIntegrationSecrets.CreateOrUpdate(ctx, integrationId, secrets); err != nil { ctx.JSON(500, utils.ErrorJson(err)) return false } return true }