multipanels
This commit is contained in:
parent
e61295dd3c
commit
189ec33874
236
app/http/endpoints/api/panel/multipanelcreate.go
Normal file
236
app/http/endpoints/api/panel/multipanelcreate.go
Normal file
@ -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
|
||||
}
|
58
app/http/endpoints/api/panel/multipaneldelete.go
Normal file
58
app/http/endpoints/api/panel/multipaneldelete.go
Normal file
@ -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)
|
||||
}
|
56
app/http/endpoints/api/panel/multipanellist.go
Normal file
56
app/http/endpoints/api/panel/multipanellist.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
143
app/http/endpoints/api/panel/multipanelupdate.go
Normal file
143
app/http/endpoints/api/panel/multipanelupdate.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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),
|
||||
}
|
||||
|
||||
|
2
go.mod
2
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
|
||||
|
@ -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;
|
||||
}
|
||||
|
147
public/templates/includes/multipaneleditmodal.tmpl
Normal file
147
public/templates/includes/multipaneleditmodal.tmpl
Normal file
@ -0,0 +1,147 @@
|
||||
{{define "multipaneleditmodal"}}
|
||||
<div class="modal fade" id="multieditmodal" tabindex="-1" role="dialog" aria-labelledby="multieditmodal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><b>Edit Multi-Panel</b></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<input type="hidden" id="multi-edit-id">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 pr-1">
|
||||
<div class="form-group">
|
||||
<label class="black">Embed Title</label>
|
||||
<input type="text" class="form-control" placeholder="React to open a ticket" id="multi-edit-title">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8 pr-1">
|
||||
<div class="form-group">
|
||||
<label class="black">Embed Content</label>
|
||||
<textarea type="text" class="form-control"
|
||||
placeholder="Let users know which reaction corresponds to which panel. You are able to use emojis here."
|
||||
id="multi-edit-content"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-2 pr-1">
|
||||
<label class="black">Embed Colour</label>
|
||||
<div class="input-group mb-3">
|
||||
<input type="color" class="form-control input-fill" id="multi-edit-colour">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 pr-1">
|
||||
<label class="black">Embed Channel</label>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">#</div>
|
||||
</div>
|
||||
<select class="form-control" id="multi-edit-channel-container">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 pr-1">
|
||||
<div class="form-group">
|
||||
<label class="black" for="mentions">Panels</label>
|
||||
<select class="selectpicker form-control" id="multi-edit-panels" multiple data-live-search="true" data-dropup-auto="false" data-size="5" data-display="static">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary btn-fill" onclick="updateMultiPanel()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function resetMultiEditModal() {
|
||||
clear('multi-edit-title', 'multi-edit-content', 'multi-edit-colour');
|
||||
$('#multi-edit-panels').selectpicker('deselectAll');
|
||||
}
|
||||
|
||||
registerHideListener('multieditmodal');
|
||||
$('#multieditmodal').on('hidden.bs.modal', resetEditModal);
|
||||
|
||||
async function openMultiEditModal(id) {
|
||||
resetMultiEditModal();
|
||||
|
||||
const res = await axios.get('/api/{{.guildId}}/multipanels');
|
||||
if (res.status !== 200) {
|
||||
showToast("Error", res.data);
|
||||
return;
|
||||
}
|
||||
|
||||
const panel = res.data.data.find(panel => panel.id === id);
|
||||
if (panel === undefined) {
|
||||
showToast('Error', 'Panel not found');
|
||||
return;
|
||||
}
|
||||
|
||||
await fillMultiEditData(panel);
|
||||
|
||||
$('#multieditmodal').modal('show');
|
||||
showBackdrop();
|
||||
}
|
||||
|
||||
async function fillMultiEditData(panel) {
|
||||
document.getElementById('multi-edit-id').value = panel.id;
|
||||
document.getElementById('multi-edit-title').value = panel.title;
|
||||
document.getElementById('multi-edit-content').value = panel.content;
|
||||
document.getElementById('multi-edit-colour').value = `#${panel.colour.toString(16)}`;
|
||||
|
||||
const channels = await getChannels();
|
||||
fillChannels('multi-edit-channel-container', channels);
|
||||
setActiveChannel('multi-edit-channel-container', panel.channel_id);
|
||||
|
||||
// fill panel dropdown
|
||||
const res = await axios.get('/api/{{.guildId}}/panels');
|
||||
if (res.status !== 200) {
|
||||
showToast("Error", res.data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$('#multi-edit-panels').selectpicker('val', panel.panels.map(p => p.message_id));
|
||||
}
|
||||
|
||||
async function updateMultiPanel() {
|
||||
const channelContainer = document.getElementById('multi-edit-channel-container');
|
||||
|
||||
const panelId = getValue('multi-edit-id');
|
||||
|
||||
const data = {
|
||||
'title': getValue('multi-edit-title'),
|
||||
'content': getValue('multi-edit-content'),
|
||||
'colour': parseInt(`0x${getValue('multi-edit-colour').slice(1)}`),
|
||||
'channel_id': channelContainer.options[channelContainer.selectedIndex].value,
|
||||
'panels': $('#multi-edit-panels').val()
|
||||
};
|
||||
|
||||
$('#multieditmodal').modal('hide');
|
||||
|
||||
const res = await axios.patch('/api/{{.guildId}}/multipanels/' + panelId, data);
|
||||
|
||||
if (res.status !== 200 || !res.data.success) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// update table
|
||||
const tr = document.getElementById(panelId);
|
||||
tr.children[0].textContent = data.title;
|
||||
|
||||
notify('Success', 'Multi-panel updated');
|
||||
}
|
||||
</script>
|
||||
{{end}}
|
52
public/templates/includes/notifymodal.tmpl
Normal file
52
public/templates/includes/notifymodal.tmpl
Normal file
@ -0,0 +1,52 @@
|
||||
{{define "notifymodal"}}
|
||||
<div class="modal fade" id="notificationmodal" tabindex="-1" role="dialog" aria-labelledby="notificationmodal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><b id="notification-title"></b></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-10 offset-md-1">
|
||||
<p id="notification-message" style="text-align: center"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary btn-fill" onclick="closeNotificationModal()">Dismiss</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
registerHideListener('notificationmodal');
|
||||
|
||||
function notify(title, message) {
|
||||
document.getElementById('notification-title').textContent = title;
|
||||
document.getElementById('notification-message').textContent = message;
|
||||
|
||||
$('#notificationmodal').modal('show');
|
||||
showBackdrop();
|
||||
}
|
||||
|
||||
function notifyError(message) {
|
||||
notify('Error', message);
|
||||
}
|
||||
|
||||
function notifySuccess(message) {
|
||||
notify('Success', message);
|
||||
}
|
||||
|
||||
function closeNotificationModal() {
|
||||
$('#notificationmodal').modal('hide');
|
||||
}
|
||||
</script>
|
||||
{{end}}
|
@ -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++) {
|
||||
|
@ -8,6 +8,10 @@
|
||||
<div class="main-panel" style="width: 100% !important;">
|
||||
{{template "navbar" .}}
|
||||
{{template "loadingscreen" .}}
|
||||
|
||||
<script src="/assets/js/modalbackdrop.js"></script>
|
||||
{{template "notifymodal" .}}
|
||||
|
||||
{{template "content" .}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,12 +1,12 @@
|
||||
{{define "content"}}
|
||||
<script src="/assets/js/modalbackdrop.js"></script>
|
||||
{{template "substitutions" .}}
|
||||
{{template "paneleditmodal" .}}
|
||||
{{template "multipaneleditmodal" .}}
|
||||
|
||||
<div class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">Reaction Panels</h4>
|
||||
@ -31,6 +31,30 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">Multi-Panels</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Embed Title</th>
|
||||
<th>Edit</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="multi-panel-container">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">Create A Panel</h4>
|
||||
@ -133,15 +157,83 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">Create A Multi-Panel</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<form onsubmit="createMultiPanel(); return false;">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="black">Embed Title</label>
|
||||
<input type="text" class="form-control" placeholder="React to open a ticket" id="multi-title">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="black">Embed Content</label>
|
||||
<textarea type="text" class="form-control"
|
||||
placeholder="Let users know which reaction corresponds to which panel. You are able to use emojis here." id="multi-content"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label class="black">Embed Colour</label>
|
||||
<div class="input-group mb-3">
|
||||
<input name="colour" type="color" class="form-control input-fill" value="#7289da" id="multi-colour">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-9">
|
||||
<label class="black">Embed Channel</label>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">#</div>
|
||||
</div>
|
||||
<select class="form-control" id="multi-channel-container">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label class="black" for="mentions">Panels</label>
|
||||
<select class="selectpicker form-control" id="multi-panels" multiple data-live-search="true" data-dropup-auto="false" data-size="5" data-display="static">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 pr-1 offset-md-3">
|
||||
<div class="form-group text-center">
|
||||
<button type="submit" class="btn btn-primary btn-fill" style="width: 100%;"><i class="fas fa-paper-plane"></i> Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
|
||||
<div aria-live="polite" aria-atomic="true" style="position: relative">
|
||||
<div style="position: absolute; right: 10px" id="toast-container">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function getValue(elementId) {
|
||||
return document.getElementById(elementId).value;
|
||||
}
|
||||
|
||||
async function getChannels() {
|
||||
const res = await axios.get('/api/{{.guildId}}/channels');
|
||||
return res.data;
|
||||
@ -169,6 +261,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteMultiPanel(panelId) {
|
||||
const res = await axios.delete('/api/{{.guildId}}/multipanels/' + panelId);
|
||||
|
||||
if (res.status === 200 && res.data.success) {
|
||||
notifySuccess('Multi-panel deleted successfully');
|
||||
|
||||
const el = document.getElementById(panelId);
|
||||
el.parentNode.removeChild(el);
|
||||
} else {
|
||||
notifyError(res.data.error);
|
||||
}
|
||||
}
|
||||
|
||||
async function createPanel() {
|
||||
const title = document.getElementById('title').value;
|
||||
const content = document.getElementById('content').value;
|
||||
@ -196,7 +301,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function fillChannels(elementId, channels) {
|
||||
async function createMultiPanel() {
|
||||
const channelContainer = document.getElementById('multi-channel-container');
|
||||
|
||||
const data = {
|
||||
'title': getValue('multi-title'),
|
||||
'content': getValue('multi-content'),
|
||||
'colour': parseInt(`0x${getValue('multi-colour').slice(1)}`),
|
||||
'channel_id': channelContainer.options[channelContainer.selectedIndex].value,
|
||||
'panels': $('#multi-panels').val()
|
||||
};
|
||||
|
||||
const res = await axios.post('/api/{{.guildId}}/multipanels', data);
|
||||
if (res.status !== 200 || !res.data.success) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
appendMultiPanel(res.data.data);
|
||||
notify('Success', 'Multi-panel created successfully. Note: Don\'t delete the existing panels, or your they will disappear from your multi-panel.');
|
||||
}
|
||||
|
||||
function fillChannels(elementId, channels) {
|
||||
const container = document.getElementById(elementId);
|
||||
|
||||
channels.filter(ch => ch.type === 0).forEach(ch => {
|
||||
@ -207,7 +333,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
async function fillCategories(elementId, channels) {
|
||||
function fillCategories(elementId, channels) {
|
||||
const container = document.getElementById(elementId);
|
||||
|
||||
channels.filter(ch => ch.type === 4).forEach(ch => {
|
||||
@ -218,6 +344,16 @@
|
||||
});
|
||||
}
|
||||
|
||||
function setActiveChannel(elementId, channelId) {
|
||||
const select = document.getElementById(elementId);
|
||||
for (let i = 0; i < select.children.length; i++) {
|
||||
const child = select.children[i];
|
||||
if (child.value === channelId) {
|
||||
select.selectedIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Update on append / delete
|
||||
async function fillPanelQuota(panelCount) {
|
||||
const res = await axios.get('/api/{{.guildId}}/premium');
|
||||
@ -267,6 +403,37 @@
|
||||
container.appendChild(tr);
|
||||
}
|
||||
|
||||
function appendMultiPanel(panel) {
|
||||
const container = document.getElementById('multi-panel-container');
|
||||
|
||||
const tr = document.createElement('tr');
|
||||
tr.id = panel.id;
|
||||
|
||||
appendTd(tr, panel.title);
|
||||
|
||||
// build edit button
|
||||
const editTd = document.createElement('td');
|
||||
const editButton = document.createElement('button');
|
||||
editButton.type = 'button';
|
||||
editButton.classList.add('btn', 'btn-primary', 'btn-fill', 'mx-auto');
|
||||
editButton.appendChild(document.createTextNode('Edit'));
|
||||
editButton.onclick = () => { openMultiEditModal(panel.id) };
|
||||
editTd.appendChild(editButton);
|
||||
tr.appendChild(editTd);
|
||||
|
||||
// build remove button
|
||||
const deleteTd = document.createElement('td');
|
||||
const deleteButton = document.createElement('button');
|
||||
deleteButton.type = 'submit';
|
||||
deleteButton.classList.add('btn', 'btn-primary', 'btn-fill', 'mx-auto');
|
||||
deleteButton.appendChild(document.createTextNode('Delete'));
|
||||
deleteButton.onclick = () => {deleteMultiPanel(panel.id)};
|
||||
deleteTd.appendChild(deleteButton);
|
||||
tr.appendChild(deleteTd);
|
||||
|
||||
container.appendChild(tr);
|
||||
}
|
||||
|
||||
async function fillPanels(channels) {
|
||||
const res = await axios.get('/api/{{.guildId}}/panels');
|
||||
if (res.status !== 200) {
|
||||
@ -278,9 +445,37 @@
|
||||
appendPanel(panel, channels);
|
||||
}
|
||||
|
||||
appendPanelDropdownPanels('multi-panels', res.data);
|
||||
appendPanelDropdownPanels('multi-edit-panels', res.data);
|
||||
|
||||
return res.data.length;
|
||||
}
|
||||
|
||||
async function fillMultiPanels() {
|
||||
const res = await axios.get('/api/{{.guildId}}/multipanels');
|
||||
if (res.status !== 200) {
|
||||
showToast("Error", res.data);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const multiPanel of res.data.data) {
|
||||
appendMultiPanel(multiPanel);
|
||||
}
|
||||
}
|
||||
|
||||
function appendPanelDropdownPanels(elementId, panels) {
|
||||
const select = document.getElementById(elementId);
|
||||
|
||||
for (const panel of panels) {
|
||||
const option = document.createElement('option');
|
||||
option.value = panel.message_id;
|
||||
option.appendChild(document.createTextNode(panel.title));
|
||||
select.appendChild(option);
|
||||
}
|
||||
|
||||
$(`#${elementId}`).selectpicker('refresh');
|
||||
}
|
||||
|
||||
async function fillMentions(elementId) {
|
||||
const select = document.getElementById(elementId);
|
||||
|
||||
@ -313,9 +508,12 @@
|
||||
const channels = await getChannels();
|
||||
const panelCount = await fillPanels(channels);
|
||||
|
||||
await fillMultiPanels();
|
||||
|
||||
await fillPanelQuota(panelCount);
|
||||
await fillChannels('channel-container', channels);
|
||||
await fillCategories('category-container', channels);
|
||||
fillChannels('channel-container', channels);
|
||||
fillChannels('multi-channel-container', channels);
|
||||
fillCategories('category-container', channels);
|
||||
await fillMentions('mentions');
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@
|
||||
document.getElementById('id').value = '';
|
||||
document.getElementById('content').value = '';
|
||||
|
||||
appendTag(data);
|
||||
appendTag(data.id, data.content);
|
||||
} else {
|
||||
showToast('Error', res.data.error);
|
||||
}
|
||||
|
14
utils/requestutils.go
Normal file
14
utils/requestutils.go
Normal file
@ -0,0 +1,14 @@
|
||||
package utils
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func ErrorToResponse(err error) map[string]interface{} {
|
||||
return gin.H {
|
||||
"success": false,
|
||||
"error": err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
var SuccessResponse = gin.H{
|
||||
"success": true,
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user