From 30890d6f0a08d7d7c10fecafe49d5b8816959439 Mon Sep 17 00:00:00 2001 From: Shubham Prajapati Date: Thu, 16 May 2024 16:56:41 +0530 Subject: [PATCH] Adding Chat Api in backend --- .vscode/launch.json | 15 ++ Router/router.go | 2 + go.mod | 3 + go.sum | 6 + index.html | 32 ++++ main.go | 4 + real_time_chat/real.time.chat.go | 237 ++++++++++++++++++++++++++++++ real_time_chat/type.go | 15 ++ voyagerRouting/voyager.routing.go | 2 +- 9 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 .vscode/launch.json create mode 100644 index.html create mode 100644 real_time_chat/real.time.chat.go create mode 100644 real_time_chat/type.go diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6deb9a6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "main.go" + } + ] +} \ No newline at end of file diff --git a/Router/router.go b/Router/router.go index 1d4f7c1..67fb1b0 100644 --- a/Router/router.go +++ b/Router/router.go @@ -2,6 +2,7 @@ package router import ( "fmt" + realtimechat "hack/real_time_chat" voyagerrouting "hack/voyagerRouting" "time" @@ -53,6 +54,7 @@ func ApplyRoutes(r *gin.RouterGroup) { fmt.Println("---------------------------------------------------------------------------------------------------------------------------------------") voyagerrouting.VoyagerApplyRoutes(v1) + realtimechat.RealTimeVoyagerApplyRoutes(v1) // suiRouter.SuiApplyRoutes(v1) } } diff --git a/go.mod b/go.mod index f006a4f..8389cfb 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,10 @@ require ( github.com/gin-contrib/cors v1.7.2 github.com/gin-contrib/static v1.1.2 github.com/gin-gonic/gin v1.10.0 + github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.0 github.com/joho/godotenv v1.5.1 + github.com/overseedio/realtime-go v0.0.0 gorm.io/driver/postgres v1.5.7 gorm.io/gorm v1.25.10 ) diff --git a/go.sum b/go.sum index 6626c87..4114ac7 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,10 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= @@ -64,6 +68,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/overseedio/realtime-go v0.0.0 h1:WHP0lRBWxCWEmDzMoMer3yB26X1gp9xvWCd8w6L1icQ= +github.com/overseedio/realtime-go v0.0.0/go.mod h1:2pcz14UcMk1/e+JxorOm0nSaxR+fRwN6msynYsLHccQ= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/index.html b/index.html new file mode 100644 index 0000000..c63ce3e --- /dev/null +++ b/index.html @@ -0,0 +1,32 @@ + + + + + + WebSocket Chat + + + + + +
+ + + + diff --git a/main.go b/main.go index f2d4441..ce9d9e9 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( router "hack/Router" + realtimechat "hack/real_time_chat" "log" "os" @@ -16,5 +17,8 @@ func main() { log.Printf("Error in reading the config file : %v\n", err) } } + + realtimechat.RealtimeChatVoigerConnection() + router.HandleRequest() } diff --git a/real_time_chat/real.time.chat.go b/real_time_chat/real.time.chat.go new file mode 100644 index 0000000..91f15df --- /dev/null +++ b/real_time_chat/real.time.chat.go @@ -0,0 +1,237 @@ +package realtimechat + +import ( + "fmt" + dbflow "hack/dbFlow" + "log" + "net/http" + "os" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/gorilla/websocket" + realtimego "github.com/overseedio/realtime-go" +) + +// func handleConnections(c *gin.Context) { +// ws, err := upgrader.Upgrade(c.Writer, c.Request, nil) +// if err != nil { +// log.Println(err) +// return +// } +// defer ws.Close() +// clients[ws] = true + +// for { +// var msg Message +// err := ws.ReadJSON(&msg) +// if err != nil { +// log.Printf("error: %v", err) +// delete(clients, ws) +// break +// } +// broadcast <- msg +// } +// } + +// func subscribeToRealtime() { +// client := supabase.NewClient(os.Getenv("SUPABASE_PROJECT_URL"), os.Getenv("SUPABASE_ANON_KEY"), nil) +// rtClient, err := realtime.NewClient(client) +// if err != nil { +// log.Fatalf("Error creating Realtime client: %v", err) +// } + +// rtClient.On("postgres_changes", map[string]string{ +// "schema": "public", +// "table": "messages", +// "event": "*", +// }, func(payload realtime.Payload) { +// log.Printf("Realtime message: %v", payload) + +// msg := Message{ +// ID: payload.New["id"].(string), +// Content: payload.New["content"].(string), +// Username: payload.New["username"].(string), +// CreatedAt: payload.New["created_at"].(string), +// } + +// broadcast <- msg +// }) + +// err = rtClient.Start() +// if err != nil { +// log.Fatalf("Error starting Realtime client: %v", err) +// } +// } + +// func handleMessages() { +// db, dbClose := dbflow.ConnectHackDatabase() +// defer dbClose.Close() +// for { +// msg := <-broadcast + +// for client := range clients { +// err := client.WriteJSON(msg) +// if err != nil { +// log.Printf("error: %v", err) +// client.Close() +// delete(clients, client) +// } +// } + +// db.Create(&msg) +// } +// } + +// Message represents a chat message +// type Message struct { +// ID uint `json:"id" gorm:"primaryKey"` +// Content string `json:"content"` +// Username string `json:"username"` +// CreatedAt time.Time `json:"created_at"` +// } + +// var db *gorm.DB +var clients = make(map[*websocket.Conn]bool) +var broadcast = make(chan VoyagerRandomeMessages) +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} +var mutex = &sync.Mutex{} + +func RealtimeChatVoigerConnection() { + var ENDPOINT = os.Getenv("SUPABASE_PROJECT_URL") + var API_KEY = os.Getenv("SUPABASE_ANON_KEY") + var RLS_TOKEN = os.Getenv("SUPABASE_RLS_TOKEN") + + c, err := realtimego.NewClient(ENDPOINT, API_KEY, realtimego.WithUserToken(RLS_TOKEN)) + if err != nil { + log.Fatal(err) + } + + err = c.Connect() + if err != nil { + log.Fatal(err) + } + + dbName := os.Getenv("SUPABASE_DB_NAME") + schema := os.Getenv("SCHEMA") + table := os.Getenv("TABLE") + ch, err := c.Channel(realtimego.WithTable(&dbName, &schema, &table)) + if err != nil { + log.Fatal(err) + } + + ch.OnInsert = func(m realtimego.Message) { + log.Println("***ON INSERT....", m) + } + ch.OnDelete = func(m realtimego.Message) { + log.Println("***ON DELETE....", m) + } + ch.OnUpdate = func(m realtimego.Message) { + log.Println("***ON UPDATE....", m) + } + + err = ch.Subscribe() + if err != nil { + log.Fatal(err) + } +} + +func RealTimeVoyagerApplyRoutes(p *gin.RouterGroup) { + r := p.Group("/voyager_web_socket/") + { + r.POST("/messages", CreateMessage) + r.GET("/messages", GetMessages) + r.GET("/ws", HandleWebSocket) + go HandleMessages() + } +} + +func HandleWebSocket(c *gin.Context) { + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Printf("WebSocket upgrade error: %v", err) + return + } + db, dbClose := dbflow.ConnectHackDatabase() + defer dbClose.Close() + defer conn.Close() + + mutex.Lock() + clients[conn] = true + mutex.Unlock() + + for { + var message VoyagerRandomeMessages + err := conn.ReadJSON(&message) + if err != nil { + log.Printf("WebSocket read error: %v", err) + mutex.Lock() + delete(clients, conn) + mutex.Unlock() + break + } + + // Save the received message to the database + message.CreatedAt = time.Now() + message.ID = uuid.New() + if err := db.Create(&message).Error; err != nil { + log.Printf("Database insert error: %v", err) + continue + } + + // Broadcast the message to other clients + broadcast <- message + + } +} + +func HandleMessages() { + for { + message := <-broadcast + mutex.Lock() + for client := range clients { + err := client.WriteJSON(message) + if err != nil { + log.Printf("WebSocket error: %v", err) + client.Close() + delete(clients, client) + } + } + mutex.Unlock() + } +} + +func CreateMessage(c *gin.Context) { + db, dbClose := dbflow.ConnectHackDatabase() + defer dbClose.Close() + var message VoyagerRandomeMessages + message.ID = uuid.New() + fmt.Println("message.ID = ", message.ID) + if err := c.ShouldBindJSON(&message); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + message.CreatedAt = time.Now() + if err := db.Create(&message).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, message) +} + +func GetMessages(c *gin.Context) { + db, dbClose := dbflow.ConnectHackDatabase() + defer dbClose.Close() + var messages []VoyagerRandomeMessages + if err := db.Find(&messages).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, messages) +} diff --git a/real_time_chat/type.go b/real_time_chat/type.go new file mode 100644 index 0000000..12cef38 --- /dev/null +++ b/real_time_chat/type.go @@ -0,0 +1,15 @@ +package realtimechat + +import ( + "time" + + "github.com/google/uuid" +) + +type VoyagerRandomeMessages struct { + ID uuid.UUID `json:"id" gorm:"type:uuid;primary_key;"` + UserId int `json:"userId" gorm:"type:integer"` + Content string `json:"content"` + Username string `json:"username"` + CreatedAt time.Time `json:"created_at"` +} diff --git a/voyagerRouting/voyager.routing.go b/voyagerRouting/voyager.routing.go index ec59e67..1179092 100644 --- a/voyagerRouting/voyager.routing.go +++ b/voyagerRouting/voyager.routing.go @@ -7,7 +7,7 @@ import ( ) func VoyagerApplyRoutes(p *gin.RouterGroup) { - r := p.Group("voyager") + r := p.Group("/voyager") { // GET API for people listing according to interest or location