Support teams
This commit is contained in:
parent
d6951e89ad
commit
741e041190
@ -46,7 +46,7 @@ func GetPermissionLevel(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
if err := group.Wait(); err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -33,14 +33,14 @@ func MultiPanelCreate(ctx *gin.Context) {
|
||||
|
||||
var data multiPanelCreateData
|
||||
if err := ctx.ShouldBindJSON(&data); err != nil {
|
||||
ctx.JSON(400, utils.ErrorToResponse(err))
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// validate body & get sub-panels
|
||||
panels, err := data.doValidations(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorToResponse(err))
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -61,9 +61,9 @@ func MultiPanelCreate(ctx *gin.Context) {
|
||||
if err != nil {
|
||||
var unwrapped request.RestError
|
||||
if errors.As(err, &unwrapped); unwrapped.ErrorCode == 403 {
|
||||
ctx.JSON(500, utils.ErrorToResponse(errors.New("I do not have permission to send messages in the provided channel")))
|
||||
ctx.JSON(500, utils.ErrorJson(errors.New("I do not have permission to send messages in the provided channel")))
|
||||
} else {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
}
|
||||
|
||||
return
|
||||
@ -72,9 +72,9 @@ func MultiPanelCreate(ctx *gin.Context) {
|
||||
if err := data.addReactions(&botContext, data.ChannelId, messageId, panels); err != nil {
|
||||
var unwrapped request.RestError
|
||||
if errors.As(err, &unwrapped); unwrapped.ErrorCode == 403{
|
||||
ctx.JSON(500, utils.ErrorToResponse(errors.New("I do not have permission to add reactions in the provided channel")))
|
||||
ctx.JSON(500, utils.ErrorJson(errors.New("I do not have permission to add reactions in the provided channel")))
|
||||
} else {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
}
|
||||
|
||||
return
|
||||
@ -91,7 +91,7 @@ func MultiPanelCreate(ctx *gin.Context) {
|
||||
|
||||
multiPanel.Id, err = dbclient.Client.MultiPanels.Create(multiPanel)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ func MultiPanelCreate(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
if err := group.Wait(); err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -16,42 +16,42 @@ func MultiPanelDelete(ctx *gin.Context) {
|
||||
|
||||
multiPanelId, err := strconv.Atoi(ctx.Param("panelid"))
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorToResponse(err))
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// get bot context
|
||||
botContext, err := botcontext.ContextForGuild(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(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")))
|
||||
ctx.JSON(404, utils.ErrorJson(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")))
|
||||
ctx.JSON(403, utils.ErrorJson(errors.New("Guild ID doesn't match")))
|
||||
return
|
||||
}
|
||||
|
||||
var unwrapped request.RestError
|
||||
if err := rest.DeleteMessage(botContext.Token, botContext.RateLimiter, panel.ChannelId, panel.MessageId); err != nil && !(errors.As(err, &unwrapped) && unwrapped.IsClientError()) {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
success, err := dbclient.Client.MultiPanels.Delete(guildId, multiPanelId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
if !success {
|
||||
ctx.JSON(404, utils.ErrorToResponse(errors.New("No panel with matching ID found")))
|
||||
ctx.JSON(404, utils.ErrorJson(errors.New("No panel with matching ID found")))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ func MultiPanelList(ctx *gin.Context) {
|
||||
|
||||
multiPanels, err := dbclient.Client.MultiPanels.GetByGuild(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ func MultiPanelList(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
if err := group.Wait(); err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -22,54 +22,54 @@ func MultiPanelUpdate(ctx *gin.Context) {
|
||||
// parse body
|
||||
var data multiPanelCreateData
|
||||
if err := ctx.ShouldBindJSON(&data); err != nil {
|
||||
ctx.JSON(400, utils.ErrorToResponse(err))
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// parse panel ID
|
||||
panelId, err := strconv.Atoi(ctx.Param("panelid"))
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorToResponse(err))
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// retrieve panel from DB
|
||||
multiPanel, ok, err := dbclient.Client.MultiPanels.Get(panelId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// check panel exists
|
||||
if !ok {
|
||||
ctx.JSON(404, utils.ErrorToResponse(errors.New("No panel with the provided ID found")))
|
||||
ctx.JSON(404, utils.ErrorJson(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")))
|
||||
ctx.JSON(403, utils.ErrorJson(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))
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// get bot context
|
||||
botContext, err := botcontext.ContextForGuild(guildId)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// delete old message
|
||||
var unwrapped request.RestError
|
||||
if err := rest.DeleteMessage(botContext.Token, botContext.RateLimiter, multiPanel.ChannelId, multiPanel.MessageId); err != nil && !(errors.As(err, &unwrapped) && unwrapped.IsClientError()) {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -81,9 +81,9 @@ func MultiPanelUpdate(ctx *gin.Context) {
|
||||
if err != nil {
|
||||
var unwrapped request.RestError
|
||||
if errors.As(err, &unwrapped) && unwrapped.ErrorCode == 403 {
|
||||
ctx.JSON(500, utils.ErrorToResponse(errors.New("I do not have permission to send messages in the provided channel")))
|
||||
ctx.JSON(500, utils.ErrorJson(errors.New("I do not have permission to send messages in the provided channel")))
|
||||
} else {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
}
|
||||
|
||||
return
|
||||
@ -93,9 +93,9 @@ func MultiPanelUpdate(ctx *gin.Context) {
|
||||
if err := data.addReactions(&botContext, data.ChannelId, messageId, panels); err != nil {
|
||||
var unwrapped request.RestError
|
||||
if errors.As(err, &unwrapped) && unwrapped.ErrorCode == 403 {
|
||||
ctx.JSON(500, utils.ErrorToResponse(errors.New("I do not have permission to add reactions in the provided channel")))
|
||||
ctx.JSON(500, utils.ErrorJson(errors.New("I do not have permission to add reactions in the provided channel")))
|
||||
} else {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
}
|
||||
|
||||
return
|
||||
@ -113,14 +113,14 @@ func MultiPanelUpdate(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
if err = dbclient.Client.MultiPanels.Update(multiPanel.Id, updated); err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(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))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -135,7 +135,7 @@ func MultiPanelUpdate(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
if err := group.Wait(); err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TicketsBot/GoPanel/botcontext"
|
||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/rpc"
|
||||
@ -15,13 +17,28 @@ import (
|
||||
"github.com/rxdn/gdl/objects/channel/message"
|
||||
"github.com/rxdn/gdl/rest"
|
||||
"github.com/rxdn/gdl/rest/request"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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"`
|
||||
Teams []string `json:"teams"`
|
||||
}
|
||||
|
||||
func CreatePanel(ctx *gin.Context) {
|
||||
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
botContext, err := botcontext.ContextForGuild(guildId)
|
||||
@ -33,7 +50,7 @@ func CreatePanel(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var data panel
|
||||
var data panelBody
|
||||
|
||||
if err := ctx.BindJSON(&data); err != nil {
|
||||
ctx.AbortWithStatusJSON(400, gin.H{
|
||||
@ -120,6 +137,7 @@ func CreatePanel(ctx *gin.Context) {
|
||||
TargetCategory: data.CategoryId,
|
||||
ReactionEmote: emoji,
|
||||
WelcomeMessage: data.WelcomeMessage,
|
||||
WithDefaultTeam: utils.ContainsString(data.Teams, "default"),
|
||||
}
|
||||
|
||||
if err = dbclient.Client.Panel.Create(panel); err != nil {
|
||||
@ -164,13 +182,50 @@ func CreatePanel(ctx *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if responseCode, err := insertTeams(guildId, msgId, data.Teams); err != nil {
|
||||
ctx.JSON(responseCode, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, gin.H{
|
||||
"success": true,
|
||||
"message_id": strconv.FormatUint(msgId, 10),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *panel) doValidations(ctx *gin.Context, guildId uint64) bool {
|
||||
// returns (response_code, error)
|
||||
func insertTeams(guildId, panelMessageId uint64, teamIds []string) (int, error) {
|
||||
// insert teams
|
||||
group, _ := errgroup.WithContext(context.Background())
|
||||
for _, teamId := range teamIds {
|
||||
if teamId == "default" {
|
||||
continue // already handled
|
||||
}
|
||||
|
||||
teamId, err := strconv.Atoi(teamId)
|
||||
if err != nil {
|
||||
return 400, err
|
||||
}
|
||||
|
||||
group.Go(func() error {
|
||||
// ensure team exists
|
||||
exists, err := dbclient.Client.SupportTeam.Exists(teamId, guildId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("team with id %d not found", teamId)
|
||||
}
|
||||
|
||||
return dbclient.Client.PanelTeams.Add(panelMessageId, teamId)
|
||||
})
|
||||
}
|
||||
|
||||
return 500, group.Wait()
|
||||
}
|
||||
|
||||
func (p *panelBody) doValidations(ctx *gin.Context, guildId uint64) bool {
|
||||
if !p.verifyTitle() {
|
||||
ctx.AbortWithStatusJSON(400, gin.H{
|
||||
"success": false,
|
||||
@ -225,22 +280,22 @@ func (p *panel) doValidations(ctx *gin.Context, guildId uint64) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *panel) verifyTitle() bool {
|
||||
func (p *panelBody) verifyTitle() bool {
|
||||
return len(p.Title) > 0 && len(p.Title) < 256
|
||||
}
|
||||
|
||||
func (p *panel) verifyContent() bool {
|
||||
func (p *panelBody) verifyContent() bool {
|
||||
return len(p.Content) > 0 && len(p.Content) < 1025
|
||||
}
|
||||
|
||||
func (p *panel) getEmoji() (emoji string, ok bool) {
|
||||
func (p *panelBody) getEmoji() (emoji string, ok bool) {
|
||||
p.Emote = strings.Replace(p.Emote, ":", "", -1)
|
||||
|
||||
emoji, ok = utils.GetEmoji(p.Emote)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *panel) verifyChannel(channels []channel.Channel) bool {
|
||||
func (p *panelBody) verifyChannel(channels []channel.Channel) bool {
|
||||
var valid bool
|
||||
for _, ch := range channels {
|
||||
if ch.Id == p.ChannelId && ch.Type == channel.ChannelTypeGuildText {
|
||||
@ -252,7 +307,7 @@ func (p *panel) verifyChannel(channels []channel.Channel) bool {
|
||||
return valid
|
||||
}
|
||||
|
||||
func (p *panel) verifyCategory(channels []channel.Channel) bool {
|
||||
func (p *panelBody) verifyCategory(channels []channel.Channel) bool {
|
||||
var valid bool
|
||||
for _, ch := range channels {
|
||||
if ch.Id == p.CategoryId && ch.Type == channel.ChannelTypeGuildCategory {
|
||||
@ -264,11 +319,11 @@ func (p *panel) verifyCategory(channels []channel.Channel) bool {
|
||||
return valid
|
||||
}
|
||||
|
||||
func (p *panel) verifyWelcomeMessage() bool {
|
||||
func (p *panelBody) verifyWelcomeMessage() bool {
|
||||
return p.WelcomeMessage == nil || (len(*p.WelcomeMessage) > 0 && len(*p.WelcomeMessage) < 1025)
|
||||
}
|
||||
|
||||
func (p *panel) sendEmbed(ctx *botcontext.BotContext, isPremium bool) (messageId uint64, err error) {
|
||||
func (p *panelBody) sendEmbed(ctx *botcontext.BotContext, isPremium bool) (messageId uint64, err error) {
|
||||
e := embed.NewEmbed().
|
||||
SetTitle(p.Title).
|
||||
SetDescription(p.Content).
|
||||
|
@ -2,13 +2,15 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/TicketsBot/GoPanel/database"
|
||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type panel struct {
|
||||
func ListPanels(ctx *gin.Context) {
|
||||
type panelResponse struct {
|
||||
ChannelId uint64 `json:"channel_id,string"`
|
||||
MessageId uint64 `json:"message_id,string"`
|
||||
Title string `json:"title"`
|
||||
@ -18,12 +20,13 @@ type panel struct {
|
||||
Emote string `json:"emote"`
|
||||
WelcomeMessage *string `json:"welcome_message"`
|
||||
Mentions []string `json:"mentions"`
|
||||
WithDefaultTeam bool `json:"default_team"`
|
||||
Teams []database.SupportTeam `json:"teams"`
|
||||
}
|
||||
|
||||
func ListPanels(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
panels, err := database.Client.Panel.GetByGuild(guildId)
|
||||
panels, err := dbclient.Client.Panel.GetByGuild(guildId)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(500, gin.H{
|
||||
"success": false,
|
||||
@ -32,7 +35,7 @@ func ListPanels(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
wrapped := make([]panel, len(panels))
|
||||
wrapped := make([]panelResponse, len(panels))
|
||||
|
||||
// we will need to lookup role mentions
|
||||
group, _ := errgroup.WithContext(context.Background())
|
||||
@ -45,7 +48,7 @@ func ListPanels(ctx *gin.Context) {
|
||||
var mentions []string
|
||||
|
||||
// get role mentions
|
||||
roles, err := database.Client.PanelRoleMentions.GetRoles(p.MessageId)
|
||||
roles, err := dbclient.Client.PanelRoleMentions.GetRoles(p.MessageId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -56,7 +59,7 @@ func ListPanels(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
// get if we should mention the ticket opener
|
||||
shouldMention, err := database.Client.PanelUserMention.ShouldMentionUser(p.MessageId)
|
||||
shouldMention, err := dbclient.Client.PanelUserMention.ShouldMentionUser(p.MessageId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -65,7 +68,12 @@ func ListPanels(ctx *gin.Context) {
|
||||
mentions = append(mentions, "user")
|
||||
}
|
||||
|
||||
wrapped[i] = panel{
|
||||
teams, err := dbclient.Client.PanelTeams.GetTeams(p.MessageId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wrapped[i] = panelResponse{
|
||||
MessageId: p.MessageId,
|
||||
ChannelId: p.ChannelId,
|
||||
Title: p.Title,
|
||||
@ -75,6 +83,8 @@ func ListPanels(ctx *gin.Context) {
|
||||
Emote: p.ReactionEmote,
|
||||
WelcomeMessage: p.WelcomeMessage,
|
||||
Mentions: mentions,
|
||||
WithDefaultTeam: p.WithDefaultTeam,
|
||||
Teams: teams,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -22,20 +22,20 @@ func UpdatePanel(ctx *gin.Context) {
|
||||
|
||||
botContext, err := botcontext.ContextForGuild(guildId)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
var data panel
|
||||
var data panelBody
|
||||
|
||||
if err := ctx.BindJSON(&data); err != nil {
|
||||
ctx.AbortWithStatusJSON(400, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
messageId, err := strconv.ParseUint(ctx.Param("message"), 10, 64)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(400, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ func UpdatePanel(ctx *gin.Context) {
|
||||
// get existing
|
||||
existing, err := dbclient.Client.Panel.Get(data.MessageId)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ func UpdatePanel(ctx *gin.Context) {
|
||||
// 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))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -103,13 +103,13 @@ func UpdatePanel(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
if err := group.Wait(); err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(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")))
|
||||
ctx.JSON(400, utils.ErrorJson(errors.New("Changing the reaction emote to this value would cause a conflict in a multi-panel")))
|
||||
return
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ func UpdatePanel(ctx *gin.Context) {
|
||||
})
|
||||
} else {
|
||||
// TODO: Most appropriate error?
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||
}
|
||||
|
||||
return
|
||||
@ -160,7 +160,7 @@ func UpdatePanel(ctx *gin.Context) {
|
||||
})
|
||||
} else {
|
||||
// TODO: Most appropriate error?
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||
}
|
||||
|
||||
return
|
||||
@ -178,23 +178,24 @@ func UpdatePanel(ctx *gin.Context) {
|
||||
TargetCategory: data.CategoryId,
|
||||
ReactionEmote: emoji,
|
||||
WelcomeMessage: data.WelcomeMessage,
|
||||
WithDefaultTeam: utils.ContainsString(data.Teams, "default"),
|
||||
}
|
||||
|
||||
if err = dbclient.Client.Panel.Update(messageId, panel); err != nil {
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// insert role mention data
|
||||
// delete old data
|
||||
if err = dbclient.Client.PanelRoleMentions.DeleteAll(newMessageId); err != nil {
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Reduce to 1 query
|
||||
if err = dbclient.Client.PanelUserMention.Set(newMessageId, false); err != nil {
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -202,13 +203,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, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
roleId, err := strconv.ParseUint(mention, 10, 64)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -216,12 +217,26 @@ 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, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert support teams
|
||||
// TODO: Stop race conditions - 1 transaction
|
||||
// delete teams
|
||||
if err := dbclient.Client.PanelTeams.DeleteAll(newMessageId); err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// insert new
|
||||
if responseCode, err := insertTeams(guildId, newMessageId, data.Teams); err != nil {
|
||||
ctx.JSON(responseCode, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, gin.H{
|
||||
"success": true,
|
||||
"message_id": strconv.FormatUint(newMessageId, 10),
|
||||
|
@ -16,14 +16,14 @@ func ReloadGuildsHandler(ctx *gin.Context) {
|
||||
key := fmt.Sprintf("tickets:dashboard:guildreload:%d", userId)
|
||||
res, err := messagequeue.Client.SetNX(key, 1, time.Second*10).Result()
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
if !res {
|
||||
ttl, err := messagequeue.Client.TTL(key).Result()
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ func ReloadGuildsHandler(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
if err := utils.LoadGuilds(accessToken, userId); err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
31
app/http/endpoints/api/searchmembers.go
Normal file
31
app/http/endpoints/api/searchmembers.go
Normal file
@ -0,0 +1,31 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/botcontext"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SearchMembers(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
botCtx, err := botcontext.ContextForGuild(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
query := ctx.Query("query")
|
||||
if len(query) == 0 || len(query) > 32 {
|
||||
ctx.JSON(400, utils.ErrorStr("Invalid query"))
|
||||
return
|
||||
}
|
||||
|
||||
members, err := botCtx.SearchMembers(guildId, query)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, members)
|
||||
}
|
88
app/http/endpoints/api/team/addmember.go
Normal file
88
app/http/endpoints/api/team/addmember.go
Normal file
@ -0,0 +1,88 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func AddMember(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
snowflake, err := strconv.ParseUint(ctx.Param("snowflake"), 10, 64)
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// get entity type
|
||||
typeParsed, err := strconv.Atoi(ctx.Query("type"))
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
entityType, ok := entityTypes[typeParsed]
|
||||
if !ok {
|
||||
ctx.JSON(400, utils.ErrorStr("Invalid entity type"))
|
||||
return
|
||||
}
|
||||
|
||||
teamId := ctx.Param("teamid")
|
||||
if teamId == "default" {
|
||||
addDefaultMember(ctx, guildId, snowflake, entityType)
|
||||
} else {
|
||||
parsed, err := strconv.Atoi(teamId)
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorStr("Invalid team ID"))
|
||||
return
|
||||
}
|
||||
|
||||
addTeamMember(ctx, parsed, guildId, snowflake, entityType)
|
||||
}
|
||||
}
|
||||
|
||||
func addDefaultMember(ctx *gin.Context, guildId, snowflake uint64, entityType entityType) {
|
||||
var err error
|
||||
switch entityType {
|
||||
case entityTypeUser:
|
||||
err = dbclient.Client.Permissions.AddSupport(guildId, snowflake)
|
||||
case entityTypeRole:
|
||||
err = dbclient.Client.RolePermissions.AddSupport(guildId, snowflake)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, utils.SuccessResponse)
|
||||
}
|
||||
|
||||
func addTeamMember(ctx *gin.Context, teamId int, guildId, snowflake uint64, entityType entityType) {
|
||||
exists, err := dbclient.Client.SupportTeam.Exists(teamId, guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
if !exists {
|
||||
ctx.JSON(404, utils.ErrorStr("Support team with provided ID not found"))
|
||||
return
|
||||
}
|
||||
|
||||
switch entityType {
|
||||
case entityTypeUser:
|
||||
err = dbclient.Client.SupportTeamMembers.Add(teamId, snowflake)
|
||||
case entityTypeRole:
|
||||
err = dbclient.Client.SupportTeamRoles.Add(teamId, snowflake)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, utils.SuccessResponse)
|
||||
}
|
39
app/http/endpoints/api/team/create.go
Normal file
39
app/http/endpoints/api/team/create.go
Normal file
@ -0,0 +1,39 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/TicketsBot/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func CreateTeam(ctx *gin.Context) {
|
||||
type body struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
var data body
|
||||
if err := ctx.BindJSON(&data); err != nil {
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
if len(data.Name) == 0 || len(data.Name) > 32 {
|
||||
ctx.JSON(400, utils.ErrorStr("Team name must be between 1 and 32 characters"))
|
||||
return
|
||||
}
|
||||
|
||||
id, err := dbclient.Client.SupportTeam.Create(guildId, data.Name)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, database.SupportTeam{
|
||||
Id: id,
|
||||
GuildId: guildId,
|
||||
Name: data.Name,
|
||||
})
|
||||
}
|
37
app/http/endpoints/api/team/delete.go
Normal file
37
app/http/endpoints/api/team/delete.go
Normal file
@ -0,0 +1,37 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func DeleteTeam(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
teamId, err := strconv.Atoi(ctx.Param("teamid"))
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// check team belongs to guild
|
||||
exists, err := dbclient.Client.SupportTeam.Exists(teamId, guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
if !exists {
|
||||
ctx.JSON(400, utils.ErrorStr("Team not found"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := dbclient.Client.SupportTeam.Delete(teamId); err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, utils.SuccessResponse)
|
||||
}
|
186
app/http/endpoints/api/team/getmembers.go
Normal file
186
app/http/endpoints/api/team/getmembers.go
Normal file
@ -0,0 +1,186 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/TicketsBot/GoPanel/botcontext"
|
||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
syncutils "github.com/TicketsBot/common/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rxdn/gdl/objects/user"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func GetMembers(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
teamId := ctx.Param("teamid")
|
||||
if teamId == "default" {
|
||||
getDefaultMembers(ctx, guildId)
|
||||
} else {
|
||||
parsed, err := strconv.Atoi(teamId)
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorStr("Invalid team ID"))
|
||||
return
|
||||
}
|
||||
|
||||
getTeamMembers(ctx, parsed, guildId)
|
||||
}
|
||||
}
|
||||
|
||||
func getDefaultMembers(ctx *gin.Context, guildId uint64) {
|
||||
group, _ := errgroup.WithContext(context.Background())
|
||||
|
||||
// get IDs of support users & roles
|
||||
var userIds []uint64
|
||||
group.Go(func() (err error) {
|
||||
userIds, err = dbclient.Client.Permissions.GetSupport(guildId)
|
||||
return
|
||||
})
|
||||
|
||||
var roleIds []uint64
|
||||
group.Go(func() (err error) {
|
||||
roleIds, err = dbclient.Client.RolePermissions.GetSupportRoles(guildId)
|
||||
return
|
||||
})
|
||||
|
||||
if err := group.Wait(); err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
data, err := formatMembers(guildId, userIds, roleIds)
|
||||
if err == nil {
|
||||
ctx.JSON(200, data)
|
||||
} else {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
}
|
||||
}
|
||||
|
||||
func getTeamMembers(ctx *gin.Context, teamId int, guildId uint64) {
|
||||
// Verify team exists
|
||||
exists, err := dbclient.Client.SupportTeam.Exists(teamId, guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
if !exists {
|
||||
ctx.JSON(404, utils.ErrorStr("Support team with provided ID not found"))
|
||||
return
|
||||
}
|
||||
|
||||
group, _ := errgroup.WithContext(context.Background())
|
||||
|
||||
// get IDs of support users & roles
|
||||
var userIds []uint64
|
||||
group.Go(func() (err error) {
|
||||
userIds, err = dbclient.Client.SupportTeamMembers.Get(teamId)
|
||||
return
|
||||
})
|
||||
|
||||
var roleIds []uint64
|
||||
group.Go(func() (err error) {
|
||||
roleIds, err = dbclient.Client.SupportTeamRoles.Get(teamId)
|
||||
return
|
||||
})
|
||||
|
||||
if err := group.Wait(); err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
data, err := formatMembers(guildId, userIds, roleIds)
|
||||
if err == nil {
|
||||
ctx.JSON(200, data)
|
||||
} else {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
}
|
||||
}
|
||||
|
||||
func formatMembers(guildId uint64, userIds, roleIds []uint64) ([]entity, error) {
|
||||
ctx, err := botcontext.ContextForGuild(guildId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get role objects so we can get name
|
||||
roles, err := ctx.GetGuildRoles(guildId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// map role ids to names
|
||||
var data []entity
|
||||
for _, roleId := range roleIds {
|
||||
for _, role := range roles {
|
||||
if roleId == role.Id {
|
||||
data = append(data, entity{
|
||||
Id: roleId,
|
||||
Name: role.Name,
|
||||
Type: entityTypeRole,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// map user ids to names & discrims
|
||||
group, _ := errgroup.WithContext(context.Background())
|
||||
|
||||
users := make(chan user.User)
|
||||
wg := syncutils.NewChannelWaitGroup()
|
||||
wg.Add(len(userIds))
|
||||
|
||||
for _, userId := range userIds {
|
||||
userId := userId
|
||||
|
||||
group.Go(func() error {
|
||||
defer wg.Done()
|
||||
|
||||
user, err := ctx.GetUser(userId)
|
||||
if err != nil {
|
||||
// TODO: Log w sentry
|
||||
return nil // We should skip the error, since it's probably 403 / 404 etc
|
||||
}
|
||||
|
||||
users <- user
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
group.Go(func() error {
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-wg.Wait():
|
||||
break loop
|
||||
case user := <-users:
|
||||
data = append(data, entity{
|
||||
Id: user.Id,
|
||||
Name: fmt.Sprintf("%s#%s", user.Username, user.PadDiscriminator()),
|
||||
Type: entityTypeUser,
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := group.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// sort
|
||||
sort.Slice(data, func(i, j int) bool {
|
||||
if data[i].Type == data[j].Type {
|
||||
return data[i].Id < data[j].Id
|
||||
} else {
|
||||
return data[i].Type > data[j].Type
|
||||
}
|
||||
})
|
||||
|
||||
return data, nil
|
||||
}
|
25
app/http/endpoints/api/team/getteams.go
Normal file
25
app/http/endpoints/api/team/getteams.go
Normal file
@ -0,0 +1,25 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/TicketsBot/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GetTeams(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
teams, err := dbclient.Client.SupportTeam.Get(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// prevent serving null
|
||||
if teams == nil {
|
||||
teams = make([]database.SupportTeam, 0)
|
||||
}
|
||||
|
||||
ctx.JSON(200, teams)
|
||||
}
|
123
app/http/endpoints/api/team/removemember.go
Normal file
123
app/http/endpoints/api/team/removemember.go
Normal file
@ -0,0 +1,123 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/botcontext"
|
||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func RemoveMember(ctx *gin.Context) {
|
||||
guildId, selfId := ctx.Keys["guildid"].(uint64), ctx.Keys["userid"].(uint64)
|
||||
|
||||
snowflake, err := strconv.ParseUint(ctx.Param("snowflake"), 10, 64)
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// get entity type
|
||||
typeParsed, err := strconv.Atoi(ctx.Query("type"))
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
entityType, ok := entityTypes[typeParsed]
|
||||
if !ok {
|
||||
ctx.JSON(400, utils.ErrorStr("Invalid entity type"))
|
||||
return
|
||||
}
|
||||
|
||||
teamId := ctx.Param("teamid")
|
||||
if teamId == "default" {
|
||||
removeDefaultMember(ctx, guildId, selfId, snowflake, entityType)
|
||||
} else {
|
||||
parsed, err := strconv.Atoi(teamId)
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorStr("Invalid team ID"))
|
||||
return
|
||||
}
|
||||
|
||||
removeTeamMember(ctx, parsed, guildId, snowflake, entityType)
|
||||
}
|
||||
}
|
||||
|
||||
func removeDefaultMember(ctx *gin.Context, guildId, selfId, snowflake uint64, entityType entityType) {
|
||||
// permission check
|
||||
var isAdmin bool
|
||||
var err error
|
||||
switch entityType {
|
||||
case entityTypeUser:
|
||||
isAdmin, err = dbclient.Client.Permissions.IsAdmin(guildId, snowflake)
|
||||
case entityTypeRole:
|
||||
isAdmin, err = dbclient.Client.RolePermissions.IsAdmin(snowflake)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
// only guild owner can remove admins
|
||||
if isAdmin {
|
||||
botCtx, err := botcontext.ContextForGuild(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
guild, err := botCtx.GetGuild(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
if guild.OwnerId != selfId {
|
||||
ctx.JSON(403, utils.ErrorStr("Only the server owner can remove admins"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch entityType {
|
||||
case entityTypeUser:
|
||||
err = dbclient.Client.Permissions.RemoveSupport(guildId, snowflake)
|
||||
case entityTypeRole:
|
||||
err = dbclient.Client.RolePermissions.RemoveSupport(guildId, snowflake)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, utils.SuccessResponse)
|
||||
}
|
||||
|
||||
func removeTeamMember(ctx *gin.Context, teamId int, guildId, snowflake uint64, entityType entityType) {
|
||||
exists, err := dbclient.Client.SupportTeam.Exists(teamId, guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
if !exists {
|
||||
ctx.JSON(404, utils.ErrorStr("Support team with provided ID not found"))
|
||||
return
|
||||
}
|
||||
|
||||
switch entityType {
|
||||
case entityTypeUser:
|
||||
err = dbclient.Client.SupportTeamMembers.Delete(teamId, snowflake)
|
||||
case entityTypeRole:
|
||||
err = dbclient.Client.SupportTeamRoles.Delete(teamId, snowflake)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, utils.SuccessResponse)
|
||||
}
|
19
app/http/endpoints/api/team/types.go
Normal file
19
app/http/endpoints/api/team/types.go
Normal file
@ -0,0 +1,19 @@
|
||||
package api
|
||||
|
||||
type entityType int
|
||||
|
||||
const (
|
||||
entityTypeUser entityType = iota
|
||||
entityTypeRole
|
||||
)
|
||||
|
||||
var entityTypes = map[int]entityType{
|
||||
int(entityTypeUser): entityTypeUser,
|
||||
int(entityTypeRole): entityTypeRole,
|
||||
}
|
||||
|
||||
type entity struct {
|
||||
Id uint64 `json:"id,string"`
|
||||
Name string `json:"name"`
|
||||
Type entityType `json:"type"`
|
||||
}
|
@ -61,7 +61,7 @@ func LogViewHandler(ctx *gin.Context) {
|
||||
// Verify the user has permissions to be here
|
||||
permLevel, err := utils.GetPermissionLevel(guildId, userId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ func AuthenticateGuild(isApiMethod bool, requiredPermissionLevel permission.Perm
|
||||
|
||||
permLevel, err := utils.GetPermissionLevel(guild.Id, userId)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
||||
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
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_team "github.com/TicketsBot/GoPanel/app/http/endpoints/api/team"
|
||||
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"
|
||||
@ -89,6 +90,7 @@ func StartServer() {
|
||||
guildAuthApiSupport.GET("/premium", api.PremiumHandler)
|
||||
guildAuthApiSupport.GET("/user/:user", api.UserHandler)
|
||||
guildAuthApiSupport.GET("/roles", api.RolesHandler)
|
||||
guildAuthApiSupport.GET("/members/search", createLimiter(10, time.Second * 30), createLimiter(75, time.Minute * 30), api.SearchMembers)
|
||||
|
||||
guildAuthApiAdmin.GET("/settings", api_settings.GetSettingsHandler)
|
||||
guildAuthApiAdmin.POST("/settings", api_settings.UpdateSettingsHandler)
|
||||
@ -123,6 +125,13 @@ func StartServer() {
|
||||
|
||||
guildAuthApiAdmin.GET("/autoclose", api_autoclose.GetAutoClose)
|
||||
guildAuthApiAdmin.POST("/autoclose", api_autoclose.PostAutoClose)
|
||||
|
||||
guildAuthApiAdmin.GET("/team", api_team.GetTeams)
|
||||
guildAuthApiAdmin.GET("/team/:teamid", createLimiter(5, time.Second * 15), api_team.GetMembers)
|
||||
guildAuthApiAdmin.POST("/team", createLimiter(10, time.Minute), api_team.CreateTeam)
|
||||
guildAuthApiAdmin.PUT("/team/:teamid/:snowflake", createLimiter(5, time.Second * 10), api_team.AddMember)
|
||||
guildAuthApiAdmin.DELETE("/team/:teamid", api_team.DeleteTeam)
|
||||
guildAuthApiAdmin.DELETE("/team/:teamid/:snowflake", createLimiter(30, time.Minute), api_team.RemoveMember)
|
||||
}
|
||||
|
||||
userGroup := router.Group("/user", middleware.AuthenticateToken)
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/rxdn/gdl/objects/channel"
|
||||
"github.com/rxdn/gdl/objects/guild"
|
||||
"github.com/rxdn/gdl/objects/member"
|
||||
"github.com/rxdn/gdl/objects/user"
|
||||
"github.com/rxdn/gdl/rest"
|
||||
"github.com/rxdn/gdl/rest/ratelimit"
|
||||
)
|
||||
@ -76,6 +77,15 @@ func (ctx BotContext) GetGuildMember(guildId, userId uint64) (m member.Member, e
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx BotContext) GetUser(userId uint64) (u user.User, err error) {
|
||||
u, err = rest.GetUser(ctx.Token, ctx.RateLimiter, userId)
|
||||
if err == nil {
|
||||
go cache.Instance.StoreUser(u)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx BotContext) GetGuildRoles(guildId uint64) (roles []guild.Role, err error) {
|
||||
if roles := cache.Instance.GetGuildRoles(guildId); len(roles) > 0 {
|
||||
return roles, nil
|
||||
@ -88,3 +98,17 @@ func (ctx BotContext) GetGuildRoles(guildId uint64) (roles []guild.Role, err err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx BotContext) SearchMembers(guildId uint64, query string) (members []member.Member, err error) {
|
||||
data := rest.SearchGuildMembersData{
|
||||
Query: query,
|
||||
Limit: 100,
|
||||
}
|
||||
|
||||
members, err = rest.SearchGuildMembers(ctx.Token, ctx.RateLimiter, guildId, data)
|
||||
if err == nil {
|
||||
go cache.Instance.StoreMembers(members, guildId)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
4
go.mod
4
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-20210118172556-0b20b84f7df4
|
||||
github.com/TicketsBot/database v0.0.0-20210215164209-6ec5ebcbc399
|
||||
github.com/TicketsBot/database v0.0.0-20210218163040-99158e109ab9
|
||||
github.com/TicketsBot/worker v0.0.0-20210207182653-fabef254ea30
|
||||
github.com/apex/log v1.1.2
|
||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
|
||||
@ -21,7 +21,7 @@ require (
|
||||
github.com/jackc/pgx/v4 v4.7.1
|
||||
github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rxdn/gdl v0.0.0-20210213145645-ea1107cdc3e1
|
||||
github.com/rxdn/gdl v0.0.0-20210215161213-1eb4c25c602c
|
||||
github.com/sirupsen/logrus v1.5.0
|
||||
github.com/ulule/limiter/v3 v3.5.0
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||
|
@ -75,7 +75,6 @@ button.close {
|
||||
|
||||
h1, .h1, h2, .h2, h3, .h3, h4, .h4 {
|
||||
font-weight: 300;
|
||||
margin: 30px 0 15px;
|
||||
}
|
||||
|
||||
h1, .h1 {
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
body, h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6, p, .navbar, .brand, .btn-simple, .alert, a, .td-name, td, button.close {
|
||||
font-family: 'Noto Sans', sans-serif !important;
|
||||
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
@ -102,7 +101,6 @@ html > ::-webkit-scrollbar {
|
||||
}
|
||||
|
||||
.table td, .table th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.close-container {
|
||||
@ -309,7 +307,6 @@ html > ::-webkit-scrollbar {
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -331,14 +328,17 @@ html > ::-webkit-scrollbar {
|
||||
.tcontent-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tcontent-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.team-card-container {
|
||||
display: flex;
|
||||
margin-top: 4%;
|
||||
height: 50%;
|
||||
margin: 4% 0;
|
||||
min-height: 50%;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
@ -346,7 +346,6 @@ html > ::-webkit-scrollbar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
background-color: #272727 !important;
|
||||
@ -371,7 +370,7 @@ html > ::-webkit-scrollbar {
|
||||
.tcard-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 2%;
|
||||
padding: 2%;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
color: white;
|
||||
@ -380,7 +379,120 @@ html > ::-webkit-scrollbar {
|
||||
.flex-center {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.columns {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.team-item {
|
||||
display: flex !important;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.add-search {
|
||||
height: 100% !important;
|
||||
border-top-right-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
.add-search-btn {
|
||||
border-top-left-radius: 0 !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.trow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-dropdown, .search-dropdown:active, .search-dropdown:focus {
|
||||
width: 100%;
|
||||
|
||||
background-color: #2e3136;
|
||||
|
||||
border-top: 1px solid rgba(0,0,0,.125);
|
||||
border-left-color: #2e3136 !important;
|
||||
border-right-color: #2e3136 !important;
|
||||
border-bottom-color: #2e3136 !important;
|
||||
border-radius: 0 !important;
|
||||
|
||||
scrollbar-color: dark;
|
||||
outline: none;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.search-dropdown-item {
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.search-dropdown-item:active, .search-dropdown-item::selection {
|
||||
background-color: #272727;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
width: 100%;
|
||||
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.25);
|
||||
border-top-left-radius: 0 !important;
|
||||
border-top-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
#user-dropdown-wrapper, #role-dropdown-wrapper {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.team-creation-form {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.team-creation-name {
|
||||
display: flex;
|
||||
width: 80% !important;
|
||||
max-width: 320px;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.delete-team-icon {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
#notificationmodal {
|
||||
z-index: 10000 !important;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
function showLoadingScreen() {
|
||||
const content = document.getElementsByClassName('content')[0];
|
||||
const content = document.getElementsByClassName('content')[0] || document.getElementsByClassName('tcontent-container')[0];
|
||||
content.style.display = 'none';
|
||||
document.getElementById('loading-container').style.display = 'block';
|
||||
}
|
||||
@ -7,8 +7,13 @@ function showLoadingScreen() {
|
||||
function hideLoadingScreen() {
|
||||
document.getElementById('loading-container').style.display = 'none';
|
||||
|
||||
const content = document.getElementsByClassName('content')[0];
|
||||
const content = document.getElementsByClassName('content')[0] || document.getElementsByClassName('tcontent-container')[0];
|
||||
if (content.classList.contains('tcontent-container')) {
|
||||
content.style.display = 'flex';
|
||||
} else {
|
||||
content.style.display = 'block';
|
||||
}
|
||||
|
||||
content.classList.add('fade-in');
|
||||
}
|
||||
|
||||
|
@ -26,12 +26,12 @@ function appendTd(tr, content) {
|
||||
return td
|
||||
}
|
||||
|
||||
function appendButton(tr, content, onclick) {
|
||||
function appendButton(tr, content, onclick, ...classList) {
|
||||
const tdRemove = document.createElement('td');
|
||||
const btn = document.createElement('button');
|
||||
|
||||
btn.type = 'submit';
|
||||
btn.classList.add('btn', 'btn-primary', 'btn-fill', 'mx-auto');
|
||||
btn.classList.add('btn', 'btn-primary', 'btn-fill', 'mx-auto', ...classList);
|
||||
btn.appendChild(document.createTextNode(content));
|
||||
btn.onclick = onclick;
|
||||
|
||||
@ -59,3 +59,9 @@ function prependChild(parent, child) {
|
||||
parent.insertBefore(child, parent.children[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function createElement(tag, ...classList) {
|
||||
const el = document.createElement(tag);
|
||||
el.classList.add(...classList);
|
||||
return el;
|
||||
}
|
@ -45,6 +45,10 @@
|
||||
notify('Success', message);
|
||||
}
|
||||
|
||||
function notifyRatelimit() {
|
||||
notifyError("You're doing that too fast: please wait a few seconds and try again");
|
||||
}
|
||||
|
||||
function closeNotificationModal() {
|
||||
$('#notificationmodal').modal('hide');
|
||||
}
|
||||
|
@ -82,13 +82,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 pr-1">
|
||||
<div class="col-md-6 pr-1">
|
||||
<div class="form-group">
|
||||
<label class="black">Mention On Open</label>
|
||||
<select class="selectpicker form-control" id="edit-mentions" multiple data-live-search="true" data-dropup-auto="false" data-size="5" data-display="static">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 pr-1">
|
||||
<div class="form-group">
|
||||
<label class="black">Support Teams</label>
|
||||
<select class="selectpicker form-control" id="edit-teams" multiple data-live-search="true" data-dropup-auto="false" data-size="5" data-display="static">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="edit-message-id">
|
||||
@ -150,10 +157,12 @@
|
||||
fillChannels('edit-channel-container', channels);
|
||||
fillCategories('edit-category-container', channels);
|
||||
await fillMentions('edit-mentions');
|
||||
await fillTeams('edit-teams');
|
||||
|
||||
setActiveChannel('edit-channel-container', panel.channel_id);
|
||||
setActiveCategory(panel);
|
||||
setActiveMentions(panel);
|
||||
setActiveTeams(panel);
|
||||
}
|
||||
|
||||
function setActiveCategory(panel) {
|
||||
@ -172,6 +181,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
function setActiveTeams(panel) {
|
||||
const values = [];
|
||||
if (panel['default_team']) {
|
||||
values.push('default');
|
||||
$('#edit-teams').selectpicker('val', 'default');
|
||||
}
|
||||
|
||||
if (panel.teams) {
|
||||
values.push(...panel.teams.map((t) => t.id));
|
||||
}
|
||||
|
||||
|
||||
$('#edit-teams').selectpicker('val', values);
|
||||
}
|
||||
|
||||
async function updatePanel() {
|
||||
const messageId = document.getElementById('edit-message-id').value;
|
||||
const title = document.getElementById('edit-title').value;
|
||||
@ -188,7 +212,8 @@
|
||||
channel_id: document.getElementById('edit-channel-container').options[document.getElementById('edit-channel-container').selectedIndex].value,
|
||||
category_id: document.getElementById('edit-category-container').options[document.getElementById('edit-category-container').selectedIndex].value,
|
||||
welcome_message: welcomeMessage === '' ? null : welcomeMessage,
|
||||
mentions: $('#edit-mentions').val()
|
||||
mentions: $('#edit-mentions').val(),
|
||||
teams: $('#edit-teams').val()
|
||||
};
|
||||
|
||||
const res = await axios.put('/api/{{.guildId}}/panels/' + messageId, data);
|
||||
|
@ -124,7 +124,7 @@
|
||||
<div class="collapse" id="advanced" style="width: 100%">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-6 pr-1">
|
||||
<div class="col-md-12 pr-1">
|
||||
<div class="form-group">
|
||||
<label class="black">Welcome Message
|
||||
<i class="fas fa-question pointer" onclick="showSubstitutionModal()"></i>
|
||||
@ -132,7 +132,8 @@
|
||||
<textarea type="text" class="form-control" placeholder="If not provided, your server's default welcome message will be used" id="welcome_message"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 pr-1">
|
||||
<div class="form-group">
|
||||
<label class="black" for="mentions">Mention On Open</label>
|
||||
@ -140,6 +141,13 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 pr-1">
|
||||
<div class="form-group">
|
||||
<label class="black" for="teams">Support Teams</label>
|
||||
<select class="selectpicker form-control" id="teams" multiple data-live-search="true" data-dropup-auto="false" data-size="5" data-display="static">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -287,7 +295,8 @@
|
||||
channel_id: document.getElementById('channel-container').options[document.getElementById('channel-container').selectedIndex].value,
|
||||
category_id: document.getElementById('category-container').options[document.getElementById('category-container').selectedIndex].value,
|
||||
welcome_message: welcomeMessage === '' ? null : welcomeMessage,
|
||||
mentions: $('#mentions').val()
|
||||
mentions: $('#mentions').val(),
|
||||
teams: $('#teams').val()
|
||||
};
|
||||
|
||||
const res = await axios.put('/api/{{.guildId}}/panels', data);
|
||||
@ -476,6 +485,7 @@
|
||||
|
||||
async function fillMentions(elementId) {
|
||||
const select = document.getElementById(elementId);
|
||||
select.innerHTML = '';
|
||||
|
||||
// ticket opener
|
||||
const ticketOpener = document.createElement('option');
|
||||
@ -502,6 +512,33 @@
|
||||
$(`#${elementId}`).selectpicker('refresh');
|
||||
}
|
||||
|
||||
async function fillTeams(elementId) {
|
||||
const select = document.getElementById(elementId);
|
||||
select.innerHTML = '';
|
||||
|
||||
const defaultTeam = createElement('option');
|
||||
defaultTeam.value = 'default';
|
||||
defaultTeam.appendChild(document.createTextNode('Default'));
|
||||
select.appendChild(defaultTeam);
|
||||
|
||||
const res = await axios.get('/api/{{.guildId}}/team');
|
||||
if (res.status !== 200) {
|
||||
notifyError(res.data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.data) {
|
||||
for (const team of res.data) {
|
||||
const option = document.createElement('option');
|
||||
option.value = team.id;
|
||||
option.appendChild(document.createTextNode(team.name));
|
||||
select.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
$(`#${elementId}`).selectpicker('refresh');
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
const channels = await getChannels();
|
||||
const panelCount = await fillPanels(channels);
|
||||
@ -513,6 +550,9 @@
|
||||
fillChannels('multi-channel-container', channels);
|
||||
fillCategories('category-container', channels);
|
||||
await fillMentions('mentions');
|
||||
|
||||
await fillTeams('teams');
|
||||
$(`#teams`).selectpicker('val', 'default');
|
||||
}
|
||||
|
||||
withLoadingScreen(loadData);
|
||||
|
@ -6,14 +6,343 @@
|
||||
<span>Support Teams</span>
|
||||
</div>
|
||||
<div class="tcard-body">
|
||||
<h2><b>Create Team</b></h2>
|
||||
<div id="team-creation-wrapper">
|
||||
<form class="team-creation-form" onsubmit="createTeam(); return false">
|
||||
<input type="text" class="form-control team-creation-name" id="team-creation-name" placeholder="Team Name">
|
||||
<button class="btn btn-primary btn-fill" type="submit" style="margin-left: 2%"><i class="fas fa-paper-plane"></i> Create</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h2 style="margin-top: 2%"><b>Manage Teams</b></h2>
|
||||
<div class="flex-center">
|
||||
<label for="team-selection">Select a team</label>
|
||||
<select name="team-selection" style="width: 100%">
|
||||
<option value="default">Default</option>
|
||||
</select>
|
||||
<div class="dropdown" style="width: 100%">
|
||||
<button class="btn btn-primary btn-fill dropdown-toggle" type="button" id="teamDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" style="width: 100%">
|
||||
Select a team
|
||||
</button>
|
||||
|
||||
<div class="dropdown-menu" id="team-selector" aria-labelledby="teamDropdown" style="width: 100%">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column" style="padding-right: 1%">
|
||||
<h3>Manage Members</h3>
|
||||
<table class="table table-hover">
|
||||
<tbody id="members-body">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="column" style="padding-left: 1%">
|
||||
<div class="trow" style="position: relative">
|
||||
<h3>Add Member</h3>
|
||||
<div class="inline">
|
||||
<input type="text" class="form-control add-search" id="add-search-user" placeholder="Username">
|
||||
<button type="button" class="btn btn-primary btn-fill add-search-btn" id="user-search-btn" onclick="searchUser()"><i class="fas fa-search search-icon"></i></button>
|
||||
</div>
|
||||
|
||||
<div id="user-dropdown-wrapper">
|
||||
<select id="add-search-user-dropdown" class="search-dropdown" size="4"></select>
|
||||
<button type="button" class="btn btn-primary btn-fill add-button" onclick="addUser()"><i class="fas fa-plus"></i> Add Selected User</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="trow" style="position:relative;">
|
||||
<h3>Add Role</h3>
|
||||
<div class="inline">
|
||||
<input
|
||||
type="text" class="form-control add-search" id="add-search-role" placeholder="Role Name"
|
||||
oninput="updateRoleSearch()" onfocusin="showRoleDropdown()">
|
||||
|
||||
<div id="role-dropdown-wrapper">
|
||||
<select id="add-search-role-dropdown" class="search-dropdown" size="4"></select>
|
||||
<button type="button" class="btn btn-primary btn-fill add-button" onclick="addRole()"><i class="fas fa-plus"></i> Add Selected Role</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let activeTeam = 'default';
|
||||
let roles = [];
|
||||
|
||||
function showUserDropdown() {
|
||||
const dropdown = document.getElementById('user-dropdown-wrapper');
|
||||
dropdown.style.display = 'block';
|
||||
|
||||
const input = document.getElementById('add-search-user');
|
||||
input.style.borderBottomLeftRadius = '0';
|
||||
|
||||
const searchBtn = document.getElementById('user-search-btn');
|
||||
searchBtn.style.borderBottomRightRadius = '0';
|
||||
}
|
||||
|
||||
function hideUserDropdown() {
|
||||
const dropdown = document.getElementById('user-dropdown-wrapper');
|
||||
dropdown.style.display = 'none';
|
||||
|
||||
const input = document.getElementById('add-search-user');
|
||||
input.style.borderBottomLeftRadius = '4px';
|
||||
|
||||
const searchBtn = document.getElementById('user-search-btn');
|
||||
searchBtn.style.borderBottomRightRadius = '0.25rem';
|
||||
}
|
||||
|
||||
function showRoleDropdown() {
|
||||
const dropdown = document.getElementById('role-dropdown-wrapper');
|
||||
dropdown.style.display = 'block';
|
||||
|
||||
const input = document.getElementById('add-search-role');
|
||||
input.style.borderBottomLeftRadius = '0';
|
||||
|
||||
updateRoleSearch();
|
||||
}
|
||||
|
||||
function hideRoleDropdown() {
|
||||
const dropdown = document.getElementById('role-dropdown-wrapper');
|
||||
dropdown.style.display = 'none';
|
||||
|
||||
const input = document.getElementById('add-search-role');
|
||||
input.style.borderBottomLeftRadius = '4px';
|
||||
}
|
||||
|
||||
function updateRoleSearch() {
|
||||
const input = document.getElementById('add-search-role');
|
||||
|
||||
const dropdown = document.getElementById('add-search-role-dropdown');
|
||||
dropdown.innerHTML = '';
|
||||
|
||||
roles.filter((role) => role.name.toLowerCase().includes(input.value.toLowerCase())).forEach((role) => {
|
||||
const option = createElement('option', 'search-dropdown-item');
|
||||
option.id = role.id;
|
||||
option.appendChild(document.createTextNode(role.name));
|
||||
dropdown.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function searchUser() {
|
||||
showUserDropdown();
|
||||
|
||||
const query = document.getElementById('add-search-user').value;
|
||||
|
||||
const res = await axios.get('/api/{{.guildId}}/members/search?query=' + encodeURIComponent(query));
|
||||
if (res.status !== 200) {
|
||||
if (res.status === 429){
|
||||
notifyRatelimit();
|
||||
} else {
|
||||
notifyError(res.data.error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const dropdown = document.getElementById('add-search-user-dropdown');
|
||||
dropdown.innerHTML = '';
|
||||
for (const member of res.data) {
|
||||
const option = createElement('option', 'search-dropdown-item');
|
||||
option.id = member.user.id;
|
||||
option.appendChild(document.createTextNode(`${member.user.username}#${member.user.discriminator}`));
|
||||
dropdown.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
async function addUser() {
|
||||
const dropdown = document.getElementById('add-search-user-dropdown');
|
||||
const option = dropdown.options[dropdown.selectedIndex];
|
||||
|
||||
if (option === undefined) {
|
||||
notifyError("You need to select a user");
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await axios.put('/api/{{.guildId}}/team/' + activeTeam + '/' + option.id + '?type=0');
|
||||
if (res.status !== 200) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
await displayTeam(activeTeam);
|
||||
notifySuccess("Added user to support team!")
|
||||
}
|
||||
|
||||
async function addRole() {
|
||||
const dropdown = document.getElementById('add-search-role-dropdown');
|
||||
const option = dropdown.options[dropdown.selectedIndex];
|
||||
|
||||
if (option === undefined) {
|
||||
notifyError("You need to select a role");
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await axios.put('/api/{{.guildId}}/team/' + activeTeam + '/' + option.id + '?type=1');
|
||||
if (res.status !== 200) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
await displayTeam(activeTeam);
|
||||
notifySuccess("Added role to support team!")
|
||||
}
|
||||
|
||||
// hide user dropdown
|
||||
window.addEventListener('click', (e) => {
|
||||
const allowedElements = ['search-dropdown', 'search-dropdown-item', 'add-button', 'add-search-btn', 'add-search', 'search-icon'];
|
||||
|
||||
const wrapper = document.getElementById('user-dropdown-wrapper');
|
||||
if (window.getComputedStyle(wrapper).display !== 'none') {
|
||||
if(!allowedElements.some((clazz) => e.target.classList.contains(clazz))) {
|
||||
hideUserDropdown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// hide role dropdown
|
||||
window.addEventListener('click', (e) => {
|
||||
const allowedElements = ['search-dropdown', 'search-dropdown-item', 'add-button', 'add-search'];
|
||||
|
||||
const wrapper = document.getElementById('role-dropdown-wrapper');
|
||||
if (window.getComputedStyle(wrapper).display !== 'none') {
|
||||
if(!allowedElements.some((clazz) => e.target.classList.contains(clazz))) {
|
||||
hideRoleDropdown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function displayTeam(id) {
|
||||
const res = await axios.get('/api/{{.guildId}}/team/' + id);
|
||||
if (res.status !== 200) {
|
||||
if (res.status === 429){
|
||||
notifyRatelimit();
|
||||
} else {
|
||||
notifyError(res.data.error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
activeTeam = id;
|
||||
|
||||
const table = document.getElementById('members-body');
|
||||
table.innerHTML = '';
|
||||
if (res.data) {
|
||||
for (const entity of res.data) {
|
||||
const tr = document.createElement('tr');
|
||||
appendTd(tr, entity.name);
|
||||
appendButton(tr, "Remove", () => removeMember(id, entity.id, entity.type), 'float-right');
|
||||
table.appendChild(tr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function removeMember(teamId, snowflake, entityType) {
|
||||
const res = await axios.delete('/api/{{.guildId}}/team/' + teamId + '/' + snowflake + '?type=' + entityType);
|
||||
if (res.status !== 200) {
|
||||
if (res.status === 429){
|
||||
notifyRatelimit();
|
||||
} else {
|
||||
notifyError(res.data.error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (entityType === 0) { // user
|
||||
notifySuccess("Support user removed successfully")
|
||||
} else if (entityType === 1) { //role
|
||||
notifySuccess("Support role removed successfully")
|
||||
}
|
||||
|
||||
await displayTeam(activeTeam);
|
||||
}
|
||||
|
||||
async function createTeam() {
|
||||
const input = document.getElementById('team-creation-name');
|
||||
const data = {
|
||||
name: input.value
|
||||
};
|
||||
|
||||
const res = await axios.post('/api/{{.guildId}}/team', data);
|
||||
if (res.status !== 200) {
|
||||
if (res.status === 429){
|
||||
notifyRatelimit();
|
||||
} else {
|
||||
notifyError(res.data.error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
input.value = '';
|
||||
await loadTeams();
|
||||
notifySuccess("Team created successfully");
|
||||
}
|
||||
|
||||
async function deleteTeam(id) {
|
||||
const res = await axios.delete('/api/{{.guildId}}/team/' + id);
|
||||
if (res.status !== 200) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
notifySuccess("Team deleted successfully");
|
||||
await displayTeam('default');
|
||||
}
|
||||
|
||||
function appendTeam(id, name) {
|
||||
const wrapper = document.getElementById('team-selector');
|
||||
|
||||
const option = createElement('a', 'dropdown-item', 'team-item');
|
||||
option.onclick = () => displayTeam(id);
|
||||
option.appendChild(document.createTextNode(name));
|
||||
|
||||
if (id !== 'default') {
|
||||
const deleteIcon = createElement('i', 'fas', 'fa-window-close', 'delete-team-icon');
|
||||
deleteIcon.onclick = () => deleteTeam(id);
|
||||
option.appendChild(deleteIcon);
|
||||
}
|
||||
|
||||
wrapper.appendChild(option);
|
||||
}
|
||||
|
||||
async function loadTeams() {
|
||||
const res = await axios.get('/api/{{.guildId}}/team');
|
||||
if (res.status !== 200) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
const wrapper = document.getElementById('team-selector');
|
||||
wrapper.innerHTML = '';
|
||||
|
||||
appendTeam('default', 'Default');
|
||||
for (const team of res.data) {
|
||||
appendTeam(team.id, team.name);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRoles() {
|
||||
const res = await axios.get('/api/{{.guildId}}/roles');
|
||||
if (res.status !== 200) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
roles = res.data.roles;
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
await loadRoles();
|
||||
await loadTeams();
|
||||
await displayTeam('default');
|
||||
}
|
||||
|
||||
withLoadingScreen(loadData);
|
||||
</script>
|
||||
{{end}}
|
@ -5,7 +5,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func ErrorToResponse(err error) map[string]interface{} {
|
||||
func ErrorJson(err error) map[string]interface{} {
|
||||
return ErrorStr(err.Error())
|
||||
}
|
||||
|
||||
|
@ -2,22 +2,14 @@ package utils
|
||||
|
||||
import (
|
||||
"github.com/rxdn/gdl/objects/channel/message"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func Contains(s interface{}, elem interface{}) bool {
|
||||
arrV := reflect.ValueOf(s)
|
||||
|
||||
if arrV.Kind() == reflect.Slice {
|
||||
for i := 0; i < arrV.Len(); i++ {
|
||||
|
||||
// XXX - panics if slice element points to an unexported struct field
|
||||
// see https://golang.org/pkg/reflect/#Value.Interface
|
||||
if arrV.Index(i).Interface() == elem {
|
||||
func ContainsString(slice []string, target string) bool {
|
||||
for _, elem := range slice {
|
||||
if elem == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user