Spaces:
Runtime error
Runtime error
RyZ commited on
Commit ·
6e3fa96
1
Parent(s): 44c4b7e
feat: adding qrcode to the json
Browse files- controllers/connection_controller.go +6 -7
- go.mod +0 -3
- middleware/auth_middleware.go +0 -1
- models/dto/connection_dto.go +2 -1
- repositories/connection_repositories.go +2 -12
- services/auth_service.go +0 -7
- services/connection_service.go +25 -26
controllers/connection_controller.go
CHANGED
|
@@ -27,7 +27,6 @@ func NewConnectionController(connectionService services.ConnectionService) Conne
|
|
| 27 |
}
|
| 28 |
|
| 29 |
func (cc *connectionController) Connect(ctx *gin.Context) {
|
| 30 |
-
// 1. Extract UserID from Context (set by AuthMiddleware)
|
| 31 |
userID, exists := ctx.Get("user_id")
|
| 32 |
if !exists {
|
| 33 |
utils.SendResponse[any, any](ctx, dto.AuthResponse{}, nil, http_error.UNAUTHORIZED)
|
|
@@ -35,20 +34,20 @@ func (cc *connectionController) Connect(ctx *gin.Context) {
|
|
| 35 |
}
|
| 36 |
accountID := userID.(uuid.UUID)
|
| 37 |
|
| 38 |
-
// 2. Bind JSON (Optional)
|
| 39 |
var req dto.ConnectRequest
|
| 40 |
_ = ctx.ShouldBindJSON(&req)
|
| 41 |
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
|
|
|
| 46 |
|
| 47 |
-
// 4. Return Standardized Response
|
| 48 |
response := dto.ConnectResponse{
|
| 49 |
Message: entity.CONNECTION_INIT_SUCCESS,
|
| 50 |
AccountID: accountID,
|
| 51 |
Details: entity.QR_SCAN_INSTRUCTION,
|
|
|
|
| 52 |
}
|
| 53 |
|
| 54 |
utils.SendResponse[dto.ConnectResponse, any](ctx, dto.AuthResponse{}, response, nil)
|
|
|
|
| 27 |
}
|
| 28 |
|
| 29 |
func (cc *connectionController) Connect(ctx *gin.Context) {
|
|
|
|
| 30 |
userID, exists := ctx.Get("user_id")
|
| 31 |
if !exists {
|
| 32 |
utils.SendResponse[any, any](ctx, dto.AuthResponse{}, nil, http_error.UNAUTHORIZED)
|
|
|
|
| 34 |
}
|
| 35 |
accountID := userID.(uuid.UUID)
|
| 36 |
|
|
|
|
| 37 |
var req dto.ConnectRequest
|
| 38 |
_ = ctx.ShouldBindJSON(&req)
|
| 39 |
|
| 40 |
+
qrCode, err := cc.connectionService.Connect(context.Background(), accountID)
|
| 41 |
+
if err != nil {
|
| 42 |
+
utils.SendResponse[any, any](ctx, dto.AuthResponse{}, nil, err)
|
| 43 |
+
return
|
| 44 |
+
}
|
| 45 |
|
|
|
|
| 46 |
response := dto.ConnectResponse{
|
| 47 |
Message: entity.CONNECTION_INIT_SUCCESS,
|
| 48 |
AccountID: accountID,
|
| 49 |
Details: entity.QR_SCAN_INSTRUCTION,
|
| 50 |
+
QRCode: qrCode,
|
| 51 |
}
|
| 52 |
|
| 53 |
utils.SendResponse[dto.ConnectResponse, any](ctx, dto.AuthResponse{}, response, nil)
|
go.mod
CHANGED
|
@@ -8,7 +8,6 @@ require (
|
|
| 8 |
github.com/golang-jwt/jwt/v5 v5.3.0
|
| 9 |
github.com/google/uuid v1.6.0
|
| 10 |
github.com/joho/godotenv v1.5.1
|
| 11 |
-
github.com/mdp/qrterminal/v3 v3.2.1
|
| 12 |
go.mau.fi/whatsmeow v0.0.0-20251217143725-11cf47c62d32
|
| 13 |
golang.org/x/crypto v0.46.0
|
| 14 |
gorm.io/driver/postgres v1.6.0
|
|
@@ -59,8 +58,6 @@ require (
|
|
| 59 |
golang.org/x/net v0.48.0 // indirect
|
| 60 |
golang.org/x/sync v0.19.0 // indirect
|
| 61 |
golang.org/x/sys v0.39.0 // indirect
|
| 62 |
-
golang.org/x/term v0.38.0 // indirect
|
| 63 |
golang.org/x/text v0.32.0 // indirect
|
| 64 |
google.golang.org/protobuf v1.36.11 // indirect
|
| 65 |
-
rsc.io/qr v0.2.0 // indirect
|
| 66 |
)
|
|
|
|
| 8 |
github.com/golang-jwt/jwt/v5 v5.3.0
|
| 9 |
github.com/google/uuid v1.6.0
|
| 10 |
github.com/joho/godotenv v1.5.1
|
|
|
|
| 11 |
go.mau.fi/whatsmeow v0.0.0-20251217143725-11cf47c62d32
|
| 12 |
golang.org/x/crypto v0.46.0
|
| 13 |
gorm.io/driver/postgres v1.6.0
|
|
|
|
| 58 |
golang.org/x/net v0.48.0 // indirect
|
| 59 |
golang.org/x/sync v0.19.0 // indirect
|
| 60 |
golang.org/x/sys v0.39.0 // indirect
|
|
|
|
| 61 |
golang.org/x/text v0.32.0 // indirect
|
| 62 |
google.golang.org/protobuf v1.36.11 // indirect
|
|
|
|
| 63 |
)
|
middleware/auth_middleware.go
CHANGED
|
@@ -49,7 +49,6 @@ func (m *authMiddleware) RequireAuth() gin.HandlerFunc {
|
|
| 49 |
return
|
| 50 |
}
|
| 51 |
|
| 52 |
-
// Set user info in context
|
| 53 |
ctx.Set("user_id", claims.UserID)
|
| 54 |
ctx.Set("username", claims.Username)
|
| 55 |
|
|
|
|
| 49 |
return
|
| 50 |
}
|
| 51 |
|
|
|
|
| 52 |
ctx.Set("user_id", claims.UserID)
|
| 53 |
ctx.Set("username", claims.Username)
|
| 54 |
|
models/dto/connection_dto.go
CHANGED
|
@@ -3,13 +3,14 @@ package dto
|
|
| 3 |
import "github.com/google/uuid"
|
| 4 |
|
| 5 |
type ConnectRequest struct {
|
| 6 |
-
|
| 7 |
}
|
| 8 |
|
| 9 |
type ConnectResponse struct {
|
| 10 |
Message string `json:"message"`
|
| 11 |
AccountID uuid.UUID `json:"account_id"`
|
| 12 |
Details string `json:"details"`
|
|
|
|
| 13 |
}
|
| 14 |
|
| 15 |
type ConnectionStatusResponse struct {
|
|
|
|
| 3 |
import "github.com/google/uuid"
|
| 4 |
|
| 5 |
type ConnectRequest struct {
|
| 6 |
+
|
| 7 |
}
|
| 8 |
|
| 9 |
type ConnectResponse struct {
|
| 10 |
Message string `json:"message"`
|
| 11 |
AccountID uuid.UUID `json:"account_id"`
|
| 12 |
Details string `json:"details"`
|
| 13 |
+
QRCode string `json:"qr_code,omitempty"`
|
| 14 |
}
|
| 15 |
|
| 16 |
type ConnectionStatusResponse struct {
|
repositories/connection_repositories.go
CHANGED
|
@@ -27,18 +27,15 @@ type connectionRepository struct {
|
|
| 27 |
container *sqlstore.Container
|
| 28 |
}
|
| 29 |
|
| 30 |
-
// NewConnectionRepository initializes the SQLStore ONCE during app startup
|
| 31 |
func NewConnectionRepository(db *gorm.DB) (ConnectionRepository, error) {
|
| 32 |
sqlDB, err := db.DB()
|
| 33 |
if err != nil {
|
| 34 |
return nil, http_error.ERR_DB_CONNECTION_FAILED
|
| 35 |
}
|
| 36 |
|
| 37 |
-
|
| 38 |
dbLog := waLog.Stdout("Database", "INFO", true)
|
| 39 |
|
| 40 |
-
// We use "postgres" because your connection is Postgres.
|
| 41 |
-
// whatsmeow_device, whatsmeow_session, etc.
|
| 42 |
container := sqlstore.NewWithDB(sqlDB, entity.POSTGRES_DIALECT, dbLog)
|
| 43 |
if err := container.Upgrade(context.Background()); err != nil {
|
| 44 |
return nil, http_error.ERR_SQLSTORE_FAILED
|
|
@@ -53,7 +50,6 @@ func NewConnectionRepository(db *gorm.DB) (ConnectionRepository, error) {
|
|
| 53 |
func (r *connectionRepository) InitializeClient(ctx context.Context, accountID uuid.UUID) (*whatsmeow.Client, <-chan whatsmeow.QRChannelItem, error) {
|
| 54 |
var acc entity.WhatsAppAccount
|
| 55 |
|
| 56 |
-
// 1. Find or Create your GORM entity
|
| 57 |
acc = entity.WhatsAppAccount{
|
| 58 |
ID: accountID,
|
| 59 |
AccountName: "Default Device",
|
|
@@ -65,7 +61,6 @@ func (r *connectionRepository) InitializeClient(ctx context.Context, accountID u
|
|
| 65 |
var deviceStore *store.Device
|
| 66 |
var err error
|
| 67 |
|
| 68 |
-
// 2. Determine if we use an existing session (JID) or start a new one
|
| 69 |
if acc.JID != "" {
|
| 70 |
jid, parseErr := types.ParseJID(acc.JID)
|
| 71 |
if parseErr != nil {
|
|
@@ -73,7 +68,6 @@ func (r *connectionRepository) InitializeClient(ctx context.Context, accountID u
|
|
| 73 |
}
|
| 74 |
deviceStore, err = r.container.GetDevice(ctx, jid)
|
| 75 |
} else {
|
| 76 |
-
// No JID means this is a fresh account/new login
|
| 77 |
deviceStore = r.container.NewDevice()
|
| 78 |
}
|
| 79 |
|
|
@@ -81,11 +75,9 @@ func (r *connectionRepository) InitializeClient(ctx context.Context, accountID u
|
|
| 81 |
return nil, nil, http_error.ERR_DEVICE_STORE_FAILED
|
| 82 |
}
|
| 83 |
|
| 84 |
-
// 3. Create the whatsmeow client
|
| 85 |
clientLog := waLog.Stdout(fmt.Sprintf("Client-%s", accountID), "INFO", true)
|
| 86 |
client := whatsmeow.NewClient(deviceStore, clientLog)
|
| 87 |
|
| 88 |
-
// 4. Generate QR Channel if not logged in
|
| 89 |
var qrChan <-chan whatsmeow.QRChannelItem
|
| 90 |
if client.Store.ID == nil {
|
| 91 |
qrChan, _ = client.GetQRChannel(ctx)
|
|
@@ -95,7 +87,6 @@ func (r *connectionRepository) InitializeClient(ctx context.Context, accountID u
|
|
| 95 |
}
|
| 96 |
|
| 97 |
func (r *connectionRepository) UpdateAccountStatus(accountID uuid.UUID, jid string, isActive bool) error {
|
| 98 |
-
// Updates both the JID (bridge) and the active status in your GORM entity
|
| 99 |
return r.db.Model(&entity.WhatsAppAccount{ID: accountID}).Updates(map[string]interface{}{
|
| 100 |
"jid": jid,
|
| 101 |
"is_active": isActive,
|
|
@@ -112,10 +103,9 @@ func (r *connectionRepository) DeleteDevice(accountID uuid.UUID) error {
|
|
| 112 |
jid, _ := types.ParseJID(acc.JID)
|
| 113 |
device, err := r.container.GetDevice(context.Background(), jid)
|
| 114 |
if err == nil && device != nil {
|
| 115 |
-
_ = device.Delete(context.Background())
|
| 116 |
}
|
| 117 |
}
|
| 118 |
|
| 119 |
-
// Also clear from DB
|
| 120 |
return r.UpdateAccountStatus(accountID, "", false)
|
| 121 |
}
|
|
|
|
| 27 |
container *sqlstore.Container
|
| 28 |
}
|
| 29 |
|
|
|
|
| 30 |
func NewConnectionRepository(db *gorm.DB) (ConnectionRepository, error) {
|
| 31 |
sqlDB, err := db.DB()
|
| 32 |
if err != nil {
|
| 33 |
return nil, http_error.ERR_DB_CONNECTION_FAILED
|
| 34 |
}
|
| 35 |
|
| 36 |
+
|
| 37 |
dbLog := waLog.Stdout("Database", "INFO", true)
|
| 38 |
|
|
|
|
|
|
|
| 39 |
container := sqlstore.NewWithDB(sqlDB, entity.POSTGRES_DIALECT, dbLog)
|
| 40 |
if err := container.Upgrade(context.Background()); err != nil {
|
| 41 |
return nil, http_error.ERR_SQLSTORE_FAILED
|
|
|
|
| 50 |
func (r *connectionRepository) InitializeClient(ctx context.Context, accountID uuid.UUID) (*whatsmeow.Client, <-chan whatsmeow.QRChannelItem, error) {
|
| 51 |
var acc entity.WhatsAppAccount
|
| 52 |
|
|
|
|
| 53 |
acc = entity.WhatsAppAccount{
|
| 54 |
ID: accountID,
|
| 55 |
AccountName: "Default Device",
|
|
|
|
| 61 |
var deviceStore *store.Device
|
| 62 |
var err error
|
| 63 |
|
|
|
|
| 64 |
if acc.JID != "" {
|
| 65 |
jid, parseErr := types.ParseJID(acc.JID)
|
| 66 |
if parseErr != nil {
|
|
|
|
| 68 |
}
|
| 69 |
deviceStore, err = r.container.GetDevice(ctx, jid)
|
| 70 |
} else {
|
|
|
|
| 71 |
deviceStore = r.container.NewDevice()
|
| 72 |
}
|
| 73 |
|
|
|
|
| 75 |
return nil, nil, http_error.ERR_DEVICE_STORE_FAILED
|
| 76 |
}
|
| 77 |
|
|
|
|
| 78 |
clientLog := waLog.Stdout(fmt.Sprintf("Client-%s", accountID), "INFO", true)
|
| 79 |
client := whatsmeow.NewClient(deviceStore, clientLog)
|
| 80 |
|
|
|
|
| 81 |
var qrChan <-chan whatsmeow.QRChannelItem
|
| 82 |
if client.Store.ID == nil {
|
| 83 |
qrChan, _ = client.GetQRChannel(ctx)
|
|
|
|
| 87 |
}
|
| 88 |
|
| 89 |
func (r *connectionRepository) UpdateAccountStatus(accountID uuid.UUID, jid string, isActive bool) error {
|
|
|
|
| 90 |
return r.db.Model(&entity.WhatsAppAccount{ID: accountID}).Updates(map[string]interface{}{
|
| 91 |
"jid": jid,
|
| 92 |
"is_active": isActive,
|
|
|
|
| 103 |
jid, _ := types.ParseJID(acc.JID)
|
| 104 |
device, err := r.container.GetDevice(context.Background(), jid)
|
| 105 |
if err == nil && device != nil {
|
| 106 |
+
_ = device.Delete(context.Background())
|
| 107 |
}
|
| 108 |
}
|
| 109 |
|
|
|
|
| 110 |
return r.UpdateAccountStatus(accountID, "", false)
|
| 111 |
}
|
services/auth_service.go
CHANGED
|
@@ -27,18 +27,15 @@ func NewAuthService(authRepo repositories.AuthRepository, jwtConfig config.JWTCo
|
|
| 27 |
}
|
| 28 |
|
| 29 |
func (s *authService) Register(req dto.RegisterRequest) (*dto.AuthResponse, error) {
|
| 30 |
-
// 1. Check if user exists
|
| 31 |
if _, err := s.authRepo.FindUserByUsername(req.Username); err == nil {
|
| 32 |
return nil, http_error.ERR_USER_ALREADY_EXISTS
|
| 33 |
}
|
| 34 |
|
| 35 |
-
// 2. Hash Password
|
| 36 |
hashedPassword, err := utils.HashPassword(req.Password)
|
| 37 |
if err != nil {
|
| 38 |
return nil, err
|
| 39 |
}
|
| 40 |
|
| 41 |
-
// 3. Create User
|
| 42 |
user := &entity.User{
|
| 43 |
Username: req.Username,
|
| 44 |
Password: hashedPassword,
|
|
@@ -48,7 +45,6 @@ func (s *authService) Register(req dto.RegisterRequest) (*dto.AuthResponse, erro
|
|
| 48 |
return nil, err
|
| 49 |
}
|
| 50 |
|
| 51 |
-
// 4. Generate Token
|
| 52 |
token, err := utils.GenerateToken(user.ID, user.Username, s.jwtConfig)
|
| 53 |
if err != nil {
|
| 54 |
return nil, http_error.ERR_TOKEN_GENERATION_FAILED
|
|
@@ -62,18 +58,15 @@ func (s *authService) Register(req dto.RegisterRequest) (*dto.AuthResponse, erro
|
|
| 62 |
}
|
| 63 |
|
| 64 |
func (s *authService) Login(req dto.LoginRequest) (*dto.AuthResponse, error) {
|
| 65 |
-
// 1. Find User
|
| 66 |
user, err := s.authRepo.FindUserByUsername(req.Username)
|
| 67 |
if err != nil {
|
| 68 |
return nil, http_error.ERR_USER_NOT_FOUND
|
| 69 |
}
|
| 70 |
|
| 71 |
-
// 2. Check Password
|
| 72 |
if !utils.CheckPasswordHash(req.Password, user.Password) {
|
| 73 |
return nil, http_error.ERR_WRONG_PASSWORD
|
| 74 |
}
|
| 75 |
|
| 76 |
-
// 3. Generate Token
|
| 77 |
token, err := utils.GenerateToken(user.ID, user.Username, s.jwtConfig)
|
| 78 |
if err != nil {
|
| 79 |
return nil, http_error.ERR_TOKEN_GENERATION_FAILED
|
|
|
|
| 27 |
}
|
| 28 |
|
| 29 |
func (s *authService) Register(req dto.RegisterRequest) (*dto.AuthResponse, error) {
|
|
|
|
| 30 |
if _, err := s.authRepo.FindUserByUsername(req.Username); err == nil {
|
| 31 |
return nil, http_error.ERR_USER_ALREADY_EXISTS
|
| 32 |
}
|
| 33 |
|
|
|
|
| 34 |
hashedPassword, err := utils.HashPassword(req.Password)
|
| 35 |
if err != nil {
|
| 36 |
return nil, err
|
| 37 |
}
|
| 38 |
|
|
|
|
| 39 |
user := &entity.User{
|
| 40 |
Username: req.Username,
|
| 41 |
Password: hashedPassword,
|
|
|
|
| 45 |
return nil, err
|
| 46 |
}
|
| 47 |
|
|
|
|
| 48 |
token, err := utils.GenerateToken(user.ID, user.Username, s.jwtConfig)
|
| 49 |
if err != nil {
|
| 50 |
return nil, http_error.ERR_TOKEN_GENERATION_FAILED
|
|
|
|
| 58 |
}
|
| 59 |
|
| 60 |
func (s *authService) Login(req dto.LoginRequest) (*dto.AuthResponse, error) {
|
|
|
|
| 61 |
user, err := s.authRepo.FindUserByUsername(req.Username)
|
| 62 |
if err != nil {
|
| 63 |
return nil, http_error.ERR_USER_NOT_FOUND
|
| 64 |
}
|
| 65 |
|
|
|
|
| 66 |
if !utils.CheckPasswordHash(req.Password, user.Password) {
|
| 67 |
return nil, http_error.ERR_WRONG_PASSWORD
|
| 68 |
}
|
| 69 |
|
|
|
|
| 70 |
token, err := utils.GenerateToken(user.ID, user.Username, s.jwtConfig)
|
| 71 |
if err != nil {
|
| 72 |
return nil, http_error.ERR_TOKEN_GENERATION_FAILED
|
services/connection_service.go
CHANGED
|
@@ -3,19 +3,18 @@ package services
|
|
| 3 |
import (
|
| 4 |
"context"
|
| 5 |
"fmt"
|
| 6 |
-
"os"
|
| 7 |
"sync"
|
|
|
|
| 8 |
http_error "whatsapp-backend/models/error"
|
| 9 |
"whatsapp-backend/repositories"
|
| 10 |
|
| 11 |
"github.com/google/uuid"
|
| 12 |
-
"github.com/mdp/qrterminal/v3"
|
| 13 |
"go.mau.fi/whatsmeow"
|
| 14 |
"go.mau.fi/whatsmeow/types/events"
|
| 15 |
)
|
| 16 |
|
| 17 |
type ConnectionService interface {
|
| 18 |
-
Connect(ctx context.Context, accountID uuid.UUID) error
|
| 19 |
GetActiveClient(accountID uuid.UUID) (*whatsmeow.Client, error)
|
| 20 |
}
|
| 21 |
|
|
@@ -30,35 +29,33 @@ func NewConnectionService(connectionRepo repositories.ConnectionRepository) Conn
|
|
| 30 |
}
|
| 31 |
}
|
| 32 |
|
| 33 |
-
func (s *connectionService) Connect(ctx context.Context, accountID uuid.UUID) error {
|
| 34 |
if existingClient, ok := s.activeClients.Load(accountID); ok {
|
| 35 |
client := existingClient.(*whatsmeow.Client)
|
| 36 |
if client.IsConnected() {
|
| 37 |
-
return
|
| 38 |
}
|
| 39 |
}
|
| 40 |
|
| 41 |
-
// 2. Fetch or Init Client from Repository
|
| 42 |
-
// We assume your Repo now has a method that takes accountID and returns (client, qrChan, err)
|
| 43 |
-
// by looking up the entity in the DB first.
|
| 44 |
client, qrChan, err := s.connectionRepo.InitializeClient(ctx, accountID)
|
| 45 |
if err != nil {
|
| 46 |
-
return http_error.ERR_CLIENT_INIT_FAILED
|
| 47 |
}
|
| 48 |
|
| 49 |
-
// 3. Register Event Handlers
|
| 50 |
-
// This is the "hook" that updates your database when the status changes
|
| 51 |
client.AddEventHandler(func(evt interface{}) {
|
| 52 |
s.handleWhatsAppEvent(accountID, client, evt)
|
| 53 |
})
|
| 54 |
|
| 55 |
-
|
|
|
|
| 56 |
if qrChan != nil {
|
| 57 |
go func() {
|
| 58 |
for evt := range qrChan {
|
| 59 |
if evt.Event == "code" {
|
| 60 |
-
|
| 61 |
-
|
|
|
|
|
|
|
| 62 |
} else {
|
| 63 |
fmt.Printf("[ACCOUNT %s] Login Event: %s\n", accountID, evt.Event)
|
| 64 |
}
|
|
@@ -66,16 +63,23 @@ func (s *connectionService) Connect(ctx context.Context, accountID uuid.UUID) er
|
|
| 66 |
}()
|
| 67 |
}
|
| 68 |
|
| 69 |
-
// 5. Connect to WhatsApp Socket
|
| 70 |
if err := client.Connect(); err != nil {
|
| 71 |
-
_ = s.connectionRepo.DeleteDevice(accountID)
|
| 72 |
-
return http_error.ERR_CLIENT_CONNECT_FAILED
|
| 73 |
}
|
| 74 |
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
}
|
| 80 |
|
| 81 |
func (s *connectionService) GetActiveClient(accountID uuid.UUID) (*whatsmeow.Client, error) {
|
|
@@ -86,23 +90,18 @@ func (s *connectionService) GetActiveClient(accountID uuid.UUID) (*whatsmeow.Cli
|
|
| 86 |
return val.(*whatsmeow.Client), nil
|
| 87 |
}
|
| 88 |
|
| 89 |
-
// handleWhatsAppEvent coordinates between the live socket and your database
|
| 90 |
func (s *connectionService) handleWhatsAppEvent(accountID uuid.UUID, client *whatsmeow.Client, evt interface{}) {
|
| 91 |
switch evt.(type) {
|
| 92 |
case *events.Connected:
|
| 93 |
-
// When the socket confirms connection, update the JID in your DB entity
|
| 94 |
if client.Store.ID != nil {
|
| 95 |
_ = s.connectionRepo.UpdateAccountStatus(accountID, client.Store.ID.String(), true)
|
| 96 |
}
|
| 97 |
|
| 98 |
case *events.LoggedOut:
|
| 99 |
-
// Cleanup when the user unlinks the device from their phone
|
| 100 |
s.activeClients.Delete(accountID)
|
| 101 |
_ = s.connectionRepo.UpdateAccountStatus(accountID, "", false)
|
| 102 |
|
| 103 |
case *events.Disconnected:
|
| 104 |
-
// Temporary network loss - whatsmeow handles auto-reconnect,
|
| 105 |
-
// but we might want to log this.
|
| 106 |
fmt.Printf("Account %s disconnected from WhatsApp\n", accountID)
|
| 107 |
}
|
| 108 |
}
|
|
|
|
| 3 |
import (
|
| 4 |
"context"
|
| 5 |
"fmt"
|
|
|
|
| 6 |
"sync"
|
| 7 |
+
"time"
|
| 8 |
http_error "whatsapp-backend/models/error"
|
| 9 |
"whatsapp-backend/repositories"
|
| 10 |
|
| 11 |
"github.com/google/uuid"
|
|
|
|
| 12 |
"go.mau.fi/whatsmeow"
|
| 13 |
"go.mau.fi/whatsmeow/types/events"
|
| 14 |
)
|
| 15 |
|
| 16 |
type ConnectionService interface {
|
| 17 |
+
Connect(ctx context.Context, accountID uuid.UUID) (string, error)
|
| 18 |
GetActiveClient(accountID uuid.UUID) (*whatsmeow.Client, error)
|
| 19 |
}
|
| 20 |
|
|
|
|
| 29 |
}
|
| 30 |
}
|
| 31 |
|
| 32 |
+
func (s *connectionService) Connect(ctx context.Context, accountID uuid.UUID) (string, error) {
|
| 33 |
if existingClient, ok := s.activeClients.Load(accountID); ok {
|
| 34 |
client := existingClient.(*whatsmeow.Client)
|
| 35 |
if client.IsConnected() {
|
| 36 |
+
return "", nil
|
| 37 |
}
|
| 38 |
}
|
| 39 |
|
|
|
|
|
|
|
|
|
|
| 40 |
client, qrChan, err := s.connectionRepo.InitializeClient(ctx, accountID)
|
| 41 |
if err != nil {
|
| 42 |
+
return "", http_error.ERR_CLIENT_INIT_FAILED
|
| 43 |
}
|
| 44 |
|
|
|
|
|
|
|
| 45 |
client.AddEventHandler(func(evt interface{}) {
|
| 46 |
s.handleWhatsAppEvent(accountID, client, evt)
|
| 47 |
})
|
| 48 |
|
| 49 |
+
qrCodeChan := make(chan string, 1)
|
| 50 |
+
|
| 51 |
if qrChan != nil {
|
| 52 |
go func() {
|
| 53 |
for evt := range qrChan {
|
| 54 |
if evt.Event == "code" {
|
| 55 |
+
select {
|
| 56 |
+
case qrCodeChan <- evt.Code:
|
| 57 |
+
default:
|
| 58 |
+
}
|
| 59 |
} else {
|
| 60 |
fmt.Printf("[ACCOUNT %s] Login Event: %s\n", accountID, evt.Event)
|
| 61 |
}
|
|
|
|
| 63 |
}()
|
| 64 |
}
|
| 65 |
|
|
|
|
| 66 |
if err := client.Connect(); err != nil {
|
| 67 |
+
_ = s.connectionRepo.DeleteDevice(accountID)
|
| 68 |
+
return "", http_error.ERR_CLIENT_CONNECT_FAILED
|
| 69 |
}
|
| 70 |
|
| 71 |
+
select {
|
| 72 |
+
case code := <-qrCodeChan:
|
| 73 |
+
s.activeClients.Store(accountID, client)
|
| 74 |
+
return code, nil
|
| 75 |
+
case <-time.After(3 * time.Second):
|
| 76 |
+
if client.IsConnected() {
|
| 77 |
+
s.activeClients.Store(accountID, client)
|
| 78 |
+
return "", nil
|
| 79 |
+
}
|
| 80 |
+
s.activeClients.Store(accountID, client)
|
| 81 |
+
return "", nil
|
| 82 |
+
}
|
| 83 |
}
|
| 84 |
|
| 85 |
func (s *connectionService) GetActiveClient(accountID uuid.UUID) (*whatsmeow.Client, error) {
|
|
|
|
| 90 |
return val.(*whatsmeow.Client), nil
|
| 91 |
}
|
| 92 |
|
|
|
|
| 93 |
func (s *connectionService) handleWhatsAppEvent(accountID uuid.UUID, client *whatsmeow.Client, evt interface{}) {
|
| 94 |
switch evt.(type) {
|
| 95 |
case *events.Connected:
|
|
|
|
| 96 |
if client.Store.ID != nil {
|
| 97 |
_ = s.connectionRepo.UpdateAccountStatus(accountID, client.Store.ID.String(), true)
|
| 98 |
}
|
| 99 |
|
| 100 |
case *events.LoggedOut:
|
|
|
|
| 101 |
s.activeClients.Delete(accountID)
|
| 102 |
_ = s.connectionRepo.UpdateAccountStatus(accountID, "", false)
|
| 103 |
|
| 104 |
case *events.Disconnected:
|
|
|
|
|
|
|
| 105 |
fmt.Printf("Account %s disconnected from WhatsApp\n", accountID)
|
| 106 |
}
|
| 107 |
}
|