RyZ commited on
Commit
eef1ec2
·
unverified ·
2 Parent(s): 1767f11ba5de78

Merge pull request #20 from orion-ai-project/dev

Browse files

feat: adding whatsapp/socket for websocket connection

controllers/socket_controller.go ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controllers
2
+
3
+ import (
4
+ "log"
5
+ http_error "whatsapp-backend/models/error"
6
+ "whatsapp-backend/services"
7
+ "whatsapp-backend/utils"
8
+
9
+ "github.com/gin-gonic/gin"
10
+ "github.com/google/uuid"
11
+ )
12
+
13
+ type SocketController interface {
14
+ HandleWebSocket(ctx *gin.Context)
15
+ }
16
+
17
+ type socketController struct {
18
+ socketService services.SocketService
19
+ }
20
+
21
+ func NewSocketController(socketService services.SocketService) SocketController {
22
+ return &socketController{socketService: socketService}
23
+ }
24
+
25
+ // HandleWebSocket godoc
26
+ // @Summary Connect to WebSocket
27
+ // @Description Upgrade HTTP connection to WebSocket for real-time updates
28
+ // @Tags whatsapp
29
+ // @Security BearerAuth
30
+ // @Success 101
31
+ // @Router /whatsapp/socket [get]
32
+ func (c *socketController) HandleWebSocket(ctx *gin.Context) {
33
+ userID, exists := ctx.Get("user_id")
34
+ if !exists {
35
+ log.Println("[WebSocket] User ID not found in context")
36
+ utils.SendResponse[any, any](ctx, nil, nil, http_error.UNAUTHORIZED)
37
+ return
38
+ }
39
+ accountID := userID.(uuid.UUID)
40
+ c.socketService.HandleConnection(ctx.Writer, ctx.Request, accountID)
41
+ }
docs/docs.go CHANGED
@@ -443,7 +443,70 @@ const docTemplate = `{
443
  }
444
  }
445
  },
446
- "/chat/send": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
  "post": {
448
  "security": [
449
  {
@@ -460,7 +523,7 @@ const docTemplate = `{
460
  "tags": [
461
  "chat"
462
  ],
463
- "summary": "Send WhatsApp message",
464
  "parameters": [
465
  {
466
  "description": "Send Message Request",
@@ -545,6 +608,25 @@ const docTemplate = `{
545
  }
546
  }
547
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
548
  "/whatsapp/status": {
549
  "get": {
550
  "security": [
 
443
  }
444
  }
445
  },
446
+ "/chat/send_img": {
447
+ "post": {
448
+ "security": [
449
+ {
450
+ "BearerAuth": []
451
+ }
452
+ ],
453
+ "description": "Send an image to a WhatsApp number via file upload",
454
+ "consumes": [
455
+ "multipart/form-data"
456
+ ],
457
+ "produces": [
458
+ "application/json"
459
+ ],
460
+ "tags": [
461
+ "chat"
462
+ ],
463
+ "summary": "Send WhatsApp image",
464
+ "parameters": [
465
+ {
466
+ "type": "file",
467
+ "description": "Image file to send",
468
+ "name": "image",
469
+ "in": "formData",
470
+ "required": true
471
+ },
472
+ {
473
+ "type": "string",
474
+ "example": "628123456789",
475
+ "description": "Recipient phone number",
476
+ "name": "recipient",
477
+ "in": "formData",
478
+ "required": true
479
+ },
480
+ {
481
+ "type": "string",
482
+ "description": "Image caption",
483
+ "name": "caption",
484
+ "in": "formData"
485
+ }
486
+ ],
487
+ "responses": {
488
+ "200": {
489
+ "description": "OK",
490
+ "schema": {
491
+ "$ref": "#/definitions/dto.SendMessageResponse"
492
+ }
493
+ },
494
+ "400": {
495
+ "description": "Bad Request",
496
+ "schema": {
497
+ "$ref": "#/definitions/dto.ErrorResponse"
498
+ }
499
+ },
500
+ "401": {
501
+ "description": "Unauthorized",
502
+ "schema": {
503
+ "$ref": "#/definitions/dto.ErrorResponse"
504
+ }
505
+ }
506
+ }
507
+ }
508
+ },
509
+ "/chat/send_msg": {
510
  "post": {
511
  "security": [
512
  {
 
523
  "tags": [
524
  "chat"
525
  ],
526
+ "summary": "Send WhatsApp text message",
527
  "parameters": [
528
  {
529
  "description": "Send Message Request",
 
608
  }
609
  }
610
  },
611
+ "/whatsapp/socket": {
612
+ "get": {
613
+ "security": [
614
+ {
615
+ "BearerAuth": []
616
+ }
617
+ ],
618
+ "description": "Upgrade HTTP connection to WebSocket for real-time updates",
619
+ "tags": [
620
+ "whatsapp"
621
+ ],
622
+ "summary": "Connect to WebSocket",
623
+ "responses": {
624
+ "101": {
625
+ "description": "Switching Protocols"
626
+ }
627
+ }
628
+ }
629
+ },
630
  "/whatsapp/status": {
631
  "get": {
632
  "security": [
docs/swagger.json CHANGED
@@ -436,7 +436,70 @@
436
  }
437
  }
438
  },
439
- "/chat/send": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
  "post": {
441
  "security": [
442
  {
@@ -453,7 +516,7 @@
453
  "tags": [
454
  "chat"
455
  ],
456
- "summary": "Send WhatsApp message",
457
  "parameters": [
458
  {
459
  "description": "Send Message Request",
@@ -538,6 +601,25 @@
538
  }
539
  }
540
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
  "/whatsapp/status": {
542
  "get": {
543
  "security": [
 
436
  }
437
  }
438
  },
439
+ "/chat/send_img": {
440
+ "post": {
441
+ "security": [
442
+ {
443
+ "BearerAuth": []
444
+ }
445
+ ],
446
+ "description": "Send an image to a WhatsApp number via file upload",
447
+ "consumes": [
448
+ "multipart/form-data"
449
+ ],
450
+ "produces": [
451
+ "application/json"
452
+ ],
453
+ "tags": [
454
+ "chat"
455
+ ],
456
+ "summary": "Send WhatsApp image",
457
+ "parameters": [
458
+ {
459
+ "type": "file",
460
+ "description": "Image file to send",
461
+ "name": "image",
462
+ "in": "formData",
463
+ "required": true
464
+ },
465
+ {
466
+ "type": "string",
467
+ "example": "628123456789",
468
+ "description": "Recipient phone number",
469
+ "name": "recipient",
470
+ "in": "formData",
471
+ "required": true
472
+ },
473
+ {
474
+ "type": "string",
475
+ "description": "Image caption",
476
+ "name": "caption",
477
+ "in": "formData"
478
+ }
479
+ ],
480
+ "responses": {
481
+ "200": {
482
+ "description": "OK",
483
+ "schema": {
484
+ "$ref": "#/definitions/dto.SendMessageResponse"
485
+ }
486
+ },
487
+ "400": {
488
+ "description": "Bad Request",
489
+ "schema": {
490
+ "$ref": "#/definitions/dto.ErrorResponse"
491
+ }
492
+ },
493
+ "401": {
494
+ "description": "Unauthorized",
495
+ "schema": {
496
+ "$ref": "#/definitions/dto.ErrorResponse"
497
+ }
498
+ }
499
+ }
500
+ }
501
+ },
502
+ "/chat/send_msg": {
503
  "post": {
504
  "security": [
505
  {
 
516
  "tags": [
517
  "chat"
518
  ],
519
+ "summary": "Send WhatsApp text message",
520
  "parameters": [
521
  {
522
  "description": "Send Message Request",
 
601
  }
602
  }
603
  },
604
+ "/whatsapp/socket": {
605
+ "get": {
606
+ "security": [
607
+ {
608
+ "BearerAuth": []
609
+ }
610
+ ],
611
+ "description": "Upgrade HTTP connection to WebSocket for real-time updates",
612
+ "tags": [
613
+ "whatsapp"
614
+ ],
615
+ "summary": "Connect to WebSocket",
616
+ "responses": {
617
+ "101": {
618
+ "description": "Switching Protocols"
619
+ }
620
+ }
621
+ }
622
+ },
623
  "/whatsapp/status": {
624
  "get": {
625
  "security": [
docs/swagger.yaml CHANGED
@@ -453,7 +453,48 @@ paths:
453
  summary: Fetch WhatsApp contacts
454
  tags:
455
  - chat
456
- /chat/send:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
  post:
458
  consumes:
459
  - application/json
@@ -482,7 +523,7 @@ paths:
482
  $ref: '#/definitions/dto.ErrorResponse'
483
  security:
484
  - BearerAuth: []
485
- summary: Send WhatsApp message
486
  tags:
487
  - chat
488
  /whatsapp/connect:
@@ -518,6 +559,17 @@ paths:
518
  summary: Connect new WhatsApp account
519
  tags:
520
  - whatsapp
 
 
 
 
 
 
 
 
 
 
 
521
  /whatsapp/status:
522
  get:
523
  consumes:
 
453
  summary: Fetch WhatsApp contacts
454
  tags:
455
  - chat
456
+ /chat/send_img:
457
+ post:
458
+ consumes:
459
+ - multipart/form-data
460
+ description: Send an image to a WhatsApp number via file upload
461
+ parameters:
462
+ - description: Image file to send
463
+ in: formData
464
+ name: image
465
+ required: true
466
+ type: file
467
+ - description: Recipient phone number
468
+ example: "628123456789"
469
+ in: formData
470
+ name: recipient
471
+ required: true
472
+ type: string
473
+ - description: Image caption
474
+ in: formData
475
+ name: caption
476
+ type: string
477
+ produces:
478
+ - application/json
479
+ responses:
480
+ "200":
481
+ description: OK
482
+ schema:
483
+ $ref: '#/definitions/dto.SendMessageResponse'
484
+ "400":
485
+ description: Bad Request
486
+ schema:
487
+ $ref: '#/definitions/dto.ErrorResponse'
488
+ "401":
489
+ description: Unauthorized
490
+ schema:
491
+ $ref: '#/definitions/dto.ErrorResponse'
492
+ security:
493
+ - BearerAuth: []
494
+ summary: Send WhatsApp image
495
+ tags:
496
+ - chat
497
+ /chat/send_msg:
498
  post:
499
  consumes:
500
  - application/json
 
523
  $ref: '#/definitions/dto.ErrorResponse'
524
  security:
525
  - BearerAuth: []
526
+ summary: Send WhatsApp text message
527
  tags:
528
  - chat
529
  /whatsapp/connect:
 
559
  summary: Connect new WhatsApp account
560
  tags:
561
  - whatsapp
562
+ /whatsapp/socket:
563
+ get:
564
+ description: Upgrade HTTP connection to WebSocket for real-time updates
565
+ responses:
566
+ "101":
567
+ description: Switching Protocols
568
+ security:
569
+ - BearerAuth: []
570
+ summary: Connect to WebSocket
571
+ tags:
572
+ - whatsapp
573
  /whatsapp/status:
574
  get:
575
  consumes:
go.mod CHANGED
@@ -8,12 +8,14 @@ require (
8
  github.com/go-playground/validator/v10 v10.29.0
9
  github.com/golang-jwt/jwt/v5 v5.3.0
10
  github.com/google/uuid v1.6.0
 
11
  github.com/joho/godotenv v1.5.1
12
  github.com/swaggo/files v1.0.1
13
  github.com/swaggo/gin-swagger v1.6.1
14
  github.com/swaggo/swag v1.16.6
15
  go.mau.fi/whatsmeow v0.0.0-20251217143725-11cf47c62d32
16
  golang.org/x/crypto v0.46.0
 
17
  gorm.io/driver/postgres v1.6.0
18
  gorm.io/gorm v1.31.1
19
  )
@@ -77,5 +79,4 @@ require (
77
  golang.org/x/sys v0.39.0 // indirect
78
  golang.org/x/text v0.32.0 // indirect
79
  golang.org/x/tools v0.40.0 // indirect
80
- google.golang.org/protobuf v1.36.11 // indirect
81
  )
 
8
  github.com/go-playground/validator/v10 v10.29.0
9
  github.com/golang-jwt/jwt/v5 v5.3.0
10
  github.com/google/uuid v1.6.0
11
+ github.com/gorilla/websocket v1.5.3
12
  github.com/joho/godotenv v1.5.1
13
  github.com/swaggo/files v1.0.1
14
  github.com/swaggo/gin-swagger v1.6.1
15
  github.com/swaggo/swag v1.16.6
16
  go.mau.fi/whatsmeow v0.0.0-20251217143725-11cf47c62d32
17
  golang.org/x/crypto v0.46.0
18
+ google.golang.org/protobuf v1.36.11
19
  gorm.io/driver/postgres v1.6.0
20
  gorm.io/gorm v1.31.1
21
  )
 
79
  golang.org/x/sys v0.39.0 // indirect
80
  golang.org/x/text v0.32.0 // indirect
81
  golang.org/x/tools v0.40.0 // indirect
 
82
  )
go.sum CHANGED
@@ -82,6 +82,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
82
  github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
83
  github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
84
  github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 
 
85
  github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
86
  github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
87
  github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
 
82
  github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
83
  github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
84
  github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
85
+ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
86
+ github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
87
  github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
88
  github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
89
  github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
middleware/auth_middleware.go CHANGED
@@ -29,14 +29,14 @@ func NewAuthMiddleware(jwtConfig config.JWTConfig, authRepo repositories.AuthRep
29
  func (m *authMiddleware) RequireAuth() gin.HandlerFunc {
30
  return func(ctx *gin.Context) {
31
  authHeader := ctx.GetHeader("Authorization")
32
- if authHeader == "" {
33
- utils.SendResponse[any, any](ctx, nil, nil, http_error.UNAUTHORIZED)
34
- ctx.Abort()
35
- return
36
- }
37
 
38
- tokenString := strings.TrimPrefix(authHeader, "Bearer")
39
- tokenString = strings.TrimSpace(tokenString)
 
 
 
 
40
 
41
  if tokenString == "" {
42
  utils.SendResponse[any, any](ctx, nil, nil, http_error.UNAUTHORIZED)
 
29
  func (m *authMiddleware) RequireAuth() gin.HandlerFunc {
30
  return func(ctx *gin.Context) {
31
  authHeader := ctx.GetHeader("Authorization")
32
+ tokenString := ""
 
 
 
 
33
 
34
+ if authHeader != "" {
35
+ tokenString = strings.TrimPrefix(authHeader, "Bearer")
36
+ tokenString = strings.TrimSpace(tokenString)
37
+ } else {
38
+ tokenString = ctx.Query("token")
39
+ }
40
 
41
  if tokenString == "" {
42
  utils.SendResponse[any, any](ctx, nil, nil, http_error.UNAUTHORIZED)
provider/controller_provider.go CHANGED
@@ -7,6 +7,7 @@ type ControllerProvider interface {
7
  ProvideAuthController() controllers.AuthController
8
  ProvideChatController() controllers.ChatController
9
  ProvideAdminController() controllers.AdminController
 
10
  }
11
 
12
  type controllerProvider struct {
@@ -14,6 +15,7 @@ type controllerProvider struct {
14
  authController controllers.AuthController
15
  chatController controllers.ChatController
16
  adminController controllers.AdminController
 
17
  }
18
 
19
  func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider {
@@ -22,12 +24,14 @@ func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider
22
  authController := controllers.NewAuthController(servicesProvider.ProvideAuthService())
23
  chatController := controllers.NewChatController(servicesProvider.ProvideChatService())
24
  adminController := controllers.NewAdminController(servicesProvider.ProvideAdminService())
 
25
 
26
  return &controllerProvider{
27
  connectionController: connectionController,
28
  authController: authController,
29
  chatController: chatController,
30
  adminController: adminController,
 
31
  }
32
  }
33
 
@@ -46,3 +50,7 @@ func (c *controllerProvider) ProvideChatController() controllers.ChatController
46
  func (c *controllerProvider) ProvideAdminController() controllers.AdminController {
47
  return c.adminController
48
  }
 
 
 
 
 
7
  ProvideAuthController() controllers.AuthController
8
  ProvideChatController() controllers.ChatController
9
  ProvideAdminController() controllers.AdminController
10
+ ProvideSocketController() controllers.SocketController
11
  }
12
 
13
  type controllerProvider struct {
 
15
  authController controllers.AuthController
16
  chatController controllers.ChatController
17
  adminController controllers.AdminController
18
+ socketController controllers.SocketController
19
  }
20
 
21
  func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider {
 
24
  authController := controllers.NewAuthController(servicesProvider.ProvideAuthService())
25
  chatController := controllers.NewChatController(servicesProvider.ProvideChatService())
26
  adminController := controllers.NewAdminController(servicesProvider.ProvideAdminService())
27
+ socketController := controllers.NewSocketController(servicesProvider.ProvideSocketService())
28
 
29
  return &controllerProvider{
30
  connectionController: connectionController,
31
  authController: authController,
32
  chatController: chatController,
33
  adminController: adminController,
34
+ socketController: socketController,
35
  }
36
  }
37
 
 
50
  func (c *controllerProvider) ProvideAdminController() controllers.AdminController {
51
  return c.adminController
52
  }
53
+
54
+ func (c *controllerProvider) ProvideSocketController() controllers.SocketController {
55
+ return c.socketController
56
+ }
provider/services_provider.go CHANGED
@@ -7,6 +7,7 @@ type ServicesProvider interface {
7
  ProvideAuthService() services.AuthService
8
  ProvideChatService() services.ChatService
9
  ProvideAdminService() services.AdminService
 
10
  }
11
 
12
  type servicesProvider struct {
@@ -14,10 +15,12 @@ type servicesProvider struct {
14
  authService services.AuthService
15
  chatService services.ChatService
16
  adminService services.AdminService
 
17
  }
18
 
19
  func NewServicesProvider(repoProvider RepositoriesProvider, configProvider ConfigProvider) ServicesProvider {
20
- connectionService := services.NewConnectionService(repoProvider.ProvideConnectionRepository())
 
21
  authService := services.NewAuthService(repoProvider.ProvideAuthRepository(), repoProvider.ProvideConnectionRepository(), configProvider.ProvideJWTConfig(), configProvider.ProvideEnvConfig())
22
  chatService := services.NewChatService(connectionService, repoProvider.ProvideChatRepository())
23
  adminService := services.NewAdminService(repoProvider.ProvideAuthRepository(), configProvider.ProvideEnvConfig())
@@ -27,6 +30,7 @@ func NewServicesProvider(repoProvider RepositoriesProvider, configProvider Confi
27
  authService: authService,
28
  chatService: chatService,
29
  adminService: adminService,
 
30
  }
31
  }
32
 
@@ -45,3 +49,7 @@ func (s *servicesProvider) ProvideChatService() services.ChatService {
45
  func (s *servicesProvider) ProvideAdminService() services.AdminService {
46
  return s.adminService
47
  }
 
 
 
 
 
7
  ProvideAuthService() services.AuthService
8
  ProvideChatService() services.ChatService
9
  ProvideAdminService() services.AdminService
10
+ ProvideSocketService() services.SocketService
11
  }
12
 
13
  type servicesProvider struct {
 
15
  authService services.AuthService
16
  chatService services.ChatService
17
  adminService services.AdminService
18
+ socketService services.SocketService
19
  }
20
 
21
  func NewServicesProvider(repoProvider RepositoriesProvider, configProvider ConfigProvider) ServicesProvider {
22
+ socketService := services.NewSocketService()
23
+ connectionService := services.NewConnectionService(repoProvider.ProvideConnectionRepository(), socketService)
24
  authService := services.NewAuthService(repoProvider.ProvideAuthRepository(), repoProvider.ProvideConnectionRepository(), configProvider.ProvideJWTConfig(), configProvider.ProvideEnvConfig())
25
  chatService := services.NewChatService(connectionService, repoProvider.ProvideChatRepository())
26
  adminService := services.NewAdminService(repoProvider.ProvideAuthRepository(), configProvider.ProvideEnvConfig())
 
30
  authService: authService,
31
  chatService: chatService,
32
  adminService: adminService,
33
+ socketService: socketService,
34
  }
35
  }
36
 
 
49
  func (s *servicesProvider) ProvideAdminService() services.AdminService {
50
  return s.adminService
51
  }
52
+
53
+ func (s *servicesProvider) ProvideSocketService() services.SocketService {
54
+ return s.socketService
55
+ }
router/router.go CHANGED
@@ -16,6 +16,7 @@ func RunRouter(appProvider provider.AppProvider) {
16
  AuthRouter(router, controller, middleware)
17
  ChatRouter(router, controller, middleware)
18
  AdminRouter(router, controller, middleware)
 
19
 
20
  router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
21
 
 
16
  AuthRouter(router, controller, middleware)
17
  ChatRouter(router, controller, middleware)
18
  AdminRouter(router, controller, middleware)
19
+ SocketRouter(router, controller, middleware)
20
 
21
  router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
22
 
router/socket_router.go ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package router
2
+
3
+ import (
4
+ "whatsapp-backend/provider"
5
+
6
+ "github.com/gin-gonic/gin"
7
+ )
8
+
9
+ func SocketRouter(router *gin.Engine, controller provider.ControllerProvider, middleware provider.MiddlewareProvider) {
10
+ socketController := controller.ProvideSocketController()
11
+ authMiddleware := middleware.ProvideAuthMiddleware()
12
+
13
+ // WebSocket endpoint needs to be authenticated
14
+ routerGroup := router.Group("/api/whatsapp", authMiddleware.RequireAuth())
15
+ // routerGroup.Use(gzip.Gzip(gzip.DefaultCompression)) // gzip might interfere with websocket upgrade
16
+ {
17
+ routerGroup.GET("/socket", socketController.HandleWebSocket)
18
+ }
19
+ }
services/connection_service.go CHANGED
@@ -20,12 +20,14 @@ type ConnectionService interface {
20
 
21
  type connectionService struct {
22
  connectionRepo repositories.ConnectionRepository
 
23
  activeClients sync.Map
24
  }
25
 
26
- func NewConnectionService(connectionRepo repositories.ConnectionRepository) ConnectionService {
27
  return &connectionService{
28
  connectionRepo: connectionRepo,
 
29
  }
30
  }
31
 
@@ -52,6 +54,7 @@ func (s *connectionService) Connect(ctx context.Context, accountID uuid.UUID) (s
52
  go func() {
53
  for evt := range qrChan {
54
  if evt.Event == "code" {
 
55
  select {
56
  case qrCodeChan <- evt.Code:
57
  default:
@@ -95,19 +98,22 @@ func (s *connectionService) handleWhatsAppEvent(accountID uuid.UUID, client *wha
95
  jid := client.Store.ID.String()
96
  if s.connectionRepo.IsJIDUsedByOther(jid, accountID) {
97
  log.Printf("[WhatsApp] JID conflict detected for account %s", accountID)
 
98
  client.Disconnect()
99
  s.activeClients.Delete(accountID)
100
  return
101
  }
102
 
103
  _ = s.connectionRepo.UpdateAccountStatus(accountID, jid, true)
 
104
  }
105
 
106
  case *events.LoggedOut:
107
  s.activeClients.Delete(accountID)
108
  _ = s.connectionRepo.UpdateAccountStatus(accountID, "", false)
 
109
 
110
  case *events.Disconnected:
111
- // Client disconnected, status will be reflected in GetStatus endpoint
112
  }
113
  }
 
20
 
21
  type connectionService struct {
22
  connectionRepo repositories.ConnectionRepository
23
+ socketService SocketService
24
  activeClients sync.Map
25
  }
26
 
27
+ func NewConnectionService(connectionRepo repositories.ConnectionRepository, socketService SocketService) ConnectionService {
28
  return &connectionService{
29
  connectionRepo: connectionRepo,
30
+ socketService: socketService,
31
  }
32
  }
33
 
 
54
  go func() {
55
  for evt := range qrChan {
56
  if evt.Event == "code" {
57
+ s.socketService.EmitEvent(accountID, "qr_code", evt.Code)
58
  select {
59
  case qrCodeChan <- evt.Code:
60
  default:
 
98
  jid := client.Store.ID.String()
99
  if s.connectionRepo.IsJIDUsedByOther(jid, accountID) {
100
  log.Printf("[WhatsApp] JID conflict detected for account %s", accountID)
101
+ s.socketService.EmitEvent(accountID, "error", "JID conflict: This number is already connected to another account")
102
  client.Disconnect()
103
  s.activeClients.Delete(accountID)
104
  return
105
  }
106
 
107
  _ = s.connectionRepo.UpdateAccountStatus(accountID, jid, true)
108
+ s.socketService.EmitEvent(accountID, "connected", map[string]string{"jid": jid})
109
  }
110
 
111
  case *events.LoggedOut:
112
  s.activeClients.Delete(accountID)
113
  _ = s.connectionRepo.UpdateAccountStatus(accountID, "", false)
114
+ s.socketService.EmitEvent(accountID, "logged_out", nil)
115
 
116
  case *events.Disconnected:
117
+ s.socketService.EmitEvent(accountID, "disconnected", nil)
118
  }
119
  }
services/socket_service.go ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package services
2
+
3
+ import (
4
+ "log"
5
+ "net/http"
6
+ "sync"
7
+ "time"
8
+
9
+ "github.com/google/uuid"
10
+ "github.com/gorilla/websocket"
11
+ )
12
+
13
+ type SocketService interface {
14
+ HandleConnection(w http.ResponseWriter, r *http.Request, userID uuid.UUID)
15
+ EmitEvent(userID uuid.UUID, eventType string, payload interface{})
16
+ BroadcastEvent(eventType string, payload interface{})
17
+ }
18
+
19
+ type socketService struct {
20
+ clients map[uuid.UUID][]*websocket.Conn
21
+ clientsMux sync.RWMutex
22
+ upgrader websocket.Upgrader
23
+ }
24
+
25
+ func NewSocketService() SocketService {
26
+ return &socketService{
27
+ clients: make(map[uuid.UUID][]*websocket.Conn),
28
+ upgrader: websocket.Upgrader{
29
+ ReadBufferSize: 1024,
30
+ WriteBufferSize: 1024,
31
+ CheckOrigin: func(r *http.Request) bool {
32
+ return true // Development mode
33
+ },
34
+ },
35
+ }
36
+ }
37
+
38
+ func (s *socketService) HandleConnection(w http.ResponseWriter, r *http.Request, userID uuid.UUID) {
39
+ conn, err := s.upgrader.Upgrade(w, r, nil)
40
+ if err != nil {
41
+ log.Printf("Failed to upgrade connection: %v", err)
42
+ return
43
+ }
44
+
45
+ s.registerClient(userID, conn)
46
+ defer s.unregisterClient(userID, conn)
47
+
48
+ // Keep connection alive and listen for disconnects
49
+ for {
50
+ _, _, err := conn.ReadMessage()
51
+ if err != nil {
52
+ if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
53
+ log.Printf("error: %v", err)
54
+ }
55
+ break
56
+ }
57
+ }
58
+ }
59
+
60
+ func (s *socketService) registerClient(userID uuid.UUID, conn *websocket.Conn) {
61
+ s.clientsMux.Lock()
62
+ defer s.clientsMux.Unlock()
63
+
64
+ s.clients[userID] = append(s.clients[userID], conn)
65
+ log.Printf("Client connected: %s", userID)
66
+ }
67
+
68
+ func (s *socketService) unregisterClient(userID uuid.UUID, conn *websocket.Conn) {
69
+ s.clientsMux.Lock()
70
+ defer s.clientsMux.Unlock()
71
+
72
+ conn.Close()
73
+
74
+ if conns, ok := s.clients[userID]; ok {
75
+ for i, c := range conns {
76
+ if c == conn {
77
+ s.clients[userID] = append(conns[:i], conns[i+1:]...)
78
+ break
79
+ }
80
+ }
81
+ if len(s.clients[userID]) == 0 {
82
+ delete(s.clients, userID)
83
+ }
84
+ }
85
+ log.Printf("Client disconnected: %s", userID)
86
+ }
87
+
88
+ func (s *socketService) EmitEvent(userID uuid.UUID, eventType string, payload interface{}) {
89
+ s.clientsMux.RLock()
90
+ conns, ok := s.clients[userID]
91
+ s.clientsMux.RUnlock()
92
+
93
+ if !ok {
94
+ return
95
+ }
96
+
97
+ message := map[string]interface{}{
98
+ "type": eventType,
99
+ "payload": payload,
100
+ "time": time.Now(),
101
+ }
102
+
103
+ for _, conn := range conns {
104
+ err := conn.WriteJSON(message)
105
+ if err != nil {
106
+ log.Printf("Error sending message to user %s: %v", userID, err)
107
+ conn.Close()
108
+ }
109
+ }
110
+ }
111
+
112
+ func (s *socketService) BroadcastEvent(eventType string, payload interface{}) {
113
+ s.clientsMux.RLock()
114
+ defer s.clientsMux.RUnlock()
115
+
116
+ message := map[string]interface{}{
117
+ "type": eventType,
118
+ "payload": payload,
119
+ "time": time.Now(),
120
+ }
121
+
122
+ for userID, conns := range s.clients {
123
+ for _, conn := range conns {
124
+ err := conn.WriteJSON(message)
125
+ if err != nil {
126
+ log.Printf("Error broadcasting to user %s: %v", userID, err)
127
+ }
128
+ }
129
+ }
130
+ }