feat(api): 🎉 Add business profile and catalog management
* Implement endpoints for managing business profiles: - Get business profile - Update business profile * Add catalog management features: - List catalogs - List products in a catalog - Send catalog messages - Send single product messages - Send product list messages * Introduce media upload functionality for sending media files. * Add flow management capabilities: - Deprecate flows * Update API documentation to reflect new endpoints and features.
This commit is contained in:
215
pkg/handlers/catalog.go
Normal file
215
pkg/handlers/catalog.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/utils"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/whatsapp/businessapi"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
)
|
||||
|
||||
// ListCatalogs returns product catalogs for a Business API account.
|
||||
// POST /api/catalogs {"account_id"}
|
||||
func (h *Handlers) ListCatalogs(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := baClient.ListCatalogs(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, resp)
|
||||
}
|
||||
|
||||
// ListProducts returns products in a specific catalog.
|
||||
// POST /api/catalogs/products {"account_id","catalog_id"}
|
||||
func (h *Handlers) ListProducts(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
CatalogID string `json:"catalog_id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.CatalogID == "" {
|
||||
http.Error(w, "catalog_id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := baClient.ListProducts(r.Context(), req.CatalogID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, resp)
|
||||
}
|
||||
|
||||
// SendCatalogMessage sends a catalog message that shares the full product catalog.
|
||||
// POST /api/send/catalog {"account_id","to","body_text","thumbnail_product_retailer_id"}
|
||||
func (h *Handlers) SendCatalogMessage(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
To string `json:"to"`
|
||||
BodyText string `json:"body_text"`
|
||||
ThumbnailProductRetailerID string `json:"thumbnail_product_retailer_id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.BodyText == "" {
|
||||
http.Error(w, "body_text is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
jid, err := types.ParseJID(utils.FormatPhoneToJID(req.To, h.config.Server.DefaultCountryCode))
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid phone number", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := baClient.SendCatalogMessage(r.Context(), jid, req.BodyText, req.ThumbnailProductRetailerID); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// SendSingleProduct sends a single-product interactive message.
|
||||
// POST /api/send/product {"account_id","to","catalog_id","product_retailer_id","body_text","footer_text"}
|
||||
func (h *Handlers) SendSingleProduct(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
To string `json:"to"`
|
||||
CatalogID string `json:"catalog_id"`
|
||||
ProductRetailerID string `json:"product_retailer_id"`
|
||||
BodyText string `json:"body_text"`
|
||||
FooterText string `json:"footer_text"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.CatalogID == "" || req.ProductRetailerID == "" || req.BodyText == "" {
|
||||
http.Error(w, "catalog_id, product_retailer_id, and body_text are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
jid, err := types.ParseJID(utils.FormatPhoneToJID(req.To, h.config.Server.DefaultCountryCode))
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid phone number", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := baClient.SendSingleProduct(r.Context(), jid, req.CatalogID, req.ProductRetailerID, req.BodyText, req.FooterText); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// SendProductList sends a multi-product list message (up to 30 products across up to 10 sections).
|
||||
// POST /api/send/product-list {"account_id","to","header_text","body_text","footer_text","catalog_id","sections":[...]}
|
||||
func (h *Handlers) SendProductList(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
To string `json:"to"`
|
||||
HeaderText string `json:"header_text"`
|
||||
BodyText string `json:"body_text"`
|
||||
FooterText string `json:"footer_text"`
|
||||
CatalogID string `json:"catalog_id"`
|
||||
Sections []businessapi.ProductListSection `json:"sections"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.CatalogID == "" || req.HeaderText == "" || req.BodyText == "" || len(req.Sections) == 0 {
|
||||
http.Error(w, "catalog_id, header_text, body_text, and sections are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
jid, err := types.ParseJID(utils.FormatPhoneToJID(req.To, h.config.Server.DefaultCountryCode))
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid phone number", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := baClient.SendProductList(r.Context(), jid, req.HeaderText, req.BodyText, req.FooterText, req.CatalogID, req.Sections); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]string{"status": "ok"})
|
||||
}
|
||||
Reference in New Issue
Block a user