From a3f4bb24e7bf0eecb7e11df870ecc97227fc08ed Mon Sep 17 00:00:00 2001 From: arzumify Date: Wed, 13 Nov 2024 17:59:01 +0000 Subject: [PATCH] Updated everything, fixed deleting message synchronisation, fixed chromium support, added inviting other users as an admin --- server/main.go | 27 +++-- web/go.mod | 5 +- web/go.sum | 10 +- web/resources/index.html | 1 + web/resources/static/css/styles.css | 8 +- web/wasm/app.go | 152 +++++++++++++++++++++------- web/wasm/login.go | 7 +- web/wasm/signup.go | 2 +- 8 files changed, 155 insertions(+), 57 deletions(-) diff --git a/server/main.go b/server/main.go index aa372cb..5d299c7 100644 --- a/server/main.go +++ b/server/main.go @@ -298,7 +298,8 @@ func main() { } var userId uuid.UUID - err = conn.QueryRow("SELECT id FROM users WHERE username = ?", data.Username).Scan(&userId) + var administrator int + err = conn.QueryRow("SELECT administrator, id FROM users WHERE username = ?", data.Username).Scan(&administrator, &userId) if err != nil { w.WriteHeader(http.StatusBadRequest) renderJSON(map[string]interface{}{"error": "Invalid username"}, w) @@ -328,7 +329,11 @@ func main() { } w.WriteHeader(http.StatusOK) - renderJSON(map[string]interface{}{"token": token, "userId": userId.String()}, w) + returnJSON := map[string]interface{}{"token": token, "userId": userId.String()} + if administrator == 1 { + returnJSON["admin"] = true + } + renderJSON(returnJSON, w) }) router.Post("/api/invite", func(w http.ResponseWriter, r *http.Request) { @@ -412,7 +417,7 @@ func main() { return } - messages := make(map[string]interface{}) + var messages []map[string]interface{} rows, err := conn.Query("SELECT sender, senderName, message, sent, id FROM messages ORDER BY sent ASC") if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -435,12 +440,13 @@ func main() { return } - messages[id.String()] = map[string]interface{}{ + messages = append(messages, map[string]interface{}{ "sender": sender.String(), "name": senderName, "message": message, "sent": sent, - } + "id": id.String(), + }) } w.WriteHeader(http.StatusOK) @@ -590,7 +596,16 @@ func main() { }() }) - err = http.ListenAndServe(":1974", router) + err = http.ListenAndServe(":1974", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + if r.Method != "OPTIONS" { + router.ServeHTTP(w, r) + } else { + w.WriteHeader(http.StatusOK) + } + })) if err != nil { slog.Error("Failed to start server: " + err.Error()) os.Exit(1) diff --git a/web/go.mod b/web/go.mod index c7f001a..fd23565 100644 --- a/web/go.mod +++ b/web/go.mod @@ -3,12 +3,11 @@ module git.ailur.dev/arzumify/lchat/web go 1.23.3 require ( - git.ailur.dev/ailur/jsFetch v1.1.1 - github.com/golang-jwt/jwt/v5 v5.2.1 + git.ailur.dev/ailur/jsFetch v1.2.0 golang.org/x/crypto v0.29.0 ) require ( - git.ailur.dev/ailur/jsStreams v1.2.0 // indirect + git.ailur.dev/ailur/jsStreams v1.2.1 // indirect golang.org/x/sys v0.27.0 // indirect ) diff --git a/web/go.sum b/web/go.sum index 5041dde..81a6547 100644 --- a/web/go.sum +++ b/web/go.sum @@ -1,9 +1,7 @@ -git.ailur.dev/ailur/jsFetch v1.1.1 h1:kdCkrNr2mRvTG6hlK3YwnqlwfvzIQaw4z4AXLXewQ38= -git.ailur.dev/ailur/jsFetch v1.1.1/go.mod h1:eaQVFOlHwcPHCqh3oyQkQrpltmILOaiA9DKq3oTHBbM= -git.ailur.dev/ailur/jsStreams v1.2.0 h1:BRtLEyjkUoPKPu0Y6odUbSMlKCYNyR792TYRtujKfPw= -git.ailur.dev/ailur/jsStreams v1.2.0/go.mod h1:/ZCvbUcWkZRuKIkO7jH6b5vIjzdxIOP8ET8X0src5Go= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +git.ailur.dev/ailur/jsFetch v1.2.0 h1:OgZ/jYheuz/v5mAO6hKE2IetANQCcJVT4xHlFi25YYU= +git.ailur.dev/ailur/jsFetch v1.2.0/go.mod h1:B7qkj7z5gTXOP/YiUH2DwbDimSgBLI+Pd8PcGD4DRmY= +git.ailur.dev/ailur/jsStreams v1.2.1 h1:nXZYZrxHJCVwR0Kx/X+TenMBmS6Gh8Uc2DMinbyiGoo= +git.ailur.dev/ailur/jsStreams v1.2.1/go.mod h1:/ZCvbUcWkZRuKIkO7jH6b5vIjzdxIOP8ET8X0src5Go= golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= diff --git a/web/resources/index.html b/web/resources/index.html index 4a7da48..b2d55db 100644 --- a/web/resources/index.html +++ b/web/resources/index.html @@ -16,6 +16,7 @@
LChat +
diff --git a/web/resources/static/css/styles.css b/web/resources/static/css/styles.css index 6ae3c30..18f7f71 100644 --- a/web/resources/static/css/styles.css +++ b/web/resources/static/css/styles.css @@ -32,13 +32,17 @@ body { } .topBar button { - margin-left: auto; background: none; border: none; color: white; font-size: 16px; } +.topBar .invite { + margin-left: auto; + display: none; +} + .topBar span { font-size: 18px; font-weight: 400; @@ -47,7 +51,7 @@ body { .messageBox { display: flex; flex-direction: column; - overflow-y: scroll; + overflow-y: auto; max-height: calc(100% - 148px); padding: 11.5px 10px 0 10px; margin: 65px 0; diff --git a/web/wasm/app.go b/web/wasm/app.go index d5207fc..43ff0ac 100644 --- a/web/wasm/app.go +++ b/web/wasm/app.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "git.ailur.dev/ailur/jsFetch" + "strconv" "syscall/js" "time" ) @@ -39,7 +40,7 @@ func refreshMessages(localStorage js.Value, messageBox js.Value, userId string) return } - var body map[string]interface{} + var body []map[string]interface{} err = json.NewDecoder(response.Body).Decode(&body) if err != nil { alert("Failed to decode response: " + err.Error()) @@ -50,9 +51,14 @@ func refreshMessages(localStorage js.Value, messageBox js.Value, userId string) messageBox.Get("lastChild").Get("style").Set("margin-bottom", "15px") } + ids := make(map[string]struct{}) + for _, message := range body { + ids[message["id"].(string)] = struct{}{} + } + if len(messageDivs) > len(body) { - for id, _ := range messageDivs { - _, exists := body[id] + for id := range messageDivs { + _, exists := ids[id] if !exists { messageDivs[id].Call("remove") delete(messageDivs, id) @@ -60,19 +66,19 @@ func refreshMessages(localStorage js.Value, messageBox js.Value, userId string) } } - for id, message := range body { - _, exists := messageDivs[id] + for _, message := range body { + _, exists := messageDivs[message["id"].(string)] if !exists { - messageDiv := createMessage(message.(map[string]interface{})["message"].(string), message.(map[string]interface{})["name"].(string), message.(map[string]interface{})["sender"].(string) == userId, message.(map[string]interface{})["id"].(string), time.Unix(int64(message.(map[string]interface{})["sent"].(float64)), 0)) + messageDiv := createMessage(message["message"].(string), message["name"].(string), message["sender"].(string) == userId, message["id"].(string), time.Unix(int64(message["sent"].(float64)), 0)) messageBox.Call("appendChild", messageDiv) - messageDivs[id] = messageDiv + messageDivs[message["id"].(string)] = messageDiv } } if !messageBox.Get("lastChild").IsNull() { messageBox.Get("lastChild").Get("style").Set("margin-bottom", "22.5px") } - messageBox.Set("scrollTop", messageBox.Get("scrollTopMax").Float()) + messageBox.Set("scrollTop", messageBox.Get("scrollHeight").Float()) } func alert(message string) { @@ -143,6 +149,40 @@ func createMessage(message string, user string, delete bool, id string, timestam return div } +func handleSend(sendField js.Value, localStorage js.Value) { + jsonBody, err := json.Marshal(map[string]interface{}{ + "message": sendField.Get("value").String(), + "token": localStorage.Call("getItem", "token").String(), + }) + if err != nil { + alert("Failed to encode request: " + err.Error()) + return + } + + response, err := jsFetch.Post(localStorage.Call("getItem", "server").String()+"/api/send", "application/json", bytes.NewReader(jsonBody)) + if err != nil { + alert("Failed to contact server: " + err.Error()) + return + } + + if response.StatusCode != 200 { + if response.StatusCode == 500 { + alert("Something went wrong on our end. Please try again later.") + } else { + var body map[string]interface{} + err := json.NewDecoder(response.Body).Decode(&body) + if err != nil { + alert("Failed to decode response: " + err.Error()) + } else { + alert(body["error"].(string)) + } + } + return + } + + sendField.Set("value", "") +} + func main() { localStorage := js.Global().Get("localStorage") login := js.Global().Get("document").Call("getElementById", "login") @@ -151,6 +191,7 @@ func main() { sendField := js.Global().Get("document").Call("getElementById", "sendField") send := js.Global().Get("document").Call("getElementById", "send") messageBox := js.Global().Get("document").Call("getElementById", "messageBox") + inviteButton := js.Global().Get("document").Call("getElementById", "invite") if localStorage.Call("getItem", "server").IsNull() { localStorage.Call("setItem", "server", "https://chat.ailur.dev:1974") @@ -160,6 +201,62 @@ func main() { return } + if localStorage.Call("getItem", "admin").String() == "true" { + inviteButton.Get("style").Set("display", "initial") + inviteButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go func() { + uses := js.Global().Get("prompt").Invoke("How many uses should the invite have?") + usesInt, err := strconv.Atoi(uses.String()) + if err != nil { + alert("Invalid number of uses.") + return + } + + jsonBody, err := json.Marshal(map[string]interface{}{ + "token": localStorage.Call("getItem", "token").String(), + "uses": usesInt, + }) + if err != nil { + alert("Failed to encode request: " + err.Error()) + return + } + + response, err := jsFetch.Post(localStorage.Call("getItem", "server").String()+"/api/invite", "application/json", bytes.NewReader(jsonBody)) + if err != nil { + alert("Failed to contact server: " + err.Error()) + return + } + + if response.StatusCode != 200 { + if response.StatusCode == 500 { + alert("Something went wrong on our end. Please try again later.") + } else { + var body map[string]interface{} + err := json.NewDecoder(response.Body).Decode(&body) + if err != nil { + alert("Failed to decode response: " + err.Error()) + } else { + alert(body["error"].(string)) + } + } + return + } + + var body map[string]interface{} + err = json.NewDecoder(response.Body).Decode(&body) + if err != nil { + alert("Failed to decode response: " + err.Error()) + return + } + + alert("Your invite URL is: " + js.Global().Get("location").Get("origin").String() + "/invite?invite=" + body["code"].(string)) + }() + return nil + })) + } else { + logout.Get("style").Set("margin-left", "auto") + } + userId := localStorage.Call("getItem", "userId").String() login.Get("style").Set("display", "none") @@ -168,43 +265,22 @@ func main() { logout.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} { go func() { localStorage.Call("removeItem", "token") + localStorage.Call("removeItem", "userId") + localStorage.Call("removeItem", "admin") js.Global().Get("location").Set("href", "login") }() return nil })) send.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} { - go func() { - jsonBody, err := json.Marshal(map[string]interface{}{ - "message": sendField.Get("value").String(), - "token": localStorage.Call("getItem", "token").String(), - }) - if err != nil { - alert("Failed to encode request: " + err.Error()) - return - } + go handleSend(sendField, localStorage) + return nil + })) - response, err := jsFetch.Post(localStorage.Call("getItem", "server").String()+"/api/send", "application/json", bytes.NewReader(jsonBody)) - if err != nil { - alert("Failed to contact server: " + err.Error()) - return - } - - if response.StatusCode != 200 { - if response.StatusCode == 500 { - alert("Something went wrong on our end. Please try again later.") - } else { - var body map[string]interface{} - err := json.NewDecoder(response.Body).Decode(&body) - if err != nil { - alert("Failed to decode response: " + err.Error()) - } else { - alert(body["error"].(string)) - } - } - return - } - }() + sendField.Call("addEventListener", "keypress", js.FuncOf(func(this js.Value, args []js.Value) interface{} { + if args[0].Get("key").String() == "Enter" { + go handleSend(sendField, localStorage) + } return nil })) diff --git a/web/wasm/login.go b/web/wasm/login.go index 9f3ac48..3cf44cd 100644 --- a/web/wasm/login.go +++ b/web/wasm/login.go @@ -158,8 +158,13 @@ func main() { } localStorage.Call("setItem", "token", body["token"].(string)) - localStorage.Call("setItem", "username", username.Get("value").String()) localStorage.Call("setItem", "userId", body["userId"].(string)) + _, exists := body["admin"] + if exists { + localStorage.Call("setItem", "admin", "true") + } else { + localStorage.Call("setItem", "admin", "false") + } status.Set("innerText", "Welcome, "+username.Get("value").String()+", to LChat!") time.Sleep(300 * time.Millisecond) diff --git a/web/wasm/signup.go b/web/wasm/signup.go index 2a9f844..77a0fd3 100644 --- a/web/wasm/signup.go +++ b/web/wasm/signup.go @@ -115,8 +115,8 @@ func main() { } localStorage.Call("setItem", "token", body["token"].(string)) - localStorage.Call("setItem", "username", username.Get("value").String()) localStorage.Call("setItem", "userId", body["userId"].(string)) + localStorage.Call("setItem", "admin", "false") status.Set("innerText", "Welcome, "+username.Get("value").String()+", to LChat!") time.Sleep(300 * time.Millisecond)