diff --git a/app/http/endpoints/api/autocloseget.go b/app/http/endpoints/api/autoclose/autocloseget.go similarity index 100% rename from app/http/endpoints/api/autocloseget.go rename to app/http/endpoints/api/autoclose/autocloseget.go diff --git a/app/http/endpoints/api/autoclosepost.go b/app/http/endpoints/api/autoclose/autoclosepost.go similarity index 100% rename from app/http/endpoints/api/autoclosepost.go rename to app/http/endpoints/api/autoclose/autoclosepost.go diff --git a/app/http/endpoints/api/blacklist.go b/app/http/endpoints/api/blacklist/blacklist.go similarity index 100% rename from app/http/endpoints/api/blacklist.go rename to app/http/endpoints/api/blacklist/blacklist.go diff --git a/app/http/endpoints/api/blacklistadd.go b/app/http/endpoints/api/blacklist/blacklistadd.go similarity index 100% rename from app/http/endpoints/api/blacklistadd.go rename to app/http/endpoints/api/blacklist/blacklistadd.go diff --git a/app/http/endpoints/api/blacklistremove.go b/app/http/endpoints/api/blacklist/blacklistremove.go similarity index 100% rename from app/http/endpoints/api/blacklistremove.go rename to app/http/endpoints/api/blacklist/blacklistremove.go diff --git a/app/http/endpoints/api/logslist.go b/app/http/endpoints/api/logs/logslist.go similarity index 100% rename from app/http/endpoints/api/logslist.go rename to app/http/endpoints/api/logs/logslist.go diff --git a/app/http/endpoints/api/modmaillogslist.go b/app/http/endpoints/api/logs/modmaillogslist.go similarity index 100% rename from app/http/endpoints/api/modmaillogslist.go rename to app/http/endpoints/api/logs/modmaillogslist.go diff --git a/app/http/endpoints/api/panel/multipanelcreate.go b/app/http/endpoints/api/panel/multipanelcreate.go new file mode 100644 index 0000000..00582cc --- /dev/null +++ b/app/http/endpoints/api/panel/multipanelcreate.go @@ -0,0 +1,236 @@ +package api + +import ( + "context" + "errors" + "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/premium" + "github.com/TicketsBot/database" + "github.com/gin-gonic/gin" + "github.com/rxdn/gdl/objects/channel" + "github.com/rxdn/gdl/objects/channel/embed" + "github.com/rxdn/gdl/objects/channel/message" + "github.com/rxdn/gdl/rest" + "github.com/rxdn/gdl/rest/request" + gdlutils "github.com/rxdn/gdl/utils" + "golang.org/x/sync/errgroup" +) + +type multiPanelCreateData struct { + Title string `json:"title"` + Content string `json:"content"` + Colour int32 `json:"colour"` + ChannelId uint64 `json:"channel_id,string"` + Panels gdlutils.Uint64StringSlice `json:"panels"` +} + +func MultiPanelCreate(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + var data multiPanelCreateData + if err := ctx.ShouldBindJSON(&data); err != nil { + ctx.JSON(400, utils.ErrorToResponse(err)) + return + } + + // validate body & get sub-panels + panels, err := data.doValidations(guildId) + if err != nil { + ctx.JSON(400, utils.ErrorToResponse(err)) + return + } + + // get bot context + botContext, err := botcontext.ContextForGuild(guildId) + if err != nil { + ctx.AbortWithStatusJSON(500, gin.H{ + "success": false, + "error": err.Error(), + }) + return + } + + // get premium status + premiumTier := rpc.PremiumClient.GetTierByGuildId(guildId, true, botContext.Token, botContext.RateLimiter) + + messageId, err := data.sendEmbed(&botContext, premiumTier > premium.None) + if err != nil { + if err == request.ErrForbidden { + ctx.JSON(500, utils.ErrorToResponse(errors.New("I do not have permission to send messages in the provided channel"))) + } else { + ctx.JSON(500, utils.ErrorToResponse(err)) + } + + return + } + + if err := data.addReactions(&botContext, data.ChannelId, messageId, panels); err != nil { + if err == request.ErrForbidden { + ctx.JSON(500, utils.ErrorToResponse(errors.New("I do not have permission to add reactions in the provided channel"))) + } else { + ctx.JSON(500, utils.ErrorToResponse(err)) + } + + return + } + + multiPanel := database.MultiPanel{ + MessageId: messageId, + ChannelId: data.ChannelId, + GuildId: guildId, + Title: data.Title, + Content: data.Content, + Colour: int(data.Colour), + } + + multiPanel.Id, err = dbclient.Client.MultiPanels.Create(multiPanel) + if err != nil { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + + group, _ := errgroup.WithContext(context.Background()) + for _, panel := range panels { + panel := panel + + group.Go(func() error { + return dbclient.Client.MultiPanelTargets.Insert(multiPanel.Id, panel.MessageId) + }) + } + + if err := group.Wait(); err != nil { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + + ctx.JSON(200, gin.H{ + "success": true, + "data": multiPanel, + }) +} + +func (d *multiPanelCreateData) doValidations(guildId uint64) (panels []database.Panel, err error) { + group, _ := errgroup.WithContext(context.Background()) + + group.Go(d.validateTitle) + group.Go(d.validateContent) + group.Go(d.validateChannel(guildId)) + group.Go(func() (e error) { + panels, e = d.validatePanels(guildId) + return + }) + + err = group.Wait() + return +} + +func (d *multiPanelCreateData) validateTitle() (err error) { + if len(d.Title) > 255 || len(d.Title) < 1 { + err = errors.New("embed title must be between 1 and 255 characters") + } + return +} + +func (d *multiPanelCreateData) validateContent() (err error) { + if len(d.Content) > 1024 || len(d.Title) < 1 { + err = errors.New("embed content must be between 1 and 1024 characters") + } + return +} + +func (d *multiPanelCreateData) validateChannel(guildId uint64) func() error { + return func() (err error) { + channels := cache.Instance.GetGuildChannels(guildId) + + var valid bool + for _, ch := range channels { + if ch.Id == d.ChannelId && ch.Type == channel.ChannelTypeGuildText { + valid = true + break + } + } + + if !valid { + err = errors.New("channel does not exist") + } + + return + } +} + +func (d *multiPanelCreateData) validatePanels(guildId uint64) (panels []database.Panel, err error) { + if len(d.Panels) < 2 { + err = errors.New("a multi-panel must contain at least 2 sub-panels") + return + } + + if len(d.Panels) > 15 { + err = errors.New("multi-panels cannot contain more than 15 sub-panels") + return + } + + existingPanels, err := dbclient.Client.Panel.GetByGuild(guildId) + if err != nil { + return nil, err + } + + for _, panelId := range d.Panels { + var valid bool + // find panel struct + for _, panel := range existingPanels { + if panel.MessageId == panelId { + // check there isn't a panel with the same reaction emote + for _, previous := range panels { + if previous.ReactionEmote == panel.ReactionEmote { + return nil, errors.New("2 sub-panels cannot have the same reaction emotes") + } + } + + valid = true + panels = append(panels, panel) + } + } + + if !valid { + return nil, errors.New("invalid panel ID") + } + } + + return +} + +func (d *multiPanelCreateData) sendEmbed(ctx *botcontext.BotContext, isPremium bool) (messageId uint64, err error) { + e := embed.NewEmbed(). + SetTitle(d.Title). + SetDescription(d.Content). + SetColor(int(d.Colour)) + + if !isPremium { + // TODO: Don't harcode + e.SetFooter("Powered by ticketsbot.net", "https://cdn.discordapp.com/avatars/508391840525975553/ac2647ffd4025009e2aa852f719a8027.png?size=256") + } + + var msg message.Message + msg, err = rest.CreateMessage(ctx.Token, ctx.RateLimiter, d.ChannelId, rest.CreateMessageData{Embed: e}) + if err != nil { + return + } + + messageId = msg.Id + return +} + + +func (d *multiPanelCreateData) addReactions(ctx *botcontext.BotContext, channelId, messageId uint64, panels []database.Panel) (err error) { + for _, panel := range panels { + if err = rest.CreateReaction(ctx.Token, ctx.RateLimiter, channelId, messageId, panel.ReactionEmote); err != nil { + return err + } + } + + return +} \ No newline at end of file diff --git a/app/http/endpoints/api/panel/multipaneldelete.go b/app/http/endpoints/api/panel/multipaneldelete.go new file mode 100644 index 0000000..1899a95 --- /dev/null +++ b/app/http/endpoints/api/panel/multipaneldelete.go @@ -0,0 +1,58 @@ +package api + +import ( + "errors" + "github.com/TicketsBot/GoPanel/botcontext" + dbclient "github.com/TicketsBot/GoPanel/database" + "github.com/TicketsBot/GoPanel/utils" + "github.com/gin-gonic/gin" + "github.com/rxdn/gdl/rest" + "github.com/rxdn/gdl/rest/request" + "strconv" +) + +func MultiPanelDelete(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + multiPanelId, err := strconv.Atoi(ctx.Param("panelid")) + if err != nil { + ctx.JSON(400, utils.ErrorToResponse(err)) + return + } + + // get bot context + botContext, err := botcontext.ContextForGuild(guildId) + if err != nil { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + + panel, ok, err := dbclient.Client.MultiPanels.Get(multiPanelId) + if !ok { + ctx.JSON(404, utils.ErrorToResponse(errors.New("No panel with matching ID found"))) + return + } + + if panel.GuildId != guildId { + ctx.JSON(403, utils.ErrorToResponse(errors.New("Guild ID doesn't match"))) + return + } + + if err := rest.DeleteMessage(botContext.Token, botContext.RateLimiter, panel.ChannelId, panel.MessageId); err != nil && !request.IsClientError(err) { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + + success, err := dbclient.Client.MultiPanels.Delete(guildId, multiPanelId) + if err != nil { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + + if !success { + ctx.JSON(404, utils.ErrorToResponse(errors.New("No panel with matching ID found"))) + return + } + + ctx.JSON(200, utils.SuccessResponse) +} diff --git a/app/http/endpoints/api/panel/multipanellist.go b/app/http/endpoints/api/panel/multipanellist.go new file mode 100644 index 0000000..dc83766 --- /dev/null +++ b/app/http/endpoints/api/panel/multipanellist.go @@ -0,0 +1,56 @@ +package api + +import ( + "context" + dbclient "github.com/TicketsBot/GoPanel/database" + "github.com/TicketsBot/GoPanel/utils" + "github.com/TicketsBot/database" + "github.com/gin-gonic/gin" + "golang.org/x/sync/errgroup" +) + +func MultiPanelList(ctx *gin.Context) { + type multiPanelResponse struct { + database.MultiPanel + Panels []database.Panel `json:"panels"` + } + + guildId := ctx.Keys["guildid"].(uint64) + + multiPanels, err := dbclient.Client.MultiPanels.GetByGuild(guildId) + if err != nil { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + + data := make([]multiPanelResponse, len(multiPanels)) + group, _ := errgroup.WithContext(context.Background()) + for i, multiPanel := range multiPanels { + i := i + multiPanel := multiPanel + + data[i] = multiPanelResponse{ + MultiPanel: multiPanel, + } + + group.Go(func() error { + panels, err := dbclient.Client.MultiPanelTargets.GetPanels(multiPanel.Id) + if err != nil { + return err + } + + data[i].Panels = panels + return nil + }) + } + + if err := group.Wait(); err != nil { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + + ctx.JSON(200, gin.H{ + "success": true, + "data": data, + }) +} diff --git a/app/http/endpoints/api/panel/multipanelupdate.go b/app/http/endpoints/api/panel/multipanelupdate.go new file mode 100644 index 0000000..65dcc5b --- /dev/null +++ b/app/http/endpoints/api/panel/multipanelupdate.go @@ -0,0 +1,143 @@ +package api + +import ( + "context" + "errors" + "github.com/TicketsBot/GoPanel/botcontext" + dbclient "github.com/TicketsBot/GoPanel/database" + "github.com/TicketsBot/GoPanel/rpc" + "github.com/TicketsBot/GoPanel/utils" + "github.com/TicketsBot/common/premium" + "github.com/TicketsBot/database" + "github.com/gin-gonic/gin" + "github.com/rxdn/gdl/rest" + "github.com/rxdn/gdl/rest/request" + "golang.org/x/sync/errgroup" + "strconv" +) + +func MultiPanelUpdate(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + // parse body + var data multiPanelCreateData + if err := ctx.ShouldBindJSON(&data); err != nil { + ctx.JSON(400, utils.ErrorToResponse(err)) + return + } + + // parse panel ID + panelId, err := strconv.Atoi(ctx.Param("panelid")) + if err != nil { + ctx.JSON(400, utils.ErrorToResponse(err)) + return + } + + // retrieve panel from DB + multiPanel, ok, err := dbclient.Client.MultiPanels.Get(panelId) + if err != nil { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + + // check panel exists + if !ok { + ctx.JSON(404, utils.ErrorToResponse(errors.New("No panel with the provided ID found"))) + return + } + + // check panel is in the same guild + if guildId != multiPanel.GuildId { + ctx.JSON(403, utils.ErrorToResponse(errors.New("Guild ID doesn't match"))) + return + } + + // validate body & get sub-panels + panels, err := data.doValidations(guildId) + if err != nil { + ctx.JSON(400, utils.ErrorToResponse(err)) + return + } + + // get bot context + botContext, err := botcontext.ContextForGuild(guildId) + if err != nil { + ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err)) + return + } + + // delete old message + if err := rest.DeleteMessage(botContext.Token, botContext.RateLimiter, multiPanel.ChannelId, multiPanel.MessageId); err != nil && !request.IsClientError(err) { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + + // get premium status + premiumTier := rpc.PremiumClient.GetTierByGuildId(guildId, true, botContext.Token, botContext.RateLimiter) + + // send new message + messageId, err := data.sendEmbed(&botContext, premiumTier > premium.None) + if err != nil { + if err == request.ErrForbidden { + ctx.JSON(500, utils.ErrorToResponse(errors.New("I do not have permission to send messages in the provided channel"))) + } else { + ctx.JSON(500, utils.ErrorToResponse(err)) + } + + return + } + + // add reactions to new message + if err := data.addReactions(&botContext, data.ChannelId, messageId, panels); err != nil { + if err == request.ErrForbidden { + ctx.JSON(500, utils.ErrorToResponse(errors.New("I do not have permission to add reactions in the provided channel"))) + } else { + ctx.JSON(500, utils.ErrorToResponse(err)) + } + + return + } + + // update DB + updated := database.MultiPanel{ + Id: multiPanel.Id, + MessageId: messageId, + ChannelId: data.ChannelId, + GuildId: guildId, + Title: data.Title, + Content: data.Content, + Colour: int(data.Colour), + } + + if err = dbclient.Client.MultiPanels.Update(multiPanel.Id, updated); err != nil { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + + // TODO: one query for ACID purposes + // delete old targets + if err := dbclient.Client.MultiPanelTargets.DeleteAll(multiPanel.Id); err != nil { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + + // insert new targets + group, _ := errgroup.WithContext(context.Background()) + for _, panel := range panels { + panel := panel + + group.Go(func() error { + return dbclient.Client.MultiPanelTargets.Insert(multiPanel.Id, panel.MessageId) + }) + } + + if err := group.Wait(); err != nil { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + + ctx.JSON(200, gin.H{ + "success": true, + "data": multiPanel, + }) +} diff --git a/app/http/endpoints/api/panelcreate.go b/app/http/endpoints/api/panel/panelcreate.go similarity index 98% rename from app/http/endpoints/api/panelcreate.go rename to app/http/endpoints/api/panel/panelcreate.go index 0b8c56d..e29b8c0 100644 --- a/app/http/endpoints/api/panelcreate.go +++ b/app/http/endpoints/api/panel/panelcreate.go @@ -1,6 +1,7 @@ package api import ( + "errors" "github.com/TicketsBot/GoPanel/botcontext" dbclient "github.com/TicketsBot/GoPanel/database" "github.com/TicketsBot/GoPanel/rpc" @@ -64,6 +65,7 @@ func CreatePanel(ctx *gin.Context) { } if !data.doValidations(ctx, guildId) { + ctx.JSON(400, utils.ErrorToResponse(errors.New("Validation failed"))) return } diff --git a/app/http/endpoints/api/paneldelete.go b/app/http/endpoints/api/panel/paneldelete.go similarity index 100% rename from app/http/endpoints/api/paneldelete.go rename to app/http/endpoints/api/panel/paneldelete.go diff --git a/app/http/endpoints/api/panellist.go b/app/http/endpoints/api/panel/panellist.go similarity index 100% rename from app/http/endpoints/api/panellist.go rename to app/http/endpoints/api/panel/panellist.go diff --git a/app/http/endpoints/api/panelupdate.go b/app/http/endpoints/api/panel/panelupdate.go similarity index 64% rename from app/http/endpoints/api/panelupdate.go rename to app/http/endpoints/api/panel/panelupdate.go index 1a1f580..3c6931c 100644 --- a/app/http/endpoints/api/panelupdate.go +++ b/app/http/endpoints/api/panel/panelupdate.go @@ -1,15 +1,20 @@ package api import ( + "context" + "errors" "github.com/TicketsBot/GoPanel/botcontext" dbclient "github.com/TicketsBot/GoPanel/database" "github.com/TicketsBot/GoPanel/rpc" + "github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/common/premium" "github.com/TicketsBot/database" "github.com/gin-gonic/gin" "github.com/rxdn/gdl/rest" "github.com/rxdn/gdl/rest/request" + "golang.org/x/sync/errgroup" "strconv" + "sync" ) func UpdatePanel(ctx *gin.Context) { @@ -17,29 +22,20 @@ func UpdatePanel(ctx *gin.Context) { botContext, err := botcontext.ContextForGuild(guildId) if err != nil { - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err)) return } var data panel if err := ctx.BindJSON(&data); err != nil { - ctx.AbortWithStatusJSON(400, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.AbortWithStatusJSON(400, utils.ErrorToResponse(err)) return } messageId, err := strconv.ParseUint(ctx.Param("message"), 10, 64) if err != nil { - ctx.AbortWithStatusJSON(400, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.AbortWithStatusJSON(400, utils.ErrorToResponse(err)) return } @@ -48,10 +44,7 @@ func UpdatePanel(ctx *gin.Context) { // get existing existing, err := dbclient.Client.Panel.Get(data.MessageId) if err != nil { - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err)) return } @@ -65,6 +58,59 @@ func UpdatePanel(ctx *gin.Context) { } if !data.doValidations(ctx, guildId) { + ctx.JSON(400, utils.ErrorToResponse(errors.New("Validation failed"))) + return + } + + // check if this will break a multi-panel; + // first, get any multipanels this panel belongs to + multiPanels, err := dbclient.Client.MultiPanelTargets.GetMultiPanels(existing.MessageId) + if err != nil { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + + var wouldHaveDuplicateEmote bool + + { + var duplicateLock sync.Mutex + + group, _ := errgroup.WithContext(context.Background()) + for _, multiPanelId := range multiPanels { + multiPanelId := multiPanelId + + group.Go(func() error { + // get the sub-panels of the multi-panel + subPanels, err := dbclient.Client.MultiPanelTargets.GetPanels(multiPanelId) + if err != nil { + return err + } + + for _, subPanel := range subPanels { + if subPanel.MessageId == existing.MessageId { + continue + } + + if subPanel.ReactionEmote == data.Emote { + duplicateLock.Lock() + wouldHaveDuplicateEmote = true + duplicateLock.Unlock() + break + } + } + + return nil + }) + } + + if err := group.Wait(); err != nil { + ctx.JSON(500, utils.ErrorToResponse(err)) + return + } + } + + if wouldHaveDuplicateEmote { + ctx.JSON(400, utils.ErrorToResponse(errors.New("Changing the reaction emote to this value would cause a conflict in a multi-panel"))) return } @@ -98,10 +144,7 @@ func UpdatePanel(ctx *gin.Context) { }) } else { // TODO: Most appropriate error? - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err)) } return @@ -116,10 +159,7 @@ func UpdatePanel(ctx *gin.Context) { }) } else { // TODO: Most appropriate error? - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err)) } return @@ -140,29 +180,20 @@ func UpdatePanel(ctx *gin.Context) { } if err = dbclient.Client.Panel.Update(messageId, panel); err != nil { - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err)) return } // insert role mention data // delete old data if err = dbclient.Client.PanelRoleMentions.DeleteAll(newMessageId); err != nil { - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err)) return } // TODO: Reduce to 1 query if err = dbclient.Client.PanelUserMention.Set(newMessageId, false); err != nil { - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err)) return } @@ -170,19 +201,13 @@ func UpdatePanel(ctx *gin.Context) { for _, mention := range data.Mentions { if mention == "user" { if err = dbclient.Client.PanelUserMention.Set(newMessageId, true); err != nil { - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err)) return } } else { roleId, err := strconv.ParseUint(mention, 10, 64) if err != nil { - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err)) return } @@ -190,10 +215,7 @@ func UpdatePanel(ctx *gin.Context) { // not too much of an issue if it isnt if err = dbclient.Client.PanelRoleMentions.Add(newMessageId, roleId); err != nil { - ctx.AbortWithStatusJSON(500, gin.H{ - "success": false, - "error": err.Error(), - }) + ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err)) return } } diff --git a/app/http/endpoints/api/claimsettings.go b/app/http/endpoints/api/settings/claimsettings.go similarity index 100% rename from app/http/endpoints/api/claimsettings.go rename to app/http/endpoints/api/settings/claimsettings.go diff --git a/app/http/endpoints/api/settings.go b/app/http/endpoints/api/settings/settings.go similarity index 100% rename from app/http/endpoints/api/settings.go rename to app/http/endpoints/api/settings/settings.go diff --git a/app/http/endpoints/api/updateclaimsettings.go b/app/http/endpoints/api/settings/updateclaimsettings.go similarity index 100% rename from app/http/endpoints/api/updateclaimsettings.go rename to app/http/endpoints/api/settings/updateclaimsettings.go diff --git a/app/http/endpoints/api/updatesettings.go b/app/http/endpoints/api/settings/updatesettings.go similarity index 100% rename from app/http/endpoints/api/updatesettings.go rename to app/http/endpoints/api/settings/updatesettings.go diff --git a/app/http/endpoints/api/tagcreate.go b/app/http/endpoints/api/tags/tagcreate.go similarity index 100% rename from app/http/endpoints/api/tagcreate.go rename to app/http/endpoints/api/tags/tagcreate.go diff --git a/app/http/endpoints/api/tagdelete.go b/app/http/endpoints/api/tags/tagdelete.go similarity index 100% rename from app/http/endpoints/api/tagdelete.go rename to app/http/endpoints/api/tags/tagdelete.go diff --git a/app/http/endpoints/api/tagslist.go b/app/http/endpoints/api/tags/tagslist.go similarity index 100% rename from app/http/endpoints/api/tagslist.go rename to app/http/endpoints/api/tags/tagslist.go diff --git a/app/http/endpoints/api/closeticket.go b/app/http/endpoints/api/ticket/closeticket.go similarity index 100% rename from app/http/endpoints/api/closeticket.go rename to app/http/endpoints/api/ticket/closeticket.go diff --git a/app/http/endpoints/api/getticket.go b/app/http/endpoints/api/ticket/getticket.go similarity index 100% rename from app/http/endpoints/api/getticket.go rename to app/http/endpoints/api/ticket/getticket.go diff --git a/app/http/endpoints/api/gettickets.go b/app/http/endpoints/api/ticket/gettickets.go similarity index 100% rename from app/http/endpoints/api/gettickets.go rename to app/http/endpoints/api/ticket/gettickets.go diff --git a/app/http/endpoints/api/sendmessage.go b/app/http/endpoints/api/ticket/sendmessage.go similarity index 100% rename from app/http/endpoints/api/sendmessage.go rename to app/http/endpoints/api/ticket/sendmessage.go diff --git a/app/http/endpoints/api/whitelabelget.go b/app/http/endpoints/api/whitelabel/whitelabelget.go similarity index 100% rename from app/http/endpoints/api/whitelabelget.go rename to app/http/endpoints/api/whitelabel/whitelabelget.go diff --git a/app/http/endpoints/api/whitelabelgeterrors.go b/app/http/endpoints/api/whitelabel/whitelabelgeterrors.go similarity index 100% rename from app/http/endpoints/api/whitelabelgeterrors.go rename to app/http/endpoints/api/whitelabel/whitelabelgeterrors.go diff --git a/app/http/endpoints/api/whitelabelgetguilds.go b/app/http/endpoints/api/whitelabel/whitelabelgetguilds.go similarity index 100% rename from app/http/endpoints/api/whitelabelgetguilds.go rename to app/http/endpoints/api/whitelabel/whitelabelgetguilds.go diff --git a/app/http/endpoints/api/whitelabelpost.go b/app/http/endpoints/api/whitelabel/whitelabelpost.go similarity index 100% rename from app/http/endpoints/api/whitelabelpost.go rename to app/http/endpoints/api/whitelabel/whitelabelpost.go diff --git a/app/http/endpoints/api/whitelabelpostmodmail.go b/app/http/endpoints/api/whitelabel/whitelabelpostmodmail.go similarity index 100% rename from app/http/endpoints/api/whitelabelpostmodmail.go rename to app/http/endpoints/api/whitelabel/whitelabelpostmodmail.go diff --git a/app/http/endpoints/api/whitelabelstatuspost.go b/app/http/endpoints/api/whitelabel/whitelabelstatuspost.go similarity index 100% rename from app/http/endpoints/api/whitelabelstatuspost.go rename to app/http/endpoints/api/whitelabel/whitelabelstatuspost.go diff --git a/app/http/server.go b/app/http/server.go index 47ca281..db9c4c9 100644 --- a/app/http/server.go +++ b/app/http/server.go @@ -3,6 +3,14 @@ package http import ( "fmt" "github.com/TicketsBot/GoPanel/app/http/endpoints/api" + api_autoclose "github.com/TicketsBot/GoPanel/app/http/endpoints/api/autoclose" + api_blacklist "github.com/TicketsBot/GoPanel/app/http/endpoints/api/blacklist" + api_logs "github.com/TicketsBot/GoPanel/app/http/endpoints/api/logs" + api_panels "github.com/TicketsBot/GoPanel/app/http/endpoints/api/panel" + api_settings "github.com/TicketsBot/GoPanel/app/http/endpoints/api/settings" + api_tags "github.com/TicketsBot/GoPanel/app/http/endpoints/api/tags" + api_ticket "github.com/TicketsBot/GoPanel/app/http/endpoints/api/ticket" + api_whitelabel "github.com/TicketsBot/GoPanel/app/http/endpoints/api/whitelabel" "github.com/TicketsBot/GoPanel/app/http/endpoints/manage" "github.com/TicketsBot/GoPanel/app/http/endpoints/root" "github.com/TicketsBot/GoPanel/app/http/middleware" @@ -83,35 +91,40 @@ func StartServer() { guildAuthApiSupport.GET("/user/:user", api.UserHandler) guildAuthApiSupport.GET("/roles", api.RolesHandler) - guildAuthApiAdmin.GET("/settings", api.GetSettingsHandler) - guildAuthApiAdmin.POST("/settings", api.UpdateSettingsHandler) + guildAuthApiAdmin.GET("/settings", api_settings.GetSettingsHandler) + guildAuthApiAdmin.POST("/settings", api_settings.UpdateSettingsHandler) - guildAuthApiSupport.GET("/blacklist", api.GetBlacklistHandler) - guildAuthApiSupport.PUT("/blacklist", api.AddBlacklistHandler) - guildAuthApiSupport.DELETE("/blacklist/:user", api.RemoveBlacklistHandler) + guildAuthApiSupport.GET("/blacklist", api_blacklist.GetBlacklistHandler) + guildAuthApiSupport.PUT("/blacklist", api_blacklist.AddBlacklistHandler) + guildAuthApiSupport.DELETE("/blacklist/:user", api_blacklist.RemoveBlacklistHandler) - guildAuthApiAdmin.GET("/panels", api.ListPanels) - guildAuthApiAdmin.PUT("/panels", api.CreatePanel) - guildAuthApiAdmin.PUT("/panels/:message", api.UpdatePanel) - guildAuthApiAdmin.DELETE("/panels/:message", api.DeletePanel) + guildAuthApiAdmin.GET("/panels", api_panels.ListPanels) + guildAuthApiAdmin.PUT("/panels", api_panels.CreatePanel) + guildAuthApiAdmin.PUT("/panels/:message", api_panels.UpdatePanel) + guildAuthApiAdmin.DELETE("/panels/:message", api_panels.DeletePanel) - guildAuthApiSupport.GET("/logs/", api.GetLogs) - guildAuthApiSupport.GET("/modmail/logs/", api.GetModmailLogs) + guildAuthApiAdmin.GET("/multipanels", api_panels.MultiPanelList) + guildAuthApiAdmin.POST("/multipanels", api_panels.MultiPanelCreate) + guildAuthApiAdmin.PATCH("/multipanels/:panelid", api_panels.MultiPanelUpdate) + guildAuthApiAdmin.DELETE("/multipanels/:panelid", api_panels.MultiPanelDelete) - guildAuthApiSupport.GET("/tickets", api.GetTickets) - guildAuthApiSupport.GET("/tickets/:ticketId", api.GetTicket) - guildAuthApiSupport.POST("/tickets/:ticketId", api.SendMessage) - guildAuthApiSupport.DELETE("/tickets/:ticketId", api.CloseTicket) + guildAuthApiSupport.GET("/logs/", api_logs.GetLogs) + guildAuthApiSupport.GET("/modmail/logs/", api_logs.GetModmailLogs) - guildAuthApiSupport.GET("/tags", api.TagsListHandler) - guildAuthApiSupport.PUT("/tags", api.CreateTag) - guildAuthApiSupport.DELETE("/tags/:tag", api.DeleteTag) + guildAuthApiSupport.GET("/tickets", api_ticket.GetTickets) + guildAuthApiSupport.GET("/tickets/:ticketId", api_ticket.GetTicket) + guildAuthApiSupport.POST("/tickets/:ticketId", api_ticket.SendMessage) + guildAuthApiSupport.DELETE("/tickets/:ticketId", api_ticket.CloseTicket) - guildAuthApiAdmin.GET("/claimsettings", api.GetClaimSettings) - guildAuthApiAdmin.POST("/claimsettings", api.PostClaimSettings) + guildAuthApiSupport.GET("/tags", api_tags.TagsListHandler) + guildAuthApiSupport.PUT("/tags", api_tags.CreateTag) + guildAuthApiSupport.DELETE("/tags/:tag", api_tags.DeleteTag) - guildAuthApiAdmin.GET("/autoclose", api.GetAutoClose) - guildAuthApiAdmin.POST("/autoclose", api.PostAutoClose) + guildAuthApiAdmin.GET("/claimsettings", api_settings.GetClaimSettings) + guildAuthApiAdmin.POST("/claimsettings", api_settings.PostClaimSettings) + + guildAuthApiAdmin.GET("/autoclose", api_autoclose.GetAutoClose) + guildAuthApiAdmin.POST("/autoclose", api_autoclose.PostAutoClose) } userGroup := router.Group("/user", middleware.AuthenticateToken) @@ -123,13 +136,13 @@ func StartServer() { whitelabelGroup := userGroup.Group("/whitelabel", middleware.VerifyWhitelabel(false)) whitelabelApiGroup := userGroup.Group("/whitelabel", middleware.VerifyWhitelabel(true)) - whitelabelGroup.GET("/", api.WhitelabelGet) - whitelabelApiGroup.GET("/errors", api.WhitelabelGetErrors) - whitelabelApiGroup.GET("/guilds", api.WhitelabelGetGuilds) - whitelabelApiGroup.POST("/modmail", api.WhitelabelModmailPost) + whitelabelGroup.GET("/", api_whitelabel.WhitelabelGet) + whitelabelApiGroup.GET("/errors", api_whitelabel.WhitelabelGetErrors) + whitelabelApiGroup.GET("/guilds", api_whitelabel.WhitelabelGetGuilds) + whitelabelApiGroup.POST("/modmail", api_whitelabel.WhitelabelModmailPost) - whitelabelApiGroup.Group("/").Use(createLimiter(10, time.Minute)).POST("/", api.WhitelabelPost) - whitelabelApiGroup.Group("/").Use(createLimiter(1, time.Second * 5)).POST("/status", api.WhitelabelStatusPost) + whitelabelApiGroup.Group("/").Use(createLimiter(10, time.Minute)).POST("/", api_whitelabel.WhitelabelPost) + whitelabelApiGroup.Group("/").Use(createLimiter(1, time.Second * 5)).POST("/status", api_whitelabel.WhitelabelStatusPost) } } @@ -150,7 +163,7 @@ func createRenderer() multitemplate.Renderer { r = addManageTemplate(r, "settings", "./public/templates/includes/substitutionmodal.tmpl") r = addManageTemplate(r, "ticketlist") r = addManageTemplate(r, "ticketview") - r = addManageTemplate(r, "panels", "./public/templates/includes/substitutionmodal.tmpl", "./public/templates/includes/paneleditmodal.tmpl") + r = addManageTemplate(r, "panels", "./public/templates/includes/substitutionmodal.tmpl", "./public/templates/includes/paneleditmodal.tmpl", "./public/templates/includes/multipaneleditmodal.tmpl") r = addManageTemplate(r, "tags") r = addErrorTemplate(r) @@ -180,6 +193,7 @@ func addManageTemplate(renderer multitemplate.Renderer, name string, extra ...st "./public/templates/includes/sidebar.tmpl", "./public/templates/includes/navbar.tmpl", "./public/templates/includes/loadingscreen.tmpl", + "./public/templates/includes/notifymodal.tmpl", fmt.Sprintf("./public/templates/views/%s.tmpl", name), } diff --git a/go.mod b/go.mod index ad5909b..9c4f776 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/BurntSushi/toml v0.3.1 github.com/TicketsBot/archiverclient v0.0.0-20200704164621-09d42dd941e0 github.com/TicketsBot/common v0.0.0-20200702195837-7afe5e77d1df - github.com/TicketsBot/database v0.0.0-20200708121851-08f2e7582b28 + github.com/TicketsBot/database v0.0.0-20200723134637-72f4cd31eef6 github.com/TicketsBot/logarchiver v0.0.0-20200425163447-199b93429026 // indirect github.com/apex/log v1.1.2 github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect diff --git a/public/static/css/style.css b/public/static/css/style.css index c5c258e..38df408 100644 --- a/public/static/css/style.css +++ b/public/static/css/style.css @@ -212,7 +212,7 @@ html > ::-webkit-scrollbar { background-color: #121212; height: 100px; margin-bottom: 10px; - border-radius: 25px; + border-radius: 10px; display: flex; cursor: pointer; } diff --git a/public/templates/includes/multipaneleditmodal.tmpl b/public/templates/includes/multipaneleditmodal.tmpl new file mode 100644 index 0000000..d2c8c8e --- /dev/null +++ b/public/templates/includes/multipaneleditmodal.tmpl @@ -0,0 +1,147 @@ +{{define "multipaneleditmodal"}} + + + +{{end}} \ No newline at end of file diff --git a/public/templates/includes/notifymodal.tmpl b/public/templates/includes/notifymodal.tmpl new file mode 100644 index 0000000..d6ce47f --- /dev/null +++ b/public/templates/includes/notifymodal.tmpl @@ -0,0 +1,52 @@ +{{define "notifymodal"}} + + + +{{end}} \ No newline at end of file diff --git a/public/templates/includes/paneleditmodal.tmpl b/public/templates/includes/paneleditmodal.tmpl index fa76cc1..2792662 100644 --- a/public/templates/includes/paneleditmodal.tmpl +++ b/public/templates/includes/paneleditmodal.tmpl @@ -147,25 +147,15 @@ } const channels = await getChannels(); - await fillChannels('edit-channel-container', channels); - await fillCategories('edit-category-container', channels); + fillChannels('edit-channel-container', channels); + fillCategories('edit-category-container', channels); await fillMentions('edit-mentions'); - setActiveChannel(panel); + setActiveChannel('edit-channel-container', panel.channel_id); setActiveCategory(panel); setActiveMentions(panel); } - function setActiveChannel(panel) { - const select = document.getElementById('edit-channel-container'); - for (let i = 0; i < select.children.length; i++) { - const child = select.children[i]; - if (child.value === panel.channel_id) { - select.selectedIndex = i; - } - } - } - function setActiveCategory(panel) { const select = document.getElementById('edit-category-container'); for (let i = 0; i < select.children.length; i++) { diff --git a/public/templates/layouts/manage.tmpl b/public/templates/layouts/manage.tmpl index 62c8a79..9e92e0b 100644 --- a/public/templates/layouts/manage.tmpl +++ b/public/templates/layouts/manage.tmpl @@ -8,6 +8,10 @@
{{template "navbar" .}} {{template "loadingscreen" .}} + + + {{template "notifymodal" .}} + {{template "content" .}}
diff --git a/public/templates/views/panels.tmpl b/public/templates/views/panels.tmpl index 101db3a..0acbc12 100644 --- a/public/templates/views/panels.tmpl +++ b/public/templates/views/panels.tmpl @@ -1,12 +1,12 @@ {{define "content"}} - {{template "substitutions" .}} {{template "paneleditmodal" .}} + {{template "multipaneleditmodal" .}}
-
+

Reaction Panels

@@ -31,6 +31,30 @@
+
+
+
+
+

Multi-Panels

+
+
+ + + + + + + + + + +
Embed TitleEditDelete
+
+
+
+
+
+

Create A Panel

@@ -133,15 +157,83 @@
+
+
+
+

Create A Multi-Panel

+
+
+ +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+ +
+ +
+
+ +
+ +
+
+
#
+
+ +
+
+
+ +
+
+ + +
+
+ +
+
+
+ +
+
+
+
+
+
+
-
+