Updated everything, fixed deleting message synchronisation, fixed chromium support, added inviting other users as an admin

This commit is contained in:
Tracker-Friendly 2024-11-13 17:59:01 +00:00
parent 1a607e30e0
commit a3f4bb24e7
8 changed files with 155 additions and 57 deletions

View File

@ -298,7 +298,8 @@ func main() {
} }
var userId uuid.UUID 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 { if err != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
renderJSON(map[string]interface{}{"error": "Invalid username"}, w) renderJSON(map[string]interface{}{"error": "Invalid username"}, w)
@ -328,7 +329,11 @@ func main() {
} }
w.WriteHeader(http.StatusOK) 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) { router.Post("/api/invite", func(w http.ResponseWriter, r *http.Request) {
@ -412,7 +417,7 @@ func main() {
return 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") rows, err := conn.Query("SELECT sender, senderName, message, sent, id FROM messages ORDER BY sent ASC")
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
@ -435,12 +440,13 @@ func main() {
return return
} }
messages[id.String()] = map[string]interface{}{ messages = append(messages, map[string]interface{}{
"sender": sender.String(), "sender": sender.String(),
"name": senderName, "name": senderName,
"message": message, "message": message,
"sent": sent, "sent": sent,
} "id": id.String(),
})
} }
w.WriteHeader(http.StatusOK) 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 { if err != nil {
slog.Error("Failed to start server: " + err.Error()) slog.Error("Failed to start server: " + err.Error())
os.Exit(1) os.Exit(1)

View File

@ -3,12 +3,11 @@ module git.ailur.dev/arzumify/lchat/web
go 1.23.3 go 1.23.3
require ( require (
git.ailur.dev/ailur/jsFetch v1.1.1 git.ailur.dev/ailur/jsFetch v1.2.0
github.com/golang-jwt/jwt/v5 v5.2.1
golang.org/x/crypto v0.29.0 golang.org/x/crypto v0.29.0
) )
require ( 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 golang.org/x/sys v0.27.0 // indirect
) )

View File

@ -1,9 +1,7 @@
git.ailur.dev/ailur/jsFetch v1.1.1 h1:kdCkrNr2mRvTG6hlK3YwnqlwfvzIQaw4z4AXLXewQ38= git.ailur.dev/ailur/jsFetch v1.2.0 h1:OgZ/jYheuz/v5mAO6hKE2IetANQCcJVT4xHlFi25YYU=
git.ailur.dev/ailur/jsFetch v1.1.1/go.mod h1:eaQVFOlHwcPHCqh3oyQkQrpltmILOaiA9DKq3oTHBbM= git.ailur.dev/ailur/jsFetch v1.2.0/go.mod h1:B7qkj7z5gTXOP/YiUH2DwbDimSgBLI+Pd8PcGD4DRmY=
git.ailur.dev/ailur/jsStreams v1.2.0 h1:BRtLEyjkUoPKPu0Y6odUbSMlKCYNyR792TYRtujKfPw= git.ailur.dev/ailur/jsStreams v1.2.1 h1:nXZYZrxHJCVwR0Kx/X+TenMBmS6Gh8Uc2DMinbyiGoo=
git.ailur.dev/ailur/jsStreams v1.2.0/go.mod h1:/ZCvbUcWkZRuKIkO7jH6b5vIjzdxIOP8ET8X0src5Go= git.ailur.dev/ailur/jsStreams v1.2.1/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=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=

View File

@ -16,6 +16,7 @@
<div class="app" id="app"> <div class="app" id="app">
<div class="topBar"> <div class="topBar">
<span>LChat</span> <span>LChat</span>
<button id="invite" class="invite">Invite</button>
<button id="logout">Logout</button> <button id="logout">Logout</button>
</div> </div>
<div class="messageBox" id="messageBox"></div> <div class="messageBox" id="messageBox"></div>

View File

@ -32,13 +32,17 @@ body {
} }
.topBar button { .topBar button {
margin-left: auto;
background: none; background: none;
border: none; border: none;
color: white; color: white;
font-size: 16px; font-size: 16px;
} }
.topBar .invite {
margin-left: auto;
display: none;
}
.topBar span { .topBar span {
font-size: 18px; font-size: 18px;
font-weight: 400; font-weight: 400;
@ -47,7 +51,7 @@ body {
.messageBox { .messageBox {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow-y: scroll; overflow-y: auto;
max-height: calc(100% - 148px); max-height: calc(100% - 148px);
padding: 11.5px 10px 0 10px; padding: 11.5px 10px 0 10px;
margin: 65px 0; margin: 65px 0;

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"git.ailur.dev/ailur/jsFetch" "git.ailur.dev/ailur/jsFetch"
"strconv"
"syscall/js" "syscall/js"
"time" "time"
) )
@ -39,7 +40,7 @@ func refreshMessages(localStorage js.Value, messageBox js.Value, userId string)
return return
} }
var body map[string]interface{} var body []map[string]interface{}
err = json.NewDecoder(response.Body).Decode(&body) err = json.NewDecoder(response.Body).Decode(&body)
if err != nil { if err != nil {
alert("Failed to decode response: " + err.Error()) 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") 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) { if len(messageDivs) > len(body) {
for id, _ := range messageDivs { for id := range messageDivs {
_, exists := body[id] _, exists := ids[id]
if !exists { if !exists {
messageDivs[id].Call("remove") messageDivs[id].Call("remove")
delete(messageDivs, id) delete(messageDivs, id)
@ -60,19 +66,19 @@ func refreshMessages(localStorage js.Value, messageBox js.Value, userId string)
} }
} }
for id, message := range body { for _, message := range body {
_, exists := messageDivs[id] _, exists := messageDivs[message["id"].(string)]
if !exists { 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) messageBox.Call("appendChild", messageDiv)
messageDivs[id] = messageDiv messageDivs[message["id"].(string)] = messageDiv
} }
} }
if !messageBox.Get("lastChild").IsNull() { if !messageBox.Get("lastChild").IsNull() {
messageBox.Get("lastChild").Get("style").Set("margin-bottom", "22.5px") 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) { func alert(message string) {
@ -143,38 +149,7 @@ func createMessage(message string, user string, delete bool, id string, timestam
return div return div
} }
func main() { func handleSend(sendField js.Value, localStorage js.Value) {
localStorage := js.Global().Get("localStorage")
login := js.Global().Get("document").Call("getElementById", "login")
app := js.Global().Get("document").Call("getElementById", "app")
logout := js.Global().Get("document").Call("getElementById", "logout")
sendField := js.Global().Get("document").Call("getElementById", "sendField")
send := js.Global().Get("document").Call("getElementById", "send")
messageBox := js.Global().Get("document").Call("getElementById", "messageBox")
if localStorage.Call("getItem", "server").IsNull() {
localStorage.Call("setItem", "server", "https://chat.ailur.dev:1974")
}
if localStorage.Call("getItem", "token").IsNull() {
return
}
userId := localStorage.Call("getItem", "userId").String()
login.Get("style").Set("display", "none")
app.Get("style").Set("display", "initial")
logout.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
go func() {
localStorage.Call("removeItem", "token")
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{}{ jsonBody, err := json.Marshal(map[string]interface{}{
"message": sendField.Get("value").String(), "message": sendField.Get("value").String(),
"token": localStorage.Call("getItem", "token").String(), "token": localStorage.Call("getItem", "token").String(),
@ -204,9 +179,110 @@ func main() {
} }
return return
} }
sendField.Set("value", "")
}
func main() {
localStorage := js.Global().Get("localStorage")
login := js.Global().Get("document").Call("getElementById", "login")
app := js.Global().Get("document").Call("getElementById", "app")
logout := js.Global().Get("document").Call("getElementById", "logout")
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")
}
if localStorage.Call("getItem", "token").IsNull() {
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 return nil
})) }))
} else {
logout.Get("style").Set("margin-left", "auto")
}
userId := localStorage.Call("getItem", "userId").String()
login.Get("style").Set("display", "none")
app.Get("style").Set("display", "initial")
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 handleSend(sendField, localStorage)
return nil
}))
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
}))
go refreshMessages(localStorage, messageBox, userId) go refreshMessages(localStorage, messageBox, userId)

View File

@ -158,8 +158,13 @@ func main() {
} }
localStorage.Call("setItem", "token", body["token"].(string)) 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", "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!") status.Set("innerText", "Welcome, "+username.Get("value").String()+", to LChat!")
time.Sleep(300 * time.Millisecond) time.Sleep(300 * time.Millisecond)

View File

@ -115,8 +115,8 @@ func main() {
} }
localStorage.Call("setItem", "token", body["token"].(string)) 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", "userId", body["userId"].(string))
localStorage.Call("setItem", "admin", "false")
status.Set("innerText", "Welcome, "+username.Get("value").String()+", to LChat!") status.Set("innerText", "Welcome, "+username.Get("value").String()+", to LChat!")
time.Sleep(300 * time.Millisecond) time.Sleep(300 * time.Millisecond)