Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 73 additions & 4 deletions app/http/endpoints/api/ticket/gettickets.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ import (

type (
listTicketsResponse struct {
Tickets []ticketData `json:"tickets"`
PanelTitles map[int]string `json:"panel_titles"`
ResolvedUsers map[uint64]user.User `json:"resolved_users"`
SelfId uint64 `json:"self_id,string"`
Tickets []ticketData `json:"tickets"`
PanelTitles map[int]string `json:"panel_titles"`
ResolvedUsers map[uint64]user.User `json:"resolved_users"`
Labels map[int][]ticketLabelData `json:"labels"`
SelfId uint64 `json:"self_id,string"`
}

ticketData struct {
Expand Down Expand Up @@ -88,6 +89,18 @@ func GetTickets(c *gin.Context) {
return
}

// Fetch label data
ticketIds := make([]int, len(tickets))
for i, ticket := range tickets {
ticketIds[i] = ticket.Id
}

labelsMap, err := fetchLabelsForTickets(c, guildId, ticketIds)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, app.NewError(err, "Failed to fetch label data"))
return
}

data := make([]ticketData, len(tickets))
for i, ticket := range tickets {
data[i] = ticketData{
Expand All @@ -105,6 +118,7 @@ func GetTickets(c *gin.Context) {
Tickets: data,
PanelTitles: panelTitles,
ResolvedUsers: users,
Labels: labelsMap,
SelfId: userId,
})
}
Expand All @@ -115,6 +129,7 @@ func buildResponseFromPlainTickets(c *gin.Context, plainTickets []database.Ticke
Tickets: []ticketData{},
PanelTitles: make(map[int]string),
ResolvedUsers: make(map[uint64]user.User),
Labels: make(map[int][]ticketLabelData),
SelfId: userId,
})
return
Expand Down Expand Up @@ -168,6 +183,18 @@ func buildResponseFromPlainTickets(c *gin.Context, plainTickets []database.Ticke
return
}

// Fetch label data
ticketIds := make([]int, len(plainTickets))
for i, ticket := range plainTickets {
ticketIds[i] = ticket.Id
}

labelsMap, err := fetchLabelsForTickets(c, guildId, ticketIds)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, app.NewError(err, "Failed to fetch label data"))
return
}

// Build ticketData from tickets with metadata
data := make([]ticketData, len(tickets))
for i, ticket := range tickets {
Expand All @@ -186,6 +213,48 @@ func buildResponseFromPlainTickets(c *gin.Context, plainTickets []database.Ticke
Tickets: data,
PanelTitles: panelTitles,
ResolvedUsers: users,
Labels: labelsMap,
SelfId: userId,
})
}

func fetchLabelsForTickets(c *gin.Context, guildId uint64, ticketIds []int) (map[int][]ticketLabelData, error) {
if len(ticketIds) == 0 {
return make(map[int][]ticketLabelData), nil
}

labelAssignments, err := dbclient.Client.TicketLabelAssignments.GetByTickets(c, guildId, ticketIds)
if err != nil {
return nil, err
}

allLabels, err := dbclient.Client.TicketLabels.GetByGuild(c, guildId)
if err != nil {
return nil, err
}

labelLookup := make(map[int]ticketLabelData)
for _, l := range allLabels {
labelLookup[l.LabelId] = ticketLabelData{
LabelId: l.LabelId,
Name: l.Name,
Colour: l.Colour,
}
}

result := make(map[int][]ticketLabelData)
for ticketId, assignedIds := range labelAssignments {
var resolved []ticketLabelData
for _, lid := range assignedIds {
if ld, exists := labelLookup[lid]; exists {
resolved = append(resolved, ld)
}
}
if resolved == nil {
resolved = []ticketLabelData{}
}
result[ticketId] = resolved
}

return result, nil
}
172 changes: 172 additions & 0 deletions app/http/endpoints/api/ticket/labelassignments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package api

import (
"fmt"
"strconv"
"strings"

"github.com/TicketsBot-cloud/dashboard/app/http/audit"
"github.com/TicketsBot-cloud/dashboard/botcontext"
dbclient "github.com/TicketsBot-cloud/dashboard/database"
"github.com/TicketsBot-cloud/dashboard/utils"
"github.com/TicketsBot-cloud/database"
"github.com/TicketsBot-cloud/gdl/rest"
"github.com/gin-gonic/gin"
)

type setLabelsBody struct {
LabelIds []int `json:"label_ids"`
}

func GetTicketLabels(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)

ticketId, err := strconv.Atoi(ctx.Param("ticketId"))
if err != nil {
ctx.JSON(400, utils.ErrorStr("Invalid ticket ID."))
return
}

labelIds, err := dbclient.Client.TicketLabelAssignments.GetByTicket(ctx, guildId, ticketId)
if err != nil {
ctx.JSON(500, utils.ErrorStr("Failed to fetch labels. Please try again."))
return
}

if labelIds == nil {
labelIds = []int{}
}

ctx.JSON(200, gin.H{"label_ids": labelIds})
}

func SetTicketLabels(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
userId := ctx.Keys["userid"].(uint64)

ticketId, err := strconv.Atoi(ctx.Param("ticketId"))
if err != nil {
ctx.JSON(400, utils.ErrorStr("Invalid ticket ID."))
return
}

var body setLabelsBody
if err := ctx.ShouldBindJSON(&body); err != nil {
ctx.JSON(400, utils.ErrorStr("Invalid request body."))
return
}

if len(body.LabelIds) > maxLabelsPerGuild {
ctx.JSON(400, utils.ErrorStr("Too many labels."))
return
}

// Validate that all label IDs belong to this guild
if len(body.LabelIds) > 0 {
labels, err := dbclient.Client.TicketLabels.GetByGuild(ctx, guildId)
if err != nil {
ctx.JSON(500, utils.ErrorStr("Failed to update labels. Please try again."))
return
}

validIds := make(map[int]bool)
for _, l := range labels {
validIds[l.LabelId] = true
}

for _, id := range body.LabelIds {
if !validIds[id] {
ctx.JSON(400, utils.ErrorStr(fmt.Sprintf("Label ID %d does not exist.", id)))
return
}
}
}

if err := dbclient.Client.TicketLabelAssignments.Replace(ctx, guildId, ticketId, body.LabelIds); err != nil {
ctx.JSON(500, utils.ErrorStr("Failed to update labels. Please try again."))
return
}

ticket, err := dbclient.Client.Tickets.Get(ctx, ticketId, guildId)
if err != nil {
ctx.JSON(500, utils.ErrorStr("Failed to fetch ticket. Please try again."))
return
}

topicMsg := ""
if ticket.PanelId != nil {
panel, err := dbclient.Client.Panel.GetById(ctx, *ticket.PanelId)
if err != nil {
ctx.JSON(500, utils.ErrorStr("Failed to fetch panel. Please try again."))
return
}
if panel.PanelId != 0 {
topicMsg = fmt.Sprintf("%s | ", panel.Title)
}
}

labelNamesList, err := dbclient.Client.TicketLabelAssignments.GetLabelNameByTicket(ctx, guildId, ticketId)
if err != nil {
ctx.JSON(500, utils.ErrorStr("Failed to fetch label names. Please try again."))
return
}

var labelNames []string
for _, name := range labelNamesList {
labelNames = append(labelNames, name)
}

if !ticket.IsThread {
botCtx, err := botcontext.ContextForGuild(guildId)
if err != nil {
ctx.JSON(500, utils.ErrorStr("Failed to update channel topic. Please try again."))
return
}
botCtx.ModifyChannel(ctx, *ticket.ChannelId, rest.ModifyChannelData{
Topic: fmt.Sprintf("%s%s", topicMsg, strings.Join(labelNames, ", ")),
})
}

audit.Log(audit.LogEntry{
GuildId: audit.Uint64Ptr(guildId),
UserId: userId,
ActionType: database.AuditActionTicketLabelAssign,
ResourceType: database.AuditResourceTicketLabelAssignment,
ResourceId: audit.StringPtr(strconv.Itoa(ticketId)),
NewData: body.LabelIds,
})

ctx.JSON(200, gin.H{"label_ids": body.LabelIds})
}

func RemoveTicketLabel(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
userId := ctx.Keys["userid"].(uint64)

ticketId, err := strconv.Atoi(ctx.Param("ticketId"))
if err != nil {
ctx.JSON(400, utils.ErrorStr("Invalid ticket ID."))
return
}

labelId, err := strconv.Atoi(ctx.Param("labelid"))
if err != nil {
ctx.JSON(400, utils.ErrorStr("Invalid label ID."))
return
}

if err := dbclient.Client.TicketLabelAssignments.Delete(ctx, guildId, ticketId, labelId); err != nil {
ctx.JSON(500, utils.ErrorStr("Failed to remove label. Please try again."))
return
}

audit.Log(audit.LogEntry{
GuildId: audit.Uint64Ptr(guildId),
UserId: userId,
ActionType: database.AuditActionTicketLabelUnassign,
ResourceType: database.AuditResourceTicketLabelAssignment,
ResourceId: audit.StringPtr(fmt.Sprintf("%d:%d", ticketId, labelId)),
})

ctx.JSON(204, nil)
}
Loading