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
import (
"context"
"fmt"
"github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
"strconv"
"sync"
)
type userData struct {
@ -16,18 +19,38 @@ type userData struct {
func GetBlacklistHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
data := make(map[string]userData)
blacklistedUsers := table.GetBlacklistNodes(guildId)
for _, row := range blacklistedUsers {
formattedId := strconv.FormatUint(row.User, 10)
user, _ := cache.Instance.GetUser(row.User)
data[formattedId] = userData{
Username: user.Username,
Discriminator: fmt.Sprintf("%04d", user.Discriminator),
}
blacklistedUsers, err := database.Client.Blacklist.GetBlacklistedUsers(guildId)
if err != nil {
ctx.JSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
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)
}

View File

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

View File

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

View File

@ -3,7 +3,7 @@ package api
import (
"fmt"
"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/ratelimit"
"github.com/TicketsBot/GoPanel/utils"
@ -18,13 +18,27 @@ var MentionRegex, _ = regexp.Compile("<@(\\d+)>")
func GetTicket(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
uuid := ctx.Param("uuid")
ticketChan := make(chan table.Ticket)
go table.GetTicket(uuid, ticketChan)
ticket := <-ticketChan
ticketId, err := strconv.Atoi(ctx.Param("ticketId"))
if err != nil {
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{
"success": false,
"error": "Guild ID doesn't match",
@ -32,7 +46,7 @@ func GetTicket(ctx *gin.Context) {
return
}
if !ticket.IsOpen {
if !ticket.Open {
ctx.AbortWithStatusJSON(404, gin.H{
"success": false,
"error": "Ticket does not exist",
@ -40,8 +54,16 @@ func GetTicket(ctx *gin.Context) {
return
}
if ticket.ChannelId == nil {
ctx.AbortWithStatusJSON(404, gin.H{
"success": false,
"error": "Ticket channel does not exist",
})
return
}
// 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
messagesFormatted := make([]map[string]interface{}, 0)

View File

@ -1,48 +1,71 @@
package api
import (
"context"
"fmt"
"github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
"strconv"
"strings"
)
func GetTickets(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
tickets := table.GetOpenTickets(guildId)
ticketsFormatted := make([]map[string]interface{}, 0)
for _, ticket := range tickets {
membersFormatted := make([]map[string]interface{}, 0)
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,
tickets, err := database.Client.Tickets.GetGuildOpenTickets(guildId)
if err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
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)

View File

@ -1,28 +1,48 @@
package api
import (
"github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
"github.com/rxdn/gdl/objects/guild"
)
type wrappedGuild struct {
Id uint64 `json:"id,string"`
Name string `json:"name"`
}
func GetGuilds(ctx *gin.Context) {
userId := ctx.Keys["userid"].(uint64)
userGuilds := table.GetGuilds(userId)
adminGuilds := make([]guild.Guild, 0)
for _, g := range userGuilds {
guilds, err := database.Client.UserGuilds.Get(userId)
if err != nil {
ctx.JSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
adminGuilds := make([]wrappedGuild, 0)
for _, g := range guilds {
fakeGuild := guild.Guild{
Id: g.Id,
OwnerId: g.OwnerId,
Permissions: g.Permissions,
Id: g.GuildId,
Owner: g.Owner,
Permissions: int(g.UserPermissions),
}
if g.Owner {
fakeGuild.OwnerId = userId
}
isAdmin := make(chan bool)
go utils.IsAdmin(fakeGuild, userId, 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"
"fmt"
"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/ratelimit"
"github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/database"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/rxdn/gdl/rest"
@ -15,15 +16,15 @@ import (
)
const (
pageLimit = 30
pageLimit = 2
)
func GetLogs(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
page, err := strconv.Atoi(ctx.Param("page"))
if page < 1 {
page = 1
before, err := strconv.Atoi(ctx.Query("before"))
if before < 0 {
before = 0
}
// Get ticket ID from URL
@ -32,15 +33,20 @@ func GetLogs(ctx *gin.Context) {
ticketId, _ = strconv.Atoi(ctx.Query("ticketid"))
}
var tickets []table.Ticket
var tickets []database.Ticket
// Get tickets from DB
if ticketId > 0 {
ticketChan := make(chan table.Ticket)
go table.GetTicketById(guildId, ticketId, ticketChan)
ticket := <-ticketChan
ticket, err := dbclient.Client.Tickets.Get(ticketId, guildId)
if err != nil {
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)
}
} else {
@ -76,25 +82,27 @@ func GetLogs(ctx *gin.Context) {
}
if ctx.Query("userid") != "" || ctx.Query("username") != "" {
tickets = table.GetClosedTicketsByUserId(guildId, filteredIds)
tickets, err = dbclient.Client.Tickets.GetMemberClosedTickets(guildId, filteredIds, pageLimit, before)
} 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
formattedLogs := make([]map[string]interface{}, 0)
for i := (page - 1) * pageLimit; i < (page-1)*pageLimit+pageLimit; i++ {
if i >= len(tickets) {
break
}
ticket := tickets[i]
for _, ticket := range tickets {
// get username
user, found := cache.Instance.GetUser(ticket.Owner)
user, found := cache.Instance.GetUser(ticket.UserId)
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 {
log.Error(err.Error())
}
@ -102,8 +110,8 @@ func GetLogs(ctx *gin.Context) {
}
formattedLogs = append(formattedLogs, map[string]interface{}{
"ticketid": ticket.TicketId,
"userid": strconv.FormatUint(ticket.Owner, 10),
"ticketid": ticket.Id,
"userid": strconv.FormatUint(ticket.UserId, 10),
"username": user.Username,
})
}

View File

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

View File

@ -1,12 +1,19 @@
package api
import (
"github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/messagequeue"
"github.com/TicketsBot/GoPanel/config"
dbclient "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/rpc/ratelimit"
"github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/database"
"github.com/gin-gonic/gin"
"github.com/rxdn/gdl/objects/channel"
"github.com/rxdn/gdl/objects/channel/embed"
"github.com/rxdn/gdl/objects/channel/message"
"github.com/rxdn/gdl/rest"
"github.com/rxdn/gdl/rest/request"
"strconv"
"strings"
)
@ -25,12 +32,19 @@ func CreatePanel(ctx *gin.Context) {
data.MessageId = 0
// Check panel quota
premium := make(chan bool)
go utils.IsPremiumGuild(guildId, premium)
if !<-premium {
panels := make(chan []table.Panel)
go table.GetPanelsByGuild(guildId, panels)
if len(<-panels) > 0 {
premiumChan := make(chan bool)
go utils.IsPremiumGuild(guildId, premiumChan)
isPremium := <-premiumChan
if !isPremium {
panels, err := dbclient.Client.Panel.GetByGuild(guildId)
if err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
}
if len(panels) > 0 {
ctx.AbortWithStatusJSON(402, gin.H{
"success": false,
"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{
"success": false,
"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{
"success": false,
"error": "Panel content must be between 1 - 1024 characters in length",
})
return
return false
}
channels := cache.Instance.GetGuildChannels(guildId)
if !data.verifyChannel(channels) {
if !p.verifyChannel(channels) {
ctx.AbortWithStatusJSON(400, gin.H{
"success": false,
"error": "Invalid channel",
})
return
return false
}
if !data.verifyCategory(channels) {
if !p.verifyCategory(channels) {
ctx.AbortWithStatusJSON(400, gin.H{
"success": false,
"error": "Invalid channel category",
})
return
return false
}
emoji, validEmoji := data.getEmoji()
_, validEmoji := p.getEmoji()
if !validEmoji {
ctx.AbortWithStatusJSON(400, gin.H{
"success": false,
"error": "Invalid emoji. Simply use the emoji's name from Discord.",
})
return
return false
}
// TODO: Move panel create logic here
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,
})
return true
}
func (p *panel) verifyTitle() bool {
@ -136,3 +205,24 @@ func (p *panel) verifyCategory(channels []channel.Channel) bool {
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 (
"github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/ratelimit"
"github.com/gin-gonic/gin"
"github.com/rxdn/gdl/rest"
@ -21,11 +21,16 @@ func DeletePanel(ctx *gin.Context) {
return
}
// verify panel belongs to guild
panelChan := make(chan table.Panel)
go table.GetPanel(messageId, panelChan)
panel := <-panelChan
panel, err := database.Client.Panel.Get(messageId)
if err != nil {
ctx.JSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
// verify panel belongs to guild
if panel.GuildId != guildId {
ctx.AbortWithStatusJSON(403, gin.H{
"success": false,
@ -34,8 +39,21 @@ func DeletePanel(ctx *gin.Context) {
return
}
go table.DeletePanel(messageId)
go rest.DeleteMessage(config.Conf.Bot.Token, ratelimit.Ratelimiter, panel.ChannelId, panel.MessageId)
if err := database.Client.Panel.Delete(messageId); err != nil {
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{
"success": true,

View File

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

View File

@ -1,18 +1,16 @@
package api
import (
"bytes"
"encoding/json"
"fmt"
"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/ratelimit"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
"github.com/rxdn/gdl/rest"
"net/http"
"time"
"github.com/rxdn/gdl/rest/request"
"strconv"
)
type sendMessageBody struct {
@ -23,6 +21,16 @@ func SendMessage(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(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
if err := ctx.BindJSON(&body); err != nil {
ctx.AbortWithStatusJSON(400, gin.H{
@ -44,12 +52,10 @@ func SendMessage(ctx *gin.Context) {
}
// Get ticket
ticketChan := make(chan table.Ticket)
go table.GetTicket(ctx.Param("uuid"), ticketChan)
ticket := <-ticketChan
ticket, err := database.Client.Tickets.Get(ticketId, guildId)
// Verify the ticket exists
if ticket.TicketId == 0 {
if ticket.UserId == 0 {
ctx.AbortWithStatusJSON(404, gin.H{
"success": false,
"error": "Ticket not found",
@ -58,7 +64,7 @@ func SendMessage(ctx *gin.Context) {
}
// Verify the user has permission to send to this guild
if ticket.Guild != guildId {
if ticket.GuildId != guildId {
ctx.AbortWithStatusJSON(403, gin.H{
"success": false,
"error": "Guild ID doesn't match",
@ -73,60 +79,61 @@ func SendMessage(ctx *gin.Context) {
}
// Preferably send via a webhook
webhookChan := make(chan *string)
go table.GetWebhookByUuid(ticket.Uuid, webhookChan)
webhook := <-webhookChan
success := false
if webhook != nil {
// TODO: Use gdl execute webhook wrapper
success = executeWebhook(ticket.Uuid, *webhook, body.Message, user.Username, user.AvatarUrl(256))
webhook, err := database.Client.Webhooks.Get(guildId, ticketId)
if err != nil {
ctx.AbortWithStatusJSON(500, gin.H{
"success": false,
"error": err.Error(),
})
return
}
if !success {
body.Message = fmt.Sprintf("**%s**: %s", user.Username, body.Message)
if len(body.Message) > 2000 {
body.Message = body.Message[0:1999]
}
if webhook.Id != 0 {
// TODO: Use gdl execute webhook wrapper
_, err = rest.ExecuteWebhook(webhook.Token, ratelimit.Ratelimiter, webhook.Id, true, rest.WebhookBody{
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{
"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
import (
"github.com/TicketsBot/GoPanel/database/table"
"context"
dbclient "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/database"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)
type Settings struct {
Prefix string `json:"prefix"`
WelcomeMessaage string `json:"welcome_message"`
TicketLimit int `json:"ticket_limit"`
Category uint64 `json:"category,string"`
ArchiveChannel uint64 `json:"archive_channel,string"`
NamingScheme table.NamingScheme `json:"naming_scheme"`
PingEveryone bool `json:"ping_everyone"`
UsersCanClose bool `json:"users_can_close"`
Prefix string `json:"prefix"`
WelcomeMessaage string `json:"welcome_message"`
TicketLimit uint8 `json:"ticket_limit"`
Category uint64 `json:"category,string"`
ArchiveChannel uint64 `json:"archive_channel,string"`
NamingScheme database.NamingScheme `json:"naming_scheme"`
PingEveryone bool `json:"ping_everyone"`
UsersCanClose bool `json:"users_can_close"`
}
func GetSettingsHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
prefix := make(chan string)
go table.GetPrefix(guildId, prefix)
var prefix, welcomeMessage string
var ticketLimit uint8
var category, archiveChannel uint64
var allowUsersToClose, pingEveryone bool
var namingScheme database.NamingScheme
welcomeMessage := make(chan string)
go table.GetWelcomeMessage(guildId, welcomeMessage)
group, _ := errgroup.WithContext(context.Background())
ticketLimit := make(chan int)
go table.GetTicketLimit(guildId, ticketLimit)
// prefix
group.Go(func() (err error) {
prefix, err = dbclient.Client.Prefix.Get(guildId)
return
})
category := make(chan uint64)
go table.GetChannelCategory(guildId, category)
// welcome message
group.Go(func() (err error) {
welcomeMessage, err = dbclient.Client.WelcomeMessages.Get(guildId)
return
})
archiveChannel := make(chan uint64)
go table.GetArchiveChannel(guildId, archiveChannel)
// ticket limit
group.Go(func() (err error) {
ticketLimit, err = dbclient.Client.TicketLimit.Get(guildId)
return
})
allowUsersToClose := make(chan bool)
go table.IsUserCanClose(guildId, allowUsersToClose)
// category
group.Go(func() (err error) {
category, err = dbclient.Client.ChannelCategory.Get(guildId)
return
})
namingScheme := make(chan table.NamingScheme)
go table.GetTicketNamingScheme(guildId, namingScheme)
// archive channel
group.Go(func() (err error) {
archiveChannel, err = dbclient.Client.ArchiveChannel.Get(guildId)
return
})
pingEveryone := make(chan bool)
go table.GetPingEveryone(guildId, pingEveryone)
// allow users to close
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{
Prefix: <-prefix,
WelcomeMessaage: <-welcomeMessage,
TicketLimit: <-ticketLimit,
Category: <-category,
ArchiveChannel: <-archiveChannel,
NamingScheme: <-namingScheme,
PingEveryone: <-pingEveryone,
UsersCanClose: <-allowUsersToClose,
Prefix: prefix,
WelcomeMessaage: welcomeMessage,
TicketLimit: ticketLimit,
Category: category,
ArchiveChannel: archiveChannel,
NamingScheme: namingScheme,
PingEveryone: pingEveryone,
UsersCanClose: allowUsersToClose,
})
}

View File

@ -1,7 +1,7 @@
package api
import (
"github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/database"
"github.com/gin-gonic/gin"
)
@ -38,7 +38,7 @@ func CreateTag(ctx *gin.Context) {
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{
"success": false,
"error": err.Error(),

View File

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

View File

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

View File

@ -1,7 +1,8 @@
package api
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/gin-gonic/gin"
"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 {
if s.Prefix == "" || len(s.Prefix) > 8 {
return false
}
go table.UpdatePrefix(guildId, s.Prefix)
go dbclient.Client.Prefix.Set(guildId, s.Prefix)
return true
}
@ -56,7 +58,7 @@ func (s *Settings) updateWelcomeMessage(guildId uint64) bool {
return false
}
go table.UpdateWelcomeMessage(guildId, s.WelcomeMessaage)
go dbclient.Client.WelcomeMessages.Set(guildId, s.WelcomeMessaage)
return true
}
@ -65,7 +67,7 @@ func (s *Settings) updateTicketLimit(guildId uint64) bool {
return false
}
go table.UpdateTicketLimit(guildId, s.TicketLimit)
go dbclient.Client.TicketLimit.Set(guildId, s.TicketLimit)
return true
}
@ -82,7 +84,7 @@ func (s *Settings) updateCategory(channels []channel.Channel, guildId uint64) bo
return false
}
go table.UpdateChannelCategory(guildId, s.Category)
go dbclient.Client.ChannelCategory.Set(guildId, s.Category)
return true
}
@ -99,11 +101,11 @@ func (s *Settings) updateArchiveChannel(channels []channel.Channel, guildId uint
return false
}
go table.UpdateArchiveChannel(guildId, s.ArchiveChannel)
go dbclient.Client.ArchiveChannel.Set(guildId, s.ArchiveChannel)
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 {
var valid bool
for _, scheme := range validScheme {
@ -117,14 +119,14 @@ func (s *Settings) updateNamingScheme(guildId uint64) bool {
return false
}
go table.SetTicketNamingScheme(guildId, s.NamingScheme)
go dbclient.Client.NamingScheme.Set(guildId, s.NamingScheme)
return true
}
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) {
go table.SetUserCanClose(guildId, s.UsersCanClose)
go dbclient.Client.UsersCanClose.Set(guildId, s.UsersCanClose)
}

View File

@ -4,7 +4,7 @@ import (
"errors"
"fmt"
"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/utils"
"github.com/TicketsBot/archiverclient"
@ -41,20 +41,27 @@ func LogViewHandler(ctx *gin.Context) {
}
// get ticket object
ticketChan := make(chan table.Ticket)
go table.GetTicketById(guildId, ticketId, ticketChan)
ticket := <-ticketChan
ticket, err := database.Client.Tickets.Get(ticketId, guildId)
if err != nil {
// 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
if ticket.Uuid == "" || ticket.IsOpen {
if ticket.UserId == 0 || ticket.Open {
ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs", guild.Id))
return
}
// Verify the user has permissions to be here
// TODO: Allow support reps to view
isAdmin := make(chan bool)
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
return
}

View File

@ -4,12 +4,13 @@ import (
"errors"
"fmt"
"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/utils"
"github.com/TicketsBot/archiverclient"
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
"strconv"
)
@ -33,21 +34,35 @@ func ModmailLogViewHandler(ctx *gin.Context) {
guild, _ := cache.Instance.GetGuild(guildId, false)
// 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
archiveCh := make(chan table.ModMailArchive)
go table.GetModmailArchive(uuid, archiveCh)
archive := <-archiveCh
archive, err := database.Client.ModmailArchive.Get(archiveUuid)
if err != nil {
// 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
if archive.Uuid == "" {
if archive.Uuid == uuid.Nil{
ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs/modmail", guild.Id))
return
}
// 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))
return
}
@ -55,13 +70,13 @@ func ModmailLogViewHandler(ctx *gin.Context) {
// Verify the user has permissions to be here
isAdmin := make(chan bool)
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
return
}
// retrieve ticket messages from bucket
messages, err := Archiver.GetModmail(guildId, uuid)
messages, err := Archiver.GetModmail(guildId, archiveUuid.String())
if err != nil {
if errors.Is(err, archiverclient.ErrExpired) {
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
html, err := Archiver.Encode(messages, fmt.Sprintf("modmail-%s", uuid))
html, err := Archiver.Encode(messages, fmt.Sprintf("modmail-%s", archiveUuid))
if err != nil {
ctx.String(500, fmt.Sprintf("Failed to retrieve archive - please contact the developers: %s", err.Error()))
return

View File

@ -1,85 +1,19 @@
package manage
import (
"fmt"
"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/gin"
"github.com/rxdn/gdl/objects/channel"
"strconv"
)
func SettingsHandler(ctx *gin.Context) {
store := sessions.Default(ctx)
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"
guildId := ctx.Keys["guildid"].(uint64)
ctx.HTML(200, "manage/settings", gin.H{
"name": store.Get("name").(string),
"guildId": guildIdStr,
"avatar": store.Get("avatar").(string),
"categories": categories,
"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),
"name": store.Get("name").(string),
"guildId": guildId,
"avatar": store.Get("avatar").(string),
"baseUrl": config.Conf.Server.BaseUrl,
})
}

View File

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

View File

@ -1,14 +1,13 @@
package root
import (
"encoding/base64"
"encoding/json"
"fmt"
"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/discord"
userEndpoint "github.com/TicketsBot/GoPanel/utils/discord/endpoints/user"
"github.com/TicketsBot/database"
"github.com/apex/log"
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
@ -92,6 +91,8 @@ func CallbackHandler(ctx *gin.Context) {
return
}
var wrappedGuilds []database.UserGuild
// 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
// 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 {
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)
if err != nil {
log.Error(err.Error())
return
}
// TODO: unfuck this
table.UpdateGuilds(currentUser.Id, base64.StdEncoding.EncodeToString(marshalled))
// TODO: Error handling
go dbclient.Client.UserGuilds.Set(currentUser.Id, wrappedGuilds)
}()
}

View File

@ -2,7 +2,7 @@ package root
import (
"github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
@ -13,19 +13,31 @@ func IndexHandler(ctx *gin.Context) {
store := sessions.Default(ctx)
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)
for _, g := range userGuilds {
fakeGuild := guild.Guild{
Id: g.Id,
OwnerId: g.OwnerId,
Permissions: g.Permissions,
Id: g.GuildId,
Owner: g.Owner,
Permissions: int(g.UserPermissions),
}
if g.Owner {
fakeGuild.OwnerId = userId
}
isAdmin := make(chan bool)
go utils.IsAdmin(fakeGuild, userId, isAdmin)
if <-isAdmin {
adminGuilds = append(adminGuilds, g)
adminGuilds = append(adminGuilds, fakeGuild)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

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 (
github.com/BurntSushi/toml v0.3.1
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/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
@ -14,6 +15,7 @@ require (
github.com/gin-gonic/gin v1.6.2
github.com/go-redis/redis v6.15.7+incompatible
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/websocket v1.4.2
github.com/jackc/pgx/v4 v4.6.0
@ -21,8 +23,9 @@ require (
github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c
github.com/pkg/errors v0.9.1
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/sirupsen/logrus v1.5.0
github.com/ulule/limiter/v3 v3.5.0
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
)

View File

@ -2,11 +2,11 @@ package messagequeue
import (
"encoding/json"
"github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/database"
"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 {
log.Error(err.Error())
return

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -39,7 +39,7 @@
const members = ticket.members.map(member => `${member.username}#${member.discrim}`).join(', ');
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);
}

View File

@ -45,7 +45,7 @@
const reason = 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: {
reason: reason
}
@ -86,20 +86,20 @@
async function loadData() {
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) {
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);
startWebsocket(data.ticket.TicketId);
startWebsocket(data.ticket.Id);
} else {
showToast('Error', res.data.error);
}
const el = document.getElementById('message');
if (premium) {
el.placeholder = `Message #ticket-${res.data.ticket.TicketId}`;
el.placeholder = `Message #ticket-${res.data.ticket.Id}`;
} else {
el.disabled = true;
el.placeholder = 'Premium users get live messages and can respond through webchat'
@ -110,12 +110,13 @@
</script>
<script>
// Scroll to bottom
// Scroll to bottom
const container = document.getElementById("message-container");
container.scrollTop = container.scrollHeight;
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.send(JSON.stringify({
@ -138,7 +139,7 @@
const msg = 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) {
showToast('Error', res.data.error);
}

View File

@ -4,7 +4,7 @@ import (
"context"
"errors"
"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/ratelimit"
"github.com/apex/log"
@ -16,17 +16,22 @@ import (
"sync"
)
// TODO: Use Redis cache
// TODO: Error handling
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
return
}
if g.OwnerId == userId {
res <- true
return
}
if table.IsAdmin(g.Id, userId) {
if isAdmin, _ := database.Client.Permissions.IsAdmin(g.Id, userId); isAdmin {
res <- true
return
}
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
if hasAdministratorPermission(g.Id, userRoles) {
res <- true
return
}
adminRolesChan := make(chan []uint64)
go table.GetAdminRoles(g.Id, adminRolesChan)
adminRoles := <-adminRolesChan
adminRoles, _ := database.Client.RolePermissions.GetAdminRoles(g.Id)
hasTicketAdminRole := false
for _, userRole := range userRoles {
@ -52,6 +56,7 @@ func IsAdmin(g guild.Guild, userId uint64, res chan bool) {
if hasTicketAdminRole {
res <- true
return
}
res <- false

View File

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