From b61fc7fe76c23012101e4dd11b9cf07a2754b996 Mon Sep 17 00:00:00 2001 From: Gitea Date: Sat, 7 May 2022 10:21:35 +0300 Subject: [PATCH] Added message "memorisation" --- scripts/chat.js | 4 +- src/api/db.go | 35 +++++++------ src/api/limits.go | 7 +++ src/api/message.go | 92 ++++++++++++++++++++++------------ src/api/user.go | 4 -- src/api/websocket.go | 3 +- src/log/log.go | 2 +- src/server/websocketHandler.go | 26 +++++++++- 8 files changed, 116 insertions(+), 57 deletions(-) create mode 100644 src/api/limits.go diff --git a/scripts/chat.js b/scripts/chat.js index 3d51a1d..b0cabd5 100644 --- a/scripts/chat.js +++ b/scripts/chat.js @@ -48,8 +48,8 @@ areCredentialsValid({username:username, secret_hash:secret_hash}) socket.onmessage = event => { let message = JSON.parse(event.data); let from_user = message.from.username; - - let constructed_message_to_display = `${from_user}: ${message.contents}` + "\n"; + let date = new Date(message.timestamp).toLocaleString(); + let constructed_message_to_display = `[${date}] ${from_user}: ${message.contents}` + "\n"; let chatbox = document.getElementById("chatbox"); chatbox.innerHTML += constructed_message_to_display; diff --git a/src/api/db.go b/src/api/db.go index 0cac241..02021ea 100644 --- a/src/api/db.go +++ b/src/api/db.go @@ -25,8 +25,8 @@ import ( ) const ( - UsersTablename string = "users" - // MessagesTablename string = "messages" + UsersTablename string = "users" + MessagesTablename string = "messages" ) // SQL database wrapper @@ -51,21 +51,21 @@ func (db *DB) createUsersTable() error { return nil } -// func (db *DB) createMessagesTable() error { -// command := fmt.Sprintf( -// `CREATE TABLE IF NOT EXISTS %s -// (id INTEGER NOT NULL PRIMARY KEY, content TEXT NOT NULL, -// from_name TEXT NOT NULL, FOREIGN KEY(from_name) REFERENCES %s(username))`, -// MessagesTablename, UsersTablename, -// ) +func (db *DB) createMessagesTable() error { + command := fmt.Sprintf( + `CREATE TABLE IF NOT EXISTS %s + (id INTEGER NOT NULL PRIMARY KEY, content TEXT NOT NULL, + sender TEXT NOT NULL, timestamp INTEGER, FOREIGN KEY(sender) REFERENCES %s(username))`, + MessagesTablename, UsersTablename, + ) -// _, err := db.Exec(command) -// if err != nil { -// return err -// } + _, err := db.Exec(command) + if err != nil { + return err + } -// return nil -// } + return nil +} func (db *DB) setUpTables() error { err := db.createUsersTable() @@ -73,6 +73,11 @@ func (db *DB) setUpTables() error { return fmt.Errorf("error creating users table: %s", err) } + err = db.createMessagesTable() + if err != nil { + return fmt.Errorf("error creating messages table: %s", err) + } + return nil } diff --git a/src/api/limits.go b/src/api/limits.go new file mode 100644 index 0000000..09f2831 --- /dev/null +++ b/src/api/limits.go @@ -0,0 +1,7 @@ +package api + +// How many characters can message hold +const MaxMessageContentLen uint = 500 + +// How many messages can be "remembered" until removal +const MaxMessagesRemembered uint = 50 diff --git a/src/api/message.go b/src/api/message.go index d3df6ce..02e1a48 100644 --- a/src/api/message.go +++ b/src/api/message.go @@ -16,43 +16,69 @@ along with this program. If not, see . package api +import ( + "fmt" + "strconv" +) + // A message struct that is converted back and forth to JSON in order to communicate with frontend type Message struct { + ID uint64 TimeStamp uint64 `json:"timestamp"` From User `json:"from"` Contents string `json:"contents"` } -/* PROBABLY come back later and implement a message-remembering feature */ - -// func (db *DB) AddMessage(message Message) error { -// command := fmt.Sprintf("INSERT INTO %s(from, contents) VALUES(%s, %s)", MessagesTablename, message.From.Name, message.Contents) -// _, err := db.Exec(command) -// if err != nil { -// return err -// } - -// return nil -// } - -// func (db *DB) GetAllMessages() (*[]Message, error) { -// command := fmt.Sprintf("SELECT * FROM %s", MessagesTablename) -// rows, err := db.Query(command) -// if err != nil { -// return nil, err -// } - -// var messages []Message - -// /* -// (id INTEGER NOT NULL PRIMARY KEY, content TEXT NOT NULL, -// to_name TEXT NOT NULL, from_name TEXT NOT NULL, FOREIGN KEY(from_name, to_name) REFERENCES %s(username, username))`, -// */ -// for rows.Next() { -// var message Message -// rows.Scan(&message.ID, &message.Contents, message.From.Name) -// messages = append(messages, message) -// } - -// return &messages, nil -// } +// Add a new message to the database, following the limitations and removing the +// oldest one as well if the capacity has been exceeded +func (db *DB) AddMessage(message Message) error { + // check how many messages are already stored + command := fmt.Sprintf("SELECT COUNT(*) FROM %s", MessagesTablename) + result := db.QueryRow(command) + err := result.Err() + if err != nil { + return err + } + + var countStr string + result.Scan(&countStr) + count, err := strconv.ParseUint(countStr, 10, 64) + if err != nil { + return err + } + + if count >= uint64(MaxMessagesRemembered) { + // remove the last one + command = fmt.Sprintf("DELETE FROM %s WHERE timestamp = (SELECT MIN(timestamp) FROM %s)", MessagesTablename, MessagesTablename) + _, err := db.Exec(command) + if err != nil { + return err + } + } + + command = fmt.Sprintf("INSERT INTO %s(sender, content, timestamp) VALUES(\"%s\", \"%s\", %d)", MessagesTablename, message.From.Name, message.Contents, message.TimeStamp) + _, err = db.Exec(command) + if err != nil { + return err + } + + return nil +} + +func (db *DB) GetAllMessages() (*[]Message, error) { + command := fmt.Sprintf("SELECT * FROM %s", MessagesTablename) + rows, err := db.Query(command) + if err != nil { + return nil, err + } + + var messages []Message + + for rows.Next() { + var message Message + rows.Scan(&message.ID, &message.Contents, &message.From.Name, &message.TimeStamp) + messages = append(messages, message) + } + + return &messages, nil +} diff --git a/src/api/user.go b/src/api/user.go index fff6329..7bdc295 100644 --- a/src/api/user.go +++ b/src/api/user.go @@ -18,8 +18,6 @@ package api import ( "fmt" - - "unbewohnte.xyz/gochat/log" ) const HashLength uint = 64 @@ -63,8 +61,6 @@ func (db *DB) CreateUser(user *User) error { return err } - log.Info("created user \"%s\"", user.Name) - return nil } diff --git a/src/api/websocket.go b/src/api/websocket.go index b9bb630..77f5704 100644 --- a/src/api/websocket.go +++ b/src/api/websocket.go @@ -95,11 +95,12 @@ func (holder *WSHolder) HandleNewWebSocketMessages(ws *WS, output chan Message) break } - if len(newMessage.Contents) < 1 { + if len(newMessage.Contents) < 1 || uint(len(newMessage.Contents)) > MaxMessageContentLen { break } newMessage.From = ws.User + newMessage.TimeStamp = uint64(time.Now().UnixMilli()) output <- newMessage } diff --git a/src/log/log.go b/src/log/log.go index fa96419..531398c 100644 --- a/src/log/log.go +++ b/src/log/log.go @@ -32,7 +32,7 @@ var ( func init() { infoLog = log.New(os.Stdout, "[INFO] ", log.Ldate|log.Ltime) warningLog = log.New(os.Stdout, "[WARNING] ", log.Ldate|log.Ltime) - errorLog = log.New(os.Stdout, "[ERROR] ", log.Ldate|log.Ltime|log.Lshortfile) + errorLog = log.New(os.Stdout, "[ERROR] ", log.Ldate|log.Ltime) } // Set up loggers to write to the given writer diff --git a/src/server/websocketHandler.go b/src/server/websocketHandler.go index 909905f..2a7b25e 100644 --- a/src/server/websocketHandler.go +++ b/src/server/websocketHandler.go @@ -19,6 +19,7 @@ package server import ( "fmt" "net/http" + "time" "github.com/gorilla/websocket" "unbewohnte.xyz/gochat/api" @@ -64,9 +65,21 @@ func (s *Server) HandlerWebsockets(w http.ResponseWriter, req *http.Request) { // add this new authorized socket to the broadcast s.websockets.Sockets = append(s.websockets.Sockets, &newWS) - log.Info("A new websocket connection has been established with %s as \"%s\"", newWS.Socket.RemoteAddr(), newWS.User.Name) + log.Info("a new websocket connection has been established with %s as \"%s\"", newWS.Socket.RemoteAddr(), newWS.User.Name) go s.websockets.HandleNewWebSocketMessages(&newWS, s.incomingMessages) + // send this new socket all previous messages to display + allMessages, err := s.db.GetAllMessages() + if err != nil { + // well, or not + log.Error("could not get all previous messages to display for %s: %s", newWS.User.Name, err) + } else { + for _, message := range *allMessages { + newWS.Socket.WriteJSON(&message) + } + } + + // notify chat that a new user has connected newConnectionMessage := api.Message{ From: api.UserSystem, Contents: fmt.Sprintf("%s has connected", newWS.User.Name), @@ -82,6 +95,17 @@ func (s *Server) BroadcastMessages() { break } + if message.From.Name == api.UserSystem.Name { + // add timestapm manually for the system user + message.TimeStamp = uint64(time.Now().Unix()) + } + + // add incoming message to the db + err := s.db.AddMessage(message) + if err != nil { + log.Error("could not add a new message from %s to the db: %s", message.From.Name, err) + } + for _, ws := range s.websockets.Sockets { ws.Socket.WriteJSON(&message) }