diff --git a/cmd/web/main.go b/cmd/web/main.go index 38331a1..577da2f 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -119,6 +119,8 @@ func main() { Session: session, } + go app.ListenToWsChannel() + app.infoLog.Println("Connected to MariaDB") err = app.serve() diff --git a/cmd/web/routes.go b/cmd/web/routes.go index ec1d25c..ca01da7 100644 --- a/cmd/web/routes.go +++ b/cmd/web/routes.go @@ -12,6 +12,8 @@ func (app *application) routes() http.Handler { mux.Get("/", app.Home) + mux.Get("/ws", app.WsEndPoint) + mux.Route("/admin", func(mux chi.Router) { mux.Use(app.Auth) mux.Get("/virtual-terminal", app.VirtualTerminal) diff --git a/cmd/web/templates/base.layout.gohtml b/cmd/web/templates/base.layout.gohtml index 561c0fa..927344f 100644 --- a/cmd/web/templates/base.layout.gohtml +++ b/cmd/web/templates/base.layout.gohtml @@ -105,7 +105,12 @@ - + {{ block "js" . }} {{ end }} diff --git a/cmd/web/ws-handlers.go b/cmd/web/ws-handlers.go new file mode 100644 index 0000000..072b5bd --- /dev/null +++ b/cmd/web/ws-handlers.go @@ -0,0 +1,109 @@ +package main + +import ( + "fmt" + "net/http" + + "github.com/gorilla/websocket" +) + +type WebSocketConnection struct { + *websocket.Conn +} + +type WsPayload struct { + Action string `json:"action"` + Message string `json:"message"` + UserName string `json:"user_name"` + MessageType string `json:"message_type"` + UserID int `json:"user_id"` + Conn WebSocketConnection `json:"-"` +} + +type WsJSONResponse struct { + Action string `json:"action"` + Message string `json:"message"` + UserID int `json:"user_id"` +} + +var upgradeConnection = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + // since we don't expect any communication from the client side + CheckOrigin: func(r *http.Request) bool { return true }, +} + +var clients = make(map[WebSocketConnection]string) + +var wsChan = make(chan WsPayload) + +func (app *application) WsEndPoint(w http.ResponseWriter, r *http.Request) { + ws, err := upgradeConnection.Upgrade(w, r, nil) + if err != nil { + app.errorLog.Println(err) + return + } + + app.infoLog.Printf("Client connected from %s\n", r.RemoteAddr) + var response WsJSONResponse + response.Message = "Connected to server" + + err = ws.WriteJSON(response) + if err != nil { + app.errorLog.Println(err) + return + } + + conn := WebSocketConnection{Conn: ws} + clients[conn] = "" + + go app.ListenForWS(&conn) +} + +func (app *application) ListenForWS(conn *WebSocketConnection) { + defer func() { + if r := recover(); r != nil { + app.errorLog.Println("ERROR:", fmt.Sprintf("%v", r)) + } + }() + + var payload WsPayload + + for { + err := conn.ReadJSON(&payload) + if err != nil { + // do nothing + app.errorLog.Println(err) + break + } + payload.Conn = *conn + wsChan <- payload + } +} + +func (app *application) ListenToWsChannel() { + var response WsJSONResponse + for { + e := <-wsChan + switch e.Action { + case "deleteUser": + response.Action = "logout" + response.Message = "Your account has ben deleted" + response.UserID = e.UserID + app.broadcastToAll(response) + default: + } + } +} + +func (app *application) broadcastToAll(response WsJSONResponse) { + for client := range clients { + // broadcast to every connected client + err := client.WriteJSON(response) + if err != nil { + app.errorLog.Printf("Websocket err on %s: %s", response.Action, err) + _ = client.Close() + delete(clients, client) + } + } +} diff --git a/go.mod b/go.mod index 2c4428b..85f88ff 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/cors v1.2.1 github.com/go-sql-driver/mysql v1.8.1 + github.com/gorilla/websocket v1.5.3 github.com/stripe/stripe-go/v79 v79.6.0 github.com/xhit/go-simple-mail/v2 v2.16.0 golang.org/x/crypto v0.26.0 diff --git a/go.sum b/go.sum index 4f397a2..596620a 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/static/js/base.js b/static/js/base.js index c5ef50f..0f589a1 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -1,3 +1,32 @@ +export let socket; + +export function wsConn(is_authenticated, user_id) { + if (is_authenticated !== 1) { + return; + } + + socket = new WebSocket("ws://localhost:4000/ws") + socket.onopen = () => { + console.log("Successfully connected to websockets") + } + + socket.onclose = event => {}; + socket.onerror = error => {}; + + socket.onmessage = msg => { + let data = JSON.parse(msg.data); + + switch (data.action) { + case "logout": + if (data.user_id === user_id) { + logout() + } + break; + default: + } + } +} + // let loginLink = document.getElementById("login-link"); // let vtLink = document.getElementById("vt-link"); // @@ -11,9 +40,9 @@ // loginLink.classList.remove('d-none') // }); -// function logout() { -// localStorage.removeItem("token"); -// localStorage.removeItem("token_expiry"); -// location.href = "/logout"; -// } +function logout() { + localStorage.removeItem("token"); + localStorage.removeItem("token_expiry"); + location.href = "/logout"; +} diff --git a/static/js/users.js b/static/js/users.js index e311007..76c4ca6 100644 --- a/static/js/users.js +++ b/static/js/users.js @@ -1,3 +1,5 @@ +import {socket} from "./base.js" + export function showUsers(api) { const tbody = document.getElementById("user-table").getElementsByTagName("tbody")[0]; const token = localStorage.getItem("token"); @@ -14,6 +16,7 @@ export function showUsers(api) { fetch(api + "/api/admin/all-users", requestOptions) .then(response => response.json()) .then(function (data) { + console.log(data) if (data) { data.forEach(i => { let newRow = tbody.insertRow(); @@ -63,21 +66,6 @@ export function showUser(api, userID) { document.getElementById("last_name").value = data.last_name; document.getElementById("email").value = data.email; }); - - delBtn.addEventListener("click", function () { - Swal.fire({ - title: "Are you sure?", - text: "You won't be able to undo this!", - icon: "warning", - showCancelButton: true, - confirmButtonColor: "#3085d6", - cancelButtonColor: "#d33", - confirmButtonText: 'Delete User', - }).then((result) => { - console.log("would delete user id", id); - }); - }) - } export function saveUser(api, event) { @@ -160,6 +148,11 @@ export function deleteUser(api) { if (!data.ok) { Swal.fire("Error" + data.message) } else { + let jsonData = { + action: "deleteUser", + user_id: parseInt(id), + }; + socket.send(JSON.stringify(jsonData)); location.href = "/admin/all-users" } });