diff --git a/server/db.go b/server/db.go index 6eb0dec..304c1f0 100644 --- a/server/db.go +++ b/server/db.go @@ -67,6 +67,7 @@ func ConnectDbFromEnv() { type Dummy struct { ID uuid.UUID `gorm:"primaryKey;type:uuid;unique" json:"id"` + Name string `json:"name"` Connected bool `json:"connected"` ConnectedAt time.Time `json:"connectedAt"` DisconnectedAt time.Time `json:"disconnectedAt"` diff --git a/server/main.go b/server/main.go index 12338a4..499a550 100644 --- a/server/main.go +++ b/server/main.go @@ -9,6 +9,7 @@ import ( "syscall" "time" + "github.com/google/uuid" "github.com/gorilla/websocket" "github.com/joho/godotenv" "github.com/labstack/echo/v4" @@ -19,19 +20,32 @@ import ( const TimeFormat = "2006-01-02 15:04:05" type Client struct { - ID int - conn *websocket.Conn - Exit bool + Obj *Dummy `json:"obj"` + conn *websocket.Conn `json:"-"` + ExitWanted bool `json:"exitWanted"` } -var id = 0 var clients = []*Client{} var dataDir = "." var token = "" var upgrader = websocket.Upgrader{} type ReqData struct { - ID int `json:"id"` + ID uuid.UUID `json:"id"` +} + +type IDParam struct { + ID string `param:"id"` +} + +func authed(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if c.Request().Header.Get("Authorization") != token { + return c.NoContent(http.StatusUnauthorized) + } + + return next(c) + } } func main() { @@ -104,31 +118,28 @@ func main() { } } + // client connection app.GET("/", func(c echo.Context) error { return c.JSON(http.StatusOK, "Hello World!") }) app.GET("/keys", keys) - app.GET("/clients", func(c echo.Context) error { + // administration + app.GET("/admin/clients", func(c echo.Context) error { if c.Request().Header.Get("Authorization") != token { return c.NoContent(http.StatusUnauthorized) } - var a = []echo.Map{} - for _, client := range clients { - a = append(a, echo.Map{ - "id": client.ID, - "exit": client.Exit, - }) - } - - return c.JSON(http.StatusOK, a) + return c.JSON(http.StatusOK, clients) }) - app.POST("/exit", func(c echo.Context) error { - if c.Request().Header.Get("Authorization") != token { - return c.NoContent(http.StatusUnauthorized) - } - + app.GET("/admin/:id/logs", authed(func(c echo.Context) error { + // TODO: finish + return nil + })) + app.POST("/admin/:id/name", authed(func(c echo.Context) error { + return nil + })) + app.POST("/admin/exit", authed(func(c echo.Context) error { var data ReqData err := c.Bind(&data) if err != nil { @@ -136,13 +147,13 @@ func main() { } for _, client := range clients { - if client.ID == data.ID { - client.Exit = true + if client.Obj.ID == data.ID { + client.ExitWanted = true return c.JSON(http.StatusOK, echo.Map{"message": "ok"}) } } return c.JSON(http.StatusNotFound, echo.Map{"message": "not found"}) - }) + })) log.Fatal(app.Start(":8080")) } @@ -154,25 +165,53 @@ func keys(c echo.Context) error { } defer ws.Close() + now := time.Now() client := Client{ - ID: id, - conn: ws, - Exit: false, + Obj: &Dummy{ + ID: uuid.New(), + Name: "Dummy", + Connected: true, + ConnectedAt: now, + DisconnectedAt: now, + CreatedAt: now, + UpdatedAt: now, + }, + conn: ws, + ExitWanted: false, + } + + tx := db.Create(client.Obj) + if tx.Error != nil { + log.Error(err) + fmt.Printf("%s client %s crashed (couldn't create record)\n", time.Now().Format(TimeFormat), client.Obj.ID.String()) + client.ExitWanted = true + client.Obj.Connected = false + client.Obj.DisconnectedAt = time.Now() + return c.NoContent(http.StatusInternalServerError) } clients = append(clients, &client) - id += 1 - fmt.Printf("%s client %-3d connected\n", time.Now().Format(TimeFormat), client.ID) + fmt.Printf("%s client %s connected\n", time.Now().Format(TimeFormat), client.Obj.ID.String()) err = ws.WriteMessage(websocket.TextMessage, []byte("welcome")) if err != nil { log.Error(err) } - f, err := os.OpenFile(fmt.Sprintf("%s/%s_%d.txt", dataDir, time.Now().Format("2006-01-02_15-04"), client.ID), syscall.O_CREAT|syscall.O_APPEND|syscall.O_WRONLY, 0644) + f, err := os.OpenFile(fmt.Sprintf("%s/%s_%d.txt", dataDir, time.Now().Format("2006-01-02_15-04"), client.Obj.ID.String()), syscall.O_CREAT|syscall.O_APPEND|syscall.O_WRONLY, 0644) if err != nil { log.Error(err) - fmt.Printf("%s client %-3d crashed (couldn't open file)\n", time.Now().Format(TimeFormat), client.ID) - client.Exit = true + fmt.Printf("%s client %s crashed (couldn't open file)\n", time.Now().Format(TimeFormat), client.Obj.ID) + now = time.Now() + + client.ExitWanted = true + client.Obj.Connected = false + client.Obj.DisconnectedAt = now + client.Obj.UpdatedAt = now + + tx = db.Save(client.Obj) + if tx.Error != nil { + log.Error(err) + } return c.NoContent(http.StatusInternalServerError) } defer f.Close() @@ -180,13 +219,23 @@ func keys(c echo.Context) error { close := func() { _ = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) _ = ws.Close() - client.Exit = true + now = time.Now() + + client.ExitWanted = true + client.Obj.Connected = false + client.Obj.DisconnectedAt = now + client.Obj.UpdatedAt = now + + tx = db.Save(client.Obj) + if tx.Error != nil { + log.Error(err) + } } for { - if client.Exit { + if client.ExitWanted { close() - fmt.Printf("%s client %-3d disconnected\n", time.Now().Format(TimeFormat), client.ID) + fmt.Printf("%s client %s disconnected\n", time.Now().Format(TimeFormat), client.Obj.ID.String()) break } @@ -194,16 +243,16 @@ func keys(c echo.Context) error { if err != nil { log.Error(err) close() - fmt.Printf("%s client %-3d crashed (websocket error)\n", time.Now().Format(TimeFormat), client.ID) - break + fmt.Printf("%s client %s crashed (websocket error)\n", time.Now().Format(TimeFormat), client.Obj.ID.String()) + return c.NoContent(http.StatusInternalServerError) } _, err = f.Write(msg) if err != nil { log.Error(err) close() - fmt.Printf("%s client %-3d crashed (file write error)\n", time.Now().Format(TimeFormat), client.ID) - break + fmt.Printf("%s client %s crashed (file write error)\n", time.Now().Format(TimeFormat), client.Obj.ID.String()) + return c.NoContent(http.StatusInternalServerError) } }