mirror of
https://github.com/abhinavxd/libredesk.git
synced 2025-11-03 13:33:32 +00:00
fix: pagination, adds total pages count and total rows count to results.
This commit is contained in:
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/zerodha/fastglue"
|
||||
)
|
||||
|
||||
// handleGetAllConversations retrieves all conversations with pagination, sorting, and filtering.
|
||||
// handleGetAllConversations retrieves all conversations.
|
||||
func handleGetAllConversations(r *fastglue.Request) error {
|
||||
var (
|
||||
app = r.Context.(*App)
|
||||
@@ -20,12 +20,22 @@ func handleGetAllConversations(r *fastglue.Request) error {
|
||||
page, _ = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("page")))
|
||||
pageSize, _ = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("page_size")))
|
||||
filters = string(r.RequestCtx.QueryArgs().Peek("filters"))
|
||||
total = 0
|
||||
)
|
||||
c, err := app.conversation.GetAllConversationsList(order, orderBy, filters, page, pageSize)
|
||||
conversations, pageSize, err := app.conversation.GetAllConversationsList(order, orderBy, filters, page, pageSize)
|
||||
if err != nil {
|
||||
return sendErrorEnvelope(r, err)
|
||||
}
|
||||
return r.SendEnvelope(c)
|
||||
if len(conversations) > 0 {
|
||||
total = conversations[0].Total
|
||||
}
|
||||
return r.SendEnvelope(envelope.PageResults{
|
||||
Results: conversations,
|
||||
Total: total,
|
||||
PerPage: pageSize,
|
||||
TotalPages: total / pageSize,
|
||||
Page: page,
|
||||
})
|
||||
}
|
||||
|
||||
// handleGetAssignedConversations retrieves conversations assigned to the current user.
|
||||
@@ -38,12 +48,22 @@ func handleGetAssignedConversations(r *fastglue.Request) error {
|
||||
page, _ = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("page")))
|
||||
pageSize, _ = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("page_size")))
|
||||
filters = string(r.RequestCtx.QueryArgs().Peek("filters"))
|
||||
total = 0
|
||||
)
|
||||
c, err := app.conversation.GetAssignedConversationsList(user.ID, order, orderBy, filters, page, pageSize)
|
||||
conversations, pageSize, err := app.conversation.GetAssignedConversationsList(user.ID, order, orderBy, filters, page, pageSize)
|
||||
if err != nil {
|
||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, err.Error(), nil, "")
|
||||
}
|
||||
return r.SendEnvelope(c)
|
||||
if len(conversations) > 0 {
|
||||
total = conversations[0].Total
|
||||
}
|
||||
return r.SendEnvelope(envelope.PageResults{
|
||||
Results: conversations,
|
||||
Total: total,
|
||||
PerPage: pageSize,
|
||||
TotalPages: total / pageSize,
|
||||
Page: page,
|
||||
})
|
||||
}
|
||||
|
||||
// handleGetUnassignedConversations retrieves unassigned conversations.
|
||||
@@ -56,12 +76,22 @@ func handleGetUnassignedConversations(r *fastglue.Request) error {
|
||||
page, _ = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("page")))
|
||||
pageSize, _ = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("page_size")))
|
||||
filters = string(r.RequestCtx.QueryArgs().Peek("filters"))
|
||||
total = 0
|
||||
)
|
||||
c, err := app.conversation.GetUnassignedConversationsList(user.ID, order, orderBy, filters, page, pageSize)
|
||||
conversations, pageSize, err := app.conversation.GetUnassignedConversationsList(user.ID, order, orderBy, filters, page, pageSize)
|
||||
if err != nil {
|
||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, err.Error(), nil, "")
|
||||
}
|
||||
return r.SendEnvelope(c)
|
||||
if len(conversations) > 0 {
|
||||
total = conversations[0].Total
|
||||
}
|
||||
return r.SendEnvelope(envelope.PageResults{
|
||||
Results: conversations,
|
||||
Total: total,
|
||||
PerPage: pageSize,
|
||||
TotalPages: total / pageSize,
|
||||
Page: page,
|
||||
})
|
||||
}
|
||||
|
||||
// handleGetConversation retrieves a single conversation by UUID with permission checks.
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/abhinavxd/artemis/internal/conversation"
|
||||
cmodels "github.com/abhinavxd/artemis/internal/conversation/models"
|
||||
"github.com/abhinavxd/artemis/internal/envelope"
|
||||
medModels "github.com/abhinavxd/artemis/internal/media/models"
|
||||
umodels "github.com/abhinavxd/artemis/internal/user/models"
|
||||
"github.com/valyala/fasthttp"
|
||||
@@ -24,6 +25,7 @@ func handleGetMessages(r *fastglue.Request) error {
|
||||
user = r.RequestCtx.UserValue("user").(umodels.User)
|
||||
page, _ = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("page")))
|
||||
pageSize, _ = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("page_size")))
|
||||
total = 0
|
||||
)
|
||||
|
||||
// Check permission
|
||||
@@ -32,17 +34,24 @@ func handleGetMessages(r *fastglue.Request) error {
|
||||
return sendErrorEnvelope(r, err)
|
||||
}
|
||||
|
||||
messages, err := app.conversation.GetConversationMessages(uuid, page, pageSize)
|
||||
messages, pageSize, err := app.conversation.GetConversationMessages(uuid, page, pageSize)
|
||||
if err != nil {
|
||||
return sendErrorEnvelope(r, err)
|
||||
}
|
||||
|
||||
for i := range messages {
|
||||
total = messages[i].Total
|
||||
for j := range messages[i].Attachments {
|
||||
messages[i].Attachments[j].URL = app.media.GetURL(messages[i].Attachments[j].UUID)
|
||||
}
|
||||
}
|
||||
return r.SendEnvelope(messages)
|
||||
return r.SendEnvelope(envelope.PageResults{
|
||||
Total: total,
|
||||
Results: messages,
|
||||
Page: page,
|
||||
PerPage: pageSize,
|
||||
TotalPages: total / pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetMessage(r *fastglue.Request) error {
|
||||
|
||||
@@ -66,7 +66,7 @@ const nonInlineAttachments = computed(() =>
|
||||
)
|
||||
|
||||
const getFullName = computed(() => {
|
||||
return convStore.current.contact.first_name + ' ' + convStore.current.last_name
|
||||
return convStore.current.contact.first_name + ' ' + convStore.current.contact.last_name
|
||||
})
|
||||
|
||||
const avatarFallback = computed(() => {
|
||||
|
||||
@@ -146,29 +146,19 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
messages.loading = true
|
||||
try {
|
||||
const response = await api.getConversationMessages(uuid, messages.page)
|
||||
const fetchedMessages = response.data?.data || []
|
||||
|
||||
const result = response.data?.data || {}
|
||||
const results = result.results || []
|
||||
// Filter out messages already seen.
|
||||
const newMessages = fetchedMessages.filter((message) => {
|
||||
const newMessages = results.filter((message) => {
|
||||
if (!seenMessageUUIDs.has(message.uuid)) {
|
||||
seenMessageUUIDs.add(message.uuid)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (newMessages.length === 0 && messages.page === 1) {
|
||||
messages.data = []
|
||||
return
|
||||
}
|
||||
|
||||
if (newMessages.length === 0 && messages.page > 1) {
|
||||
messages.hasMore = false
|
||||
}
|
||||
|
||||
// Add new messages to the messages state.
|
||||
if (newMessages.length === 0 && messages.page === 1) messages.data = []
|
||||
if (result.total_pages <= messages.page) messages.hasMore = false
|
||||
messages.data.unshift(...newMessages)
|
||||
|
||||
} catch (error) {
|
||||
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
|
||||
title: 'Something went wrong',
|
||||
@@ -250,22 +240,17 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
if (response?.data?.data) {
|
||||
const newConversations = response.data.data.filter((conversation) => {
|
||||
if (!seenConversationUUIDs.has(conversation.uuid)) {
|
||||
seenConversationUUIDs.set(conversation.uuid, true)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (!conversations.data) conversations.data = []
|
||||
if (newConversations.length === 0) conversations.hasMore = false
|
||||
conversations.data.push(...newConversations)
|
||||
} else {
|
||||
conversations.hasMore = false
|
||||
}
|
||||
const apiResponse = response.data.data
|
||||
const newConversations = apiResponse.results.filter((conversation) => {
|
||||
if (!seenConversationUUIDs.has(conversation.uuid)) {
|
||||
seenConversationUUIDs.set(conversation.uuid, true)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if (apiResponse.total_pages <= conversations.page) conversations.hasMore = false
|
||||
if (!conversations.data) conversations.data = []
|
||||
conversations.data.push(...newConversations)
|
||||
} catch (error) {
|
||||
conversations.errorMessage = handleHTTPError(error).message
|
||||
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
|
||||
|
||||
@@ -289,59 +289,48 @@ func (c *Manager) GetConversationUUID(id int) (string, error) {
|
||||
}
|
||||
|
||||
// GetAllConversationsList retrieves all conversations with optional filtering, ordering, and pagination.
|
||||
func (c *Manager) GetAllConversationsList(order, orderBy, filters string, page, pageSize int) ([]models.Conversation, error) {
|
||||
func (c *Manager) GetAllConversationsList(order, orderBy, filters string, page, pageSize int) ([]models.Conversation, int, error) {
|
||||
return c.GetConversations(0, models.AllConversations, order, orderBy, filters, page, pageSize)
|
||||
}
|
||||
|
||||
// GetAssignedConversationsList retrieves conversations assigned to a specific user with optional filtering, ordering, and pagination.
|
||||
func (c *Manager) GetAssignedConversationsList(userID int, order, orderBy, filters string, page, pageSize int) ([]models.Conversation, error) {
|
||||
func (c *Manager) GetAssignedConversationsList(userID int, order, orderBy, filters string, page, pageSize int) ([]models.Conversation, int, error) {
|
||||
return c.GetConversations(userID, models.AssignedConversations, order, orderBy, filters, page, pageSize)
|
||||
}
|
||||
|
||||
// GetUnassignedConversationsList retrieves conversations assigned to a team the user is part of with optional filtering, ordering, and pagination.
|
||||
func (c *Manager) GetUnassignedConversationsList(userID int, order, orderBy, filters string, page, pageSize int) ([]models.Conversation, error) {
|
||||
func (c *Manager) GetUnassignedConversationsList(userID int, order, orderBy, filters string, page, pageSize int) ([]models.Conversation, int, error) {
|
||||
return c.GetConversations(userID, models.UnassignedConversations, order, orderBy, filters, page, pageSize)
|
||||
}
|
||||
|
||||
// GetConversations retrieves conversations list based on user ID, type, and optional filtering, ordering, and pagination.
|
||||
func (c *Manager) GetConversations(userID int, listType, order, orderBy, filters string, page, pageSize int) ([]models.Conversation, error) {
|
||||
func (c *Manager) GetConversations(userID int, listType, order, orderBy, filters string, page, pageSize int) ([]models.Conversation, int, error) {
|
||||
var conversations = make([]models.Conversation, 0)
|
||||
|
||||
if orderBy == "" {
|
||||
orderBy = "last_message_at"
|
||||
}
|
||||
if order == "" {
|
||||
order = "DESC"
|
||||
}
|
||||
|
||||
if filters == "" {
|
||||
filters = "[]"
|
||||
}
|
||||
|
||||
query, qArgs, err := c.makeConversationsListQuery(userID, c.q.GetConversations, listType, order, orderBy, page, pageSize, filters)
|
||||
query, pageSize, qArgs, err := c.makeConversationsListQuery(userID, c.q.GetConversations, listType, order, orderBy, page, pageSize, filters)
|
||||
if err != nil {
|
||||
return conversations, envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.entities.conversations}"), nil)
|
||||
return conversations, pageSize, envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.entities.conversations}"), nil)
|
||||
}
|
||||
|
||||
tx, err := c.db.BeginTxx(context.Background(), nil)
|
||||
defer tx.Rollback()
|
||||
if err != nil {
|
||||
c.lo.Error("error preparing get conversations query", "error", err)
|
||||
return conversations, envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.entities.conversations}"), nil)
|
||||
return conversations, pageSize, envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.entities.conversations}"), nil)
|
||||
}
|
||||
|
||||
if err := tx.Select(&conversations, query, qArgs...); err != nil {
|
||||
c.lo.Error("error fetching conversations", "error", err)
|
||||
return conversations, envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.entities.conversations}"), nil)
|
||||
return conversations, pageSize, envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.entities.conversations}"), nil)
|
||||
}
|
||||
return conversations, nil
|
||||
return conversations, pageSize, nil
|
||||
}
|
||||
|
||||
// GetConversationsListUUIDs retrieves the UUIDs of conversations list.
|
||||
func (c *Manager) GetConversationsListUUIDs(userID, page, pageSize int, typ string) ([]string, error) {
|
||||
var ids = make([]string, 0)
|
||||
|
||||
query, qArgs, err := c.makeConversationsListQuery(userID, c.q.GetConversationsListUUIDs, typ, "", "", page, pageSize, "")
|
||||
query, _, qArgs, err := c.makeConversationsListQuery(userID, c.q.GetConversationsListUUIDs, typ, "", "", page, pageSize, "")
|
||||
if err != nil {
|
||||
c.lo.Error("error generating conversations query", "error", err)
|
||||
return ids, err
|
||||
@@ -560,12 +549,27 @@ func (t *Manager) UpsertConversationTags(uuid string, tagIDs []int) error {
|
||||
}
|
||||
|
||||
// makeConversationsListQuery prepares a SQL query string for conversations list
|
||||
func (c *Manager) makeConversationsListQuery(userID int, baseQuery, listType, order, orderBy string, page, pageSize int, filtersJSON string) (string, []interface{}, error) {
|
||||
func (c *Manager) makeConversationsListQuery(userID int, baseQuery, listType, order, orderBy string, page, pageSize int, filtersJSON string) (string, int, []interface{}, error) {
|
||||
var qArgs []interface{}
|
||||
|
||||
if orderBy == "" {
|
||||
orderBy = "last_message_at"
|
||||
}
|
||||
if order == "" {
|
||||
order = "DESC"
|
||||
}
|
||||
if filtersJSON == "" {
|
||||
filtersJSON = "[]"
|
||||
}
|
||||
if pageSize > conversationsListMaxPageSize {
|
||||
pageSize = conversationsListMaxPageSize
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
// Set condition based on the list type.
|
||||
switch listType {
|
||||
@@ -578,7 +582,7 @@ func (c *Manager) makeConversationsListQuery(userID int, baseQuery, listType, or
|
||||
case models.AllConversations:
|
||||
baseQuery = fmt.Sprintf(baseQuery, "")
|
||||
default:
|
||||
return "", nil, fmt.Errorf("invalid conversation type %s", listType)
|
||||
return "", pageSize, nil, fmt.Errorf("invalid conversation type %s", listType)
|
||||
}
|
||||
|
||||
query, qArgs, err := dbutil.PaginateAndFilterQuery(baseQuery, qArgs, dbutil.PaginationOptions{
|
||||
@@ -591,10 +595,10 @@ func (c *Manager) makeConversationsListQuery(userID int, baseQuery, listType, or
|
||||
})
|
||||
if err != nil {
|
||||
c.lo.Error("error preparing query", "error", err)
|
||||
return "", nil, err
|
||||
return "", pageSize, nil, err
|
||||
}
|
||||
|
||||
return query, qArgs, err
|
||||
return query, pageSize, qArgs, err
|
||||
}
|
||||
|
||||
// GetToAddress retrieves the recipient addresses for a conversation.
|
||||
|
||||
@@ -212,32 +212,32 @@ func (m *Manager) RenderContentInTemplate(channel string, message *models.Messag
|
||||
}
|
||||
|
||||
// GetConversationMessages retrieves messages for a specific conversation.
|
||||
func (m *Manager) GetConversationMessages(conversationUUID string, page, pageSize int) ([]models.Message, error) {
|
||||
func (m *Manager) GetConversationMessages(conversationUUID string, page, pageSize int) ([]models.Message, int, error) {
|
||||
var (
|
||||
messages = make([]models.Message, 0)
|
||||
qArgs []interface{}
|
||||
)
|
||||
|
||||
qArgs = append(qArgs, conversationUUID)
|
||||
query, qArgs, err := m.generateMessagesQuery(m.q.GetMessages, qArgs, page, pageSize)
|
||||
query, pageSize, qArgs, err := m.generateMessagesQuery(m.q.GetMessages, qArgs, page, pageSize)
|
||||
if err != nil {
|
||||
m.lo.Error("error generating messages query", "error", err)
|
||||
return messages, envelope.NewError(envelope.GeneralError, "Error fetching messages", nil)
|
||||
return messages, pageSize, envelope.NewError(envelope.GeneralError, "Error fetching messages", nil)
|
||||
}
|
||||
|
||||
tx, err := m.db.BeginTxx(context.Background(), nil)
|
||||
defer tx.Rollback()
|
||||
if err != nil {
|
||||
m.lo.Error("error preparing get messages query", "error", err)
|
||||
return messages, envelope.NewError(envelope.GeneralError, "Error fetching messages", nil)
|
||||
return messages, pageSize, envelope.NewError(envelope.GeneralError, "Error fetching messages", nil)
|
||||
}
|
||||
|
||||
if err := tx.Select(&messages, query, qArgs...); err != nil {
|
||||
m.lo.Error("error fetching conversations", "error", err)
|
||||
return messages, envelope.NewError(envelope.GeneralError, "Error fetching messages", nil)
|
||||
return messages, pageSize, envelope.NewError(envelope.GeneralError, "Error fetching messages", nil)
|
||||
}
|
||||
|
||||
return messages, nil
|
||||
return messages, pageSize, nil
|
||||
}
|
||||
|
||||
// GetMessage retrieves a message by UUID.
|
||||
@@ -489,14 +489,16 @@ func (m *Manager) GetConversationByMessageID(id int) (models.Conversation, error
|
||||
}
|
||||
|
||||
// generateMessagesQuery generates the SQL query for fetching messages in a conversation.
|
||||
func (c *Manager) generateMessagesQuery(baseQuery string, qArgs []interface{}, page, pageSize int) (string, []interface{}, error) {
|
||||
// Set default values for page and page size if they are invalid
|
||||
func (c *Manager) generateMessagesQuery(baseQuery string, qArgs []interface{}, page, pageSize int) (string, int, []interface{}, error) {
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > maxMessagesPerPage {
|
||||
if pageSize > maxMessagesPerPage {
|
||||
pageSize = maxMessagesPerPage
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
// Calculate the offset
|
||||
offset := (page - 1) * pageSize
|
||||
@@ -506,7 +508,7 @@ func (c *Manager) generateMessagesQuery(baseQuery string, qArgs []interface{}, p
|
||||
|
||||
// Include LIMIT and OFFSET in the SQL query
|
||||
sqlQuery := fmt.Sprintf(baseQuery, fmt.Sprintf("LIMIT $%d OFFSET $%d", len(qArgs)-1, len(qArgs)))
|
||||
return sqlQuery, qArgs, nil
|
||||
return sqlQuery, pageSize, qArgs, nil
|
||||
}
|
||||
|
||||
// uploadMessageAttachments uploads attachments for a message.
|
||||
|
||||
@@ -44,6 +44,7 @@ type Conversation struct {
|
||||
LastMessageAt null.Time `db:"last_message_at" json:"last_message_at"`
|
||||
LastMessage string `db:"last_message" json:"last_message"`
|
||||
Contact cmodels.Contact `db:"contact" json:"contact"`
|
||||
Total int `db:"total" json:"-"`
|
||||
}
|
||||
|
||||
type ConversationParticipant struct {
|
||||
@@ -93,6 +94,7 @@ type Message struct {
|
||||
InReplyTo string `json:"-"`
|
||||
Headers textproto.MIMEHeader `json:"-"`
|
||||
Media []mmodels.Media `db:"-" json:"-"`
|
||||
Total int `db:"total" json:"-"`
|
||||
}
|
||||
|
||||
// IncomingMessage links a message with the contact information and inbox id.
|
||||
|
||||
@@ -9,6 +9,7 @@ RETURNING id, uuid;
|
||||
|
||||
-- name: get-conversations
|
||||
SELECT
|
||||
COUNT(*) OVER() AS total,
|
||||
conversations.updated_at,
|
||||
conversations.uuid,
|
||||
conversations.assignee_last_seen_at,
|
||||
@@ -368,6 +369,7 @@ attachments AS (
|
||||
GROUP BY message_id
|
||||
)
|
||||
SELECT
|
||||
COUNT(*) OVER() AS total,
|
||||
m.created_at,
|
||||
m.updated_at,
|
||||
m.status,
|
||||
|
||||
@@ -47,14 +47,6 @@ func PaginateAndFilterQuery(baseQuery string, existingArgs []interface{}, opts P
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
// Validate and set default values for pagination
|
||||
if opts.Page <= 0 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.PageSize <= 0 {
|
||||
opts.PageSize = 10 // Default page size
|
||||
}
|
||||
|
||||
// Calculate offset
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
|
||||
|
||||
@@ -32,6 +32,15 @@ func (e Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// PageResults is a generic struct for paginated results.
|
||||
type PageResults struct {
|
||||
Results interface{} `json:"results"`
|
||||
Total int `json:"total"`
|
||||
PerPage int `json:"per_page"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
Page int `json:"page"`
|
||||
}
|
||||
|
||||
// NewError creates and returns a new instance of Error with custom error metadata.
|
||||
func NewError(etype string, message string, data interface{}) error {
|
||||
err := Error{
|
||||
|
||||
Reference in New Issue
Block a user