Updated everything, fixed deleting message synchronisation, fixed chromium support, added inviting other users as an admin
This commit is contained in:
parent
1a607e30e0
commit
a3f4bb24e7
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
10
web/go.sum
10
web/go.sum
|
@ -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=
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
156
web/wasm/app.go
156
web/wasm/app.go
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue