Finish postgres port

This commit is contained in:
Dot-Rar 2020-05-11 18:49:07 +01:00
parent 8ed1bec46a
commit bf7dc7cf09
62 changed files with 846 additions and 1380 deletions

View File

@ -1,11 +1,14 @@
package api package api
import ( import (
"context"
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
"strconv" "strconv"
"sync"
) )
type userData struct { type userData struct {
@ -16,18 +19,38 @@ type userData struct {
func GetBlacklistHandler(ctx *gin.Context) { func GetBlacklistHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64) guildId := ctx.Keys["guildid"].(uint64)
data := make(map[string]userData) blacklistedUsers, err := database.Client.Blacklist.GetBlacklistedUsers(guildId)
if err != nil {
blacklistedUsers := table.GetBlacklistNodes(guildId) ctx.JSON(500, gin.H{
for _, row := range blacklistedUsers { "success": false,
formattedId := strconv.FormatUint(row.User, 10) "error": err.Error(),
user, _ := cache.Instance.GetUser(row.User) })
return
data[formattedId] = userData{
Username: user.Username,
Discriminator: fmt.Sprintf("%04d", user.Discriminator),
}
} }
data := make(map[string]userData)
var lock sync.Mutex
group, _ := errgroup.WithContext(context.Background())
for _, userId := range blacklistedUsers {
group.Go(func() error {
user, _ := cache.Instance.GetUser(userId)
lock.Lock()
// JS cant do big ints
data[strconv.FormatUint(userId, 10)] = userData{
Username: user.Username,
Discriminator: fmt.Sprintf("%04d", user.Discriminator),
}
lock.Unlock()
return nil
})
}
_ = group.Wait()
ctx.JSON(200, data) ctx.JSON(200, data)
} }

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4"
@ -50,11 +50,15 @@ func AddBlacklistHandler(ctx *gin.Context) {
} }
// TODO: Don't blacklist staff or guild owner // TODO: Don't blacklist staff or guild owner
if err = database.Client.Blacklist.Add(guildId, targetId); err == nil {
go table.AddBlacklist(guildId, targetId) ctx.JSON(200, gin.H{
"success": true,
ctx.JSON(200, gin.H{ "user_id": strconv.FormatUint(targetId, 10),
"success": true, })
"user_id": strconv.FormatUint(targetId, 10), } else {
}) ctx.JSON(500, gin.H{
"success": false,
"error": err.Error(),
})
}
} }

View File

@ -1,7 +1,7 @@
package api package api
import ( import (
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"strconv" "strconv"
) )
@ -18,9 +18,14 @@ func RemoveBlacklistHandler(ctx *gin.Context) {
return return
} }
go table.RemoveBlacklist(guildId, userId) if err := database.Client.Blacklist.Remove(guildId, userId); err == nil {
ctx.JSON(200, gin.H{
ctx.JSON(200, gin.H{ "success": true,
"success": true, })
}) } else {
ctx.JSON(200, gin.H{
"success": false,
"err": err.Error(),
})
}
} }

View File

@ -1,9 +1,10 @@
package api package api
import ( import (
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/messagequeue" "github.com/TicketsBot/GoPanel/messagequeue"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"strconv"
) )
type closeBody struct { type closeBody struct {
@ -13,7 +14,15 @@ type closeBody struct {
func CloseTicket(ctx *gin.Context) { func CloseTicket(ctx *gin.Context) {
userId := ctx.Keys["userid"].(uint64) userId := ctx.Keys["userid"].(uint64)
guildId := ctx.Keys["guildid"].(uint64) guildId := ctx.Keys["guildid"].(uint64)
uuid := ctx.Param("uuid")
ticketId, err := strconv.Atoi(ctx.Param("ticketId"))
if err != nil {
ctx.AbortWithStatusJSON(400, gin.H{
"success": true,
"error": "Invalid ticket ID",
})
return
}
var data closeBody var data closeBody
if err := ctx.BindJSON(&data); err != nil { if err := ctx.BindJSON(&data); err != nil {
@ -24,12 +33,18 @@ func CloseTicket(ctx *gin.Context) {
return return
} }
// Verify that the ticket exists // Get the ticket struct
ticketChan := make(chan table.Ticket) ticket, err := database.Client.Tickets.Get(ticketId, guildId)
go table.GetTicket(uuid, ticketChan) if err != nil {
ticket := <-ticketChan ctx.AbortWithStatusJSON(500, gin.H{
"success": true,
"error": err.Error(),
})
return
}
if ticket.Uuid == "" { // Verify the ticket exists
if ticket.UserId == 0 {
ctx.AbortWithStatusJSON(404, gin.H{ ctx.AbortWithStatusJSON(404, gin.H{
"success": true, "success": true,
"error": "Ticket does not exist", "error": "Ticket does not exist",
@ -37,7 +52,7 @@ func CloseTicket(ctx *gin.Context) {
return return
} }
if ticket.Guild != guildId { if ticket.GuildId != guildId {
ctx.AbortWithStatusJSON(403, gin.H{ ctx.AbortWithStatusJSON(403, gin.H{
"success": true, "success": true,
"error": "Guild ID does not matched", "error": "Guild ID does not matched",
@ -45,7 +60,7 @@ func CloseTicket(ctx *gin.Context) {
return return
} }
go messagequeue.Client.PublishTicketClose(ticket.Uuid, userId, data.Reason) go messagequeue.Client.PublishTicketClose(guildId, ticket.Id, userId, data.Reason)
ctx.JSON(200, gin.H{ ctx.JSON(200, gin.H{
"success": true, "success": true,

View File

@ -3,7 +3,7 @@ package api
import ( import (
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/rpc/ratelimit" "github.com/TicketsBot/GoPanel/rpc/ratelimit"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
@ -18,13 +18,27 @@ var MentionRegex, _ = regexp.Compile("<@(\\d+)>")
func GetTicket(ctx *gin.Context) { func GetTicket(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64) guildId := ctx.Keys["guildid"].(uint64)
uuid := ctx.Param("uuid")
ticketChan := make(chan table.Ticket) ticketId, err := strconv.Atoi(ctx.Param("ticketId"))
go table.GetTicket(uuid, ticketChan) if err != nil {
ticket := <-ticketChan ctx.AbortWithStatusJSON(400, gin.H{
"success": true,
"error": "Invalid ticket ID",
})
return
}
if ticket.Guild != guildId { // Get the ticket struct
ticket, err := database.Client.Tickets.Get(ticketId, guildId)
if err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": true,
"error": err.Error(),
})
return
}
if ticket.GuildId != guildId {
ctx.AbortWithStatusJSON(403, gin.H{ ctx.AbortWithStatusJSON(403, gin.H{
"success": false, "success": false,
"error": "Guild ID doesn't match", "error": "Guild ID doesn't match",
@ -32,7 +46,7 @@ func GetTicket(ctx *gin.Context) {
return return
} }
if !ticket.IsOpen { if !ticket.Open {
ctx.AbortWithStatusJSON(404, gin.H{ ctx.AbortWithStatusJSON(404, gin.H{
"success": false, "success": false,
"error": "Ticket does not exist", "error": "Ticket does not exist",
@ -40,8 +54,16 @@ func GetTicket(ctx *gin.Context) {
return return
} }
if ticket.ChannelId == nil {
ctx.AbortWithStatusJSON(404, gin.H{
"success": false,
"error": "Ticket channel does not exist",
})
return
}
// Get messages // Get messages
messages, _ := rest.GetChannelMessages(config.Conf.Bot.Token, ratelimit.Ratelimiter, ticket.Channel, rest.GetChannelMessagesData{Limit: 100}) messages, _ := rest.GetChannelMessages(config.Conf.Bot.Token, ratelimit.Ratelimiter, *ticket.ChannelId, rest.GetChannelMessagesData{Limit: 100})
// Format messages, exclude unneeded data // Format messages, exclude unneeded data
messagesFormatted := make([]map[string]interface{}, 0) messagesFormatted := make([]map[string]interface{}, 0)

View File

@ -1,48 +1,71 @@
package api package api
import ( import (
"context"
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
"strconv" "strconv"
"strings"
) )
func GetTickets(ctx *gin.Context) { func GetTickets(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64) guildId := ctx.Keys["guildid"].(uint64)
tickets := table.GetOpenTickets(guildId) tickets, err := database.Client.Tickets.GetGuildOpenTickets(guildId)
ticketsFormatted := make([]map[string]interface{}, 0) if err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
for _, ticket := range tickets { "success": false,
membersFormatted := make([]map[string]interface{}, 0) "error": err.Error(),
for index, memberIdStr := range strings.Split(ticket.Members, ",") {
if memberId, err := strconv.ParseUint(memberIdStr, 10, 64); err == nil {
if memberId != 0 {
var separator string
if index != len(strings.Split(ticket.Members, ","))-1 {
separator = ", "
}
member, _ := cache.Instance.GetUser(memberId)
membersFormatted = append(membersFormatted, map[string]interface{}{
"username": member.Username,
"discrim": fmt.Sprintf("%04d", member.Discriminator),
"sep": separator,
})
}
}
}
owner, _ := cache.Instance.GetUser(ticket.Owner)
ticketsFormatted = append(ticketsFormatted, map[string]interface{}{
"uuid": ticket.Uuid,
"ticketId": ticket.TicketId,
"username": owner.Username,
"discrim": fmt.Sprintf("%04d", owner.Discriminator),
"members": membersFormatted,
}) })
return
}
ticketsFormatted := make([]map[string]interface{}, len(tickets))
group, _ := errgroup.WithContext(context.Background())
for i, ticket := range tickets {
i := i
ticket := ticket
group.Go(func() error {
members, err := database.Client.TicketMembers.Get(guildId, ticket.Id)
if err != nil {
return err
}
membersFormatted := make([]map[string]interface{}, 0)
for _, userId := range members {
user, _ := cache.Instance.GetUser(userId)
membersFormatted = append(membersFormatted, map[string]interface{}{
"id": strconv.FormatUint(userId, 10),
"username": user.Username,
"discrim": fmt.Sprintf("%04d", user.Discriminator),
})
}
owner, _ := cache.Instance.GetUser(ticket.UserId)
ticketsFormatted[len(tickets) - 1 - i] = map[string]interface{}{
"ticketId": ticket.Id,
"username": owner.Username,
"discrim": fmt.Sprintf("%04d", owner.Discriminator),
"members": membersFormatted,
}
return nil
})
}
if err := group.Wait(); err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
} }
ctx.JSON(200, ticketsFormatted) ctx.JSON(200, ticketsFormatted)

View File

@ -1,28 +1,48 @@
package api package api
import ( import (
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rxdn/gdl/objects/guild" "github.com/rxdn/gdl/objects/guild"
) )
type wrappedGuild struct {
Id uint64 `json:"id,string"`
Name string `json:"name"`
}
func GetGuilds(ctx *gin.Context) { func GetGuilds(ctx *gin.Context) {
userId := ctx.Keys["userid"].(uint64) userId := ctx.Keys["userid"].(uint64)
userGuilds := table.GetGuilds(userId) guilds, err := database.Client.UserGuilds.Get(userId)
adminGuilds := make([]guild.Guild, 0) if err != nil {
for _, g := range userGuilds { ctx.JSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
adminGuilds := make([]wrappedGuild, 0)
for _, g := range guilds {
fakeGuild := guild.Guild{ fakeGuild := guild.Guild{
Id: g.Id, Id: g.GuildId,
OwnerId: g.OwnerId, Owner: g.Owner,
Permissions: g.Permissions, Permissions: int(g.UserPermissions),
}
if g.Owner {
fakeGuild.OwnerId = userId
} }
isAdmin := make(chan bool) isAdmin := make(chan bool)
go utils.IsAdmin(fakeGuild, userId, isAdmin) go utils.IsAdmin(fakeGuild, userId, isAdmin)
if <-isAdmin { if <-isAdmin {
adminGuilds = append(adminGuilds, g) adminGuilds = append(adminGuilds, wrappedGuild{
Id: g.GuildId,
Name: g.Name,
})
} }
} }

View File

@ -4,10 +4,11 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" dbclient "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/rpc/ratelimit" "github.com/TicketsBot/GoPanel/rpc/ratelimit"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/database"
"github.com/apex/log" "github.com/apex/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rxdn/gdl/rest" "github.com/rxdn/gdl/rest"
@ -15,15 +16,15 @@ import (
) )
const ( const (
pageLimit = 30 pageLimit = 2
) )
func GetLogs(ctx *gin.Context) { func GetLogs(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64) guildId := ctx.Keys["guildid"].(uint64)
page, err := strconv.Atoi(ctx.Param("page")) before, err := strconv.Atoi(ctx.Query("before"))
if page < 1 { if before < 0 {
page = 1 before = 0
} }
// Get ticket ID from URL // Get ticket ID from URL
@ -32,15 +33,20 @@ func GetLogs(ctx *gin.Context) {
ticketId, _ = strconv.Atoi(ctx.Query("ticketid")) ticketId, _ = strconv.Atoi(ctx.Query("ticketid"))
} }
var tickets []table.Ticket var tickets []database.Ticket
// Get tickets from DB // Get tickets from DB
if ticketId > 0 { if ticketId > 0 {
ticketChan := make(chan table.Ticket) ticket, err := dbclient.Client.Tickets.Get(ticketId, guildId)
go table.GetTicketById(guildId, ticketId, ticketChan) if err != nil {
ticket := <-ticketChan ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
if ticket.Uuid != "" && !ticket.IsOpen { if ticket.UserId != 0 && !ticket.Open {
tickets = append(tickets, ticket) tickets = append(tickets, ticket)
} }
} else { } else {
@ -76,25 +82,27 @@ func GetLogs(ctx *gin.Context) {
} }
if ctx.Query("userid") != "" || ctx.Query("username") != "" { if ctx.Query("userid") != "" || ctx.Query("username") != "" {
tickets = table.GetClosedTicketsByUserId(guildId, filteredIds) tickets, err = dbclient.Client.Tickets.GetMemberClosedTickets(guildId, filteredIds, pageLimit, before)
} else { } else {
tickets = table.GetClosedTickets(guildId) tickets, err = dbclient.Client.Tickets.GetGuildClosedTickets(guildId, pageLimit, before)
}
if err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
} }
} }
// Select 30 logs + format them // Select 30 logs + format them
formattedLogs := make([]map[string]interface{}, 0) formattedLogs := make([]map[string]interface{}, 0)
for i := (page - 1) * pageLimit; i < (page-1)*pageLimit+pageLimit; i++ { for _, ticket := range tickets {
if i >= len(tickets) {
break
}
ticket := tickets[i]
// get username // get username
user, found := cache.Instance.GetUser(ticket.Owner) user, found := cache.Instance.GetUser(ticket.UserId)
if !found { if !found {
user, err = rest.GetUser(config.Conf.Bot.Token, ratelimit.Ratelimiter, ticket.Owner) user, err = rest.GetUser(config.Conf.Bot.Token, ratelimit.Ratelimiter, ticket.UserId)
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
} }
@ -102,8 +110,8 @@ func GetLogs(ctx *gin.Context) {
} }
formattedLogs = append(formattedLogs, map[string]interface{}{ formattedLogs = append(formattedLogs, map[string]interface{}{
"ticketid": ticket.TicketId, "ticketid": ticket.Id,
"userid": strconv.FormatUint(ticket.Owner, 10), "userid": strconv.FormatUint(ticket.UserId, 10),
"username": user.Username, "username": user.Username,
}) })
} }

View File

@ -3,9 +3,11 @@ package api
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/database/table" dbclient "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/database"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
"regexp" "regexp"
"strconv" "strconv"
) )
@ -18,15 +20,18 @@ type wrappedModLog struct {
UserId uint64 `json:"user_id,string"` UserId uint64 `json:"user_id,string"`
} }
// TODO: Take after param
func GetModmailLogs(ctx *gin.Context) { func GetModmailLogs(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64) guildId := ctx.Keys["guildid"].(uint64)
page, err := strconv.Atoi(ctx.Param("page")) after, err := uuid.FromString(ctx.Query("after"))
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(400, gin.H{ after = uuid.Nil
"success": false, }
"error": err.Error(),
}) before, err := uuid.FromString(ctx.Query("before"))
if err != nil {
before = uuid.Nil
} }
// filter // filter
@ -53,27 +58,28 @@ func GetModmailLogs(ctx *gin.Context) {
shouldFilter := userId > 0 shouldFilter := userId > 0
start := pageLimit * (page - 1)
end := start + pageLimit - 1
wrapped := make([]wrappedModLog, 0) wrapped := make([]wrappedModLog, 0)
var archives []table.ModMailArchive var archives []database.ModmailArchive
if shouldFilter { if shouldFilter {
archivesCh := make(chan []table.ModMailArchive) archives, err = dbclient.Client.ModmailArchive.GetByMember(guildId, userId, pageLimit, after, before)
go table.GetModmailArchivesByUser(userId, guildId, archivesCh)
archives = <-archivesCh
} else { } else {
archivesCh := make(chan []table.ModMailArchive) archives, err = dbclient.Client.ModmailArchive.GetByGuild(guildId, pageLimit, after, before)
go table.GetModmailArchivesByGuild(guildId, archivesCh)
archives = <-archivesCh
} }
for i := start; i < end && i < len(archives); i++ { if err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
for _, archive := range archives {
wrapped = append(wrapped, wrappedModLog{ wrapped = append(wrapped, wrappedModLog{
Uuid: archives[i].Uuid, Uuid: archive.Uuid.String(),
GuildId: archives[i].Guild, GuildId: archive.GuildId,
UserId: archives[i].User, UserId: archive.UserId,
}) })
} }

View File

@ -1,12 +1,19 @@
package api package api
import ( import (
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/messagequeue" dbclient "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/rpc/ratelimit"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/database"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rxdn/gdl/objects/channel" "github.com/rxdn/gdl/objects/channel"
"github.com/rxdn/gdl/objects/channel/embed"
"github.com/rxdn/gdl/objects/channel/message"
"github.com/rxdn/gdl/rest"
"github.com/rxdn/gdl/rest/request"
"strconv"
"strings" "strings"
) )
@ -25,12 +32,19 @@ func CreatePanel(ctx *gin.Context) {
data.MessageId = 0 data.MessageId = 0
// Check panel quota // Check panel quota
premium := make(chan bool) premiumChan := make(chan bool)
go utils.IsPremiumGuild(guildId, premium) go utils.IsPremiumGuild(guildId, premiumChan)
if !<-premium { isPremium := <-premiumChan
panels := make(chan []table.Panel) if !isPremium {
go table.GetPanelsByGuild(guildId, panels) panels, err := dbclient.Client.Panel.GetByGuild(guildId)
if len(<-panels) > 0 { if err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
}
if len(panels) > 0 {
ctx.AbortWithStatusJSON(402, gin.H{ ctx.AbortWithStatusJSON(402, gin.H{
"success": false, "success": false,
"error": "You have exceeded your panel quota. Purchase premium to unlock more panels.", "error": "You have exceeded your panel quota. Purchase premium to unlock more panels.",
@ -39,63 +53,118 @@ func CreatePanel(ctx *gin.Context) {
} }
} }
if !data.verifyTitle() { if !data.doValidations(ctx, guildId) {
return
}
msgId, err := data.sendEmbed(isPremium)
if err != nil {
if err == request.ErrForbidden {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": "I do not have permission to send messages in the specified channel",
})
} else {
// TODO: Most appropriate error?
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
}
return
}
// Add reaction
emoji, _ := data.getEmoji() // already validated
if err = rest.CreateReaction(config.Conf.Bot.Token, ratelimit.Ratelimiter, data.ChannelId, msgId, emoji); err != nil {
if err == request.ErrForbidden {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": "I do not have permission to add reactions in the specified channel",
})
} else {
// TODO: Most appropriate error?
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
}
return
}
// Store in DB
panel := database.Panel{
MessageId: msgId,
ChannelId: data.ChannelId,
GuildId: guildId,
Title: data.Title,
Content: data.Content,
Colour: int32(data.Colour),
TargetCategory: data.CategoryId,
ReactionEmote: emoji,
}
if err = dbclient.Client.Panel.Create(panel); err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
ctx.JSON(200, gin.H{
"success": true,
"message_id": strconv.FormatUint(msgId, 10),
})
}
func (p *panel) doValidations(ctx *gin.Context, guildId uint64) bool {
if !p.verifyTitle() {
ctx.AbortWithStatusJSON(400, gin.H{ ctx.AbortWithStatusJSON(400, gin.H{
"success": false, "success": false,
"error": "Panel titles must be between 1 - 255 characters in length", "error": "Panel titles must be between 1 - 255 characters in length",
}) })
return return false
} }
if !data.verifyContent() { if !p.verifyContent() {
ctx.AbortWithStatusJSON(400, gin.H{ ctx.AbortWithStatusJSON(400, gin.H{
"success": false, "success": false,
"error": "Panel content must be between 1 - 1024 characters in length", "error": "Panel content must be between 1 - 1024 characters in length",
}) })
return return false
} }
channels := cache.Instance.GetGuildChannels(guildId) channels := cache.Instance.GetGuildChannels(guildId)
if !data.verifyChannel(channels) { if !p.verifyChannel(channels) {
ctx.AbortWithStatusJSON(400, gin.H{ ctx.AbortWithStatusJSON(400, gin.H{
"success": false, "success": false,
"error": "Invalid channel", "error": "Invalid channel",
}) })
return return false
} }
if !data.verifyCategory(channels) { if !p.verifyCategory(channels) {
ctx.AbortWithStatusJSON(400, gin.H{ ctx.AbortWithStatusJSON(400, gin.H{
"success": false, "success": false,
"error": "Invalid channel category", "error": "Invalid channel category",
}) })
return return false
} }
emoji, validEmoji := data.getEmoji() _, validEmoji := p.getEmoji()
if !validEmoji { if !validEmoji {
ctx.AbortWithStatusJSON(400, gin.H{ ctx.AbortWithStatusJSON(400, gin.H{
"success": false, "success": false,
"error": "Invalid emoji. Simply use the emoji's name from Discord.", "error": "Invalid emoji. Simply use the emoji's name from Discord.",
}) })
return return false
} }
// TODO: Move panel create logic here return true
go messagequeue.Client.PublishPanelCreate(table.Panel{
ChannelId: data.ChannelId,
GuildId: guildId,
Title: data.Title,
Content: data.Content,
Colour: data.Colour,
TargetCategory: data.CategoryId,
ReactionEmote: emoji,
})
ctx.JSON(200, gin.H{
"success": true,
})
} }
func (p *panel) verifyTitle() bool { func (p *panel) verifyTitle() bool {
@ -136,3 +205,24 @@ func (p *panel) verifyCategory(channels []channel.Channel) bool {
return valid return valid
} }
func (p *panel) sendEmbed(isPremium bool) (messageId uint64, err error) {
e := embed.NewEmbed().
SetTitle(p.Title).
SetDescription(p.Content).
SetColor(int(p.Colour))
if !isPremium {
// TODO: Don't harcode
e.SetFooter("Powered by ticketsbot.net", "https://cdn.discordapp.com/avatars/508391840525975553/ac2647ffd4025009e2aa852f719a8027.png?size=256")
}
var msg message.Message
msg, err = rest.CreateMessage(config.Conf.Bot.Token, ratelimit.Ratelimiter, p.ChannelId, rest.CreateMessageData{Embed: e})
if err != nil {
return
}
messageId = msg.Id
return
}

View File

@ -2,7 +2,7 @@ package api
import ( import (
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/ratelimit" "github.com/TicketsBot/GoPanel/rpc/ratelimit"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rxdn/gdl/rest" "github.com/rxdn/gdl/rest"
@ -21,11 +21,16 @@ func DeletePanel(ctx *gin.Context) {
return return
} }
// verify panel belongs to guild panel, err := database.Client.Panel.Get(messageId)
panelChan := make(chan table.Panel) if err != nil {
go table.GetPanel(messageId, panelChan) ctx.JSON(500, gin.H{
panel := <-panelChan "success": false,
"error": err.Error(),
})
return
}
// verify panel belongs to guild
if panel.GuildId != guildId { if panel.GuildId != guildId {
ctx.AbortWithStatusJSON(403, gin.H{ ctx.AbortWithStatusJSON(403, gin.H{
"success": false, "success": false,
@ -34,8 +39,21 @@ func DeletePanel(ctx *gin.Context) {
return return
} }
go table.DeletePanel(messageId) if err := database.Client.Panel.Delete(messageId); err != nil {
go rest.DeleteMessage(config.Conf.Bot.Token, ratelimit.Ratelimiter, panel.ChannelId, panel.MessageId) ctx.JSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
if err := rest.DeleteMessage(config.Conf.Bot.Token, ratelimit.Ratelimiter, panel.ChannelId, panel.MessageId); err != nil {
ctx.JSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
ctx.JSON(200, gin.H{ ctx.JSON(200, gin.H{
"success": true, "success": true,

View File

@ -1,7 +1,7 @@
package api package api
import ( import (
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -18,9 +18,14 @@ type panel struct {
func ListPanels(ctx *gin.Context) { func ListPanels(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64) guildId := ctx.Keys["guildid"].(uint64)
panelsChan := make(chan []table.Panel) panels, err := database.Client.Panel.GetByGuild(guildId)
go table.GetPanelsByGuild(guildId, panelsChan) if err != nil {
panels := <-panelsChan ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
wrapped := make([]panel, len(panels)) wrapped := make([]panel, len(panels))
@ -30,7 +35,7 @@ func ListPanels(ctx *gin.Context) {
MessageId: p.MessageId, MessageId: p.MessageId,
Title: p.Title, Title: p.Title,
Content: p.Content, Content: p.Content,
Colour: p.Colour, Colour: uint32(p.Colour),
CategoryId: p.TargetCategory, CategoryId: p.TargetCategory,
Emote: p.ReactionEmote, Emote: p.ReactionEmote,
} }

View File

@ -1,18 +1,16 @@
package api package api
import ( import (
"bytes"
"encoding/json"
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/rpc/ratelimit" "github.com/TicketsBot/GoPanel/rpc/ratelimit"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rxdn/gdl/rest" "github.com/rxdn/gdl/rest"
"net/http" "github.com/rxdn/gdl/rest/request"
"time" "strconv"
) )
type sendMessageBody struct { type sendMessageBody struct {
@ -23,6 +21,16 @@ func SendMessage(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64) guildId := ctx.Keys["guildid"].(uint64)
userId := ctx.Keys["userid"].(uint64) userId := ctx.Keys["userid"].(uint64)
// Get ticket ID
ticketId, err := strconv.Atoi(ctx.Param("ticketId"))
if err != nil {
ctx.AbortWithStatusJSON(400, gin.H{
"success": false,
"error": "Invalid ticket ID",
})
return
}
var body sendMessageBody var body sendMessageBody
if err := ctx.BindJSON(&body); err != nil { if err := ctx.BindJSON(&body); err != nil {
ctx.AbortWithStatusJSON(400, gin.H{ ctx.AbortWithStatusJSON(400, gin.H{
@ -44,12 +52,10 @@ func SendMessage(ctx *gin.Context) {
} }
// Get ticket // Get ticket
ticketChan := make(chan table.Ticket) ticket, err := database.Client.Tickets.Get(ticketId, guildId)
go table.GetTicket(ctx.Param("uuid"), ticketChan)
ticket := <-ticketChan
// Verify the ticket exists // Verify the ticket exists
if ticket.TicketId == 0 { if ticket.UserId == 0 {
ctx.AbortWithStatusJSON(404, gin.H{ ctx.AbortWithStatusJSON(404, gin.H{
"success": false, "success": false,
"error": "Ticket not found", "error": "Ticket not found",
@ -58,7 +64,7 @@ func SendMessage(ctx *gin.Context) {
} }
// Verify the user has permission to send to this guild // Verify the user has permission to send to this guild
if ticket.Guild != guildId { if ticket.GuildId != guildId {
ctx.AbortWithStatusJSON(403, gin.H{ ctx.AbortWithStatusJSON(403, gin.H{
"success": false, "success": false,
"error": "Guild ID doesn't match", "error": "Guild ID doesn't match",
@ -73,60 +79,61 @@ func SendMessage(ctx *gin.Context) {
} }
// Preferably send via a webhook // Preferably send via a webhook
webhookChan := make(chan *string) webhook, err := database.Client.Webhooks.Get(guildId, ticketId)
go table.GetWebhookByUuid(ticket.Uuid, webhookChan) if err != nil {
webhook := <-webhookChan ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
success := false "error": err.Error(),
if webhook != nil { })
// TODO: Use gdl execute webhook wrapper return
success = executeWebhook(ticket.Uuid, *webhook, body.Message, user.Username, user.AvatarUrl(256))
} }
if !success { if webhook.Id != 0 {
body.Message = fmt.Sprintf("**%s**: %s", user.Username, body.Message) // TODO: Use gdl execute webhook wrapper
if len(body.Message) > 2000 { _, err = rest.ExecuteWebhook(webhook.Token, ratelimit.Ratelimiter, webhook.Id, true, rest.WebhookBody{
body.Message = body.Message[0:1999] Content: body.Message,
} Username: user.Username,
AvatarUrl: user.AvatarUrl(256),
})
_, _ = rest.CreateMessage(config.Conf.Bot.Token, ratelimit.Ratelimiter, ticket.Channel, rest.CreateMessageData{Content: body.Message}) if err != nil {
fmt.Println(err.Error())
// We can delete the webhook in this case
if err == request.ErrNotFound || err == request.ErrForbidden {
go database.Client.Webhooks.Delete(guildId, ticketId)
}
} else {
ctx.JSON(200, gin.H{
"success": true,
})
return
}
}
fmt.Println(1)
body.Message = fmt.Sprintf("**%s**: %s", user.Username, body.Message)
if len(body.Message) > 2000 {
body.Message = body.Message[0:1999]
}
if ticket.ChannelId == nil {
ctx.AbortWithStatusJSON(404, gin.H{
"success": false,
"error": "Ticket channel ID is nil",
})
return
}
if _, err = rest.CreateMessage(config.Conf.Bot.Token, ratelimit.Ratelimiter, *ticket.ChannelId, rest.CreateMessageData{Content: body.Message}); err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
} }
ctx.JSON(200, gin.H{ ctx.JSON(200, gin.H{
"success": true, "success": true,
}) })
} }
func executeWebhook(uuid, webhook, content, username, avatar string) bool {
body := map[string]interface{}{
"content": content,
"username": username,
"avatar_url": avatar,
}
encoded, err := json.Marshal(&body)
if err != nil {
return false
}
req, err := http.NewRequest("POST", fmt.Sprintf("https://canary.discordapp.com/api/webhooks/%s", webhook), bytes.NewBuffer(encoded))
if err != nil {
return false
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
client.Timeout = 3 * time.Second
res, err := client.Do(req)
if err != nil {
return false
}
if res.StatusCode == 404 || res.StatusCode == 403 {
go table.DeleteWebhookByUuid(uuid)
} else {
return true
}
return false
}

View File

@ -1,56 +1,99 @@
package api package api
import ( import (
"github.com/TicketsBot/GoPanel/database/table" "context"
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"
) )
type Settings struct { type Settings struct {
Prefix string `json:"prefix"` Prefix string `json:"prefix"`
WelcomeMessaage string `json:"welcome_message"` WelcomeMessaage string `json:"welcome_message"`
TicketLimit int `json:"ticket_limit"` TicketLimit uint8 `json:"ticket_limit"`
Category uint64 `json:"category,string"` Category uint64 `json:"category,string"`
ArchiveChannel uint64 `json:"archive_channel,string"` ArchiveChannel uint64 `json:"archive_channel,string"`
NamingScheme table.NamingScheme `json:"naming_scheme"` NamingScheme database.NamingScheme `json:"naming_scheme"`
PingEveryone bool `json:"ping_everyone"` PingEveryone bool `json:"ping_everyone"`
UsersCanClose bool `json:"users_can_close"` UsersCanClose bool `json:"users_can_close"`
} }
func GetSettingsHandler(ctx *gin.Context) { func GetSettingsHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64) guildId := ctx.Keys["guildid"].(uint64)
prefix := make(chan string) var prefix, welcomeMessage string
go table.GetPrefix(guildId, prefix) var ticketLimit uint8
var category, archiveChannel uint64
var allowUsersToClose, pingEveryone bool
var namingScheme database.NamingScheme
welcomeMessage := make(chan string) group, _ := errgroup.WithContext(context.Background())
go table.GetWelcomeMessage(guildId, welcomeMessage)
ticketLimit := make(chan int) // prefix
go table.GetTicketLimit(guildId, ticketLimit) group.Go(func() (err error) {
prefix, err = dbclient.Client.Prefix.Get(guildId)
return
})
category := make(chan uint64) // welcome message
go table.GetChannelCategory(guildId, category) group.Go(func() (err error) {
welcomeMessage, err = dbclient.Client.WelcomeMessages.Get(guildId)
return
})
archiveChannel := make(chan uint64) // ticket limit
go table.GetArchiveChannel(guildId, archiveChannel) group.Go(func() (err error) {
ticketLimit, err = dbclient.Client.TicketLimit.Get(guildId)
return
})
allowUsersToClose := make(chan bool) // category
go table.IsUserCanClose(guildId, allowUsersToClose) group.Go(func() (err error) {
category, err = dbclient.Client.ChannelCategory.Get(guildId)
return
})
namingScheme := make(chan table.NamingScheme) // archive channel
go table.GetTicketNamingScheme(guildId, namingScheme) group.Go(func() (err error) {
archiveChannel, err = dbclient.Client.ArchiveChannel.Get(guildId)
return
})
pingEveryone := make(chan bool) // allow users to close
go table.GetPingEveryone(guildId, pingEveryone) group.Go(func() (err error) {
allowUsersToClose, err = dbclient.Client.UsersCanClose.Get(guildId)
return
})
// ping everyone
group.Go(func() (err error) {
pingEveryone, err = dbclient.Client.PingEveryone.Get(guildId)
return
})
// naming scheme
group.Go(func() (err error) {
namingScheme, err = dbclient.Client.NamingScheme.Get(guildId)
return
})
if err := group.Wait(); err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
ctx.JSON(200, Settings{ ctx.JSON(200, Settings{
Prefix: <-prefix, Prefix: prefix,
WelcomeMessaage: <-welcomeMessage, WelcomeMessaage: welcomeMessage,
TicketLimit: <-ticketLimit, TicketLimit: ticketLimit,
Category: <-category, Category: category,
ArchiveChannel: <-archiveChannel, ArchiveChannel: archiveChannel,
NamingScheme: <-namingScheme, NamingScheme: namingScheme,
PingEveryone: <-pingEveryone, PingEveryone: pingEveryone,
UsersCanClose: <-allowUsersToClose, UsersCanClose: allowUsersToClose,
}) })
} }

View File

@ -1,7 +1,7 @@
package api package api
import ( import (
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -38,7 +38,7 @@ func CreateTag(ctx *gin.Context) {
return return
} }
if err := table.AddTag(guildId, data.Id, data.Content); err != nil { if err := database.Client.Tag.Set(guildId, data.Id, data.Content); err != nil {
ctx.AbortWithStatusJSON(500, gin.H{ ctx.AbortWithStatusJSON(500, gin.H{
"success": false, "success": false,
"error": err.Error(), "error": err.Error(),

View File

@ -1,7 +1,7 @@
package api package api
import ( import (
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -17,7 +17,7 @@ func DeleteTag(ctx *gin.Context) {
return return
} }
if err := table.DeleteTag(guildId, tagId); err != nil { if err := database.Client.Tag.Delete(guildId, tagId); err != nil {
ctx.JSON(500, gin.H{ ctx.JSON(500, gin.H{
"success": false, "success": false,
"error": err.Error(), "error": err.Error(),

View File

@ -1,23 +1,22 @@
package api package api
import ( import (
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// TODO: Make client take new structure
func TagsListHandler(ctx *gin.Context) { func TagsListHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64) guildId := ctx.Keys["guildid"].(uint64)
wrapped := make([]tag, 0) tags, err := database.Client.Tag.GetByGuild(guildId)
if err != nil {
tags := make(chan []table.Tag) ctx.AbortWithStatusJSON(500, gin.H{
go table.GetTags(guildId, tags) "success": false,
for _, t := range <-tags { "error": err.Error(),
wrapped = append(wrapped, tag{
Id: t.Id,
Content: t.Content,
}) })
return
} }
ctx.JSON(200, wrapped) ctx.JSON(200, tags)
} }

View File

@ -1,7 +1,8 @@
package api package api
import ( import (
"github.com/TicketsBot/GoPanel/database/table" dbclient "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/database"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rxdn/gdl/objects/channel" "github.com/rxdn/gdl/objects/channel"
@ -42,12 +43,13 @@ func UpdateSettingsHandler(ctx *gin.Context) {
}) })
} }
// TODO: Return error
func (s *Settings) updatePrefix(guildId uint64) bool { func (s *Settings) updatePrefix(guildId uint64) bool {
if s.Prefix == "" || len(s.Prefix) > 8 { if s.Prefix == "" || len(s.Prefix) > 8 {
return false return false
} }
go table.UpdatePrefix(guildId, s.Prefix) go dbclient.Client.Prefix.Set(guildId, s.Prefix)
return true return true
} }
@ -56,7 +58,7 @@ func (s *Settings) updateWelcomeMessage(guildId uint64) bool {
return false return false
} }
go table.UpdateWelcomeMessage(guildId, s.WelcomeMessaage) go dbclient.Client.WelcomeMessages.Set(guildId, s.WelcomeMessaage)
return true return true
} }
@ -65,7 +67,7 @@ func (s *Settings) updateTicketLimit(guildId uint64) bool {
return false return false
} }
go table.UpdateTicketLimit(guildId, s.TicketLimit) go dbclient.Client.TicketLimit.Set(guildId, s.TicketLimit)
return true return true
} }
@ -82,7 +84,7 @@ func (s *Settings) updateCategory(channels []channel.Channel, guildId uint64) bo
return false return false
} }
go table.UpdateChannelCategory(guildId, s.Category) go dbclient.Client.ChannelCategory.Set(guildId, s.Category)
return true return true
} }
@ -99,11 +101,11 @@ func (s *Settings) updateArchiveChannel(channels []channel.Channel, guildId uint
return false return false
} }
go table.UpdateArchiveChannel(guildId, s.ArchiveChannel) go dbclient.Client.ArchiveChannel.Set(guildId, s.ArchiveChannel)
return true return true
} }
var validScheme = []table.NamingScheme{table.Id, table.Username} var validScheme = []database.NamingScheme{database.Id, database.Username}
func (s *Settings) updateNamingScheme(guildId uint64) bool { func (s *Settings) updateNamingScheme(guildId uint64) bool {
var valid bool var valid bool
for _, scheme := range validScheme { for _, scheme := range validScheme {
@ -117,14 +119,14 @@ func (s *Settings) updateNamingScheme(guildId uint64) bool {
return false return false
} }
go table.SetTicketNamingScheme(guildId, s.NamingScheme) go dbclient.Client.NamingScheme.Set(guildId, s.NamingScheme)
return true return true
} }
func (s *Settings) updatePingEveryone(guildId uint64) { func (s *Settings) updatePingEveryone(guildId uint64) {
go table.UpdatePingEveryone(guildId, s.PingEveryone) go dbclient.Client.PingEveryone.Set(guildId, s.PingEveryone)
} }
func (s *Settings) updateUsersCanClose(guildId uint64) { func (s *Settings) updateUsersCanClose(guildId uint64) {
go table.SetUserCanClose(guildId, s.UsersCanClose) go dbclient.Client.UsersCanClose.Set(guildId, s.UsersCanClose)
} }

View File

@ -4,7 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/archiverclient" "github.com/TicketsBot/archiverclient"
@ -41,20 +41,27 @@ func LogViewHandler(ctx *gin.Context) {
} }
// get ticket object // get ticket object
ticketChan := make(chan table.Ticket) ticket, err := database.Client.Tickets.Get(ticketId, guildId)
go table.GetTicketById(guildId, ticketId, ticketChan) if err != nil {
ticket := <-ticketChan // TODO: 500 error page
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
// Verify this is a valid ticket and it is closed // Verify this is a valid ticket and it is closed
if ticket.Uuid == "" || ticket.IsOpen { if ticket.UserId == 0 || ticket.Open {
ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs", guild.Id)) ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs", guild.Id))
return return
} }
// Verify the user has permissions to be here // Verify the user has permissions to be here
// TODO: Allow support reps to view
isAdmin := make(chan bool) isAdmin := make(chan bool)
go utils.IsAdmin(guild, userId, isAdmin) go utils.IsAdmin(guild, userId, isAdmin)
if !<-isAdmin && ticket.Owner != userId { if !<-isAdmin && ticket.UserId != userId {
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page
return return
} }

View File

@ -4,12 +4,13 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/archiverclient" "github.com/TicketsBot/archiverclient"
"github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
"strconv" "strconv"
) )
@ -33,21 +34,35 @@ func ModmailLogViewHandler(ctx *gin.Context) {
guild, _ := cache.Instance.GetGuild(guildId, false) guild, _ := cache.Instance.GetGuild(guildId, false)
// get ticket UUID // get ticket UUID
uuid := ctx.Param("uuid") archiveUuid, err := uuid.FromString(ctx.Param("uuid"))
if err != nil {
// TODO: 404 error page
ctx.AbortWithStatusJSON(404, gin.H{
"success": false,
"error": "Modmail archive not found",
})
return
}
// get ticket object // get ticket object
archiveCh := make(chan table.ModMailArchive) archive, err := database.Client.ModmailArchive.Get(archiveUuid)
go table.GetModmailArchive(uuid, archiveCh) if err != nil {
archive := <-archiveCh // TODO: 500 error page
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
// Verify this is a valid ticket and it is closed // Verify this is a valid ticket and it is closed
if archive.Uuid == "" { if archive.Uuid == uuid.Nil{
ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs/modmail", guild.Id)) ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs/modmail", guild.Id))
return return
} }
// Verify this modmail ticket was for this guild // Verify this modmail ticket was for this guild
if archive.Guild != guildId { if archive.GuildId != guildId {
ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs/modmail", guild.Id)) ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs/modmail", guild.Id))
return return
} }
@ -55,13 +70,13 @@ func ModmailLogViewHandler(ctx *gin.Context) {
// Verify the user has permissions to be here // Verify the user has permissions to be here
isAdmin := make(chan bool) isAdmin := make(chan bool)
go utils.IsAdmin(guild, userId, isAdmin) go utils.IsAdmin(guild, userId, isAdmin)
if !<-isAdmin && archive.User != userId { if !<-isAdmin && archive.UserId != userId {
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page
return return
} }
// retrieve ticket messages from bucket // retrieve ticket messages from bucket
messages, err := Archiver.GetModmail(guildId, uuid) messages, err := Archiver.GetModmail(guildId, archiveUuid.String())
if err != nil { if err != nil {
if errors.Is(err, archiverclient.ErrExpired) { if errors.Is(err, archiverclient.ErrExpired) {
ctx.String(200, "Archives expired: Purchase premium for permanent log storage") // TODO: Actual error page ctx.String(200, "Archives expired: Purchase premium for permanent log storage") // TODO: Actual error page
@ -73,7 +88,7 @@ func ModmailLogViewHandler(ctx *gin.Context) {
} }
// format to html // format to html
html, err := Archiver.Encode(messages, fmt.Sprintf("modmail-%s", uuid)) html, err := Archiver.Encode(messages, fmt.Sprintf("modmail-%s", archiveUuid))
if err != nil { if err != nil {
ctx.String(500, fmt.Sprintf("Failed to retrieve archive - please contact the developers: %s", err.Error())) ctx.String(500, fmt.Sprintf("Failed to retrieve archive - please contact the developers: %s", err.Error()))
return return

View File

@ -1,85 +1,19 @@
package manage package manage
import ( import (
"fmt"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rxdn/gdl/objects/channel"
"strconv"
) )
func SettingsHandler(ctx *gin.Context) { func SettingsHandler(ctx *gin.Context) {
store := sessions.Default(ctx) store := sessions.Default(ctx)
guildId := ctx.Keys["guildid"].(uint64)
userId := utils.GetUserId(store)
// Verify the guild exists
guildIdStr := ctx.Param("id")
guildId, err := strconv.ParseUint(guildIdStr, 10, 64)
if err != nil {
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page
return
}
// Check the bot is in the guild
guild, isInGuild := cache.Instance.GetGuild(guildId, false)
if !isInGuild {
ctx.Redirect(302, fmt.Sprintf("https://invite.ticketsbot.net/?guild_id=%s&disable_guild_select=true&response_type=code&scope=bot%%20identify&redirect_uri=%s", guildIdStr, config.Conf.Server.BaseUrl))
return
}
// Verify the user has permissions to be here
isAdmin := make(chan bool)
go utils.IsAdmin(guild, userId, isAdmin)
if !<-isAdmin {
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page
return
}
namingSchemeChan := make(chan table.NamingScheme)
go table.GetTicketNamingScheme(guildId, namingSchemeChan)
namingScheme := <-namingSchemeChan
// get guild channels from cache
channels := cache.Instance.GetGuildChannels(guildId)
// separate out categories
categories := make([]channel.Channel, 0)
for _, ch := range channels {
if ch.Type == channel.ChannelTypeGuildCategory {
categories = append(categories, ch)
}
}
panelSettings := table.GetPanelSettings(guildId)
// Users can close
usersCanCloseChan := make(chan bool)
go table.IsUserCanClose(guildId, usersCanCloseChan)
usersCanClose := <-usersCanCloseChan
invalidPrefix := ctx.Query("validPrefix") == "false"
invalidWelcomeMessage := ctx.Query("validWelcomeMessage") == "false"
invalidTicketLimit := ctx.Query("validTicketLimit") == "false"
ctx.HTML(200, "manage/settings", gin.H{ ctx.HTML(200, "manage/settings", gin.H{
"name": store.Get("name").(string), "name": store.Get("name").(string),
"guildId": guildIdStr, "guildId": guildId,
"avatar": store.Get("avatar").(string), "avatar": store.Get("avatar").(string),
"categories": categories, "baseUrl": config.Conf.Server.BaseUrl,
"channels": channels,
"invalidPrefix": invalidPrefix,
"invalidWelcomeMessage": invalidWelcomeMessage,
"invalidTicketLimit": invalidTicketLimit,
"csrf": store.Get("csrf").(string),
"paneltitle": panelSettings.Title,
"panelcontent": panelSettings.Content,
"panelcolour": strconv.FormatInt(int64(panelSettings.Colour), 16),
"usersCanClose": usersCanClose,
"namingScheme": string(namingScheme),
}) })
} }

View File

@ -11,10 +11,10 @@ func TicketViewHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64) guildId := ctx.Keys["guildid"].(uint64)
ctx.HTML(200, "manage/ticketview", gin.H{ ctx.HTML(200, "manage/ticketview", gin.H{
"name": store.Get("name").(string), "name": store.Get("name").(string),
"guildId": guildId, "guildId": guildId,
"avatar": store.Get("avatar").(string), "avatar": store.Get("avatar").(string),
"baseUrl": config.Conf.Server.BaseUrl, "baseUrl": config.Conf.Server.BaseUrl,
"uuid": ctx.Param("uuid"), "ticketId": ctx.Param("ticketId"),
}) })
} }

View File

@ -1,14 +1,13 @@
package root package root
import ( import (
"encoding/base64"
"encoding/json"
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" dbclient "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/GoPanel/utils/discord" "github.com/TicketsBot/GoPanel/utils/discord"
userEndpoint "github.com/TicketsBot/GoPanel/utils/discord/endpoints/user" userEndpoint "github.com/TicketsBot/GoPanel/utils/discord/endpoints/user"
"github.com/TicketsBot/database"
"github.com/apex/log" "github.com/apex/log"
"github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -92,6 +91,8 @@ func CallbackHandler(ctx *gin.Context) {
return return
} }
var wrappedGuilds []database.UserGuild
// endpoint's partial guild doesn't include ownerid // endpoint's partial guild doesn't include ownerid
// we only user cached guilds on the index page, so it doesn't matter if we don't have have the real owner id // we only user cached guilds on the index page, so it doesn't matter if we don't have have the real owner id
// if the user isn't the owner, as we pull from the cache on other endpoints // if the user isn't the owner, as we pull from the cache on other endpoints
@ -99,16 +100,17 @@ func CallbackHandler(ctx *gin.Context) {
if guild.Owner { if guild.Owner {
guild.OwnerId = currentUser.Id guild.OwnerId = currentUser.Id
} }
wrappedGuilds = append(wrappedGuilds, database.UserGuild{
GuildId: guild.Id,
Name: guild.Name,
Owner: guild.Owner,
UserPermissions: int32(guild.Permissions),
})
} }
marshalled, err := json.Marshal(guilds) // TODO: Error handling
if err != nil { go dbclient.Client.UserGuilds.Set(currentUser.Id, wrappedGuilds)
log.Error(err.Error())
return
}
// TODO: unfuck this
table.UpdateGuilds(currentUser.Id, base64.StdEncoding.EncodeToString(marshalled))
}() }()
} }

View File

@ -2,7 +2,7 @@ package root
import ( import (
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -13,19 +13,31 @@ func IndexHandler(ctx *gin.Context) {
store := sessions.Default(ctx) store := sessions.Default(ctx)
userId := utils.GetUserId(store) userId := utils.GetUserId(store)
userGuilds := table.GetGuilds(userId) userGuilds, err := database.Client.UserGuilds.Get(userId)
if err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
adminGuilds := make([]guild.Guild, 0) adminGuilds := make([]guild.Guild, 0)
for _, g := range userGuilds { for _, g := range userGuilds {
fakeGuild := guild.Guild{ fakeGuild := guild.Guild{
Id: g.Id, Id: g.GuildId,
OwnerId: g.OwnerId, Owner: g.Owner,
Permissions: g.Permissions, Permissions: int(g.UserPermissions),
}
if g.Owner {
fakeGuild.OwnerId = userId
} }
isAdmin := make(chan bool) isAdmin := make(chan bool)
go utils.IsAdmin(fakeGuild, userId, isAdmin) go utils.IsAdmin(fakeGuild, userId, isAdmin)
if <-isAdmin { if <-isAdmin {
adminGuilds = append(adminGuilds, g) adminGuilds = append(adminGuilds, fakeGuild)
} }
} }

View File

@ -1,7 +1,6 @@
package middleware package middleware
import ( import (
"errors"
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
@ -22,6 +21,7 @@ func AuthenticateToken(ctx *gin.Context) {
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(403, gin.H{ ctx.AbortWithStatusJSON(403, gin.H{
"success": false,
"error": err.Error(), "error": err.Error(),
}) })
return return
@ -31,7 +31,8 @@ func AuthenticateToken(ctx *gin.Context) {
userId, hasUserId := claims["userid"] userId, hasUserId := claims["userid"]
if !hasUserId { if !hasUserId {
ctx.AbortWithStatusJSON(403, gin.H{ ctx.AbortWithStatusJSON(403, gin.H{
"error": errors.New("token is invalid"), "success": false,
"error": "Token is invalid",
}) })
return return
} }
@ -39,7 +40,8 @@ func AuthenticateToken(ctx *gin.Context) {
parsedId, err := strconv.ParseUint(userId.(string), 10, 64) parsedId, err := strconv.ParseUint(userId.(string), 10, 64)
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(403, gin.H{ ctx.AbortWithStatusJSON(403, gin.H{
"error": errors.New("token is invalid"), "success": false,
"error": "Token is invalid",
}) })
return return
} }
@ -47,7 +49,8 @@ func AuthenticateToken(ctx *gin.Context) {
ctx.Keys["userid"] = parsedId ctx.Keys["userid"] = parsedId
} else { } else {
ctx.AbortWithStatusJSON(403, gin.H{ ctx.AbortWithStatusJSON(403, gin.H{
"error": errors.New("token is invalid"), "success": false,
"error": "Token is invalid",
}) })
return return
} }

View File

@ -66,8 +66,7 @@ func StartServer() {
authenticateGuild.GET("/manage/:id/tags", manage.TagsHandler) authenticateGuild.GET("/manage/:id/tags", manage.TagsHandler)
authenticateGuild.GET("/manage/:id/tickets", manage.TicketListHandler) authenticateGuild.GET("/manage/:id/tickets", manage.TicketListHandler)
authenticateGuild.GET("/manage/:id/tickets/view/:uuid", manage.TicketViewHandler) authenticateGuild.GET("/manage/:id/tickets/view/:ticketId", manage.TicketViewHandler)
authenticateGuild.POST("/manage/:id/tickets/view/:uuid", api.SendMessage)
authorized.GET("/webchat", manage.WebChatWs) authorized.GET("/webchat", manage.WebChatWs)
} }
@ -90,13 +89,13 @@ func StartServer() {
guildAuthApi.PUT("/:id/panels", api.CreatePanel) guildAuthApi.PUT("/:id/panels", api.CreatePanel)
guildAuthApi.DELETE("/:id/panels/:message", api.DeletePanel) guildAuthApi.DELETE("/:id/panels/:message", api.DeletePanel)
guildAuthApi.GET("/:id/logs/:page", api.GetLogs) guildAuthApi.GET("/:id/logs/", api.GetLogs)
guildAuthApi.GET("/:id/modmail/logs/:page", api.GetModmailLogs) guildAuthApi.GET("/:id/modmail/logs/", api.GetModmailLogs)
guildAuthApi.GET("/:id/tickets", api.GetTickets) guildAuthApi.GET("/:id/tickets", api.GetTickets)
guildAuthApi.GET("/:id/tickets/:uuid", api.GetTicket) guildAuthApi.GET("/:id/tickets/:ticketId", api.GetTicket)
guildAuthApi.POST("/:id/tickets/:uuid", api.SendMessage) guildAuthApi.POST("/:id/tickets/:ticketId", api.SendMessage)
guildAuthApi.DELETE("/:id/tickets/:uuid", api.CloseTicket) guildAuthApi.DELETE("/:id/tickets/:ticketId", api.CloseTicket)
guildAuthApi.GET("/:id/tags", api.TagsListHandler) guildAuthApi.GET("/:id/tags", api.TagsListHandler)
guildAuthApi.PUT("/:id/tags", api.CreateTag) guildAuthApi.PUT("/:id/tags", api.CreateTag)

View File

@ -16,12 +16,8 @@ id=
secret="" secret=""
redirectUri="" redirectUri=""
[mariadb] [database]
host="127.0.0.1" uri="postgres://user:pwd@localhost:5432/database?pool_max_conns=10"
username="ryan"
password="ryan"
database="tickets"
threads=5
[bot] [bot]
token="" token=""

View File

@ -7,13 +7,13 @@ import (
type ( type (
Config struct { Config struct {
Admins []string Admins []string
Server Server Server Server
Oauth Oauth Oauth Oauth
MariaDB MariaDB Database Database
Bot Bot Bot Bot
Redis Redis Redis Redis
Cache Cache Cache Cache
} }
Server struct { Server struct {
@ -41,12 +41,8 @@ type (
RedirectUri string RedirectUri string
} }
MariaDB struct { Database struct {
Host string Uri string
Username string
Password string
Database string
Threads int
} }
Bot struct { Bot struct {

View File

@ -1,35 +1,31 @@
package database package database
import ( import (
"fmt" "context"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
_ "github.com/go-sql-driver/mysql" "github.com/TicketsBot/database"
"github.com/jinzhu/gorm" "github.com/jackc/pgx/v4"
) "github.com/jackc/pgx/v4/log/logrusadapter"
"github.com/jackc/pgx/v4/pgxpool"
var ( "github.com/sirupsen/logrus"
Database gorm.DB )
)
var Client *database.Database
func ConnectToDatabase() {
uri := fmt.Sprintf( func ConnectToDatabase() {
"%s:%s@tcp(%s:3306)/%s?charset=utf8mb4&parseTime=True&loc=Local", config, err := pgxpool.ParseConfig(config.Conf.Database.Uri); if err != nil {
config.Conf.MariaDB.Username, panic(err)
config.Conf.MariaDB.Password, }
config.Conf.MariaDB.Host,
config.Conf.MariaDB.Database, // TODO: Sentry
) config.ConnConfig.LogLevel = pgx.LogLevelWarn
config.ConnConfig.Logger = logrusadapter.NewLogger(logrus.New())
db, err := gorm.Open("mysql", uri)
if err != nil { pool, err := pgxpool.ConnectConfig(context.Background(), config)
panic(err) if err != nil {
} panic(err)
}
db.DB().SetMaxOpenConns(config.Conf.MariaDB.Threads)
db.DB().SetMaxIdleConns(0) Client = database.NewDatabase(pool)
Client.CreateTables(pool)
db.Set("gorm:table_options", "charset=utf8mb4") }
db.BlockGlobalUpdate(true)
Database = *db
}

View File

@ -1,25 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type ArchiveChannel struct {
Guild uint64 `gorm:"column:GUILDID"`
Channel uint64 `gorm:"column:CHANNELID"`
}
func (ArchiveChannel) TableName() string {
return "archivechannel"
}
func UpdateArchiveChannel(guildId uint64, channelId uint64) {
var channel ArchiveChannel
database.Database.Where(ArchiveChannel{Guild: guildId}).Assign(ArchiveChannel{Channel: channelId}).FirstOrCreate(&channel)
}
func GetArchiveChannel(guildId uint64, ch chan uint64) {
var channel ArchiveChannel
database.Database.Where(&ArchiveChannel{Guild: guildId}).First(&channel)
ch <- channel.Channel
}

View File

@ -1,35 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type BlacklistNode struct {
Assoc int `gorm:"column:ASSOCID;type:int;primary_key;auto_increment"`
Guild uint64 `gorm:"column:GUILDID"`
User uint64 `gorm:"column:USERID"`
}
func (BlacklistNode) TableName() string {
return "blacklist"
}
func IsBlacklisted(guildId, userId uint64) bool {
var count int
database.Database.Table("blacklist").Where(&BlacklistNode{Guild: guildId, User: userId}).Count(&count)
return count > 0
}
func AddBlacklist(guildId, userId uint64) {
database.Database.Create(&BlacklistNode{Guild: guildId, User: userId})
}
func RemoveBlacklist(guildId, userId uint64) {
database.Database.Where(BlacklistNode{Guild: guildId, User: userId}).Delete(BlacklistNode{})
}
func GetBlacklistNodes(guildId uint64) []BlacklistNode {
var nodes []BlacklistNode
database.Database.Where(&BlacklistNode{Guild: guildId}).Find(&nodes)
return nodes
}

View File

@ -1,24 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type ChannelCategory struct {
GuildId uint64 `gorm:"column:GUILDID"`
Category uint64 `gorm:"column:CATEGORYID"`
}
func (ChannelCategory) TableName() string {
return "channelcategory"
}
func UpdateChannelCategory(guildId uint64, categoryId uint64) {
database.Database.Where(&ChannelCategory{GuildId: guildId}).Assign(&ChannelCategory{Category: categoryId}).FirstOrCreate(&ChannelCategory{})
}
func GetChannelCategory(guildId uint64, ch chan uint64) {
var category ChannelCategory
database.Database.Where(&ChannelCategory{GuildId: guildId}).First(&category)
ch <- category.Category
}

View File

@ -1,39 +0,0 @@
package table
import (
"encoding/base64"
"encoding/json"
"github.com/TicketsBot/GoPanel/database"
"github.com/rxdn/gdl/objects/guild"
)
type GuildCache struct {
UserId uint64 `gorm:"column:USERID"`
Guilds string `gorm:"column:guilds;type:mediumtext"`
}
func (GuildCache) TableName() string {
return "guildscache"
}
// this is horrible
func UpdateGuilds(userId uint64, guilds string) {
var cache GuildCache
database.Database.Where(&GuildCache{UserId: userId}).Assign(&GuildCache{Guilds: guilds}).FirstOrCreate(&cache)
}
func GetGuilds(userId uint64) []guild.Guild {
var cache GuildCache
database.Database.Where(&GuildCache{UserId: userId}).First(&cache)
decoded, err := base64.StdEncoding.DecodeString(cache.Guilds)
if err != nil {
return nil
}
var guilds []guild.Guild
if err := json.Unmarshal(decoded, &guilds); err != nil {
return nil
}
return guilds
}

View File

@ -1,39 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
"time"
)
type ModMailArchive struct {
Uuid string `gorm:"column:UUID;type:varchar(36);unique;primary_key"`
Guild uint64 `gorm:"column:GUILDID"`
User uint64 `gorm:"column:USERID"`
CloseTime time.Time `gorm:"column:CLOSETIME"`
}
func (ModMailArchive) TableName() string {
return "modmail_archive"
}
func (m *ModMailArchive) Store() {
database.Database.Create(m)
}
func GetModmailArchive(uuid string, ch chan ModMailArchive) {
var row ModMailArchive
database.Database.Where(ModMailArchive{Uuid: uuid}).Take(&row)
ch <- row
}
func GetModmailArchivesByUser(userId, guildId uint64, ch chan []ModMailArchive) {
var rows []ModMailArchive
database.Database.Where(ModMailArchive{User: userId, Guild: guildId}).Order("CLOSETIME desc").Find(&rows)
ch <- rows
}
func GetModmailArchivesByGuild(guildId uint64, ch chan []ModMailArchive) {
var rows []ModMailArchive
database.Database.Where(ModMailArchive{Guild: guildId}).Order("CLOSETIME desc").Find(&rows)
ch <- rows
}

View File

@ -1,37 +0,0 @@
package table
import "github.com/TicketsBot/GoPanel/database"
type TicketNamingScheme struct {
Guild uint64 `gorm:"column:GUILDID;unique;primary_key"`
NamingScheme string `gorm:"column:NAMINGSCHEME;type:VARCHAR(16)"`
}
type NamingScheme string
const (
Id NamingScheme = "id"
Username NamingScheme = "username"
)
var Schemes = []NamingScheme{Id, Username}
func (TicketNamingScheme) TableName() string {
return "TicketNamingScheme"
}
func GetTicketNamingScheme(guild uint64, ch chan NamingScheme) {
var node TicketNamingScheme
database.Database.Where(TicketNamingScheme{Guild: guild}).First(&node)
namingScheme := node.NamingScheme
if namingScheme == "" {
ch <- Id
} else {
ch <- NamingScheme(namingScheme)
}
}
func SetTicketNamingScheme(guild uint64, scheme NamingScheme) {
database.Database.Where(&TicketNamingScheme{Guild: guild}).Assign(&TicketNamingScheme{NamingScheme: string(scheme)}).FirstOrCreate(&TicketNamingScheme{})
}

View File

@ -1,56 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type Panel struct {
MessageId uint64 `gorm:"column:MESSAGEID"`
ChannelId uint64 `gorm:"column:CHANNELID"`
GuildId uint64 `gorm:"column:GUILDID"` // Might be useful in the future so we store it
Title string `gorm:"column:TITLE;type:VARCHAR(255)"`
Content string `gorm:"column:CONTENT;type:TEXT"`
Colour uint32 `gorm:"column:COLOUR`
TargetCategory uint64 `gorm:"column:TARGETCATEGORY"`
ReactionEmote string `gorm:"column:REACTIONEMOTE;type:VARCHAR(32)"`
}
func (Panel) TableName() string {
return "panels"
}
func AddPanel(messageId, channelId, guildId uint64, title, content string, colour uint32, targetCategory uint64, reactionEmote string) {
database.Database.Create(&Panel{
MessageId: messageId,
ChannelId: channelId,
GuildId: guildId,
Title: title,
Content: content,
Colour: colour,
TargetCategory: targetCategory,
ReactionEmote: reactionEmote,
})
}
func IsPanel(messageId uint64, ch chan bool) {
var count int
database.Database.Table(Panel{}.TableName()).Where(Panel{MessageId: messageId}).Count(&count)
ch <- count > 0
}
func GetPanelsByGuild(guildId uint64, ch chan []Panel) {
var panels []Panel
database.Database.Where(Panel{GuildId: guildId}).Find(&panels)
ch <- panels
}
func GetPanel(messageId uint64, ch chan Panel) {
var row Panel
database.Database.Where(Panel{MessageId: messageId}).Take(&row)
ch <- row
}
func DeletePanel(msgId uint64) {
database.Database.Where(Panel{MessageId: msgId}).Delete(Panel{})
}

View File

@ -1,61 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type PanelSettings struct {
GuildId uint64 `gorm:"column:GUILDID"`
Title string `gorm:"column:TITLE;type:VARCHAR(255)"`
Content string `gorm:"column:CONTENT;type:TEXT"`
Colour int `gorm:"column:COLOUR`
}
func (PanelSettings) TableName() string {
return "panelsettings"
}
func UpdatePanelSettings(guildId uint64, title string, content string, colour int) {
settings := PanelSettings{
Title: title,
Content: content,
Colour: colour,
}
database.Database.Where(&PanelSettings{GuildId: guildId}).Assign(&settings).FirstOrCreate(&PanelSettings{})
}
func UpdatePanelTitle(guildId uint64, title string) {
settings := PanelSettings{
Title: title,
}
database.Database.Where(&PanelSettings{GuildId: guildId}).Assign(&settings).FirstOrCreate(&PanelSettings{})
}
func UpdatePanelContent(guildId uint64, content string) {
settings := PanelSettings{
Content: content,
}
database.Database.Where(&PanelSettings{GuildId: guildId}).Assign(&settings).FirstOrCreate(&PanelSettings{})
}
func UpdatePanelColour(guildId uint64, colour int) {
settings := PanelSettings{
Colour: colour,
}
database.Database.Where(&PanelSettings{GuildId: guildId}).Assign(&settings).FirstOrCreate(&PanelSettings{})
}
func GetPanelSettings(guildId uint64) PanelSettings {
settings := PanelSettings{
Title: "Open A Ticket",
Content: "React with :envelope_with_arrow: to open a ticket",
Colour: 2335514,
}
database.Database.Where(PanelSettings{GuildId: guildId}).First(&settings)
return settings
}

View File

@ -1,44 +0,0 @@
package table
import "github.com/TicketsBot/GoPanel/database"
type PermissionNode struct {
GuildId uint64 `gorm:"column:GUILDID"`
UserId uint64 `gorm:"column:USERID"`
IsSupport bool `gorm:"column:ISSUPPORT"`
IsAdmin bool `gorm:"column:ISADMIN"`
}
func (PermissionNode) TableName() string {
return "permissions"
}
func GetAdminGuilds(userId uint64) []uint64 {
var nodes []PermissionNode
database.Database.Where(&PermissionNode{UserId: userId}).Find(&nodes)
ids := make([]uint64, 0)
for _, node := range nodes {
ids = append(ids, node.GuildId)
}
return ids
}
func IsSupport(guildId uint64, userId uint64) bool {
var node PermissionNode
database.Database.Where(&PermissionNode{GuildId: guildId, UserId: userId}).Take(&node)
return node.IsSupport
}
func IsAdmin(guildId uint64, userId uint64) bool {
var node PermissionNode
database.Database.Where(&PermissionNode{GuildId: guildId, UserId: userId}).Take(&node)
return node.IsAdmin
}
func IsStaff(guildId uint64, userId uint64) bool {
var node PermissionNode
database.Database.Where(&PermissionNode{GuildId: guildId, UserId: userId}).Take(&node)
return node.IsAdmin || node.IsSupport
}

View File

@ -1,36 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type PingEveryone struct {
GuildId uint64 `gorm:"column:GUILDID"`
PingEveryone bool `gorm:"column:PINGEVERYONE;type:TINYINT"`
}
func (PingEveryone) TableName() string {
return "pingeveryone"
}
// tldr I hate gorm
func UpdatePingEveryone(guildId uint64, pingEveryone bool) {
var settings []PingEveryone
database.Database.Where(&PingEveryone{GuildId: guildId}).Find(&settings)
updated := PingEveryone{guildId, pingEveryone}
if len(settings) == 0 {
database.Database.Create(&updated)
} else {
database.Database.Table("pingeveryone").Where("GUILDID = ?", guildId).Update("PINGEVERYONE", pingEveryone)
}
//database.Database.Where(&PingEveryone{GuildId: guildId}).Assign(&updated).FirstOrCreate(&PingEveryone{})
}
func GetPingEveryone(guildId uint64, ch chan bool) {
pingEveryone := PingEveryone{PingEveryone: true}
database.Database.Where(&PingEveryone{GuildId: guildId}).First(&pingEveryone)
ch <- pingEveryone.PingEveryone
}

View File

@ -1,24 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type Prefix struct {
GuildId uint64 `gorm:"column:GUILDID"`
Prefix string `gorm:"column:PREFIX;type:varchar(8)"`
}
func (Prefix) TableName() string {
return "prefix"
}
func UpdatePrefix(guildId uint64, prefix string) {
database.Database.Where(&Prefix{GuildId: guildId}).Assign(&Prefix{Prefix: prefix}).FirstOrCreate(&Prefix{})
}
func GetPrefix(guildId uint64, ch chan string) {
prefix := Prefix{Prefix: "t!"}
database.Database.Where(&Prefix{GuildId: guildId}).First(&prefix)
ch <- prefix.Prefix
}

View File

@ -1,72 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
"strings"
"time"
)
type PremiumGuilds struct {
Guild uint64 `gorm:"column:GUILDID;unique;primary_key"`
Expiry int64 `gorm:"column:EXPIRY"`
User uint64 `gorm:"column:USERID"`
ActivatedBy uint64 `gorm:"column:ACTIVATEDBY"`
Keys string `gorm:"column:KEYSUSED"`
}
func (PremiumGuilds) TableName() string {
return "premiumguilds"
}
func IsPremium(guild uint64, ch chan bool) {
var node PremiumGuilds
database.Database.Where(PremiumGuilds{Guild: guild}).First(&node)
if node.Expiry == 0 {
ch <- false
return
}
current := time.Now().UnixNano() / int64(time.Millisecond)
ch <- node.Expiry > current
}
func AddPremium(key string, guild, userId uint64, length int64, activatedBy uint64) {
var expiry int64
hasPrem := make(chan bool)
go IsPremium(guild, hasPrem)
isPremium := <-hasPrem
if isPremium {
expiryChan := make(chan int64)
go GetExpiry(guild, expiryChan)
currentExpiry := <-expiryChan
expiry = currentExpiry + length
} else {
current := time.Now().UnixNano() / int64(time.Millisecond)
expiry = current + length
}
keysChan := make(chan []string)
go GetKeysUsed(guild, keysChan)
keys := <-keysChan
keys = append(keys, key)
keysStr := strings.Join(keys, ",")
var node PremiumGuilds
database.Database.Where(PremiumGuilds{Guild: guild}).Assign(PremiumGuilds{Expiry: expiry, User: userId, ActivatedBy: activatedBy, Keys: keysStr}).FirstOrCreate(&node)
}
func GetExpiry(guild uint64, ch chan int64) {
var node PremiumGuilds
database.Database.Where(PremiumGuilds{Guild: guild}).First(&node)
ch <- node.Expiry
}
func GetKeysUsed(guild uint64, ch chan []string) {
var node PremiumGuilds
database.Database.Where(PremiumGuilds{Guild: guild}).First(&node)
ch <- strings.Split(node.Keys, ",")
}

View File

@ -1,77 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type RolePermissions struct {
GuildId uint64 `gorm:"column:GUILDID"`
RoleId uint64 `gorm:"column:ROLEID"`
Support bool `gorm:"column:ISSUPPORT"`
Admin bool `gorm:"column:ISADMIN"`
}
func (RolePermissions) TableName() string {
return "role_permissions"
}
func IsSupportRole(guildId, roleId uint64, ch chan bool) {
var node RolePermissions
database.Database.Where(RolePermissions{GuildId: guildId, RoleId: roleId}).First(&node)
ch <- node.Support
}
func IsAdminRole(guildId, roleId uint64, ch chan bool) {
var node RolePermissions
database.Database.Where(RolePermissions{GuildId: guildId, RoleId: roleId}).First(&node)
ch <- node.Admin
}
func GetAdminRoles(guildId uint64, ch chan []uint64) {
var nodes []RolePermissions
database.Database.Where(RolePermissions{GuildId: guildId, Admin: true}).Find(&nodes)
ids := make([]uint64, 0)
for _, node := range nodes {
ids = append(ids, node.RoleId)
}
ch <- ids
}
func GetSupportRoles(guildId uint64, ch chan []uint64) {
var nodes []RolePermissions
database.Database.Where(RolePermissions{GuildId: guildId, Support: true, Admin: false}).Find(&nodes)
ids := make([]uint64, 0)
for _, node := range nodes {
ids = append(ids, node.RoleId)
}
ch <- ids
}
func AddAdminRole(guildId, roleId uint64) {
var node RolePermissions
database.Database.Where(RolePermissions{GuildId: guildId, RoleId: roleId}).Assign(RolePermissions{Admin: true, Support: true}).FirstOrCreate(&node)
}
func AddSupportRole(guildId, roleId uint64) {
var node RolePermissions
database.Database.Where(RolePermissions{GuildId: guildId, RoleId: roleId}).Assign(RolePermissions{Support: true}).FirstOrCreate(&node)
}
func RemoveAdminRole(guildId, roleId uint64) {
var node RolePermissions
database.Database.Where(RolePermissions{GuildId: guildId, RoleId: roleId}).Take(&node)
database.Database.Model(&node).Where("GUILDID = ? AND ROLEID = ?", guildId, roleId).Update("ISADMIN", false)
}
func RemoveSupportRole(guildId, roleId uint64) {
var node RolePermissions
database.Database.Where(RolePermissions{GuildId: guildId, RoleId: roleId}).Take(&node)
database.Database.Model(&node).Where("GUILDID = ? AND ROLEID = ?", guildId, roleId).Updates(map[string]interface{}{
"ISADMIN": false,
"ISSUPPORT": false,
})
}

View File

@ -1,42 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
uuid "github.com/satori/go.uuid"
)
type Tag struct {
Uuid string `gorm:"column:UUID;type:varchar(36);unique;primary_key"`
Id string `gorm:"column:ID;type:varchar(16)"`
Guild uint64 `gorm:"column:GUILDID"`
Content string `gorm:"column:TEXT;type:TEXT"`
}
func (Tag) TableName() string {
return "cannedresponses"
}
func GetTag(guild uint64, id string, ch chan string) {
var node Tag
database.Database.Where(Tag{Id: id, Guild: guild}).Take(&node)
ch <- node.Content
}
func GetTags(guild uint64, ch chan []Tag) {
var rows []Tag
database.Database.Where(Tag{Guild: guild}).Find(&rows)
ch <- rows
}
func AddTag(guild uint64, id string, content string) error {
return database.Database.Create(&Tag{
Uuid: uuid.NewV4().String(),
Id: id,
Guild: guild,
Content: content,
}).Error
}
func DeleteTag(guild uint64, id string) error {
return database.Database.Where(Tag{Id: id, Guild: guild}).Delete(&Tag{}).Error
}

View File

@ -1,50 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type TicketArchive struct {
Uuid string `gorm:"column:UUID;type:varchar(36)"`
Guild uint64 `gorm:"column:GUILDID"`
User uint64 `gorm:"column:USERID"`
Username string `gorm:"column:USERNAME;type:varchar(32)"`
TicketId int `gorm:"column:TICKETID"`
CdnUrl string `gorm:"column:CDNURL;type:varchar(100)"`
}
func (TicketArchive) TableName() string {
return "ticketarchive"
}
func GetTicketArchives(guildId uint64) []TicketArchive {
var archives []TicketArchive
database.Database.Where(&TicketArchive{Guild: guildId}).Order("TICKETID desc").Find(&archives)
return archives
}
func GetFilteredTicketArchives(guildId uint64, userId uint64, username string, ticketId int) []TicketArchive {
var archives []TicketArchive
query := database.Database.Where(&TicketArchive{Guild: guildId})
if userId != 0 {
query = query.Where(&TicketArchive{User: userId})
}
if username != "" {
query = query.Where(&TicketArchive{Username: username})
}
if ticketId != 0 {
query = query.Where(&TicketArchive{TicketId: ticketId})
}
query.Order("TICKETID desc").Find(&archives)
return archives
}
func GetCdnUrl(guildId uint64, uuid string) string {
var archive TicketArchive
database.Database.Where(&TicketArchive{Guild: guildId, Uuid: uuid}).First(&archive)
return archive.CdnUrl
}

View File

@ -1,24 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type TicketLimit struct {
GuildId uint64 `gorm:"column:GUILDID"`
Limit int `gorm:"column:TICKETLIMIT"`
}
func (TicketLimit) TableName() string {
return "ticketlimit"
}
func UpdateTicketLimit(guildId uint64, limit int) {
database.Database.Where(&TicketLimit{GuildId: guildId}).Assign(&TicketLimit{Limit: limit}).FirstOrCreate(&TicketLimit{})
}
func GetTicketLimit(guildId uint64, ch chan int) {
limit := TicketLimit{Limit: 5}
database.Database.Where(&TicketLimit{GuildId: guildId}).First(&limit)
ch <- limit.Limit
}

View File

@ -1,54 +0,0 @@
package table
import "github.com/TicketsBot/GoPanel/database"
type Ticket struct {
Uuid string `gorm:"column:UUID;type:varchar(36);primary_key"`
TicketId int `gorm:"column:ID"`
Guild uint64 `gorm:"column:GUILDID"`
Channel uint64 `gorm:"column:CHANNELID"`
Owner uint64 `gorm:"column:OWNERID"`
Members string `gorm:"column:MEMBERS;type:text"`
IsOpen bool `gorm:"column:OPEN"`
OpenTime int64 `gorm:"column:OPENTIME"`
}
func (Ticket) TableName() string {
return "tickets"
}
func GetTickets(guild uint64) []Ticket {
var tickets []Ticket
database.Database.Where(&Ticket{Guild: guild}).Order("ID asc").Find(&tickets)
return tickets
}
func GetClosedTickets(guildId uint64) []Ticket {
var tickets []Ticket
database.Database.Where(&Ticket{Guild: guildId}).Where("OPEN = 0").Order("ID desc").Find(&tickets)
return tickets
}
func GetClosedTicketsByUserId(guildId uint64, userIds []uint64) []Ticket {
var tickets []Ticket
database.Database.Where(&Ticket{Guild: guildId}).Where("OPEN = 0").Where("OWNERID IN (?)", userIds).Order("ID desc").Find(&tickets)
return tickets
}
func GetOpenTickets(guild uint64) []Ticket {
var tickets []Ticket
database.Database.Where(&Ticket{Guild: guild, IsOpen: true}).Order("ID desc").Find(&tickets)
return tickets
}
func GetTicket(uuid string, ch chan Ticket) {
var ticket Ticket
database.Database.Where(&Ticket{Uuid: uuid}).First(&ticket)
ch <- ticket
}
func GetTicketById(guild uint64, id int, ch chan Ticket) {
var ticket Ticket
database.Database.Where(&Ticket{Guild: guild, TicketId: id}).First(&ticket)
ch <- ticket
}

View File

@ -1,31 +0,0 @@
package table
import "github.com/TicketsBot/GoPanel/database"
type TicketWebhook struct {
Uuid string `gorm:"column:UUID;type:varchar(36);unique;primary_key"`
WebhookUrl string `gorm:"column:CDNURL;type:varchar(200)"`
}
func (TicketWebhook) TableName() string {
return "webhooks"
}
func (w *TicketWebhook) AddWebhook() {
database.Database.Create(w)
}
func DeleteWebhookByUuid(uuid string) {
database.Database.Where(TicketWebhook{Uuid: uuid}).Delete(TicketWebhook{})
}
func GetWebhookByUuid(uuid string, res chan *string) {
var row TicketWebhook
database.Database.Where(TicketWebhook{Uuid: uuid}).Take(&row)
if row.WebhookUrl == "" {
res <- nil
} else {
res <- &row.WebhookUrl
}
}

View File

@ -1,27 +0,0 @@
package table
import "github.com/TicketsBot/GoPanel/database"
type UserCanClose struct {
Guild uint64 `gorm:"column:GUILDID;unique;primary_key"`
CanClose *bool `gorm:"column:CANCLOSE"`
}
func (UserCanClose) TableName() string {
return "usercanclose"
}
func IsUserCanClose(guild uint64, ch chan bool) {
var node UserCanClose
database.Database.Where(UserCanClose{Guild: guild}).First(&node)
if node.CanClose == nil {
ch <- true
} else {
ch <- *node.CanClose
}
}
func SetUserCanClose(guild uint64, value bool) {
database.Database.Where(&UserCanClose{Guild: guild}).Assign(&UserCanClose{CanClose: &value}).FirstOrCreate(&UserCanClose{})
}

View File

@ -1,22 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
"time"
)
type Votes struct {
Id uint64 `gorm:"type:bigint;unique_index;primary_key"`
VoteTime time.Time
}
func (Votes) TableName() string {
return "votes"
}
func HasVoted(owner uint64, ch chan bool) {
var node Votes
database.Database.Where(Votes{Id: owner}).First(&node)
ch <- time.Now().Sub(node.VoteTime) < 24*time.Hour
}

View File

@ -1,24 +0,0 @@
package table
import (
"github.com/TicketsBot/GoPanel/database"
)
type WelcomeMessage struct {
GuildId uint64 `gorm:"column:GUILDID"`
Message string `gorm:"column:MESSAGE;type:text"`
}
func (WelcomeMessage) TableName() string {
return "welcomemessages"
}
func UpdateWelcomeMessage(guildId uint64, message string) {
database.Database.Where(&WelcomeMessage{GuildId: guildId}).Assign(&WelcomeMessage{Message: message}).FirstOrCreate(&WelcomeMessage{})
}
func GetWelcomeMessage(guildId uint64, ch chan string) {
message := WelcomeMessage{Message: "No message specified"}
database.Database.Where(&WelcomeMessage{GuildId: guildId}).First(&message)
ch <- message.Message
}

5
go.mod
View File

@ -5,6 +5,7 @@ go 1.14
require ( require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v0.3.1
github.com/TicketsBot/archiverclient v0.0.0-20200425115930-0ca198cc8306 github.com/TicketsBot/archiverclient v0.0.0-20200425115930-0ca198cc8306
github.com/TicketsBot/database v0.0.0-20200511174804-50ba8d78e1f9
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
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
@ -14,6 +15,7 @@ require (
github.com/gin-gonic/gin v1.6.2 github.com/gin-gonic/gin v1.6.2
github.com/go-redis/redis v6.15.7+incompatible github.com/go-redis/redis v6.15.7+incompatible
github.com/go-sql-driver/mysql v1.5.0 github.com/go-sql-driver/mysql v1.5.0
github.com/gofrs/uuid v3.3.0+incompatible
github.com/gorilla/sessions v1.2.0 // indirect github.com/gorilla/sessions v1.2.0 // indirect
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/jackc/pgx/v4 v4.6.0 github.com/jackc/pgx/v4 v4.6.0
@ -21,8 +23,9 @@ require (
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/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62
github.com/rxdn/gdl v0.0.0-20200421193445-f200b9f466d7 github.com/rxdn/gdl v0.0.0-20200511170555-8ab2206d70df
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.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
) )

View File

@ -2,11 +2,11 @@ package messagequeue
import ( import (
"encoding/json" "encoding/json"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/database"
"github.com/apex/log" "github.com/apex/log"
) )
func (c *RedisClient) PublishPanelCreate(settings table.Panel) { func (c *RedisClient) PublishPanelCreate(settings database.Panel) {
encoded, err := json.Marshal(settings); if err != nil { encoded, err := json.Marshal(settings); if err != nil {
log.Error(err.Error()) log.Error(err.Error())
return return

View File

@ -6,23 +6,25 @@ import (
) )
type TicketCloseMessage struct { type TicketCloseMessage struct {
Uuid string GuildId uint64
User uint64 TicketId int
Reason string User uint64
Reason string
} }
func (c *RedisClient) PublishTicketClose(ticket string, userId uint64, reason string) { func (c *RedisClient) PublishTicketClose(guildId uint64, ticketId int, userId uint64, reason string) {
settings := TicketCloseMessage{ settings := TicketCloseMessage{
Uuid: ticket, GuildId: guildId,
User: userId, TicketId: ticketId,
Reason: reason, User: userId,
Reason: reason,
} }
encoded, err := json.Marshal(settings); if err != nil { encoded, err := json.Marshal(settings)
if err != nil {
log.Error(err.Error()) log.Error(err.Error())
return return
} }
c.Publish("tickets:close", string(encoded)) c.Publish("tickets:close", string(encoded))
} }

View File

@ -84,13 +84,16 @@
</div> </div>
</div> </div>
<!--<div aria-live="polite" aria-atomic="true" style="position: relative"> <div aria-live="polite" aria-atomic="true" style="position: relative">
<div style="position: absolute; right: 10px" id="toast-container"> <div style="position: absolute; right: 10px" id="toast-container">
</div> </div>
</div>--> </div>
<script> <script>
// TODO: Implement before param
const pageLimit = 30;
let currentPage = 1; let currentPage = 1;
let logs = [];
function appendLog(log) { function appendLog(log) {
const container = document.getElementById('log-container'); const container = document.getElementById('log-container');
@ -105,30 +108,35 @@
container.appendChild(tr); container.appendChild(tr);
} }
async function loadPage(page, ticketId, username, userId) { async function loadData(before, ticketId, username, userId) {
if (before === undefined) {
before = 0;
}
const container = document.getElementById('log-container'); const container = document.getElementById('log-container');
container.innerHTML = ''; container.innerHTML = '';
let url = '/api/{{.guildId}}/logs/' + page; let url = '/api/{{.guildId}}/logs?before=' + before;
if (ticketId !== undefined) { if (ticketId !== undefined) {
url += `?ticketid=${ticketId}`; url += `&ticketid=${ticketId}`;
} else if (username !== undefined) { } else if (username !== undefined) {
url += `?username=${username}`; url += `&username=${username}`;
} else if (userId !== undefined) { } else if (userId !== undefined) {
url += `?userid=${userId}`; url += `&userid=${userId}`;
} }
const res = await axios.get(url); const res = await axios.get(url);
for (log of res.data) { if (res.status === 200) {
appendLog(log); logs = res.data;
for (log of res.data) {
appendLog(log);
}
} else {
showToast('Error', res.data.error);
} }
document.getElementById('page-number').innerText = page; document.getElementById('page-number').innerText = currentPage;
}
async function loadData() {
await loadPage(currentPage);
} }
loadData(); loadData();
@ -136,8 +144,12 @@
<script> <script>
async function next() { async function next() {
if (logs.length === 0) {
return;
}
currentPage += 1; currentPage += 1;
await loadPage(currentPage); await loadData(logs[logs.length - 1].ticketid);
} }
async function previous() { async function previous() {
@ -145,8 +157,13 @@
return return
} }
let before = pageLimit;
if (logs.length > 0) {
before = logs[0] + pageLimit;
}
currentPage -= 1; currentPage -= 1;
await loadPage(currentPage); await loadData(before);
} }
async function filterLogs() { async function filterLogs() {
@ -155,13 +172,13 @@
const userId = document.getElementById('userid').value; const userId = document.getElementById('userid').value;
if (ticketId > 0) { if (ticketId > 0) {
await loadPage(1, ticketId); await loadData(0, ticketId);
} else if (username !== "") { } else if (username !== "") {
await loadPage(1, undefined, username); await loadData(0, undefined, username);
} else if (userId !== "") { } else if (userId !== "") {
await loadPage(1, undefined, undefined, userId); await loadData(0, undefined, undefined, userId);
} else { } else {
await loadPage(1); await loadData(0);
} }
} }
</script> </script>

View File

@ -84,6 +84,7 @@
<script> <script>
let currentPage = 1; let currentPage = 1;
let archives = [];
async function getUsername(userId) { async function getUsername(userId) {
const res = await axios.get('/api/{{.guildId}}/user/' + userId); const res = await axios.get('/api/{{.guildId}}/user/' + userId);
@ -103,41 +104,57 @@
container.appendChild(tr); container.appendChild(tr);
} }
async function loadPage(page, ticketId, username, userId) { async function loadData(after, before, username, userId) {
const container = document.getElementById('log-container'); if (after === undefined) after = '';
container.innerHTML = ''; if (before === undefined) before = '';
let url = '/api/{{.guildId}}/modmail/logs/' + page; let url = '/api/{{.guildId}}/modmail/logs?after=' + after + '&before=' + before;
if (username !== undefined) { if (username !== undefined) {
url += `?username=${username}`; url += `&username=${username}`;
} else if (userId !== undefined) { } else if (userId !== undefined) {
url += `?userid=${userId}`; url += `&userid=${userId}`;
} }
const res = await axios.get(url); const res = await axios.get(url);
if (res.status === 200) { if (res.status === 200) {
if (res.data.length === 0 && username === "" && userId === "") {
return false;
}
const container = document.getElementById('log-container');
container.innerHTML = '';
archives = res.data;
for (log of res.data) { for (log of res.data) {
await appendLog(log); await appendLog(log);
} }
} else { } else {
showToast('Error', res.data.error); showToast('Error', res.data.error);
return false;
} }
document.getElementById('page-number').innerText = page; return true;
}
async function loadData() {
await loadPage(currentPage);
} }
loadData(); loadData();
</script> </script>
<script> <script>
function updatePageNumber() {
document.getElementById('page-number').innerText = currentPage;
}
async function next() { async function next() {
currentPage += 1; if (archives.length === 0) {
await loadPage(currentPage); return;
}
if (await loadData(undefined, archives[archives.length - 1].uuid)) {
currentPage += 1;
updatePageNumber();
}
} }
async function previous() { async function previous() {
@ -145,21 +162,32 @@
return return
} }
currentPage -= 1; let after = '';
await loadPage(currentPage); if (archives.length > 0) {
after = archives[0].uuid;
}
if (await loadData(after)) {
currentPage -= 1;
updatePageNumber();
}
} }
// TODO: Paginate filtered logs
async function filterLogs() { async function filterLogs() {
const username = document.getElementById('username').value; const username = document.getElementById('username').value;
const userId = document.getElementById('userid').value; const userId = document.getElementById('userid').value;
if (username !== "") { if (username !== "") {
await loadPage(1, undefined, username); await loadData(undefined, undefined, username);
} else if (userId !== "") { } else if (userId !== "") {
await loadPage(1, undefined, undefined, userId); await loadData(undefined, undefined, undefined, userId);
} else { } else {
await loadPage(1); await loadData(undefined);
} }
currentPage = 1;
updatePageNumber();
} }
</script> </script>
</div> </div>

View File

@ -155,6 +155,7 @@
const res = await axios.put('/api/{{.guildId}}/panels', data); const res = await axios.put('/api/{{.guildId}}/panels', data);
if (res.status === 200 && res.data.success) { if (res.status === 200 && res.data.success) {
data.message_id = res.data.message_id;
appendPanel(data, await getChannels()); appendPanel(data, await getChannels());
showToast('Success', 'Panel created successfully') showToast('Success', 'Panel created successfully')
} else { } else {

View File

@ -39,7 +39,7 @@
<div class="col-md-12"> <div class="col-md-12">
<label>Welcome Message (Max len. 1000)</label> <label>Welcome Message (Max len. 1000)</label>
<textarea name="welcomeMessage" class="form-control" rows="3" id="welcome_message" <textarea name="welcomeMessage" class="form-control" rows="3" id="welcome_message"
style="resize: none">{{.welcomeMessage}}</textarea> style="resize: none"></textarea>
</div> </div>
</div> </div>

View File

@ -76,7 +76,7 @@
const res = await axios.put('/api/{{.guildId}}/tags', data); const res = await axios.put('/api/{{.guildId}}/tags', data);
if (res.status === 200 && res.data.success) { if (res.status === 200) {
document.getElementById('id').value = ''; document.getElementById('id').value = '';
document.getElementById('content').value = ''; document.getElementById('content').value = '';
@ -96,14 +96,14 @@
} }
} }
function appendTag(tag) { function appendTag(id, content) {
const container = document.getElementById('tag-container'); const container = document.getElementById('tag-container');
const tr = document.createElement('tr'); const tr = document.createElement('tr');
tr.id = tag.id; tr.id = id;
appendTd(tr, tag.id); appendTd(tr, id);
appendTd(tr, tag.content).classList.add('tag-content'); appendTd(tr, content).classList.add('tag-content');
appendButton(tr, 'Delete', () => { deleteTag(tag.id); }); appendButton(tr, 'Delete', () => { deleteTag(id); });
container.appendChild(tr); container.appendChild(tr);
} }
@ -112,8 +112,8 @@
const res = await axios.get('/api/{{.guildId}}/tags'); const res = await axios.get('/api/{{.guildId}}/tags');
if (res.status === 200) { if (res.status === 200) {
for (tag of res.data) { for (const [id, content] of Object.entries(res.data)) {
appendTag(tag); appendTag(id, content);
} }
} }
} }

View File

@ -39,7 +39,7 @@
const members = ticket.members.map(member => `${member.username}#${member.discrim}`).join(', '); const members = ticket.members.map(member => `${member.username}#${member.discrim}`).join(', ');
appendTd(tr, members); appendTd(tr, members);
appendButton(tr, 'View', () => { console.log(ticket); location.href = '/manage/{{.guildId}}/tickets/view/' + ticket.uuid }); appendButton(tr, 'View', () => { console.log(ticket); location.href = '/manage/{{.guildId}}/tickets/view/' + ticket.ticketId });
container.appendChild(tr); container.appendChild(tr);
} }

View File

@ -45,7 +45,7 @@
const reason = document.getElementById('reason').value; const reason = document.getElementById('reason').value;
document.getElementById('reason').value = ''; document.getElementById('reason').value = '';
const res = await axios.delete('/api/{{.guildId}}/tickets/{{.uuid}}', { const res = await axios.delete('/api/{{.guildId}}/tickets/{{.ticketId}}', {
data: { data: {
reason: reason reason: reason
} }
@ -86,20 +86,20 @@
async function loadData() { async function loadData() {
const premium = await isPremium(); const premium = await isPremium();
const res = await axios.get('/api/{{.guildId}}/tickets/{{.uuid}}'); const res = await axios.get('/api/{{.guildId}}/tickets/{{.ticketId}}');
if (res.status === 200 && res.data.success) { if (res.status === 200 && res.data.success) {
const data = res.data; const data = res.data;
document.getElementById('channel-name').innerText = `#ticket-${data.ticket.TicketId}`; document.getElementById('channel-name').innerText = `#ticket-${data.ticket.Id}`;
await appendMessages(data.messages); await appendMessages(data.messages);
startWebsocket(data.ticket.TicketId); startWebsocket(data.ticket.Id);
} else { } else {
showToast('Error', res.data.error); showToast('Error', res.data.error);
} }
const el = document.getElementById('message'); const el = document.getElementById('message');
if (premium) { if (premium) {
el.placeholder = `Message #ticket-${res.data.ticket.TicketId}`; el.placeholder = `Message #ticket-${res.data.ticket.Id}`;
} else { } else {
el.disabled = true; el.disabled = true;
el.placeholder = 'Premium users get live messages and can respond through webchat' el.placeholder = 'Premium users get live messages and can respond through webchat'
@ -110,12 +110,13 @@
</script> </script>
<script> <script>
// Scroll to bottom // Scroll to bottom
const container = document.getElementById("message-container"); const container = document.getElementById("message-container");
container.scrollTop = container.scrollHeight; container.scrollTop = container.scrollHeight;
async function startWebsocket(ticketId) { async function startWebsocket(ticketId) {
const ws = new WebSocket("wss://panel.ticketsbot.net/webchat"); //const ws = new WebSocket("wss://panel.ticketsbot.net/webchat");
const ws = new WebSocket("ws://localhost:3000/webchat");
ws.onopen = () => { ws.onopen = () => {
ws.send(JSON.stringify({ ws.send(JSON.stringify({
@ -138,7 +139,7 @@
const msg = document.getElementById("message").value; const msg = document.getElementById("message").value;
document.getElementById("message").value = ""; document.getElementById("message").value = "";
const res = await axios.post('/api/{{.guildId}}/tickets/{{.uuid}}', {message: msg}); const res = await axios.post('/api/{{.guildId}}/tickets/{{.ticketId}}', {message: msg});
if (res.status !== 200 || !res.data.success) { if (res.status !== 200 || !res.data.success) {
showToast('Error', res.data.error); showToast('Error', res.data.error);
} }

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"errors" "errors"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/rpc/ratelimit" "github.com/TicketsBot/GoPanel/rpc/ratelimit"
"github.com/apex/log" "github.com/apex/log"
@ -16,17 +16,22 @@ import (
"sync" "sync"
) )
// TODO: Use Redis cache
// TODO: Error handling
func IsAdmin(g guild.Guild, userId uint64, res chan bool) { func IsAdmin(g guild.Guild, userId uint64, res chan bool) {
if Contains(config.Conf.Admins, strconv.Itoa(int(userId))) { if Contains(config.Conf.Admins, strconv.FormatUint(userId, 10)) {
res <- true res <- true
return
} }
if g.OwnerId == userId { if g.OwnerId == userId {
res <- true res <- true
return
} }
if table.IsAdmin(g.Id, userId) { if isAdmin, _ := database.Client.Permissions.IsAdmin(g.Id, userId); isAdmin {
res <- true res <- true
return
} }
userRoles, _ := getRoles(g.Id, userId) userRoles, _ := getRoles(g.Id, userId)
@ -34,11 +39,10 @@ func IsAdmin(g guild.Guild, userId uint64, res chan bool) {
// check if user has administrator permission // check if user has administrator permission
if hasAdministratorPermission(g.Id, userRoles) { if hasAdministratorPermission(g.Id, userRoles) {
res <- true res <- true
return
} }
adminRolesChan := make(chan []uint64) adminRoles, _ := database.Client.RolePermissions.GetAdminRoles(g.Id)
go table.GetAdminRoles(g.Id, adminRolesChan)
adminRoles := <-adminRolesChan
hasTicketAdminRole := false hasTicketAdminRole := false
for _, userRole := range userRoles { for _, userRole := range userRoles {
@ -52,6 +56,7 @@ func IsAdmin(g guild.Guild, userId uint64, res chan bool) {
if hasTicketAdminRole { if hasTicketAdminRole {
res <- true res <- true
return
} }
res <- false res <- false

View File

@ -4,9 +4,9 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/rpc/ratelimit" "github.com/TicketsBot/GoPanel/rpc/ratelimit"
"github.com/TicketsBot/GoPanel/database"
gocache "github.com/robfig/go-cache" gocache "github.com/robfig/go-cache"
"github.com/rxdn/gdl/rest" "github.com/rxdn/gdl/rest"
"io/ioutil" "io/ioutil"
@ -20,6 +20,7 @@ type ProxyResponse struct {
Tier int Tier int
} }
// TODO: Use Redis cache
var premiumCache = gocache.New(10 * time.Minute, 10 * time.Minute) var premiumCache = gocache.New(10 * time.Minute, 10 * time.Minute)
func IsPremiumGuild(guildId uint64, ch chan bool) { func IsPremiumGuild(guildId uint64, ch chan bool) {
@ -31,10 +32,11 @@ func IsPremiumGuild(guildId uint64, ch chan bool) {
} }
// First lookup by premium key, then votes, then patreon // First lookup by premium key, then votes, then patreon
keyLookup := make(chan bool) // TODO: Lookup Patreon first
go table.IsPremium(guildId, keyLookup) // TODO: Handle error
hasPremiumKey, _ := database.Client.PremiumGuilds.IsPremium(guildId)
if <-keyLookup { if hasPremiumKey {
if err := premiumCache.Add(guildIdRaw, true, 10 * time.Minute); err != nil { if err := premiumCache.Add(guildIdRaw, true, 10 * time.Minute); err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
} }