MIF_E31222379_BE/internal/whatsapp/whatsapp_handler.go

312 lines
7.8 KiB
Go

package whatsapp
import (
"regexp"
"rijig/config"
"rijig/utils"
"strings"
"time"
"github.com/gofiber/fiber/v2"
)
type QRResponse struct {
QRCode string `json:"qr_code,omitempty"`
Status string `json:"status"`
Message string `json:"message"`
Timestamp int64 `json:"timestamp"`
}
type StatusResponse struct {
IsConnected bool `json:"is_connected"`
IsLoggedIn bool `json:"is_logged_in"`
Status string `json:"status"`
Message string `json:"message"`
Timestamp int64 `json:"timestamp"`
}
type SendMessageRequest struct {
PhoneNumber string `json:"phone_number" validate:"required"`
Message string `json:"message" validate:"required"`
}
type SendMessageResponse struct {
PhoneNumber string `json:"phone_number"`
Timestamp int64 `json:"timestamp"`
}
func GenerateQRHandler(c *fiber.Ctx) error {
wa := config.GetWhatsAppService()
if wa == nil {
return utils.InternalServerError(c, "WhatsApp service not initialized")
}
if wa.IsLoggedIn() {
data := QRResponse{
Status: "logged_in",
Message: "WhatsApp is already connected and logged in",
Timestamp: time.Now().Unix(),
}
return utils.SuccessWithData(c, "Already logged in", data)
}
qrDataURI, err := wa.GenerateQR()
if err != nil {
return utils.InternalServerError(c, "Failed to generate QR code: "+err.Error())
}
switch qrDataURI {
case "success":
data := QRResponse{
Status: "login_success",
Message: "WhatsApp login successful",
Timestamp: time.Now().Unix(),
}
return utils.SuccessWithData(c, "Successfully logged in", data)
case "already_connected":
data := QRResponse{
Status: "already_connected",
Message: "WhatsApp is already connected",
Timestamp: time.Now().Unix(),
}
return utils.SuccessWithData(c, "Already connected", data)
default:
data := QRResponse{
QRCode: qrDataURI,
Status: "qr_generated",
Message: "Scan QR code with WhatsApp to login",
Timestamp: time.Now().Unix(),
}
return utils.SuccessWithData(c, "QR code generated successfully", data)
}
}
func CheckLoginStatusHandler(c *fiber.Ctx) error {
wa := config.GetWhatsAppService()
if wa == nil {
return utils.InternalServerError(c, "WhatsApp service not initialized")
}
// if !wa.IsLoggedIn() {
// return utils.Unauthorized(c, "WhatsApp not logged in")
// }
isConnected := wa.IsConnected()
isLoggedIn := wa.IsLoggedIn()
var status string
var message string
if isLoggedIn && isConnected {
status = "connected_and_logged_in"
message = "WhatsApp is connected and logged in"
} else if isLoggedIn {
status = "logged_in_but_disconnected"
message = "WhatsApp is logged in but disconnected"
} else if isConnected {
status = "connected_but_not_logged_in"
message = "WhatsApp is connected but not logged in"
} else {
status = "disconnected"
message = "WhatsApp is disconnected"
}
data := StatusResponse{
IsConnected: isConnected,
IsLoggedIn: isLoggedIn,
Status: status,
Message: message,
Timestamp: time.Now().Unix(),
}
return utils.SuccessWithData(c, "Status retrieved successfully", data)
}
func WhatsAppLogoutHandler(c *fiber.Ctx) error {
wa := config.GetWhatsAppService()
if wa == nil {
return utils.InternalServerError(c, "WhatsApp service not initialized")
}
if !wa.IsLoggedIn() {
return utils.BadRequest(c, "No active session to logout")
}
err := wa.Logout()
if err != nil {
return utils.InternalServerError(c, "Failed to logout: "+err.Error())
}
data := map[string]interface{}{
"timestamp": time.Now().Unix(),
}
return utils.SuccessWithData(c, "Successfully logged out and session deleted", data)
}
func SendMessageHandler(c *fiber.Ctx) error {
wa := config.GetWhatsAppService()
if wa == nil {
return utils.InternalServerError(c, "WhatsApp service not initialized")
}
if !wa.IsLoggedIn() {
return utils.Unauthorized(c, "WhatsApp not logged in")
}
req := GetValidatedSendMessageRequest(c)
if req == nil {
return utils.BadRequest(c, "Invalid request data")
}
err := wa.SendMessage(req.PhoneNumber, req.Message)
if err != nil {
return utils.InternalServerError(c, "Failed to send message: "+err.Error())
}
data := SendMessageResponse{
PhoneNumber: req.PhoneNumber,
Timestamp: time.Now().Unix(),
}
return utils.SuccessWithData(c, "Message sent successfully", data)
}
func GetDeviceInfoHandler(c *fiber.Ctx) error {
wa := config.GetWhatsAppService()
if wa == nil {
return utils.InternalServerError(c, "WhatsApp service not initialized")
}
if !wa.IsLoggedIn() {
return utils.Unauthorized(c, "WhatsApp not logged in")
}
var deviceInfo map[string]interface{}
if wa.Client != nil && wa.Client.Store.ID != nil {
deviceInfo = map[string]interface{}{
"device_id": wa.Client.Store.ID.User,
"device_name": wa.Client.Store.ID.Device,
"is_logged_in": wa.IsLoggedIn(),
"is_connected": wa.IsConnected(),
"timestamp": time.Now().Unix(),
}
} else {
deviceInfo = map[string]interface{}{
"device_id": nil,
"device_name": nil,
"is_logged_in": false,
"is_connected": false,
"timestamp": time.Now().Unix(),
}
}
return utils.SuccessWithData(c, "Device info retrieved successfully", deviceInfo)
}
func HealthCheckHandler(c *fiber.Ctx) error {
wa := config.GetWhatsAppService()
if wa == nil {
return utils.InternalServerError(c, "WhatsApp service not initialized")
}
healthData := map[string]interface{}{
"service_status": "running",
"container_status": wa.Container != nil,
"client_status": wa.Client != nil,
"is_connected": wa.IsConnected(),
"is_logged_in": wa.IsLoggedIn(),
"timestamp": time.Now().Unix(),
}
message := "WhatsApp service is healthy"
if !wa.IsConnected() || !wa.IsLoggedIn() {
message = "WhatsApp service is running but not fully operational"
}
return utils.SuccessWithData(c, message, healthData)
}
func validatePhoneNumber(phoneNumber string) error {
cleaned := strings.ReplaceAll(phoneNumber, " ", "")
cleaned = strings.ReplaceAll(cleaned, "-", "")
cleaned = strings.ReplaceAll(cleaned, "+", "")
if !regexp.MustCompile(`^\d+$`).MatchString(cleaned) {
return fiber.NewError(fiber.StatusBadRequest, "Phone number must contain only digits")
}
if len(cleaned) < 10 {
return fiber.NewError(fiber.StatusBadRequest, "Phone number too short. Include country code (e.g., 628123456789)")
}
if len(cleaned) > 15 {
return fiber.NewError(fiber.StatusBadRequest, "Phone number too long")
}
return nil
}
func validateMessage(message string) error {
if strings.TrimSpace(message) == "" {
return fiber.NewError(fiber.StatusBadRequest, "Message cannot be empty")
}
if len(message) > 4096 {
return fiber.NewError(fiber.StatusBadRequest, "Message too long. Maximum 4096 characters allowed")
}
return nil
}
func ValidateSendMessageRequest(c *fiber.Ctx) error {
var req SendMessageRequest
if err := c.BodyParser(&req); err != nil {
return utils.BadRequest(c, "Invalid JSON format: "+err.Error())
}
if err := validatePhoneNumber(req.PhoneNumber); err != nil {
return utils.BadRequest(c, err.Error())
}
if err := validateMessage(req.Message); err != nil {
return utils.BadRequest(c, err.Error())
}
req.PhoneNumber = strings.ReplaceAll(req.PhoneNumber, " ", "")
req.PhoneNumber = strings.ReplaceAll(req.PhoneNumber, "-", "")
req.PhoneNumber = strings.ReplaceAll(req.PhoneNumber, "+", "")
c.Locals("validatedRequest", req)
return c.Next()
}
func GetValidatedSendMessageRequest(c *fiber.Ctx) *SendMessageRequest {
if req, ok := c.Locals("validatedRequest").(SendMessageRequest); ok {
return &req
}
return nil
}
func ValidateContentType() fiber.Handler {
return func(c *fiber.Ctx) error {
if c.Method() == "GET" {
return c.Next()
}
contentType := c.Get("Content-Type")
if !strings.Contains(contentType, "application/json") {
return utils.BadRequest(c, "Content-Type must be application/json")
}
return c.Next()
}
}