whatapp-go-pvnet/backend/models/models.go

443 lines
14 KiB
Go
Raw Normal View History

2025-05-09 08:14:22 +00:00
package models
import (
"fmt"
"time"
"gorm.io/gorm"
)
type UserRole string
const (
ROLE_ADMIN UserRole = "ADMIN"
ROLE_CLIENT UserRole = "CLIENT"
)
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Password string `gorm:"not null"` // hashé avec bcrypt
Email string `gorm:"uniqueIndex;not null"`
SSOID string `gorm:"uniqueIndex;not null"`
Role UserRole `gorm:"type:enum('ADMIN','CLIENT');default:'CLIENT';not null"`
IsActive bool `gorm:"default:true"`
WhatsappToken string `gorm:"type:text"` // token Meta spécifique au client
WhatsappPhoneNumberID string `gorm:"type:varchar(50)"` // ID du numéro WhatsApp Business
MonthlyCredits uint `gorm:"default:100"`
CurrentMonthCredits uint `gorm:"default:100"`
LastRecharge *time.Time `gorm:"default:null"`
Consumption []Consumption
MonthlyConsumptions []MonthlyConsumption
}
type Consumption struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
UserID uint `gorm:"index;not null"`
MessageType string `gorm:"type:varchar(20);not null"` // ex: text, image, button
Description string `gorm:"type:text"`
CreditsUsed uint `gorm:"not null"`
}
type MonthlyConsumption struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
UserID uint `gorm:"index;not null"`
Month string `gorm:"type:char(7);index;not null"` // Format YYYY-MM
TotalUsed uint `gorm:"not null"` // Crédits utilisés ce mois
}
2025-05-09 09:23:54 +00:00
type Template struct {
Name string `json:"name"`
Language TemplateLanguage `json:"language"`
Components []TemplateComponent `json:"components"`
}
type TemplateLanguage struct {
Code string `json:"code"`
}
type TemplateComponent struct {
Type string `json:"type"`
Parameters []TemplateParameter `json:"parameters"`
}
type TemplateParameter struct {
Type string `json:"type"`
Text string `json:"text"`
}
2025-05-09 08:14:22 +00:00
type WhatsappMessage struct {
MessagingProduct string `json:"messaging_product"` // always "whatsapp"
To string `json:"to"` // recipient phone number
Type string `json:"type"` // message type (text, image, video, etc.)
Context *Context `json:"context,omitempty"` // for replies
RecipientType string `json:"recipient_type,omitempty"` // optional
Text *Text `json:"text,omitempty"`
Image *Media `json:"image,omitempty"`
Video *Media `json:"video,omitempty"`
Document *Media `json:"document,omitempty"`
Audio *Media `json:"audio,omitempty"`
Sticker *Media `json:"sticker,omitempty"`
Location *Location `json:"location,omitempty"`
Contacts []Contact `json:"contacts,omitempty"`
Interactive *Interactive `json:"interactive,omitempty"`
Reaction *Reaction `json:"reaction,omitempty"`
2025-05-09 09:23:54 +00:00
Template *Template `json:"template,omitempty"` // ✅ Ajoute ce champ
2025-05-09 08:14:22 +00:00
}
type Address struct {
Street string `json:"street,omitempty"`
City string `json:"city,omitempty"`
State string `json:"state,omitempty"`
Zip string `json:"zip,omitempty"`
Country string `json:"country,omitempty"`
CountryCode string `json:"country_code,omitempty"`
Type string `json:"type,omitempty"` // home, work
}
type Contact struct {
Addresses []Address `json:"addresses,omitempty"`
Name *Name `json:"name,omitempty"`
Emails []Email `json:"emails,omitempty"`
Phones []Phone `json:"phones,omitempty"`
}
type Name struct {
FormattedName string `json:"formatted_name"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
}
type Email struct {
Email string `json:"email"`
Type string `json:"type,omitempty"` // work, home
}
type Phone struct {
Phone string `json:"phone"`
Type string `json:"type,omitempty"`
}
type Media struct {
ID string `json:"id,omitempty"` // media ID from upload
Link string `json:"link,omitempty"` // direct URL
Caption string `json:"caption,omitempty"` // for image/video
Filename string `json:"filename,omitempty"` // for documents
MimeType string `json:"mime_type,omitempty"`
}
type Location struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Name string `json:"name,omitempty"`
Address string `json:"address,omitempty"`
Url string `json:"url,omitempty"`
IsLive bool `json:"is_live,omitempty"` // si demande de localisation en direct
}
type Text struct {
Body string `json:"body"`
PreviewURL bool `json:"preview_url,omitempty"`
}
type Context struct {
MessageID string `json:"message_id"`
}
type Reaction struct {
MessageID string `json:"message_id"`
Emoji string `json:"emoji"`
}
type StatusIndicator struct {
Status string `json:"status"` // "read" or "typing"
To string `json:"to"`
}
type Interactive struct {
Type string `json:"type"` // button | list | flow
Body *BodyContent `json:"body"`
Action *Action `json:"action"`
}
type BodyContent struct {
Text string `json:"text"`
}
type Action struct {
Button string `json:"button,omitempty"`
Buttons []ReplyButton `json:"buttons,omitempty"`
Sections []Section `json:"sections,omitempty"` // for list
CatalogID string `json:"catalog_id,omitempty"` // for flow
ProductID string `json:"product_retailer_id,omitempty"` // for flow
}
type ReplyButton struct {
Type string `json:"type"` // reply
Reply ButtonReply `json:"reply"`
}
type ButtonReply struct {
ID string `json:"id"`
Title string `json:"title"`
}
type Section struct {
Title string `json:"title,omitempty"`
Rows []ListItem `json:"rows"`
}
type ListItem struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
}
type StatusMessage struct {
MessagingProduct string `json:"messaging_product"`
To string `json:"to"`
Type string `json:"type"` // "typing_on", "typing_off", "read"
}
type Conversation struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
UserID uint `gorm:"index;not null"`
From string `gorm:"not null"`
To string `gorm:"not null"`
MessageID string `gorm:"uniqueIndex;not null"`
Type string `gorm:"not null"`
Content string `gorm:"type:text"`
2025-05-09 14:06:29 +00:00
Direction string `gorm:"not null"` // inbound / outbound
Status string `gorm:"type:varchar(20)"` // sent, delivered, read, failed
ParentID *uint `gorm:"index"` // lien vers le message parent (nullable)
2025-05-09 08:14:22 +00:00
}
2025-05-09 14:06:29 +00:00
2025-05-09 08:14:22 +00:00
func NewTextMessage(to string, body string) WhatsappMessage {
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "text",
Text: &Text{Body: body},
}
}
func NewImageMessage(to, mediaID, caption string) WhatsappMessage {
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "image",
Image: &Media{ID: mediaID, Caption: caption},
}
}
func NewVideoMessage(to, mediaID, caption string) WhatsappMessage {
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "video",
Video: &Media{ID: mediaID, Caption: caption},
}
}
func NewAudioMessage(to, mediaID string) WhatsappMessage {
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "audio",
Audio: &Media{ID: mediaID},
}
}
func NewDocumentMessage(to, mediaID, filename string) WhatsappMessage {
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "document",
Document: &Media{ID: mediaID, Filename: filename},
}
}
func NewStickerMessage(to, mediaID string) WhatsappMessage {
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "sticker",
Sticker: &Media{ID: mediaID},
}
}
func NewContactMessage(to string, contact Contact) WhatsappMessage {
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "contacts",
Contacts: []Contact{contact},
}
}
func NewLocationMessage(to string, lat, lng float64, name, address string) WhatsappMessage {
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "location",
Location: &Location{Latitude: lat, Longitude: lng, Name: name, Address: address},
}
}
func NewReactionMessage(to, msgID, emoji string) WhatsappMessage {
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "reaction",
Reaction: &Reaction{MessageID: msgID, Emoji: emoji},
}
}
func NewReplyMessage(to, msgID, body string) WhatsappMessage {
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "text",
Context: &Context{MessageID: msgID},
Text: &Text{Body: body},
}
}
func NewInteractiveButtons(to string, text string, buttons []ReplyButton) WhatsappMessage {
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "interactive",
Interactive: &Interactive{Type: "button", Body: &BodyContent{Text: text}, Action: &Action{Buttons: buttons}},
}
}
func NewInteractiveList(to string, text string, sections []Section) WhatsappMessage {
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "interactive",
Interactive: &Interactive{Type: "list", Body: &BodyContent{Text: text}, Action: &Action{Sections: sections}},
}
}
func NewFlowMessage(to, text, catalogID, productID string) WhatsappMessage {
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "interactive",
Interactive: &Interactive{Type: "flow", Body: &BodyContent{Text: text}, Action: &Action{CatalogID: catalogID, ProductID: productID}},
}
}
func NewTypingIndicator(to string) StatusMessage {
return StatusMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "typing_on",
}
}
func NewReadReceipt(to string) StatusMessage {
return StatusMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "read",
}
}
func BuildMessageFromPayload(payload map[string]interface{}) (interface{}, error) {
to := fmt.Sprintf("%v", payload["to"])
typeStr := fmt.Sprintf("%v", payload["type"])
switch typeStr {
case "text":
if text, ok := payload["text"].(map[string]interface{}); ok {
return NewTextMessage(to, fmt.Sprintf("%v", text["body"])), nil
}
case "image":
if image, ok := payload["image"].(map[string]interface{}); ok {
return NewImageMessage(to, fmt.Sprintf("%v", image["id"]), fmt.Sprintf("%v", image["caption"])), nil
}
case "video":
if video, ok := payload["video"].(map[string]interface{}); ok {
return NewVideoMessage(to, fmt.Sprintf("%v", video["id"]), fmt.Sprintf("%v", video["caption"])), nil
}
case "audio":
if audio, ok := payload["audio"].(map[string]interface{}); ok {
return NewAudioMessage(to, fmt.Sprintf("%v", audio["id"])), nil
}
case "document":
if doc, ok := payload["document"].(map[string]interface{}); ok {
return NewDocumentMessage(to, fmt.Sprintf("%v", doc["id"]), fmt.Sprintf("%v", doc["filename"])), nil
}
case "sticker":
if st, ok := payload["sticker"].(map[string]interface{}); ok {
return NewStickerMessage(to, fmt.Sprintf("%v", st["id"])), nil
}
case "location":
if loc, ok := payload["location"].(map[string]interface{}); ok {
lat := loc["latitude"].(float64)
lng := loc["longitude"].(float64)
return NewLocationMessage(to, lat, lng, fmt.Sprintf("%v", loc["name"]), fmt.Sprintf("%v", loc["address"])), nil
}
case "reaction":
if react, ok := payload["reaction"].(map[string]interface{}); ok {
return NewReactionMessage(to, fmt.Sprintf("%v", react["message_id"]), fmt.Sprintf("%v", react["emoji"])), nil
}
case "typing_on", "typing_off", "read":
return StatusMessage{MessagingProduct: "whatsapp", To: to, Type: typeStr}, nil
default:
return nil, fmt.Errorf("unsupported message type: %s", typeStr)
}
return nil, fmt.Errorf("failed to build message")
}
2025-05-09 09:23:54 +00:00
func NewTemplateMessage(to, name, lang string, params []string) WhatsappMessage {
paramList := make([]TemplateParameter, len(params))
for i, p := range params {
paramList[i] = TemplateParameter{
Type: "text",
Text: p,
}
}
return WhatsappMessage{
MessagingProduct: "whatsapp",
To: to,
Type: "template",
Template: &Template{
Name: name,
Language: TemplateLanguage{Code: lang},
Components: []TemplateComponent{
{
Type: "body",
Parameters: paramList,
},
},
},
}
}
// Enregistre une erreur de statut dans la table Conversation
func SaveMessageStatusError(db *gorm.DB, userID uint, recipient, msgID, status, errorMsg string) error {
conv := Conversation{
UserID: userID,
From: "system",
To: recipient,
MessageID: msgID,
Type: "status",
Content: errorMsg,
Direction: "outbound",
Status: status,
CreatedAt: time.Now(),
}
return db.Create(&conv).Error
}