diff --git a/app/http/endpoints/api/panel/panelcreate.go b/app/http/endpoints/api/panel/panelcreate.go index be91c87..e1be8fe 100644 --- a/app/http/endpoints/api/panel/panelcreate.go +++ b/app/http/endpoints/api/panel/panelcreate.go @@ -1,21 +1,19 @@ package api import ( - "context" "errors" - "fmt" "github.com/TicketsBot/GoPanel/botcontext" dbclient "github.com/TicketsBot/GoPanel/database" "github.com/TicketsBot/GoPanel/rpc" "github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/utils" + "github.com/TicketsBot/common/collections" "github.com/TicketsBot/common/premium" "github.com/TicketsBot/database" "github.com/gin-gonic/gin" "github.com/rxdn/gdl/objects/channel" "github.com/rxdn/gdl/objects/interaction/component" "github.com/rxdn/gdl/rest/request" - "golang.org/x/sync/errgroup" "regexp" "strconv" "strings" @@ -24,21 +22,21 @@ import ( const freePanelLimit = 3 type panelBody struct { - ChannelId uint64 `json:"channel_id,string"` - MessageId uint64 `json:"message_id,string"` - Title string `json:"title"` - Content string `json:"content"` - Colour uint32 `json:"colour"` - CategoryId uint64 `json:"category_id,string"` - Emote string `json:"emote"` - WelcomeMessage *string `json:"welcome_message"` - Mentions []string `json:"mentions"` - WithDefaultTeam bool `json:"default_team"` - Teams []database.SupportTeam `json:"teams"` - ImageUrl *string `json:"image_url,omitempty"` - ThumbnailUrl *string `json:"thumbnail_url,omitempty"` - ButtonStyle component.ButtonStyle `json:"button_style,string"` - FormId *int `json:"form_id"` + ChannelId uint64 `json:"channel_id,string"` + MessageId uint64 `json:"message_id,string"` + Title string `json:"title"` + Content string `json:"content"` + Colour uint32 `json:"colour"` + CategoryId uint64 `json:"category_id,string"` + Emote string `json:"emote"` + WelcomeMessage *string `json:"welcome_message"` + Mentions []string `json:"mentions"` + WithDefaultTeam bool `json:"default_team"` + Teams []int `json:"teams"` + ImageUrl *string `json:"image_url,omitempty"` + ThumbnailUrl *string `json:"thumbnail_url,omitempty"` + ButtonStyle component.ButtonStyle `json:"button_style,string"` + FormId *int `json:"form_id"` } func (p *panelBody) IntoPanelMessageData(customId string, isPremium bool) panelMessageData { @@ -124,10 +122,7 @@ func CreatePanel(ctx *gin.Context) { if err != nil { var unwrapped request.RestError if errors.As(err, &unwrapped) && unwrapped.StatusCode == 403 { - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": "I do not have permission to send messages in the specified channel", - }) + ctx.AbortWithStatusJSON(500, utils.ErrorStr("I do not have permission to send messages in the specified channel")) } else { // TODO: Most appropriate error? ctx.AbortWithStatusJSON(500, gin.H{ @@ -169,40 +164,40 @@ func CreatePanel(ctx *gin.Context) { // insert role mention data // string is role ID or "user" to mention the ticket opener + validRoles, err := getRoleHashSet(guildId) + if err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + var roleMentions []uint64 for _, mention := range data.Mentions { if mention == "user" { if err = dbclient.Client.PanelUserMention.Set(panelId, true); err != nil { - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.JSON(500, utils.ErrorJson(err)) return } } else { roleId, err := strconv.ParseUint(mention, 10, 64) if err != nil { - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.JSON(400, utils.ErrorStr("Invalid role ID")) return } - // should we check the role is a valid role in the guild? - // not too much of an issue if it isnt - - if err = dbclient.Client.PanelRoleMentions.Add(panelId, roleId); err != nil { - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": err.Error(), - }) - return + if validRoles.Contains(roleId) { + roleMentions = append(roleMentions, roleId) } } } - if responseCode, err := insertTeams(guildId, panelId, data.Teams); err != nil { - ctx.JSON(responseCode, utils.ErrorJson(err)) + if err := dbclient.Client.PanelRoleMentions.Replace(panelId, roleMentions); err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + // Already validated, we are safe to insert + if err := dbclient.Client.PanelTeams.Replace(panelId, data.Teams); err != nil { + ctx.JSON(500, utils.ErrorJson(err)) return } @@ -212,29 +207,6 @@ func CreatePanel(ctx *gin.Context) { }) } -// returns (response_code, error) -func insertTeams(guildId uint64, panelId int, teams []database.SupportTeam) (int, error) { - // insert teams - group, _ := errgroup.WithContext(context.Background()) - for _, team := range teams { - group.Go(func() error { - // ensure team exists - exists, err := dbclient.Client.SupportTeam.Exists(team.Id, guildId) - if err != nil { - return err - } - - if !exists { - return fmt.Errorf("team with id %d not found", team.Id) - } - - return dbclient.Client.PanelTeams.Add(panelId, team.Id) - }) - } - - return 500, group.Wait() -} - var urlRegex = regexp.MustCompile(`^https?://([-a-zA-Z0-9@:%._+~#=]{1,256})\.[a-zA-Z0-9()]{1,63}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)$`) func (p *panelBody) doValidations(ctx *gin.Context, guildId uint64) bool { @@ -307,6 +279,19 @@ func (p *panelBody) doValidations(ctx *gin.Context, guildId uint64) bool { return false } + { + valid, err := p.verifyTeams(guildId) + if err != nil { + ctx.AbortWithStatusJSON(500, utils.ErrorJson(err)) + return false + } + + if !valid { + ctx.AbortWithStatusJSON(400, utils.ErrorStr("Invalid teams provided")) + return false + } + } + { ok, err := p.verifyFormId(guildId) if err != nil { @@ -419,3 +404,27 @@ func (p *panelBody) verifyFormId(guildId uint64) (bool, error) { return true, nil } } + +func (p *panelBody) verifyTeams(guildId uint64) (bool, error) { + return dbclient.Client.SupportTeam.AllTeamsExistForGuild(guildId, p.Teams) +} + +func getRoleHashSet(guildId uint64) (*collections.Set[uint64], error) { + ctx, err := botcontext.ContextForGuild(guildId) + if err != nil { + return nil, err + } + + roles, err := ctx.GetGuildRoles(guildId) + if err != nil { + return nil, err + } + + set := collections.NewSet[uint64]() + + for _, role := range roles { + set.Add(role.Id) + } + + return set, nil +} diff --git a/app/http/endpoints/api/panel/panellist.go b/app/http/endpoints/api/panel/panellist.go index 98acabe..d398851 100644 --- a/app/http/endpoints/api/panel/panellist.go +++ b/app/http/endpoints/api/panel/panellist.go @@ -12,8 +12,9 @@ import ( func ListPanels(ctx *gin.Context) { type panelResponse struct { database.Panel - Mentions []string `json:"mentions"` - Teams []database.SupportTeam `json:"teams"` + Mentions []string `json:"mentions"` + //Teams []database.SupportTeam `json:"teams"` + Teams []int `json:"teams"` } guildId := ctx.Keys["guildid"].(uint64) @@ -39,6 +40,16 @@ func ListPanels(ctx *gin.Context) { group.Go(func() error { var mentions []string + // get if we should mention the ticket opener + shouldMention, err := dbclient.Client.PanelUserMention.ShouldMentionUser(p.PanelId) + if err != nil { + return err + } + + if shouldMention { + mentions = append(mentions, "user") + } + // get role mentions roles, err := dbclient.Client.PanelRoleMentions.GetRoles(p.PanelId) if err != nil { @@ -50,25 +61,20 @@ func ListPanels(ctx *gin.Context) { mentions = append(mentions, strconv.FormatUint(roleId, 10)) } - // get if we should mention the ticket opener - shouldMention, err := dbclient.Client.PanelUserMention.ShouldMentionUser(p.PanelId) + teamIds, err := dbclient.Client.PanelTeams.GetTeamIds(p.PanelId) if err != nil { return err } - if shouldMention { - mentions = append(mentions, "user") - } - - teams, err := dbclient.Client.PanelTeams.GetTeams(p.PanelId) - if err != nil { - return err + // Don't serve null + if teamIds == nil { + teamIds = make([]int, 0) } wrapped[i] = panelResponse{ Panel: p, Mentions: mentions, - Teams: teams, + Teams: teamIds, } return nil diff --git a/app/http/endpoints/api/panel/panelupdate.go b/app/http/endpoints/api/panel/panelupdate.go index 0df5f27..37feee9 100644 --- a/app/http/endpoints/api/panel/panelupdate.go +++ b/app/http/endpoints/api/panel/panelupdate.go @@ -77,12 +77,6 @@ func UpdatePanel(ctx *gin.Context) { return } - // TODO: Optimise this - panelIds := make([]int, len(panels)) - for i, panel := range panels { - panelIds[i] = panel.PanelId - } - messageData := multiPanelMessageData{ Title: multiPanel.Title, Content: multiPanel.Content, @@ -167,15 +161,16 @@ func UpdatePanel(ctx *gin.Context) { return } - // insert role mention data - // delete old data - if err = dbclient.Client.PanelRoleMentions.DeleteAll(panel.PanelId); err != nil { - ctx.AbortWithStatusJSON(500, utils.ErrorJson(err)) + // insert mention data + validRoles, err := getRoleHashSet(guildId) + if err != nil { + ctx.JSON(500, utils.ErrorJson(err)) return } // string is role ID or "user" to mention the ticket opener var shouldMentionUser bool + var roleMentions []uint64 for _, mention := range data.Mentions { if mention == "user" { shouldMentionUser = true @@ -186,35 +181,27 @@ func UpdatePanel(ctx *gin.Context) { return } - // should we check the role is a valid role in the guild? - // not too much of an issue if it isnt - if err = dbclient.Client.PanelRoleMentions.Add(panel.PanelId, roleId); err != nil { - ctx.AbortWithStatusJSON(500, utils.ErrorJson(err)) - return + if validRoles.Contains(roleId) { + roleMentions = append(roleMentions, roleId) } } } - if err = dbclient.Client.PanelUserMention.Set(panel.PanelId, shouldMentionUser); err != nil { + if err := dbclient.Client.PanelUserMention.Set(panel.PanelId, shouldMentionUser); err != nil { ctx.AbortWithStatusJSON(500, utils.ErrorJson(err)) return } - // insert support teams - // TODO: Stop race conditions - 1 transaction - // delete teams - if err := dbclient.Client.PanelTeams.DeleteAll(panel.PanelId); err != nil { + if err := dbclient.Client.PanelRoleMentions.Replace(panel.PanelId, roleMentions); err != nil { ctx.JSON(500, utils.ErrorJson(err)) return } - // insert new - if responseCode, err := insertTeams(guildId, panel.PanelId, data.Teams); err != nil { - ctx.JSON(responseCode, utils.ErrorJson(err)) + // We are safe to insert, team IDs already validated + if err := dbclient.Client.PanelTeams.Replace(panel.PanelId, data.Teams); err != nil { + ctx.JSON(500, utils.ErrorJson(err)) return } - ctx.JSON(200, gin.H{ - "success": true, - }) + ctx.JSON(200, utils.SuccessResponse) } diff --git a/frontend/src/components/PanelDropdown.svelte b/frontend/src/components/PanelDropdown.svelte index 898ffdc..670b846 100644 --- a/frontend/src/components/PanelDropdown.svelte +++ b/frontend/src/components/PanelDropdown.svelte @@ -2,7 +2,8 @@