Skip to content

Commit f6b5cd3

Browse files
committed
feat: add pagination support in catalog & template manager
Signed-off-by: sarthakjdev <jsarthak448@gmail.com>
1 parent 53bad25 commit f6b5cd3

File tree

6 files changed

+493
-176
lines changed

6 files changed

+493
-176
lines changed

internal/types.go

Lines changed: 0 additions & 10 deletions
This file was deleted.

manager/catalog_manager.go

Lines changed: 291 additions & 100 deletions
Large diffs are not rendered by default.

manager/phone_number_manager.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"net/http"
66
"strings"
77

8-
"github.com/wapikit/wapi.go/internal"
98
"github.com/wapikit/wapi.go/internal/request_client"
109
)
1110

@@ -146,9 +145,9 @@ type WhatsappBusinessAccountPhoneNumber struct {
146145

147146
// WhatsappBusinessAccountPhoneNumberEdge represents a list of WhatsApp Business Account phone numbers.
148147
type WhatsappBusinessAccountPhoneNumberEdge struct {
149-
Data []WhatsappBusinessAccountPhoneNumber `json:"data,omitempty"`
150-
Paging internal.WhatsAppBusinessApiPaginationMeta `json:"paging,omitempty"`
151-
Summary string `json:"summary,omitempty"`
148+
Data []WhatsappBusinessAccountPhoneNumber `json:"data,omitempty"`
149+
Paging PaginationDetails `json:"paging,omitempty"`
150+
Summary string `json:"summary,omitempty"`
152151
}
153152

154153
// FetchAll fetches all phone numbers based on the provided filters.

manager/template_manager.go

Lines changed: 152 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package manager
33
import (
44
"encoding/json"
55
"net/http"
6+
"strconv"
67
"strings"
78

8-
"github.com/wapikit/wapi.go/internal"
99
"github.com/wapikit/wapi.go/internal/request_client"
1010
)
1111

@@ -69,8 +69,8 @@ func NewTemplateManager(config *TemplateManagerConfig) *TemplateManager {
6969

7070
// WhatsAppBusinessTemplatesFetchResponseEdge represents the response structure for fetching templates.
7171
type WhatsAppBusinessTemplatesFetchResponseEdge struct {
72-
Data []WhatsAppBusinessMessageTemplateNode `json:"data,omitempty"`
73-
Paging internal.WhatsAppBusinessApiPaginationMeta `json:"paging,omitempty"`
72+
Data []WhatsAppBusinessMessageTemplateNode `json:"data,omitempty"`
73+
Paging PaginationDetails `json:"paging,omitempty"`
7474
}
7575

7676
// TemplateMessageComponentCard represents a card component in a message template.
@@ -163,61 +163,7 @@ type WhatsAppBusinessHSMWhatsAppHSMComponent struct {
163163
type TemplateMessageQualityScore struct {
164164
Date int `json:"date,omitempty"`
165165
Reasons []string `json:"reasons,omitempty"`
166-
Score int `json:"score,omitempty"`
167-
}
168-
169-
// FetchAll fetches all WhatsApp Business message templates.
170-
func (manager *TemplateManager) FetchAll() (*WhatsAppBusinessTemplatesFetchResponseEdge, error) {
171-
apiRequest := manager.requester.NewApiRequest(strings.Join([]string{manager.businessAccountId, "/", "message_templates"}, ""), http.MethodGet)
172-
173-
fields := []string{
174-
"id", "category", "components", "correct_category", "cta_url_link_tracking_opted_out",
175-
"language", "library_template_name", "message_send_ttl_seconds", "name", "previous_category",
176-
"quality_score", "rejected_reason", "status", "sub_category",
177-
}
178-
179-
for _, field := range fields {
180-
apiRequest.AddField(request_client.ApiRequestQueryParamField{
181-
Name: field,
182-
Filters: map[string]string{},
183-
})
184-
}
185-
186-
// for now add a default 1000 limit on templates fetch
187-
// ! TODO: add proper pagination
188-
apiRequest.AddQueryParam("limit", "1000")
189-
190-
response, err := apiRequest.Execute()
191-
if err != nil {
192-
return nil, err
193-
}
194-
195-
var responseToReturn WhatsAppBusinessTemplatesFetchResponseEdge
196-
json.Unmarshal([]byte(response), &responseToReturn)
197-
return &responseToReturn, nil
198-
}
199-
200-
// Fetch fetches a single WhatsApp Business message template by its ID.
201-
func (manager *TemplateManager) Fetch(Id string) (*WhatsAppBusinessMessageTemplateNode, error) {
202-
apiRequest := manager.requester.NewApiRequest(strings.Join([]string{Id}, ""), http.MethodGet)
203-
fields := []string{
204-
"id", "category", "components", "correct_category", "cta_url_link_tracking_opted_out",
205-
"language", "library_template_name", "message_send_ttl_seconds", "name", "previous_category",
206-
"quality_score", "rejected_reason", "status", "sub_category",
207-
}
208-
for _, field := range fields {
209-
apiRequest.AddField(request_client.ApiRequestQueryParamField{
210-
Name: field,
211-
Filters: map[string]string{},
212-
})
213-
}
214-
response, err := apiRequest.Execute()
215-
if err != nil {
216-
return nil, err
217-
}
218-
var responseToReturn WhatsAppBusinessMessageTemplateNode
219-
json.Unmarshal([]byte(response), &responseToReturn)
220-
return &responseToReturn, nil
166+
Score string `json:"score,omitempty"`
221167
}
222168

223169
// WhatsappMessageTemplateButtonCreateRequestBody represents the request body for creating a button.
@@ -337,8 +283,8 @@ type TemplateMessagePreviewNode struct {
337283

338284
// TemplateMessagePreviewEdge represents the preview response.
339285
type TemplateMessagePreviewEdge struct {
340-
Data []TemplateMessagePreviewNode `json:"data,omitempty"`
341-
Paging internal.WhatsAppBusinessApiPaginationMeta `json:"paging,omitempty"`
286+
Data []TemplateMessagePreviewNode `json:"data,omitempty"`
287+
Paging PaginationDetails `json:"paging,omitempty"`
342288
}
343289

344290
// TemplateMigrationResponse represents the migration response.
@@ -360,3 +306,149 @@ func (manager *TemplateManager) MigrateFromOtherBusinessAccount(sourcePageNumber
360306
json.Unmarshal([]byte(response), &responseToReturn)
361307
return &responseToReturn, nil
362308
}
309+
310+
// FetchAllWithPagination fetches WhatsApp Business message templates with pagination support
311+
func (manager *TemplateManager) FetchAllWithPagination(paginationInput ...*PaginationInput) (*PaginatedResponse[WhatsAppBusinessMessageTemplateNode], error) {
312+
apiRequest := manager.requester.NewApiRequest(
313+
strings.Join([]string{manager.businessAccountId, "/", "message_templates"}, ""),
314+
http.MethodGet,
315+
)
316+
317+
fields := []string{
318+
"id", "category", "components", "correct_category", "cta_url_link_tracking_opted_out",
319+
"language", "library_template_name", "message_send_ttl_seconds", "name", "previous_category",
320+
"quality_score", "rejected_reason", "status", "sub_category",
321+
}
322+
323+
for _, field := range fields {
324+
apiRequest.AddField(request_client.ApiRequestQueryParamField{
325+
Name: field,
326+
Filters: map[string]string{},
327+
})
328+
}
329+
330+
// Apply pagination parameters
331+
if len(paginationInput) > 0 && paginationInput[0] != nil {
332+
input := paginationInput[0]
333+
if input.Limit > 0 {
334+
apiRequest.AddQueryParam("limit", strconv.Itoa(input.Limit))
335+
}
336+
if input.After != "" {
337+
apiRequest.AddQueryParam("after", input.After)
338+
}
339+
if input.Before != "" {
340+
apiRequest.AddQueryParam("before", input.Before)
341+
}
342+
} else {
343+
// Default limit
344+
apiRequest.AddQueryParam("limit", "100")
345+
}
346+
347+
response, err := apiRequest.Execute()
348+
if err != nil {
349+
return nil, err
350+
}
351+
352+
var result PaginatedResponse[WhatsAppBusinessMessageTemplateNode]
353+
if err := json.Unmarshal([]byte(response), &result); err != nil {
354+
return nil, err
355+
}
356+
357+
return &result, nil
358+
}
359+
360+
// Deprecated: Use FetchAllWithPagination for better control
361+
func (manager *TemplateManager) FetchAll() (*WhatsAppBusinessTemplatesFetchResponseEdge, error) {
362+
apiRequest := manager.requester.NewApiRequest(
363+
strings.Join([]string{manager.businessAccountId, "/", "message_templates"}, ""),
364+
http.MethodGet,
365+
)
366+
367+
fields := []string{
368+
"id", "category", "components", "correct_category", "cta_url_link_tracking_opted_out",
369+
"language", "library_template_name", "message_send_ttl_seconds", "name", "previous_category",
370+
"quality_score", "rejected_reason", "status", "sub_category",
371+
}
372+
373+
for _, field := range fields {
374+
apiRequest.AddField(request_client.ApiRequestQueryParamField{
375+
Name: field,
376+
Filters: map[string]string{},
377+
})
378+
}
379+
380+
// High limit for backwards compatibility
381+
apiRequest.AddQueryParam("limit", "1000")
382+
383+
response, err := apiRequest.Execute()
384+
if err != nil {
385+
return nil, err
386+
}
387+
388+
var responseToReturn WhatsAppBusinessTemplatesFetchResponseEdge
389+
if err := json.Unmarshal([]byte(response), &responseToReturn); err != nil {
390+
return nil, err
391+
}
392+
393+
return &responseToReturn, nil
394+
}
395+
396+
// GetAllTemplates fetches all templates across all pages
397+
// This is a helper method that handles pagination automatically
398+
func (manager *TemplateManager) GetAllTemplates(limit ...int) ([]WhatsAppBusinessMessageTemplateNode, error) {
399+
pageLimit := 100
400+
if len(limit) > 0 && limit[0] > 0 {
401+
pageLimit = limit[0]
402+
}
403+
404+
var allTemplates []WhatsAppBusinessMessageTemplateNode
405+
var nextCursor string
406+
407+
for {
408+
input := &PaginationInput{
409+
Limit: pageLimit,
410+
After: nextCursor,
411+
}
412+
413+
result, err := manager.FetchAllWithPagination(input)
414+
if err != nil {
415+
return nil, err
416+
}
417+
418+
allTemplates = append(allTemplates, result.Data...)
419+
420+
// Check if there's a next page
421+
if !result.Paging.HasNextPage() {
422+
break
423+
}
424+
425+
nextCursor = result.Paging.GetNextCursor()
426+
}
427+
428+
return allTemplates, nil
429+
}
430+
431+
// Fetch fetches a single WhatsApp Business message template by its ID (no changes needed)
432+
func (manager *TemplateManager) Fetch(Id string) (*WhatsAppBusinessMessageTemplateNode, error) {
433+
apiRequest := manager.requester.NewApiRequest(strings.Join([]string{Id}, ""), http.MethodGet)
434+
fields := []string{
435+
"id", "category", "components", "correct_category", "cta_url_link_tracking_opted_out",
436+
"language", "library_template_name", "message_send_ttl_seconds", "name", "previous_category",
437+
"quality_score", "rejected_reason", "status", "sub_category",
438+
}
439+
for _, field := range fields {
440+
apiRequest.AddField(request_client.ApiRequestQueryParamField{
441+
Name: field,
442+
Filters: map[string]string{},
443+
})
444+
}
445+
response, err := apiRequest.Execute()
446+
if err != nil {
447+
return nil, err
448+
}
449+
var responseToReturn WhatsAppBusinessMessageTemplateNode
450+
if err := json.Unmarshal([]byte(response), &responseToReturn); err != nil {
451+
return nil, err
452+
}
453+
return &responseToReturn, nil
454+
}

manager/types.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,3 +484,49 @@ const (
484484
MessageStatusFailed MessageStatusEnum = "failed"
485485
MessageStatusSent MessageStatusEnum = "sent"
486486
)
487+
488+
// PaginationCursors represents the before/after cursors used in cursor-based pagination
489+
type PaginationCursors struct {
490+
Before string `json:"before,omitempty"`
491+
After string `json:"after,omitempty"`
492+
}
493+
494+
// PaginationDetails contains the pagination metadata returned by WhatsApp API
495+
type PaginationDetails struct {
496+
Cursors PaginationCursors `json:"cursors,omitempty"`
497+
Next string `json:"next,omitempty"`
498+
Previous string `json:"previous,omitempty"`
499+
}
500+
501+
// PaginationInput represents the pagination parameters that can be passed to API calls
502+
type PaginationInput struct {
503+
Limit int `json:"limit,omitempty"` // Number of results per page
504+
After string `json:"after,omitempty"` // Cursor for next page
505+
Before string `json:"before,omitempty"` // Cursor for previous page
506+
}
507+
508+
// PaginatedResponse is a generic wrapper for paginated responses
509+
type PaginatedResponse[T any] struct {
510+
Data []T `json:"data"`
511+
Paging PaginationDetails `json:"paging,omitempty"`
512+
}
513+
514+
// HasNextPage checks if there's a next page available
515+
func (pd *PaginationDetails) HasNextPage() bool {
516+
return pd.Cursors.After != "" || pd.Next != ""
517+
}
518+
519+
// HasPreviousPage checks if there's a previous page available
520+
func (pd *PaginationDetails) HasPreviousPage() bool {
521+
return pd.Cursors.Before != "" || pd.Previous != ""
522+
}
523+
524+
// GetNextCursor returns the cursor for the next page
525+
func (pd *PaginationDetails) GetNextCursor() string {
526+
return pd.Cursors.After
527+
}
528+
529+
// GetPreviousCursor returns the cursor for the previous page
530+
func (pd *PaginationDetails) GetPreviousCursor() string {
531+
return pd.Cursors.Before
532+
}

pkg/business/client.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"strings"
88
"time"
99

10-
"github.com/wapikit/wapi.go/internal"
1110
"github.com/wapikit/wapi.go/internal/request_client"
1211
"github.com/wapikit/wapi.go/manager"
1312
)
@@ -244,7 +243,7 @@ type WhatsAppConversationAnalyticsEdge struct {
244243
Data []struct {
245244
DataPoints []WhatsAppConversationAnalyticsNode `json:"data_points,omitempty"`
246245
} `json:"data,omitempty"`
247-
Paging internal.WhatsAppBusinessApiPaginationMeta `json:"paging,omitempty"`
246+
Paging manager.PaginationDetails `json:"paging,omitempty"`
248247
}
249248

250249
type WhatsAppConversationAnalyticsResponse struct {

0 commit comments

Comments
 (0)