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)