diff --git a/app/http/endpoints/api/transcripts/list.go b/app/http/endpoints/api/transcripts/list.go index c493e98..c680677 100644 --- a/app/http/endpoints/api/transcripts/list.go +++ b/app/http/endpoints/api/transcripts/list.go @@ -1,30 +1,14 @@ package api import ( - "errors" "github.com/TicketsBot/GoPanel/botcontext" dbclient "github.com/TicketsBot/GoPanel/database" "github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/utils" - "github.com/TicketsBot/database" "github.com/gin-gonic/gin" - "math" - "net/http" - "strconv" ) -const ( - pageLimit = 30 -) - -type filterType uint8 - -const ( - filterTypeNone filterType = iota - filterTypeTicketId - filterTypeUsername - filterTypeUserId -) +const pageLimit = 15 type transcript struct { TicketId int `json:"ticket_id"` @@ -36,6 +20,24 @@ type transcript struct { func ListTranscripts(ctx *gin.Context) { guildId := ctx.Keys["guildid"].(uint64) + var queryOptions wrappedQueryOptions + if err := ctx.BindJSON(&queryOptions); err != nil { + ctx.JSON(400, utils.ErrorJson(err)) + return + } + + opts, err := queryOptions.toQueryOptions(guildId) + if err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + tickets, err := dbclient.Client.Tickets.GetByOptions(opts) + if err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + botContext, err := botcontext.ContextForGuild(guildId) if err != nil { ctx.AbortWithStatusJSON(500, gin.H{ @@ -45,30 +47,6 @@ func ListTranscripts(ctx *gin.Context) { return } - // db functions will handle if 0 - before, _ := strconv.Atoi(ctx.Query("before")) - after, _ := strconv.Atoi(ctx.Query("after")) - - var tickets []database.TicketWithCloseReason - var status int - - filterType := getFilterType(ctx) - switch filterType { - case filterTypeNone: - tickets, status, err = getTickets(guildId, before, after) - case filterTypeTicketId: - tickets, status, err = getTicketsByTicketId(guildId, ctx) - case filterTypeUsername: - tickets, status, err = getTicketsByUsername(guildId, before, after, ctx) - case filterTypeUserId: - tickets, status, err = getTicketsByUserId(guildId, before, after, ctx) - } - - if err != nil { - ctx.JSON(status, utils.ErrorJson(err)) - return - } - // Create a mapping user_id -> username so we can skip duplicates usernames := make(map[uint64]string) for _, ticket := range tickets { @@ -102,164 +80,30 @@ func ListTranscripts(ctx *gin.Context) { return } + // Get close reasons + closeReasons, err := dbclient.Client.CloseReason.GetMulti(guildId, ticketIds) + if err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + transcripts := make([]transcript, len(tickets)) for i, ticket := range tickets { - var rating *uint8 - if v, ok := ratings[ticket.Id]; ok { - rating = &v + transcript := transcript{ + TicketId: ticket.Id, + Username: usernames[ticket.UserId], } - transcripts[i] = transcript{ - TicketId: ticket.Id, - Username: usernames[ticket.UserId], - CloseReason: ticket.CloseReason, - Rating: rating, + if v, ok := ratings[ticket.Id]; ok { + transcript.Rating = &v } + + if v, ok := closeReasons[ticket.Id]; ok { + transcript.CloseReason = &v + } + + transcripts[i] = transcript } ctx.JSON(200, transcripts) } - -func getFilterType(ctx *gin.Context) filterType { - if ctx.Query("ticketid") != "" { - return filterTypeTicketId - } else if ctx.Query("username") != "" { - return filterTypeUsername - } else if ctx.Query("userid") != "" { - return filterTypeUserId - } else { - return filterTypeNone - } -} - -func getTickets(guildId uint64, before, after int) ([]database.TicketWithCloseReason, int, error) { - var tickets []database.TicketWithCloseReason - var err error - - if before <= 0 && after <= 0 { - tickets, err = dbclient.Client.Tickets.GetGuildClosedTicketsBeforeWithCloseReason(guildId, pageLimit, math.MaxInt32) - } else if before > 0 { - tickets, err = dbclient.Client.Tickets.GetGuildClosedTicketsBeforeWithCloseReason(guildId, pageLimit, before) - } else { // after > 0 - // returns in ascending order, must reverse - tickets, err = dbclient.Client.Tickets.GetGuildClosedTicketsAfterWithCloseReason(guildId, pageLimit, after) - if err == nil { - reverse(tickets) - } - } - - status := http.StatusOK - if err != nil { - status = http.StatusInternalServerError - } - - return tickets, status, err -} - -// (tickets, statusCode, error) -func getTicketsByTicketId(guildId uint64, ctx *gin.Context) ([]database.TicketWithCloseReason, int, error) { - ticketId, err := strconv.Atoi(ctx.Query("ticketid")) - if err != nil { - return nil, 400, err - } - - ticket, err := dbclient.Client.Tickets.Get(ticketId, guildId) - if err != nil { - return nil, http.StatusInternalServerError, err - } - - if ticket.Id == 0 { - return nil, http.StatusNotFound, errors.New("ticket not found") - } - - closeReason, ok, err := dbclient.Client.CloseReason.Get(guildId, ticketId) - if err != nil { - return nil, http.StatusInternalServerError, err - } - - data := database.TicketWithCloseReason{ - Ticket: ticket, - } - - if ok { - data.CloseReason = &closeReason - } - - return []database.TicketWithCloseReason{data}, http.StatusOK, nil -} - -// (tickets, statusCode, error) -func getTicketsByUsername(guildId uint64, before, after int, ctx *gin.Context) ([]database.TicketWithCloseReason, int, error) { - username := ctx.Query("username") - - botContext, err := botcontext.ContextForGuild(guildId) - if err != nil { - return nil, http.StatusInternalServerError, err - } - - members, err := botContext.SearchMembers(guildId, username) - if err != nil { - return nil, http.StatusInternalServerError, err - } - - userIds := make([]uint64, len(members)) // capped at 100 - for i, member := range members { - userIds[i] = member.User.Id - } - - var tickets []database.TicketWithCloseReason - if before <= 0 && after <= 0 { - tickets, err = dbclient.Client.Tickets.GetClosedByAnyBeforeWithCloseReason(guildId, userIds, math.MaxInt32, pageLimit) - } else if before > 0 { - tickets, err = dbclient.Client.Tickets.GetClosedByAnyBeforeWithCloseReason(guildId, userIds, before, pageLimit) - } else { // after > 0 - // returns in ascending order, must reverse - tickets, err = dbclient.Client.Tickets.GetClosedByAnyAfterWithCloseReason(guildId, userIds, after, pageLimit) - if err == nil { - reverse(tickets) - } - } - - if err != nil { - return nil, http.StatusInternalServerError, err - } - - return tickets, http.StatusOK, nil -} - -// (tickets, statusCode, error) -func getTicketsByUserId(guildId uint64, before, after int, ctx *gin.Context) ([]database.TicketWithCloseReason, int, error) { - userId, err := strconv.ParseUint(ctx.Query("userid"), 10, 64) - if err != nil { - return nil, 400, err - } - - var tickets []database.TicketWithCloseReason - if before <= 0 && after <= 0 { - tickets, err = dbclient.Client.Tickets.GetClosedByAnyBeforeWithCloseReason(guildId, []uint64{userId}, math.MaxInt32, pageLimit) - } else if before > 0 { - tickets, err = dbclient.Client.Tickets.GetClosedByAnyBeforeWithCloseReason(guildId, []uint64{userId}, before, pageLimit) - } else { // after > 0 - // returns in ascending order, must reverse - tickets, err = dbclient.Client.Tickets.GetClosedByAnyAfterWithCloseReason(guildId, []uint64{userId}, after, pageLimit) - if err == nil { - reverse(tickets) - } - } - - if err != nil { - return nil, http.StatusInternalServerError, err - } - - return tickets, http.StatusOK, nil -} - -func reverse(slice []database.TicketWithCloseReason) { - if len(slice) == 0 { - return - } - - for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 { - slice[i], slice[j] = slice[j], slice[i] - } -} diff --git a/app/http/endpoints/api/transcripts/queryoptions.go b/app/http/endpoints/api/transcripts/queryoptions.go new file mode 100644 index 0000000..0a14390 --- /dev/null +++ b/app/http/endpoints/api/transcripts/queryoptions.go @@ -0,0 +1,74 @@ +package api + +import ( + "errors" + "github.com/TicketsBot/GoPanel/botcontext" + "github.com/TicketsBot/database" + "github.com/rxdn/gdl/utils" +) + +type wrappedQueryOptions struct { + Id int `json:"id,string"` + Username string `json:"username"` + UserId uint64 `json:"user_id,string"` + Page int `json:"page"` +} + +func (o *wrappedQueryOptions) toQueryOptions(guildId uint64) (database.TicketQueryOptions, error) { + var userIds []uint64 + if len(o.Username) > 0 { + var err error + userIds, err = usernameToIds(guildId, o.Username) + if err != nil { + return database.TicketQueryOptions{}, err + } + + // TODO: Do this better + if len(userIds) == 0 { + return database.TicketQueryOptions{}, errors.New("User not found") + } + } + + if o.UserId != 0 { + userIds = append(userIds, o.UserId) + } + + var offset int + if o.Page > 1 { + offset = pageLimit * (o.Page - 1) + } + + opts := database.TicketQueryOptions{ + Id: o.Id, + GuildId: guildId, + UserIds: userIds, + Open: utils.BoolPtr(false), + Order: database.OrderTypeDescending, + Limit: pageLimit, + Offset: offset, + } + return opts, nil +} + +func usernameToIds(guildId uint64, username string) ([]uint64, error) { + if len(username) > 32 { + return nil, errors.New("username too long") + } + + botContext, err := botcontext.ContextForGuild(guildId) + if err != nil { + return nil, err + } + + members, err := botContext.SearchMembers(guildId, username) + if err != nil { + return nil, err + } + + userIds := make([]uint64, len(members)) // capped at 100 + for i, member := range members { + userIds[i] = member.User.Id + } + + return userIds, nil +} diff --git a/frontend/src/views/Transcripts.svelte b/frontend/src/views/Transcripts.svelte index bd9f238..ffb4042 100644 --- a/frontend/src/views/Transcripts.svelte +++ b/frontend/src/views/Transcripts.svelte @@ -99,7 +99,7 @@ let filterSettings = {}; let transcripts = []; - const pageLimit = 30; + const pageLimit = 15; let page = 1; let handleInputTicketId = () => { @@ -122,10 +122,8 @@ return; } - let paginationSettings = { - after: transcripts[0].ticket_id, - }; + let paginationSettings = buildPaginationSettings(page - 1); if (await loadData(paginationSettings)) { page--; } @@ -136,42 +134,25 @@ return; } - let paginationSettings = { - before: transcripts[transcripts.length - 1].ticket_id, - }; - + let paginationSettings = buildPaginationSettings(page + 1); if (await loadData(paginationSettings)) { page++; } } - function buildQuery(paginationSettings) { - let query = new URLSearchParams(); - if (paginationSettings['before'] !== undefined) { - query.append('before', paginationSettings['before']); - } - - if (paginationSettings['after'] !== undefined) { - query.append('after', paginationSettings['after']); - } - - if (filterSettings['ticketId'] !== undefined) { - query.append('ticketid', filterSettings.ticketId); - } - - if (filterSettings['username'] !== undefined) { - query.append('username', filterSettings.username); - } - - if (filterSettings['userId'] !== undefined) { - query.append('userid', filterSettings.userId); - } - - return query; + function buildPaginationSettings(page) { + // Undefined fields won't be included in the JSON + return { + id: filterSettings.ticketId, + username: filterSettings.username, + user_id: filterSettings.userId, + page: page, + }; } async function filter() { - await loadData({}); + let opts = buildPaginationSettings(1); + await loadData(opts); page = 1; } diff --git a/go.mod b/go.mod index 578579d..d5dc0a4 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/BurntSushi/toml v0.3.1 github.com/TicketsBot/archiverclient v0.0.0-20210220155137-a562b2f1bbbb github.com/TicketsBot/common v0.0.0-20210727134627-35eb7ed03a44 - github.com/TicketsBot/database v0.0.0-20210809170854-748ae1fff443 + github.com/TicketsBot/database v0.0.0-20210816195201-90c765ca95c8 github.com/TicketsBot/worker v0.0.0-20210727130432-3df3cd1246a3 github.com/apex/log v1.1.2 github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect