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 {
|
if err := group.Wait(); err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,14 +33,14 @@ func MultiPanelCreate(ctx *gin.Context) {
|
|||||||
|
|
||||||
var data multiPanelCreateData
|
var data multiPanelCreateData
|
||||||
if err := ctx.ShouldBindJSON(&data); err != nil {
|
if err := ctx.ShouldBindJSON(&data); err != nil {
|
||||||
ctx.JSON(400, utils.ErrorToResponse(err))
|
ctx.JSON(400, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate body & get sub-panels
|
// validate body & get sub-panels
|
||||||
panels, err := data.doValidations(guildId)
|
panels, err := data.doValidations(guildId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(400, utils.ErrorToResponse(err))
|
ctx.JSON(400, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,9 +61,9 @@ func MultiPanelCreate(ctx *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var unwrapped request.RestError
|
var unwrapped request.RestError
|
||||||
if errors.As(err, &unwrapped); unwrapped.ErrorCode == 403 {
|
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 {
|
} else {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -72,9 +72,9 @@ func MultiPanelCreate(ctx *gin.Context) {
|
|||||||
if err := data.addReactions(&botContext, data.ChannelId, messageId, panels); err != nil {
|
if err := data.addReactions(&botContext, data.ChannelId, messageId, panels); err != nil {
|
||||||
var unwrapped request.RestError
|
var unwrapped request.RestError
|
||||||
if errors.As(err, &unwrapped); unwrapped.ErrorCode == 403{
|
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 {
|
} else {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -91,7 +91,7 @@ func MultiPanelCreate(ctx *gin.Context) {
|
|||||||
|
|
||||||
multiPanel.Id, err = dbclient.Client.MultiPanels.Create(multiPanel)
|
multiPanel.Id, err = dbclient.Client.MultiPanels.Create(multiPanel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ func MultiPanelCreate(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := group.Wait(); err != nil {
|
if err := group.Wait(); err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,42 +16,42 @@ func MultiPanelDelete(ctx *gin.Context) {
|
|||||||
|
|
||||||
multiPanelId, err := strconv.Atoi(ctx.Param("panelid"))
|
multiPanelId, err := strconv.Atoi(ctx.Param("panelid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(400, utils.ErrorToResponse(err))
|
ctx.JSON(400, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get bot context
|
// get bot context
|
||||||
botContext, err := botcontext.ContextForGuild(guildId)
|
botContext, err := botcontext.ContextForGuild(guildId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
panel, ok, err := dbclient.Client.MultiPanels.Get(multiPanelId)
|
panel, ok, err := dbclient.Client.MultiPanels.Get(multiPanelId)
|
||||||
if !ok {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if panel.GuildId != guildId {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var unwrapped request.RestError
|
var unwrapped request.RestError
|
||||||
if err := rest.DeleteMessage(botContext.Token, botContext.RateLimiter, panel.ChannelId, panel.MessageId); err != nil && !(errors.As(err, &unwrapped) && unwrapped.IsClientError()) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
success, err := dbclient.Client.MultiPanels.Delete(guildId, multiPanelId)
|
success, err := dbclient.Client.MultiPanels.Delete(guildId, multiPanelId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !success {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ func MultiPanelList(ctx *gin.Context) {
|
|||||||
|
|
||||||
multiPanels, err := dbclient.Client.MultiPanels.GetByGuild(guildId)
|
multiPanels, err := dbclient.Client.MultiPanels.GetByGuild(guildId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ func MultiPanelList(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := group.Wait(); err != nil {
|
if err := group.Wait(); err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,54 +22,54 @@ func MultiPanelUpdate(ctx *gin.Context) {
|
|||||||
// parse body
|
// parse body
|
||||||
var data multiPanelCreateData
|
var data multiPanelCreateData
|
||||||
if err := ctx.ShouldBindJSON(&data); err != nil {
|
if err := ctx.ShouldBindJSON(&data); err != nil {
|
||||||
ctx.JSON(400, utils.ErrorToResponse(err))
|
ctx.JSON(400, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse panel ID
|
// parse panel ID
|
||||||
panelId, err := strconv.Atoi(ctx.Param("panelid"))
|
panelId, err := strconv.Atoi(ctx.Param("panelid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(400, utils.ErrorToResponse(err))
|
ctx.JSON(400, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// retrieve panel from DB
|
// retrieve panel from DB
|
||||||
multiPanel, ok, err := dbclient.Client.MultiPanels.Get(panelId)
|
multiPanel, ok, err := dbclient.Client.MultiPanels.Get(panelId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check panel exists
|
// check panel exists
|
||||||
if !ok {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check panel is in the same guild
|
// check panel is in the same guild
|
||||||
if guildId != multiPanel.GuildId {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate body & get sub-panels
|
// validate body & get sub-panels
|
||||||
panels, err := data.doValidations(guildId)
|
panels, err := data.doValidations(guildId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(400, utils.ErrorToResponse(err))
|
ctx.JSON(400, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get bot context
|
// get bot context
|
||||||
botContext, err := botcontext.ContextForGuild(guildId)
|
botContext, err := botcontext.ContextForGuild(guildId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete old message
|
// delete old message
|
||||||
var unwrapped request.RestError
|
var unwrapped request.RestError
|
||||||
if err := rest.DeleteMessage(botContext.Token, botContext.RateLimiter, multiPanel.ChannelId, multiPanel.MessageId); err != nil && !(errors.As(err, &unwrapped) && unwrapped.IsClientError()) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,9 +81,9 @@ func MultiPanelUpdate(ctx *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var unwrapped request.RestError
|
var unwrapped request.RestError
|
||||||
if errors.As(err, &unwrapped) && unwrapped.ErrorCode == 403 {
|
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 {
|
} else {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -93,9 +93,9 @@ func MultiPanelUpdate(ctx *gin.Context) {
|
|||||||
if err := data.addReactions(&botContext, data.ChannelId, messageId, panels); err != nil {
|
if err := data.addReactions(&botContext, data.ChannelId, messageId, panels); err != nil {
|
||||||
var unwrapped request.RestError
|
var unwrapped request.RestError
|
||||||
if errors.As(err, &unwrapped) && unwrapped.ErrorCode == 403 {
|
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 {
|
} else {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -113,14 +113,14 @@ func MultiPanelUpdate(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err = dbclient.Client.MultiPanels.Update(multiPanel.Id, updated); err != nil {
|
if err = dbclient.Client.MultiPanels.Update(multiPanel.Id, updated); err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: one query for ACID purposes
|
// TODO: one query for ACID purposes
|
||||||
// delete old targets
|
// delete old targets
|
||||||
if err := dbclient.Client.MultiPanelTargets.DeleteAll(multiPanel.Id); err != nil {
|
if err := dbclient.Client.MultiPanelTargets.DeleteAll(multiPanel.Id); err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ func MultiPanelUpdate(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := group.Wait(); err != nil {
|
if err := group.Wait(); err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/TicketsBot/GoPanel/botcontext"
|
"github.com/TicketsBot/GoPanel/botcontext"
|
||||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||||
"github.com/TicketsBot/GoPanel/rpc"
|
"github.com/TicketsBot/GoPanel/rpc"
|
||||||
@ -15,13 +17,28 @@ import (
|
|||||||
"github.com/rxdn/gdl/objects/channel/message"
|
"github.com/rxdn/gdl/objects/channel/message"
|
||||||
"github.com/rxdn/gdl/rest"
|
"github.com/rxdn/gdl/rest"
|
||||||
"github.com/rxdn/gdl/rest/request"
|
"github.com/rxdn/gdl/rest/request"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const freePanelLimit = 3
|
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) {
|
func CreatePanel(ctx *gin.Context) {
|
||||||
|
|
||||||
guildId := ctx.Keys["guildid"].(uint64)
|
guildId := ctx.Keys["guildid"].(uint64)
|
||||||
|
|
||||||
botContext, err := botcontext.ContextForGuild(guildId)
|
botContext, err := botcontext.ContextForGuild(guildId)
|
||||||
@ -33,7 +50,7 @@ func CreatePanel(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var data panel
|
var data panelBody
|
||||||
|
|
||||||
if err := ctx.BindJSON(&data); err != nil {
|
if err := ctx.BindJSON(&data); err != nil {
|
||||||
ctx.AbortWithStatusJSON(400, gin.H{
|
ctx.AbortWithStatusJSON(400, gin.H{
|
||||||
@ -111,15 +128,16 @@ func CreatePanel(ctx *gin.Context) {
|
|||||||
|
|
||||||
// Store in DB
|
// Store in DB
|
||||||
panel := database.Panel{
|
panel := database.Panel{
|
||||||
MessageId: msgId,
|
MessageId: msgId,
|
||||||
ChannelId: data.ChannelId,
|
ChannelId: data.ChannelId,
|
||||||
GuildId: guildId,
|
GuildId: guildId,
|
||||||
Title: data.Title,
|
Title: data.Title,
|
||||||
Content: data.Content,
|
Content: data.Content,
|
||||||
Colour: int32(data.Colour),
|
Colour: int32(data.Colour),
|
||||||
TargetCategory: data.CategoryId,
|
TargetCategory: data.CategoryId,
|
||||||
ReactionEmote: emoji,
|
ReactionEmote: emoji,
|
||||||
WelcomeMessage: data.WelcomeMessage,
|
WelcomeMessage: data.WelcomeMessage,
|
||||||
|
WithDefaultTeam: utils.ContainsString(data.Teams, "default"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = dbclient.Client.Panel.Create(panel); err != nil {
|
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{
|
ctx.JSON(200, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message_id": strconv.FormatUint(msgId, 10),
|
"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() {
|
if !p.verifyTitle() {
|
||||||
ctx.AbortWithStatusJSON(400, gin.H{
|
ctx.AbortWithStatusJSON(400, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
@ -225,22 +280,22 @@ func (p *panel) doValidations(ctx *gin.Context, guildId uint64) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *panel) verifyTitle() bool {
|
func (p *panelBody) verifyTitle() bool {
|
||||||
return len(p.Title) > 0 && len(p.Title) < 256
|
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
|
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)
|
p.Emote = strings.Replace(p.Emote, ":", "", -1)
|
||||||
|
|
||||||
emoji, ok = utils.GetEmoji(p.Emote)
|
emoji, ok = utils.GetEmoji(p.Emote)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *panel) verifyChannel(channels []channel.Channel) bool {
|
func (p *panelBody) verifyChannel(channels []channel.Channel) bool {
|
||||||
var valid bool
|
var valid bool
|
||||||
for _, ch := range channels {
|
for _, ch := range channels {
|
||||||
if ch.Id == p.ChannelId && ch.Type == channel.ChannelTypeGuildText {
|
if ch.Id == p.ChannelId && ch.Type == channel.ChannelTypeGuildText {
|
||||||
@ -252,7 +307,7 @@ func (p *panel) verifyChannel(channels []channel.Channel) bool {
|
|||||||
return valid
|
return valid
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *panel) verifyCategory(channels []channel.Channel) bool {
|
func (p *panelBody) verifyCategory(channels []channel.Channel) bool {
|
||||||
var valid bool
|
var valid bool
|
||||||
for _, ch := range channels {
|
for _, ch := range channels {
|
||||||
if ch.Id == p.CategoryId && ch.Type == channel.ChannelTypeGuildCategory {
|
if ch.Id == p.CategoryId && ch.Type == channel.ChannelTypeGuildCategory {
|
||||||
@ -264,11 +319,11 @@ func (p *panel) verifyCategory(channels []channel.Channel) bool {
|
|||||||
return valid
|
return valid
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *panel) verifyWelcomeMessage() bool {
|
func (p *panelBody) verifyWelcomeMessage() bool {
|
||||||
return p.WelcomeMessage == nil || (len(*p.WelcomeMessage) > 0 && len(*p.WelcomeMessage) < 1025)
|
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().
|
e := embed.NewEmbed().
|
||||||
SetTitle(p.Title).
|
SetTitle(p.Title).
|
||||||
SetDescription(p.Content).
|
SetDescription(p.Content).
|
||||||
|
@ -2,28 +2,31 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/TicketsBot/GoPanel/database"
|
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||||
|
"github.com/TicketsBot/database"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type panel 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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListPanels(ctx *gin.Context) {
|
func ListPanels(ctx *gin.Context) {
|
||||||
|
type panelResponse struct {
|
||||||
|
ChannelId uint64 `json:"channel_id,string"`
|
||||||
|
MessageId uint64 `json:"message_id,string"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Colour uint32 `json:"colour"`
|
||||||
|
CategoryId uint64 `json:"category_id,string"`
|
||||||
|
Emote string `json:"emote"`
|
||||||
|
WelcomeMessage *string `json:"welcome_message"`
|
||||||
|
Mentions []string `json:"mentions"`
|
||||||
|
WithDefaultTeam bool `json:"default_team"`
|
||||||
|
Teams []database.SupportTeam `json:"teams"`
|
||||||
|
}
|
||||||
|
|
||||||
guildId := ctx.Keys["guildid"].(uint64)
|
guildId := ctx.Keys["guildid"].(uint64)
|
||||||
|
|
||||||
panels, err := database.Client.Panel.GetByGuild(guildId)
|
panels, err := dbclient.Client.Panel.GetByGuild(guildId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.AbortWithStatusJSON(500, gin.H{
|
ctx.AbortWithStatusJSON(500, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
@ -32,7 +35,7 @@ func ListPanels(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapped := make([]panel, len(panels))
|
wrapped := make([]panelResponse, len(panels))
|
||||||
|
|
||||||
// we will need to lookup role mentions
|
// we will need to lookup role mentions
|
||||||
group, _ := errgroup.WithContext(context.Background())
|
group, _ := errgroup.WithContext(context.Background())
|
||||||
@ -45,7 +48,7 @@ func ListPanels(ctx *gin.Context) {
|
|||||||
var mentions []string
|
var mentions []string
|
||||||
|
|
||||||
// get role mentions
|
// get role mentions
|
||||||
roles, err := database.Client.PanelRoleMentions.GetRoles(p.MessageId)
|
roles, err := dbclient.Client.PanelRoleMentions.GetRoles(p.MessageId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -56,7 +59,7 @@ func ListPanels(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get if we should mention the ticket opener
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -65,16 +68,23 @@ func ListPanels(ctx *gin.Context) {
|
|||||||
mentions = append(mentions, "user")
|
mentions = append(mentions, "user")
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapped[i] = panel{
|
teams, err := dbclient.Client.PanelTeams.GetTeams(p.MessageId)
|
||||||
MessageId: p.MessageId,
|
if err != nil {
|
||||||
ChannelId: p.ChannelId,
|
return err
|
||||||
Title: p.Title,
|
}
|
||||||
Content: p.Content,
|
|
||||||
Colour: uint32(p.Colour),
|
wrapped[i] = panelResponse{
|
||||||
CategoryId: p.TargetCategory,
|
MessageId: p.MessageId,
|
||||||
Emote: p.ReactionEmote,
|
ChannelId: p.ChannelId,
|
||||||
WelcomeMessage: p.WelcomeMessage,
|
Title: p.Title,
|
||||||
Mentions: mentions,
|
Content: p.Content,
|
||||||
|
Colour: uint32(p.Colour),
|
||||||
|
CategoryId: p.TargetCategory,
|
||||||
|
Emote: p.ReactionEmote,
|
||||||
|
WelcomeMessage: p.WelcomeMessage,
|
||||||
|
Mentions: mentions,
|
||||||
|
WithDefaultTeam: p.WithDefaultTeam,
|
||||||
|
Teams: teams,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -84,7 +94,7 @@ func ListPanels(ctx *gin.Context) {
|
|||||||
if err := group.Wait(); err != nil {
|
if err := group.Wait(); err != nil {
|
||||||
ctx.JSON(500, gin.H{
|
ctx.JSON(500, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -22,20 +22,20 @@ func UpdatePanel(ctx *gin.Context) {
|
|||||||
|
|
||||||
botContext, err := botcontext.ContextForGuild(guildId)
|
botContext, err := botcontext.ContextForGuild(guildId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var data panel
|
var data panelBody
|
||||||
|
|
||||||
if err := ctx.BindJSON(&data); err != nil {
|
if err := ctx.BindJSON(&data); err != nil {
|
||||||
ctx.AbortWithStatusJSON(400, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(400, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
messageId, err := strconv.ParseUint(ctx.Param("message"), 10, 64)
|
messageId, err := strconv.ParseUint(ctx.Param("message"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.AbortWithStatusJSON(400, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(400, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ func UpdatePanel(ctx *gin.Context) {
|
|||||||
// get existing
|
// get existing
|
||||||
existing, err := dbclient.Client.Panel.Get(data.MessageId)
|
existing, err := dbclient.Client.Panel.Get(data.MessageId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ func UpdatePanel(ctx *gin.Context) {
|
|||||||
// first, get any multipanels this panel belongs to
|
// first, get any multipanels this panel belongs to
|
||||||
multiPanels, err := dbclient.Client.MultiPanelTargets.GetMultiPanels(existing.MessageId)
|
multiPanels, err := dbclient.Client.MultiPanelTargets.GetMultiPanels(existing.MessageId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,13 +103,13 @@ func UpdatePanel(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := group.Wait(); err != nil {
|
if err := group.Wait(); err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if wouldHaveDuplicateEmote {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ func UpdatePanel(ctx *gin.Context) {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// TODO: Most appropriate error?
|
// TODO: Most appropriate error?
|
||||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -160,7 +160,7 @@ func UpdatePanel(ctx *gin.Context) {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// TODO: Most appropriate error?
|
// TODO: Most appropriate error?
|
||||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -178,23 +178,24 @@ func UpdatePanel(ctx *gin.Context) {
|
|||||||
TargetCategory: data.CategoryId,
|
TargetCategory: data.CategoryId,
|
||||||
ReactionEmote: emoji,
|
ReactionEmote: emoji,
|
||||||
WelcomeMessage: data.WelcomeMessage,
|
WelcomeMessage: data.WelcomeMessage,
|
||||||
|
WithDefaultTeam: utils.ContainsString(data.Teams, "default"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = dbclient.Client.Panel.Update(messageId, panel); err != nil {
|
if err = dbclient.Client.Panel.Update(messageId, panel); err != nil {
|
||||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert role mention data
|
// insert role mention data
|
||||||
// delete old data
|
// delete old data
|
||||||
if err = dbclient.Client.PanelRoleMentions.DeleteAll(newMessageId); err != nil {
|
if err = dbclient.Client.PanelRoleMentions.DeleteAll(newMessageId); err != nil {
|
||||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Reduce to 1 query
|
// TODO: Reduce to 1 query
|
||||||
if err = dbclient.Client.PanelUserMention.Set(newMessageId, false); err != nil {
|
if err = dbclient.Client.PanelUserMention.Set(newMessageId, false); err != nil {
|
||||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,13 +203,13 @@ func UpdatePanel(ctx *gin.Context) {
|
|||||||
for _, mention := range data.Mentions {
|
for _, mention := range data.Mentions {
|
||||||
if mention == "user" {
|
if mention == "user" {
|
||||||
if err = dbclient.Client.PanelUserMention.Set(newMessageId, true); err != nil {
|
if err = dbclient.Client.PanelUserMention.Set(newMessageId, true); err != nil {
|
||||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
roleId, err := strconv.ParseUint(mention, 10, 64)
|
roleId, err := strconv.ParseUint(mention, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,12 +217,26 @@ func UpdatePanel(ctx *gin.Context) {
|
|||||||
// not too much of an issue if it isnt
|
// not too much of an issue if it isnt
|
||||||
|
|
||||||
if err = dbclient.Client.PanelRoleMentions.Add(newMessageId, roleId); err != nil {
|
if err = dbclient.Client.PanelRoleMentions.Add(newMessageId, roleId); err != nil {
|
||||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||||
return
|
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{
|
ctx.JSON(200, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message_id": strconv.FormatUint(newMessageId, 10),
|
"message_id": strconv.FormatUint(newMessageId, 10),
|
||||||
|
@ -16,14 +16,14 @@ func ReloadGuildsHandler(ctx *gin.Context) {
|
|||||||
key := fmt.Sprintf("tickets:dashboard:guildreload:%d", userId)
|
key := fmt.Sprintf("tickets:dashboard:guildreload:%d", userId)
|
||||||
res, err := messagequeue.Client.SetNX(key, 1, time.Second*10).Result()
|
res, err := messagequeue.Client.SetNX(key, 1, time.Second*10).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !res {
|
if !res {
|
||||||
ttl, err := messagequeue.Client.TTL(key).Result()
|
ttl, err := messagequeue.Client.TTL(key).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ func ReloadGuildsHandler(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := utils.LoadGuilds(accessToken, userId); err != nil {
|
if err := utils.LoadGuilds(accessToken, userId); err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
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
|
// Verify the user has permissions to be here
|
||||||
permLevel, err := utils.GetPermissionLevel(guildId, userId)
|
permLevel, err := utils.GetPermissionLevel(guildId, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorToResponse(err))
|
ctx.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ func AuthenticateGuild(isApiMethod bool, requiredPermissionLevel permission.Perm
|
|||||||
|
|
||||||
permLevel, err := utils.GetPermissionLevel(guild.Id, userId)
|
permLevel, err := utils.GetPermissionLevel(guild.Id, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.AbortWithStatusJSON(500, utils.ErrorToResponse(err))
|
ctx.AbortWithStatusJSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
api_panels "github.com/TicketsBot/GoPanel/app/http/endpoints/api/panel"
|
api_panels "github.com/TicketsBot/GoPanel/app/http/endpoints/api/panel"
|
||||||
api_settings "github.com/TicketsBot/GoPanel/app/http/endpoints/api/settings"
|
api_settings "github.com/TicketsBot/GoPanel/app/http/endpoints/api/settings"
|
||||||
api_tags "github.com/TicketsBot/GoPanel/app/http/endpoints/api/tags"
|
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_ticket "github.com/TicketsBot/GoPanel/app/http/endpoints/api/ticket"
|
||||||
api_whitelabel "github.com/TicketsBot/GoPanel/app/http/endpoints/api/whitelabel"
|
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/manage"
|
||||||
@ -89,6 +90,7 @@ func StartServer() {
|
|||||||
guildAuthApiSupport.GET("/premium", api.PremiumHandler)
|
guildAuthApiSupport.GET("/premium", api.PremiumHandler)
|
||||||
guildAuthApiSupport.GET("/user/:user", api.UserHandler)
|
guildAuthApiSupport.GET("/user/:user", api.UserHandler)
|
||||||
guildAuthApiSupport.GET("/roles", api.RolesHandler)
|
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.GET("/settings", api_settings.GetSettingsHandler)
|
||||||
guildAuthApiAdmin.POST("/settings", api_settings.UpdateSettingsHandler)
|
guildAuthApiAdmin.POST("/settings", api_settings.UpdateSettingsHandler)
|
||||||
@ -123,6 +125,13 @@ func StartServer() {
|
|||||||
|
|
||||||
guildAuthApiAdmin.GET("/autoclose", api_autoclose.GetAutoClose)
|
guildAuthApiAdmin.GET("/autoclose", api_autoclose.GetAutoClose)
|
||||||
guildAuthApiAdmin.POST("/autoclose", api_autoclose.PostAutoClose)
|
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)
|
userGroup := router.Group("/user", middleware.AuthenticateToken)
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/rxdn/gdl/objects/channel"
|
"github.com/rxdn/gdl/objects/channel"
|
||||||
"github.com/rxdn/gdl/objects/guild"
|
"github.com/rxdn/gdl/objects/guild"
|
||||||
"github.com/rxdn/gdl/objects/member"
|
"github.com/rxdn/gdl/objects/member"
|
||||||
|
"github.com/rxdn/gdl/objects/user"
|
||||||
"github.com/rxdn/gdl/rest"
|
"github.com/rxdn/gdl/rest"
|
||||||
"github.com/rxdn/gdl/rest/ratelimit"
|
"github.com/rxdn/gdl/rest/ratelimit"
|
||||||
)
|
)
|
||||||
@ -76,6 +77,15 @@ func (ctx BotContext) GetGuildMember(guildId, userId uint64) (m member.Member, e
|
|||||||
return
|
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) {
|
func (ctx BotContext) GetGuildRoles(guildId uint64) (roles []guild.Role, err error) {
|
||||||
if roles := cache.Instance.GetGuildRoles(guildId); len(roles) > 0 {
|
if roles := cache.Instance.GetGuildRoles(guildId); len(roles) > 0 {
|
||||||
return roles, nil
|
return roles, nil
|
||||||
@ -88,3 +98,17 @@ func (ctx BotContext) GetGuildRoles(guildId uint64) (roles []guild.Role, err err
|
|||||||
|
|
||||||
return
|
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/BurntSushi/toml v0.3.1
|
||||||
github.com/TicketsBot/archiverclient v0.0.0-20200704164621-09d42dd941e0
|
github.com/TicketsBot/archiverclient v0.0.0-20200704164621-09d42dd941e0
|
||||||
github.com/TicketsBot/common v0.0.0-20210118172556-0b20b84f7df4
|
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/TicketsBot/worker v0.0.0-20210207182653-fabef254ea30
|
||||||
github.com/apex/log v1.1.2
|
github.com/apex/log v1.1.2
|
||||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
|
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/jackc/pgx/v4 v4.7.1
|
||||||
github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c
|
github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c
|
||||||
github.com/pkg/errors v0.9.1
|
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/sirupsen/logrus v1.5.0
|
||||||
github.com/ulule/limiter/v3 v3.5.0
|
github.com/ulule/limiter/v3 v3.5.0
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||||
|
@ -75,7 +75,6 @@ button.close {
|
|||||||
|
|
||||||
h1, .h1, h2, .h2, h3, .h3, h4, .h4 {
|
h1, .h1, h2, .h2, h3, .h3, h4, .h4 {
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
margin: 30px 0 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, .h1 {
|
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 {
|
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-family: 'Noto Sans', sans-serif !important;
|
||||||
|
|
||||||
font-weight: 400 !important;
|
font-weight: 400 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +101,6 @@ html > ::-webkit-scrollbar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.table td, .table th {
|
.table td, .table th {
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-container {
|
.close-container {
|
||||||
@ -309,7 +307,6 @@ html > ::-webkit-scrollbar {
|
|||||||
|
|
||||||
.flex-container {
|
.flex-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,14 +328,17 @@ html > ::-webkit-scrollbar {
|
|||||||
.tcontent-container {
|
.tcontent-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tcontent-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.team-card-container {
|
.team-card-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 4%;
|
margin: 4% 0;
|
||||||
height: 50%;
|
min-height: 50%;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,7 +346,6 @@ html > ::-webkit-scrollbar {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
background-color: #272727 !important;
|
background-color: #272727 !important;
|
||||||
@ -371,7 +370,7 @@ html > ::-webkit-scrollbar {
|
|||||||
.tcard-body {
|
.tcard-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: 2%;
|
padding: 2%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: white;
|
color: white;
|
||||||
@ -380,7 +379,120 @@ html > ::-webkit-scrollbar {
|
|||||||
.flex-center {
|
.flex-center {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
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() {
|
function showLoadingScreen() {
|
||||||
const content = document.getElementsByClassName('content')[0];
|
const content = document.getElementsByClassName('content')[0] || document.getElementsByClassName('tcontent-container')[0];
|
||||||
content.style.display = 'none';
|
content.style.display = 'none';
|
||||||
document.getElementById('loading-container').style.display = 'block';
|
document.getElementById('loading-container').style.display = 'block';
|
||||||
}
|
}
|
||||||
@ -7,8 +7,13 @@ function showLoadingScreen() {
|
|||||||
function hideLoadingScreen() {
|
function hideLoadingScreen() {
|
||||||
document.getElementById('loading-container').style.display = 'none';
|
document.getElementById('loading-container').style.display = 'none';
|
||||||
|
|
||||||
const content = document.getElementsByClassName('content')[0];
|
const content = document.getElementsByClassName('content')[0] || document.getElementsByClassName('tcontent-container')[0];
|
||||||
content.style.display = 'block';
|
if (content.classList.contains('tcontent-container')) {
|
||||||
|
content.style.display = 'flex';
|
||||||
|
} else {
|
||||||
|
content.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
content.classList.add('fade-in');
|
content.classList.add('fade-in');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,12 +26,12 @@ function appendTd(tr, content) {
|
|||||||
return td
|
return td
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendButton(tr, content, onclick) {
|
function appendButton(tr, content, onclick, ...classList) {
|
||||||
const tdRemove = document.createElement('td');
|
const tdRemove = document.createElement('td');
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
|
|
||||||
btn.type = 'submit';
|
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.appendChild(document.createTextNode(content));
|
||||||
btn.onclick = onclick;
|
btn.onclick = onclick;
|
||||||
|
|
||||||
@ -59,3 +59,9 @@ function prependChild(parent, child) {
|
|||||||
parent.insertBefore(child, parent.children[0]);
|
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);
|
notify('Success', message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function notifyRatelimit() {
|
||||||
|
notifyError("You're doing that too fast: please wait a few seconds and try again");
|
||||||
|
}
|
||||||
|
|
||||||
function closeNotificationModal() {
|
function closeNotificationModal() {
|
||||||
$('#notificationmodal').modal('hide');
|
$('#notificationmodal').modal('hide');
|
||||||
}
|
}
|
||||||
|
@ -82,13 +82,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 pr-1">
|
<div class="col-md-6 pr-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="black">Mention On Open</label>
|
<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 class="selectpicker form-control" id="edit-mentions" multiple data-live-search="true" data-dropup-auto="false" data-size="5" data-display="static">
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<input type="hidden" id="edit-message-id">
|
<input type="hidden" id="edit-message-id">
|
||||||
@ -150,10 +157,12 @@
|
|||||||
fillChannels('edit-channel-container', channels);
|
fillChannels('edit-channel-container', channels);
|
||||||
fillCategories('edit-category-container', channels);
|
fillCategories('edit-category-container', channels);
|
||||||
await fillMentions('edit-mentions');
|
await fillMentions('edit-mentions');
|
||||||
|
await fillTeams('edit-teams');
|
||||||
|
|
||||||
setActiveChannel('edit-channel-container', panel.channel_id);
|
setActiveChannel('edit-channel-container', panel.channel_id);
|
||||||
setActiveCategory(panel);
|
setActiveCategory(panel);
|
||||||
setActiveMentions(panel);
|
setActiveMentions(panel);
|
||||||
|
setActiveTeams(panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setActiveCategory(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() {
|
async function updatePanel() {
|
||||||
const messageId = document.getElementById('edit-message-id').value;
|
const messageId = document.getElementById('edit-message-id').value;
|
||||||
const title = document.getElementById('edit-title').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,
|
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,
|
category_id: document.getElementById('edit-category-container').options[document.getElementById('edit-category-container').selectedIndex].value,
|
||||||
welcome_message: welcomeMessage === '' ? null : welcomeMessage,
|
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);
|
const res = await axios.put('/api/{{.guildId}}/panels/' + messageId, data);
|
||||||
|
@ -124,7 +124,7 @@
|
|||||||
<div class="collapse" id="advanced" style="width: 100%">
|
<div class="collapse" id="advanced" style="width: 100%">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 pr-1">
|
<div class="col-md-12 pr-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="black">Welcome Message
|
<label class="black">Welcome Message
|
||||||
<i class="fas fa-question pointer" onclick="showSubstitutionModal()"></i>
|
<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>
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
<div class="col-md-6 pr-1">
|
<div class="col-md-6 pr-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="black" for="mentions">Mention On Open</label>
|
<label class="black" for="mentions">Mention On Open</label>
|
||||||
@ -140,6 +141,13 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -287,7 +295,8 @@
|
|||||||
channel_id: document.getElementById('channel-container').options[document.getElementById('channel-container').selectedIndex].value,
|
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,
|
category_id: document.getElementById('category-container').options[document.getElementById('category-container').selectedIndex].value,
|
||||||
welcome_message: welcomeMessage === '' ? null : welcomeMessage,
|
welcome_message: welcomeMessage === '' ? null : welcomeMessage,
|
||||||
mentions: $('#mentions').val()
|
mentions: $('#mentions').val(),
|
||||||
|
teams: $('#teams').val()
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await axios.put('/api/{{.guildId}}/panels', data);
|
const res = await axios.put('/api/{{.guildId}}/panels', data);
|
||||||
@ -476,6 +485,7 @@
|
|||||||
|
|
||||||
async function fillMentions(elementId) {
|
async function fillMentions(elementId) {
|
||||||
const select = document.getElementById(elementId);
|
const select = document.getElementById(elementId);
|
||||||
|
select.innerHTML = '';
|
||||||
|
|
||||||
// ticket opener
|
// ticket opener
|
||||||
const ticketOpener = document.createElement('option');
|
const ticketOpener = document.createElement('option');
|
||||||
@ -502,6 +512,33 @@
|
|||||||
$(`#${elementId}`).selectpicker('refresh');
|
$(`#${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() {
|
async function loadData() {
|
||||||
const channels = await getChannels();
|
const channels = await getChannels();
|
||||||
const panelCount = await fillPanels(channels);
|
const panelCount = await fillPanels(channels);
|
||||||
@ -513,6 +550,9 @@
|
|||||||
fillChannels('multi-channel-container', channels);
|
fillChannels('multi-channel-container', channels);
|
||||||
fillCategories('category-container', channels);
|
fillCategories('category-container', channels);
|
||||||
await fillMentions('mentions');
|
await fillMentions('mentions');
|
||||||
|
|
||||||
|
await fillTeams('teams');
|
||||||
|
$(`#teams`).selectpicker('val', 'default');
|
||||||
}
|
}
|
||||||
|
|
||||||
withLoadingScreen(loadData);
|
withLoadingScreen(loadData);
|
||||||
|
@ -6,14 +6,343 @@
|
|||||||
<span>Support Teams</span>
|
<span>Support Teams</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tcard-body">
|
<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">
|
<div class="flex-center">
|
||||||
<label for="team-selection">Select a team</label>
|
<div class="dropdown" style="width: 100%">
|
||||||
<select name="team-selection" 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%">
|
||||||
<option value="default">Default</option>
|
Select a team
|
||||||
</select>
|
</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>
|
</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}}
|
{{end}}
|
@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ErrorToResponse(err error) map[string]interface{} {
|
func ErrorJson(err error) map[string]interface{} {
|
||||||
return ErrorStr(err.Error())
|
return ErrorStr(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,20 +2,12 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/rxdn/gdl/objects/channel/message"
|
"github.com/rxdn/gdl/objects/channel/message"
|
||||||
"reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Contains(s interface{}, elem interface{}) bool {
|
func ContainsString(slice []string, target string) bool {
|
||||||
arrV := reflect.ValueOf(s)
|
for _, elem := range slice {
|
||||||
|
if elem == target {
|
||||||
if arrV.Kind() == reflect.Slice {
|
return true
|
||||||
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 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user