diff --git a/README.md b/README.md index 554e21b..63e213b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -# csl-backend -## Combat Surf League API +# Combat Surf League API `go run .`
-`go build -o bin/hello`
diff --git a/go.mod b/go.mod index f0ddd35..92c6059 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,6 @@ require ( ) require ( - github.com/gorilla/securecookie v1.1.1 // indirect - github.com/jmoiron/sqlx v1.3.4 // indirect + github.com/gorilla/websocket v1.5.0 + github.com/jmoiron/sqlx v1.3.4 ) diff --git a/go.sum b/go.sum index c05a910..97082c4 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +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/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= diff --git a/server.go b/main.go similarity index 81% rename from server.go rename to main.go index b55e015..3ef206b 100644 --- a/server.go +++ b/main.go @@ -9,10 +9,12 @@ import ( "github.com/robyzzz/csl-backend/controller" "github.com/robyzzz/csl-backend/middleware" "github.com/robyzzz/csl-backend/model" + "github.com/robyzzz/csl-backend/websocket" ) var router *mux.Router +// start server func main() { config.GetEnvVariables() model.Connect() @@ -34,4 +36,12 @@ func setupRouter() { // player stats router.HandleFunc("/api/playerstats/{steamid}", controller.GetPlayerStats).Methods("GET") + + // + websocket.WS = websocket.NewWebsocketServer() + go websocket.WS.Run() + + log.Println("oia") + + router.HandleFunc("/ws", websocket.ServeWs) } diff --git a/model/player.go b/model/player.go index d84895d..7865c66 100644 --- a/model/player.go +++ b/model/player.go @@ -1,6 +1,6 @@ package model -type CSLPlayerStats struct { +type PlayerStats struct { ID uint64 `json:"id"` Player_SteamID string `json:"player_steamid"` Map_ID int `json:"map_id"` @@ -24,8 +24,8 @@ type CSLPlayerStats struct { Mvp int `json:"mvp"` } -func GetPlayerStats(steamID string) (CSLPlayerStats, error) { - user := CSLPlayerStats{} +func GetPlayerStats(steamID string) (PlayerStats, error) { + user := PlayerStats{} err := db.Get(&user, "SELECT * FROM player_stats WHERE player_steamid = $1;", steamID) return user, err } diff --git a/websocket/client.go b/websocket/client.go new file mode 100644 index 0000000..aa81a86 --- /dev/null +++ b/websocket/client.go @@ -0,0 +1,140 @@ +package websocket + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/gorilla/websocket" +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 4096, + WriteBufferSize: 4096, + CheckOrigin: func(r *http.Request) bool { return true }, +} + +type Client struct { + conn *websocket.Conn + wsServer *WsServer + send chan []byte + SteamID string `json:"steamid"` + Name string `json:"name"` + rooms map[*Room]bool +} + +func NewClient(conn *websocket.Conn, wsServer *WsServer, name string, steamID string) *Client { + client := &Client{ + SteamID: steamID, + Name: name, + conn: conn, + wsServer: wsServer, + send: make(chan []byte, 256), + rooms: make(map[*Room]bool), + } + return client +} + +func ServeWs(w http.ResponseWriter, r *http.Request) { + // get params + name := r.URL.Query().Get("name") + steamid := r.URL.Query().Get("steamid") + + if len(name) == 0 || len(steamid) == 0 { + log.Println("no name or no steamid") + return + } + + // receive websocket connection + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println("smt went wrong") + return + } + + client := NewClient(conn, WS, name, steamid) + + // handle I/O for client + go client.readPump() + go client.writePump() + + WS.register <- client +} + +func (client *Client) readPump() { + defer client.disconnect() + + //client.conn.SetReadLimit(maxMessageSize) + //client.conn.SetReadDeadline(time.Now().Add(pongWait)) + //client.conn.SetPongHandler(func(string) error { client.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + + // Start endless read loop, waiting for messages from client + for { + _, jsonMessage, err := client.conn.ReadMessage() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { + log.Printf("unexpected close error: %v", err) + } + break + } + + client.handleNewMessage(jsonMessage) + } +} + +func (client *Client) writePump() { + defer client.conn.Close() + for { + select { + case message, ok := <-client.send: + log.Println("write pump") + if !ok { + // The WsServer closed the channel. + client.conn.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + + w, err := client.conn.NextWriter(websocket.TextMessage) + if err != nil { + return + } + w.Write(message) + + // Attach queued chat messages to the current websocket message. + n := len(client.send) + for i := 0; i < n; i++ { + w.Write([]byte("\n")) + w.Write(<-client.send) + } + + if err := w.Close(); err != nil { + return + } + + } + } +} + +func (client *Client) disconnect() { + client.wsServer.unregister <- client + for room := range client.rooms { + room.unregister <- client + } + close(client.send) + client.conn.Close() +} + +func (client *Client) handleNewMessage(jsonMessage []byte) { + var message Message + if err := json.Unmarshal(jsonMessage, &message); err != nil { + log.Printf("Error on unmarshal JSON message %s", err) + return + } + + log.Println("handle new message: ") + log.Println(message) + + // send msg + // join room + // leave room +} diff --git a/websocket/index.html b/websocket/index.html new file mode 100644 index 0000000..093367d --- /dev/null +++ b/websocket/index.html @@ -0,0 +1,111 @@ + + + +Chat Example + + + + +
+
+ + +
+ + diff --git a/websocket/message.go b/websocket/message.go new file mode 100644 index 0000000..5179211 --- /dev/null +++ b/websocket/message.go @@ -0,0 +1,26 @@ +package websocket + +import ( + "encoding/json" + "log" +) + +const SendMessageAction = "send-message" +const JoinRoomAction = "join-room" +const LeaveRoomAction = "leave-room" + +type Message struct { + Action string `json:"action"` + Message string `json:"message"` + Target string `json:"target"` + Sender *Client `json:"sender"` +} + +func (message *Message) encode() []byte { + json, err := json.Marshal(message) + if err != nil { + log.Println(err) + } + + return json +} diff --git a/websocket/room.go b/websocket/room.go new file mode 100644 index 0000000..58894b4 --- /dev/null +++ b/websocket/room.go @@ -0,0 +1,74 @@ +package websocket + +import ( + "fmt" + "log" +) + +type Room struct { + ID uint `json:"id"` + clients map[*Client]bool + register chan *Client + unregister chan *Client + broadcast chan *Message +} + +// NewRoom creates a new Room +func NewRoom(name string, private bool) *Room { + return &Room{ + ID: 1337, + clients: make(map[*Client]bool), + register: make(chan *Client), + unregister: make(chan *Client), + broadcast: make(chan *Message), + } +} + +func (room *Room) RunRoom() { + for { + select { + + case client := <-room.register: + room.registerClientInRoom(client) + + case client := <-room.unregister: + room.unregisterClientInRoom(client) + + case message := <-room.broadcast: + room.publishRoomMessage(message.encode()) + } + + } +} + +func (room *Room) registerClientInRoom(client *Client) { + log.Println("new client %x in room %x", client, room) + room.clients[client] = true +} + +func (room *Room) unregisterClientInRoom(client *Client) { + if _, ok := room.clients[client]; ok { + delete(room.clients, client) + log.Println("removed client %x from room %x", client, room) + } +} + +func (room *Room) broadcastToClientsInRoom(message []byte) { + for client := range room.clients { + client.send <- message + } +} + +func (room *Room) publishRoomMessage(message []byte) { + log.Println("new msg in room %x: %s", room, message) +} + +func (room *Room) notifyClientJoined(client *Client) { + message := &Message{ + Action: SendMessageAction, + Target: fmt.Sprintf("room:%d", room.ID), + Message: fmt.Sprintf("client %s joined", "idk"), + } + + room.publishRoomMessage(message.encode()) +} diff --git a/websocket/server.go b/websocket/server.go new file mode 100644 index 0000000..0494e8f --- /dev/null +++ b/websocket/server.go @@ -0,0 +1,70 @@ +package websocket + +import "log" + +type WsServer struct { + clients map[*Client]bool + register chan *Client + unregister chan *Client + rooms map[*Room]bool +} + +var WS *WsServer + +func NewWebsocketServer() *WsServer { + wsServer := &WsServer{ + clients: make(map[*Client]bool), + register: make(chan *Client), + unregister: make(chan *Client), + rooms: make(map[*Room]bool), + } + + return wsServer +} + +// Run the websocket server +func (sv *WsServer) Run() { + for { + select { + + case client := <-sv.register: + sv.registerClient(client) + + case client := <-sv.unregister: + sv.unregisterClient(client) + } + } +} + +func (sv *WsServer) registerClient(client *Client) { + sv.publishClientJoined(client) + //sv.listOnlineClients(client) + sv.clients[client] = true +} + +func (sv *WsServer) unregisterClient(client *Client) { + if _, ok := sv.clients[client]; ok { + delete(sv.clients, client) + sv.publishClientLeft(client) + } +} + +func (sv *WsServer) publishClientJoined(client *Client) { + message := &Message{ + Action: "UserJoinedAction", + Sender: client, + } + + log.Println("Someone connected to the websocket") + log.Println(message) +} + +func (sv *WsServer) publishClientLeft(client *Client) { + message := &Message{ + Action: "UserLeftAction", + Sender: client, + } + + log.Println("Someone disconnected from the websocket") + log.Println(message) +}