start minIO, refactored files into packages
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
minio:
|
||||||
|
image: minio/minio:RELEASE.2024-03-19T00-00-00Z
|
||||||
|
container_name: minio
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: root
|
||||||
|
MINIO_ROOT_PASSWORD: change_to_env
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
- ./config:/root/.minio
|
||||||
|
ports:
|
||||||
|
- "9000:9000"
|
||||||
|
- "9001:9001"
|
||||||
|
command: server /data --console-address ":9001"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
data
|
||||||
@@ -7,4 +7,4 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/var/lib/postgresql/data
|
- ./data:/var/lib/postgresql/dat
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetUserById(ctx context.Context, userId uuid.UUID) (*User, error) {
|
|
||||||
user, err := CacheGetUserById(userId)
|
|
||||||
if err != nil {
|
|
||||||
user = &User{Id: userId}
|
|
||||||
err = PgGetWholeUser(ctx, user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUserByToken(ctx context.Context, token string) (*User, error) {
|
|
||||||
userId, err := TokenValidateGetId(token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return GetUserById(ctx, userId)
|
|
||||||
}
|
|
||||||
@@ -9,9 +9,23 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/go-ini/ini v1.67.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.2 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
||||||
|
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||||
|
github.com/minio/crc64nvme v1.1.1 // indirect
|
||||||
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
|
github.com/minio/minio-go/v7 v7.0.100 // indirect
|
||||||
|
github.com/philhofer/fwd v1.2.0 // indirect
|
||||||
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
|
github.com/tinylib/msgp v1.6.1 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/net v0.51.0 // indirect
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
http2 "go-socket/packages/http"
|
||||||
|
"go-socket/packages/postgresql"
|
||||||
|
"go-socket/packages/wsServer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func withCORS(h http.HandlerFunc) http.HandlerFunc {
|
func withCORS(h http.HandlerFunc) http.HandlerFunc {
|
||||||
@@ -15,23 +19,23 @@ func withCORS(h http.HandlerFunc) http.HandlerFunc {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
PgInit(ctx)
|
postgresql.PgInit(ctx)
|
||||||
|
|
||||||
http.HandleFunc("/new/user", withCORS(HttpHandleUserNew))
|
http.HandleFunc("/new/user", withCORS(http2.HandleUserNew))
|
||||||
http.HandleFunc("/new/connection", withCORS(HttpHandleUserNewConnection))
|
http.HandleFunc("/new/connection", withCORS(http2.HandleUserNewConnection))
|
||||||
http.HandleFunc("/new/token", withCORS(HttpHandleUserNewToken))
|
http.HandleFunc("/new/token", withCORS(http2.HandleUserNewToken))
|
||||||
http.HandleFunc("/mod/user/appearence", withCORS(HttpHandleUserModifyAppearance))
|
http.HandleFunc("/mod/user/appearence", withCORS(http2.HandleUserModifyAppearance))
|
||||||
http.HandleFunc("/mod/user/about", withCORS(HttpHandleUserModifyAbout))
|
http.HandleFunc("/mod/user/about", withCORS(http2.HandleUserModifyAbout))
|
||||||
http.HandleFunc("/mod/connection/accept", withCORS(HttpHandleUserElevateConnection))
|
http.HandleFunc("/mod/connection/accept", withCORS(http2.HandleUserElevateConnection))
|
||||||
|
|
||||||
http.HandleFunc("/get/connections", withCORS(HttpHandleUserGetConnections))
|
http.HandleFunc("/get/connections", withCORS(http2.HandleUserGetConnections))
|
||||||
http.HandleFunc("/get/connection/messages", withCORS(HttpHandleUserGetConnectionMessages))
|
http.HandleFunc("/get/connection/messages", withCORS(http2.HandleUserGetConnectionMessages))
|
||||||
|
|
||||||
http.HandleFunc("/del/user", withCORS(HttpHandleUserDelete))
|
http.HandleFunc("/del/user", withCORS(http2.HandleUserDelete))
|
||||||
http.HandleFunc("/del/connection", withCORS(HttpHandleUserDeleteConnection))
|
http.HandleFunc("/del/connection", withCORS(http2.HandleUserDeleteConnection))
|
||||||
|
|
||||||
http.HandleFunc("/msg/user", withCORS(HttpHandleDm))
|
http.HandleFunc("/msg/user", withCORS(http2.HandleDm))
|
||||||
http.HandleFunc("/ws", ServeWsConnection)
|
http.HandleFunc("/ws", wsServer.ServeWsConnection)
|
||||||
|
|
||||||
log.Println("listening on :8080")
|
log.Println("listening on :8080")
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
|||||||
Vendored
+23
-22
@@ -1,33 +1,35 @@
|
|||||||
package main
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"go-socket/packages/types"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mu sync.RWMutex
|
Mu sync.RWMutex
|
||||||
CacheUsers = make(map[uuid.UUID]*User)
|
Users = make(map[uuid.UUID]*types.User)
|
||||||
)
|
)
|
||||||
|
|
||||||
func CacheGetUserById(id uuid.UUID) (*User, error) {
|
func CacheGetUserById(id uuid.UUID) (*types.User, error) {
|
||||||
mu.RLock()
|
Mu.RLock()
|
||||||
defer mu.RUnlock()
|
defer Mu.RUnlock()
|
||||||
|
|
||||||
user, ok := CacheUsers[id]
|
user, ok := Users[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("user %s not found", id)
|
return nil, fmt.Errorf("user %s not found", id)
|
||||||
}
|
}
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CacheGetUserByName(name string) (*User, error) {
|
func CacheGetUserByName(name string) (*types.User, error) {
|
||||||
mu.RLock()
|
Mu.RLock()
|
||||||
defer mu.RUnlock()
|
defer Mu.RUnlock()
|
||||||
|
|
||||||
for _, user := range CacheUsers {
|
for _, user := range Users {
|
||||||
if user.Name == name {
|
if user.Name == name {
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
@@ -35,21 +37,21 @@ func CacheGetUserByName(name string) (*User, error) {
|
|||||||
return nil, fmt.Errorf("user %s not found", name)
|
return nil, fmt.Errorf("user %s not found", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CacheSaveUser(user *User) {
|
func CacheSaveUser(user *types.User) {
|
||||||
mu.Lock()
|
Mu.Lock()
|
||||||
defer mu.Unlock()
|
defer Mu.Unlock()
|
||||||
|
|
||||||
CacheUsers[user.Id] = user
|
Users[user.Id] = user
|
||||||
}
|
}
|
||||||
|
|
||||||
func CacheDeleteUser(id uuid.UUID) {
|
func CacheDeleteUser(id uuid.UUID) {
|
||||||
mu.Lock()
|
Mu.Lock()
|
||||||
defer mu.Unlock()
|
defer Mu.Unlock()
|
||||||
|
|
||||||
delete(CacheUsers, id)
|
delete(Users, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CacheAddConnection(a, b *User, conn *Connection) {
|
func CacheAddConnection(a, b *types.User, conn *types.Connection) {
|
||||||
first, second := a, b
|
first, second := a, b
|
||||||
if a.Id.String() > b.Id.String() {
|
if a.Id.String() > b.Id.String() {
|
||||||
first, second = b, a
|
first, second = b, a
|
||||||
@@ -62,7 +64,7 @@ func CacheAddConnection(a, b *User, conn *Connection) {
|
|||||||
first.Mu.Unlock()
|
first.Mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func CacheDeleteConnection(a, b *User, id uuid.UUID) {
|
func CacheDeleteConnection(a, b *types.User, id uuid.UUID) {
|
||||||
first, second := a, b
|
first, second := a, b
|
||||||
if a.Id.String() > b.Id.String() {
|
if a.Id.String() > b.Id.String() {
|
||||||
first, second = b, a
|
first, second = b, a
|
||||||
@@ -75,10 +77,9 @@ func CacheDeleteConnection(a, b *User, id uuid.UUID) {
|
|||||||
first.Mu.Unlock()
|
first.Mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func CacheGetConnection(user *User, id uuid.UUID) (*Connection, bool) {
|
func CacheGetConnection(user *types.User, id uuid.UUID) (*types.Connection, bool) {
|
||||||
user.Mu.RLock()
|
user.Mu.RLock()
|
||||||
defer user.Mu.RUnlock()
|
defer user.Mu.RUnlock()
|
||||||
conn, ok := user.Connections[id]
|
conn, ok := user.Connections[id]
|
||||||
return conn, ok
|
return conn, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package convertions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go-socket/packages/types"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,12 +16,12 @@ func ConvertStringUint32(s string) (uint32, error) {
|
|||||||
return uint32(v), err
|
return uint32(v), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConvertStringToRgba(str string) (*Rgba, error) {
|
func ConvertStringToRgba(str string) (*types.Rgba, error) {
|
||||||
parts := strings.SplitN(str, ",", 5)
|
parts := strings.SplitN(str, ",", 5)
|
||||||
if len(parts) != 4 {
|
if len(parts) != 4 {
|
||||||
return nil, fmt.Errorf("invalid rgba")
|
return nil, fmt.Errorf("invalid rgba")
|
||||||
}
|
}
|
||||||
rgba := &Rgba{}
|
rgba := &types.Rgba{}
|
||||||
for i, p := range parts {
|
for i, p := range parts {
|
||||||
n, err := strconv.ParseUint(strings.TrimSpace(p), 10, 8)
|
n, err := strconv.ParseUint(strings.TrimSpace(p), 10, 8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -30,12 +32,12 @@ func ConvertStringToRgba(str string) (*Rgba, error) {
|
|||||||
return rgba, nil
|
return rgba, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RgbaToUint32(r *Rgba) uint32 {
|
func RgbaToUint32(r *types.Rgba) uint32 {
|
||||||
return uint32(r[0])<<24 | uint32(r[1])<<16 | uint32(r[2])<<8 | uint32(r[3])
|
return uint32(r[0])<<24 | uint32(r[1])<<16 | uint32(r[2])<<8 | uint32(r[3])
|
||||||
}
|
}
|
||||||
|
|
||||||
func Uint32ToRgba(v uint32) *Rgba {
|
func Uint32ToRgba(v uint32) *types.Rgba {
|
||||||
return &Rgba{uint8(v >> 24), uint8(v >> 16), uint8(v >> 8), uint8(v)}
|
return &types.Rgba{uint8(v >> 24), uint8(v >> 16), uint8(v >> 8), uint8(v)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConvertStringUuid(str string) (uuid.UUID, error) {
|
func ConvertStringUuid(str string) (uuid.UUID, error) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package globals
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MaxDirectMsgCache uint32 = 12
|
MaxDirectMsgCache uint32 = 12
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go-socket/packages/cache"
|
||||||
|
"go-socket/packages/postgresql"
|
||||||
|
"go-socket/packages/tokens"
|
||||||
|
"go-socket/packages/types"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetUserById(ctx context.Context, userId uuid.UUID) (*types.User, error) {
|
||||||
|
user, err := cache.CacheGetUserById(userId)
|
||||||
|
if err != nil {
|
||||||
|
user = &types.User{Id: userId}
|
||||||
|
err = postgresql.PgGetWholeUser(ctx, user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserByToken(ctx context.Context, token string) (*types.User, error) {
|
||||||
|
userId, err := tokens.TokenValidateGetId(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return GetUserById(ctx, userId)
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package main
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HttpMethodAllowed(response *http.ResponseWriter, request *http.Request) bool {
|
func methodAllowed(response *http.ResponseWriter, request *http.Request) bool {
|
||||||
if request.Method != http.MethodPost {
|
if request.Method != http.MethodPost {
|
||||||
http.Error(*response, "POST only", http.StatusMethodNotAllowed)
|
http.Error(*response, "POST only", http.StatusMethodNotAllowed)
|
||||||
return false
|
return false
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
json2 "encoding/json"
|
json2 "encoding/json"
|
||||||
@@ -7,14 +7,20 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-socket/Enums/ConnectionState"
|
"go-socket/packages/Enums/ConnectionState"
|
||||||
"go-socket/Enums/WsEventType"
|
"go-socket/packages/Enums/WsEventType"
|
||||||
|
"go-socket/packages/cache"
|
||||||
|
"go-socket/packages/convertions"
|
||||||
|
"go-socket/packages/globals"
|
||||||
|
"go-socket/packages/postgresql"
|
||||||
|
"go-socket/packages/types"
|
||||||
|
"go-socket/packages/wsServer"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HttpHandleDm(response http.ResponseWriter, request *http.Request) {
|
func HandleDm(response http.ResponseWriter, request *http.Request) {
|
||||||
if !HttpMethodAllowed(&response, request) {
|
if !methodAllowed(&response, request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,12 +31,12 @@ func HttpHandleDm(response http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetConnection, err := ConvertStringUuid(request.FormValue("connectionid"))
|
targetConnection, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn, ok := CacheGetConnection(user, targetConnection)
|
conn, ok := cache.CacheGetConnection(user, targetConnection)
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -42,7 +48,7 @@ func HttpHandleDm(response http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var target *User
|
var target *types.User
|
||||||
|
|
||||||
if user.Id == conn.RequestorId {
|
if user.Id == conn.RequestorId {
|
||||||
target, err = GetUserById(ctx, conn.RecipientId)
|
target, err = GetUserById(ctx, conn.RecipientId)
|
||||||
@@ -57,7 +63,7 @@ func HttpHandleDm(response http.ResponseWriter, request *http.Request) {
|
|||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
message := &Message{
|
message := &types.Message{
|
||||||
Id: uuid.New(),
|
Id: uuid.New(),
|
||||||
Content: msgContent,
|
Content: msgContent,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
@@ -65,12 +71,12 @@ func HttpHandleDm(response http.ResponseWriter, request *http.Request) {
|
|||||||
Receiver: conn.Id,
|
Receiver: conn.Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
WsSendMessageCloseIfTimeout(target, WsEventMessage{
|
wsServer.WsSendMessageCloseIfTimeout(target, types.WsEventMessage{
|
||||||
Type: WsEventType.DirectMessage,
|
Type: WsEventType.DirectMessage,
|
||||||
Event: message,
|
Event: message,
|
||||||
})
|
})
|
||||||
|
|
||||||
err = PgConnectionMessageSave(ctx, message)
|
err = postgresql.PgConnectionMessageSave(ctx, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -79,8 +85,8 @@ func HttpHandleDm(response http.ResponseWriter, request *http.Request) {
|
|||||||
response.WriteHeader(http.StatusAccepted)
|
response.WriteHeader(http.StatusAccepted)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpHandleUserGetConnectionMessages(response http.ResponseWriter, request *http.Request) {
|
func HandleUserGetConnectionMessages(response http.ResponseWriter, request *http.Request) {
|
||||||
if !HttpMethodAllowed(&response, request) {
|
if !methodAllowed(&response, request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := request.Context()
|
ctx := request.Context()
|
||||||
@@ -90,23 +96,23 @@ func HttpHandleUserGetConnectionMessages(response http.ResponseWriter, request *
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionId, err := ConvertStringUuid(request.FormValue("connectionid"))
|
connectionId, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
before, err := ConvertStringTimestamp(request.FormValue("before"))
|
before, err := convertions.ConvertStringTimestamp(request.FormValue("before"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
before = time.Now()
|
before = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
messagesCap, err := ConvertStringUint32(request.FormValue("messages"))
|
messagesCap, err := convertions.ConvertStringUint32(request.FormValue("messages"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
messagesCap = MaxDirectMsgCache
|
messagesCap = globals.MaxDirectMsgCache
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, ok := CacheGetConnection(user, connectionId)
|
conn, ok := cache.CacheGetConnection(user, connectionId)
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -119,11 +125,11 @@ func HttpHandleUserGetConnectionMessages(response http.ResponseWriter, request *
|
|||||||
validBufCount++
|
validBufCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
var messages []*Message
|
var messages []*types.Message
|
||||||
|
|
||||||
if validBufCount >= messagesCap {
|
if validBufCount >= messagesCap {
|
||||||
start := validBufCount - messagesCap
|
start := validBufCount - messagesCap
|
||||||
messages = make([]*Message, messagesCap)
|
messages = make([]*types.Message, messagesCap)
|
||||||
for i := uint32(0); i < messagesCap; i++ {
|
for i := uint32(0); i < messagesCap; i++ {
|
||||||
messages[i] = buffer[start+i]
|
messages[i] = buffer[start+i]
|
||||||
}
|
}
|
||||||
@@ -133,12 +139,12 @@ func HttpHandleUserGetConnectionMessages(response http.ResponseWriter, request *
|
|||||||
if validBufCount > 0 {
|
if validBufCount > 0 {
|
||||||
cutoff = buffer[0].CreatedAt
|
cutoff = buffer[0].CreatedAt
|
||||||
}
|
}
|
||||||
dbMessages, err := PgConnectionGetMessagesBefore(ctx, cutoff, connectionId, remaining)
|
dbMessages, err := postgresql.PgConnectionGetMessagesBefore(ctx, cutoff, connectionId, remaining)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
messages = make([]*Message, 0, uint32(len(dbMessages))+validBufCount)
|
messages = make([]*types.Message, 0, uint32(len(dbMessages))+validBufCount)
|
||||||
messages = append(messages, dbMessages...)
|
messages = append(messages, dbMessages...)
|
||||||
for i := uint32(0); i < validBufCount; i++ {
|
for i := uint32(0); i < validBufCount; i++ {
|
||||||
messages = append(messages, buffer[i])
|
messages = append(messages, buffer[i])
|
||||||
@@ -155,8 +161,8 @@ func HttpHandleUserGetConnectionMessages(response http.ResponseWriter, request *
|
|||||||
response.Write(json)
|
response.Write(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpHandleUserNewConnection(response http.ResponseWriter, request *http.Request) {
|
func HandleUserNewConnection(response http.ResponseWriter, request *http.Request) {
|
||||||
if !HttpMethodAllowed(&response, request) {
|
if !methodAllowed(&response, request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := request.Context()
|
ctx := request.Context()
|
||||||
@@ -165,7 +171,7 @@ func HttpHandleUserNewConnection(response http.ResponseWriter, request *http.Req
|
|||||||
http.Error(response, "invalid token", http.StatusUnauthorized)
|
http.Error(response, "invalid token", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
recipientId, err := ConvertStringUuid(request.FormValue("recipient"))
|
recipientId, err := convertions.ConvertStringUuid(request.FormValue("recipient"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "no such user", http.StatusUnauthorized)
|
http.Error(response, "no such user", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
@@ -192,20 +198,20 @@ func HttpHandleUserNewConnection(response http.ResponseWriter, request *http.Req
|
|||||||
}
|
}
|
||||||
requestor.Mu.RUnlock()
|
requestor.Mu.RUnlock()
|
||||||
|
|
||||||
connection := &Connection{
|
connection := &types.Connection{
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
RequestorId: requestor.Id,
|
RequestorId: requestor.Id,
|
||||||
RecipientId: recipient.Id,
|
RecipientId: recipient.Id,
|
||||||
State: ConnectionState.Stranger,
|
State: ConnectionState.Stranger,
|
||||||
}
|
}
|
||||||
err = PgConnectionSave(ctx, connection)
|
err = postgresql.PgConnectionSave(ctx, connection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
CacheAddConnection(requestor, recipient, connection)
|
cache.CacheAddConnection(requestor, recipient, connection)
|
||||||
|
|
||||||
WsSendMessageCloseIfTimeout(recipient, WsEventMessage{
|
wsServer.WsSendMessageCloseIfTimeout(recipient, types.WsEventMessage{
|
||||||
Type: WsEventType.ConnectionCreated,
|
Type: WsEventType.ConnectionCreated,
|
||||||
Event: connection,
|
Event: connection,
|
||||||
})
|
})
|
||||||
@@ -214,8 +220,8 @@ func HttpHandleUserNewConnection(response http.ResponseWriter, request *http.Req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpHandleUserDeleteConnection(response http.ResponseWriter, request *http.Request) {
|
func HandleUserDeleteConnection(response http.ResponseWriter, request *http.Request) {
|
||||||
if !HttpMethodAllowed(&response, request) {
|
if !methodAllowed(&response, request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := request.Context()
|
ctx := request.Context()
|
||||||
@@ -226,19 +232,19 @@ func HttpHandleUserDeleteConnection(response http.ResponseWriter, request *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionId, err := ConvertStringUuid(request.FormValue("connectionid"))
|
connectionId, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, ok := CacheGetConnection(user, connectionId)
|
conn, ok := cache.CacheGetConnection(user, connectionId)
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var user2 *User
|
var user2 *types.User
|
||||||
if conn.RequestorId == user.Id {
|
if conn.RequestorId == user.Id {
|
||||||
recipient, err := GetUserById(ctx, conn.RecipientId)
|
recipient, err := GetUserById(ctx, conn.RecipientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -258,14 +264,14 @@ func HttpHandleUserDeleteConnection(response http.ResponseWriter, request *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = PgConnectionDelete(ctx, conn)
|
err = postgresql.PgConnectionDelete(ctx, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
CacheDeleteConnection(user, user2, connectionId)
|
cache.CacheDeleteConnection(user, user2, connectionId)
|
||||||
WsSendMessageCloseIfTimeout(user2, WsEventMessage{
|
wsServer.WsSendMessageCloseIfTimeout(user2, types.WsEventMessage{
|
||||||
Type: WsEventType.ConnectionDeleted,
|
Type: WsEventType.ConnectionDeleted,
|
||||||
Event: connectionId,
|
Event: connectionId,
|
||||||
})
|
})
|
||||||
@@ -273,8 +279,8 @@ func HttpHandleUserDeleteConnection(response http.ResponseWriter, request *http.
|
|||||||
response.WriteHeader(http.StatusAccepted)
|
response.WriteHeader(http.StatusAccepted)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpHandleUserElevateConnection(response http.ResponseWriter, request *http.Request) {
|
func HandleUserElevateConnection(response http.ResponseWriter, request *http.Request) {
|
||||||
if !HttpMethodAllowed(&response, request) {
|
if !methodAllowed(&response, request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := request.Context()
|
ctx := request.Context()
|
||||||
@@ -283,12 +289,12 @@ func HttpHandleUserElevateConnection(response http.ResponseWriter, request *http
|
|||||||
http.Error(response, "invalid token", http.StatusUnauthorized)
|
http.Error(response, "invalid token", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
connectionId, err := ConvertStringUuid(request.FormValue("connectionid"))
|
connectionId, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn, ok := CacheGetConnection(user, connectionId)
|
conn, ok := cache.CacheGetConnection(user, connectionId)
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -309,14 +315,14 @@ func HttpHandleUserElevateConnection(response http.ResponseWriter, request *http
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = PgConnectionUpdateState(ctx, conn)
|
err = postgresql.PgConnectionUpdateState(ctx, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
response.Write([]byte("elevated"))
|
response.Write([]byte("elevated"))
|
||||||
|
|
||||||
var user2 *User
|
var user2 *types.User
|
||||||
if conn.RequestorId == user.Id {
|
if conn.RequestorId == user.Id {
|
||||||
user2, err = GetUserById(ctx, conn.RecipientId)
|
user2, err = GetUserById(ctx, conn.RecipientId)
|
||||||
} else {
|
} else {
|
||||||
@@ -327,9 +333,9 @@ func HttpHandleUserElevateConnection(response http.ResponseWriter, request *http
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
WsSendMessageCloseIfTimeout(user2, WsEventMessage{
|
wsServer.WsSendMessageCloseIfTimeout(user2, types.WsEventMessage{
|
||||||
Type: WsEventType.ConnectionElevated,
|
Type: WsEventType.ConnectionElevated,
|
||||||
Event: ConnectionElevationData{
|
Event: types.ConnectionElevationData{
|
||||||
Id: connectionId,
|
Id: connectionId,
|
||||||
NewState: conn.State,
|
NewState: conn.State,
|
||||||
},
|
},
|
||||||
@@ -342,8 +348,8 @@ func HttpHandleUserElevateConnection(response http.ResponseWriter, request *http
|
|||||||
response.Write([]byte("waiting for second user to elevate"))
|
response.Write([]byte("waiting for second user to elevate"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpHandleUserGetConnections(response http.ResponseWriter, request *http.Request) {
|
func HandleUserGetConnections(response http.ResponseWriter, request *http.Request) {
|
||||||
if !HttpMethodAllowed(&response, request) {
|
if !methodAllowed(&response, request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := request.Context()
|
ctx := request.Context()
|
||||||
@@ -1,15 +1,22 @@
|
|||||||
package main
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
json2 "encoding/json"
|
json2 "encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go-socket/packages/cache"
|
||||||
|
"go-socket/packages/convertions"
|
||||||
|
"go-socket/packages/passwords"
|
||||||
|
"go-socket/packages/postgresql"
|
||||||
|
"go-socket/packages/tokens"
|
||||||
|
"go-socket/packages/types"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HttpHandleUserNewToken(response http.ResponseWriter, request *http.Request) {
|
func HandleUserNewToken(response http.ResponseWriter, request *http.Request) {
|
||||||
if !HttpMethodAllowed(&response, request) {
|
if !methodAllowed(&response, request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,27 +26,27 @@ func HttpHandleUserNewToken(response http.ResponseWriter, request *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
password := request.FormValue("password")
|
password := request.FormValue("passwords")
|
||||||
|
|
||||||
if len(password) < 8 {
|
if len(password) < 8 {
|
||||||
http.Error(response, "no or short password", http.StatusBadRequest)
|
http.Error(response, "no or short passwords", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
user *User
|
user *types.User
|
||||||
err error
|
err error
|
||||||
ctx = request.Context()
|
ctx = request.Context()
|
||||||
)
|
)
|
||||||
|
|
||||||
user, err = CacheGetUserByName(username)
|
user, err = cache.CacheGetUserByName(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
user = &User{Name: username}
|
user = &types.User{Name: username}
|
||||||
if err = PgUserGetStandardInfoByName(ctx, user); err != nil {
|
if err = postgresql.PgUserGetStandardInfoByName(ctx, user); err != nil {
|
||||||
http.Error(response, "bad login", http.StatusUnauthorized)
|
http.Error(response, "bad login", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = PgGetWholeUser(ctx, user); err != nil {
|
if err = postgresql.PgGetWholeUser(ctx, user); err != nil {
|
||||||
http.Error(response, err.Error(), http.StatusInternalServerError)
|
http.Error(response, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -51,13 +58,13 @@ func HttpHandleUserNewToken(response http.ResponseWriter, request *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := TokenCreate(user.Id)
|
token, err := tokens.TokenCreate(user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
json, err := json2.Marshal(LoginReturn{Token: token, UserId: user.Id})
|
json, err := json2.Marshal(types.LoginReturn{Token: token, UserId: user.Id})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -67,8 +74,8 @@ func HttpHandleUserNewToken(response http.ResponseWriter, request *http.Request)
|
|||||||
response.Write(json)
|
response.Write(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpHandleUserNew(response http.ResponseWriter, request *http.Request) {
|
func HandleUserNew(response http.ResponseWriter, request *http.Request) {
|
||||||
if !HttpMethodAllowed(&response, request) {
|
if !methodAllowed(&response, request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,28 +85,28 @@ func HttpHandleUserNew(response http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
password := request.FormValue("password")
|
password := request.FormValue("passwords")
|
||||||
if len(password) < 8 {
|
if len(password) < 8 {
|
||||||
http.Error(response, "no or short password", http.StatusBadRequest)
|
http.Error(response, "no or short passwords", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hashedPassword, err := PasswordHash(password)
|
hashedPassword, err := passwords.PasswordHash(password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newUser := &User{
|
newUser := &types.User{
|
||||||
Name: username,
|
Name: username,
|
||||||
PasswordHash: hashedPassword,
|
PasswordHash: hashedPassword,
|
||||||
Color: Rgba{}.GetRandom(),
|
Color: types.Rgba{}.GetRandom(),
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := request.Context()
|
ctx := request.Context()
|
||||||
|
|
||||||
err = PgUserSave(ctx, newUser)
|
err = postgresql.PgUserSave(ctx, newUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "name taken", http.StatusUnauthorized)
|
http.Error(response, "name taken", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
@@ -108,31 +115,31 @@ func HttpHandleUserNew(response http.ResponseWriter, request *http.Request) {
|
|||||||
response.WriteHeader(http.StatusCreated)
|
response.WriteHeader(http.StatusCreated)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpHandleUserDelete(response http.ResponseWriter, request *http.Request) {
|
func HandleUserDelete(response http.ResponseWriter, request *http.Request) {
|
||||||
if !HttpMethodAllowed(&response, request) {
|
if !methodAllowed(&response, request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := request.Context()
|
ctx := request.Context()
|
||||||
|
|
||||||
userId, err := TokenValidateGetId(request.FormValue("token"))
|
userId, err := tokens.TokenValidateGetId(request.FormValue("token"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "invalid token", http.StatusUnauthorized)
|
http.Error(response, "invalid token", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = PgUserDelete(ctx, userId)
|
err = postgresql.PgUserDelete(ctx, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
CacheDeleteUser(userId)
|
cache.CacheDeleteUser(userId)
|
||||||
response.WriteHeader(http.StatusAccepted)
|
response.WriteHeader(http.StatusAccepted)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HttpHandleUserModifyAppearance currently just color
|
// HandleUserModifyAppearance currently just color
|
||||||
func HttpHandleUserModifyAppearance(response http.ResponseWriter, request *http.Request) {
|
func HandleUserModifyAppearance(response http.ResponseWriter, request *http.Request) {
|
||||||
if !HttpMethodAllowed(&response, request) {
|
if !methodAllowed(&response, request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,13 +150,13 @@ func HttpHandleUserModifyAppearance(response http.ResponseWriter, request *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
color, err := ConvertStringToRgba(request.FormValue("color"))
|
color, err := convertions.ConvertStringToRgba(request.FormValue("color"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "invalid color", http.StatusBadRequest)
|
http.Error(response, "invalid color", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.Color = color
|
user.Color = color
|
||||||
err = PgUserSetColor(ctx, user)
|
err = postgresql.PgUserSetColor(ctx, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -157,9 +164,9 @@ func HttpHandleUserModifyAppearance(response http.ResponseWriter, request *http.
|
|||||||
response.WriteHeader(http.StatusAccepted)
|
response.WriteHeader(http.StatusAccepted)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HttpHandleUserModifyAbout currently just pronouns
|
// HandleUserModifyAbout currently just pronouns
|
||||||
func HttpHandleUserModifyAbout(response http.ResponseWriter, request *http.Request) {
|
func HandleUserModifyAbout(response http.ResponseWriter, request *http.Request) {
|
||||||
if !HttpMethodAllowed(&response, request) {
|
if !methodAllowed(&response, request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +184,7 @@ func HttpHandleUserModifyAbout(response http.ResponseWriter, request *http.Reque
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.Pronouns = pronouns
|
user.Pronouns = pronouns
|
||||||
err = PgUserSetPronouns(ctx, user)
|
err = postgresql.PgUserSetPronouns(ctx, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package minio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/v7"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MinInit() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
dbConn, err := minio.New("localhost:9000", &minio.Options{
|
||||||
|
Creds: credentials.NewStaticV4("root", "change_to_env", ""),
|
||||||
|
Secure: false,
|
||||||
|
}) // TODO change in production
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := dbConn.BucketExists(ctx, "main")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package passwords
|
||||||
|
|
||||||
import "golang.org/x/crypto/bcrypt"
|
import "golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package main
|
package postgresql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go-socket/packages/cache"
|
||||||
|
"go-socket/packages/convertions"
|
||||||
|
"go-socket/packages/types"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
)
|
)
|
||||||
@@ -65,12 +69,12 @@ func PgInit(ctx context.Context) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func PgUserSave(ctx context.Context, user *User) error {
|
func PgUserSave(ctx context.Context, user *types.User) error {
|
||||||
err := dbConn.QueryRow(ctx, `
|
err := dbConn.QueryRow(ctx, `
|
||||||
INSERT INTO users (name, pass_hash, pronouns, rgba, created_at)
|
INSERT INTO users (name, pass_hash, pronouns, rgba, created_at)
|
||||||
VALUES ($1, $2, $3, $4, $5)
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
`, user.Name, user.PasswordHash, user.Pronouns, RgbaToUint32(user.Color), user.CreatedAt).
|
`, user.Name, user.PasswordHash, user.Pronouns, convertions.RgbaToUint32(user.Color), user.CreatedAt).
|
||||||
Scan(&user.Id)
|
Scan(&user.Id)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -82,43 +86,43 @@ func PgUserDelete(ctx context.Context, id uuid.UUID) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func PgUserGetStandardInfoByName(ctx context.Context, user *User) error {
|
func PgUserGetStandardInfoByName(ctx context.Context, user *types.User) error {
|
||||||
var rgba int64
|
var rgba int64
|
||||||
err := dbConn.QueryRow(ctx, `
|
err := dbConn.QueryRow(ctx, `
|
||||||
SELECT id, name, pass_hash, COALESCE(pronouns, ''), rgba, created_at FROM users WHERE name = $1
|
SELECT id, name, pass_hash, COALESCE(pronouns, ''), rgba, created_at FROM users WHERE name = $1
|
||||||
`, user.Name).Scan(&user.Id, &user.Name, &user.PasswordHash, &user.Pronouns, &rgba, &user.CreatedAt)
|
`, user.Name).Scan(&user.Id, &user.Name, &user.PasswordHash, &user.Pronouns, &rgba, &user.CreatedAt)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
user.Color = Uint32ToRgba(uint32(rgba))
|
user.Color = convertions.Uint32ToRgba(uint32(rgba))
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func PgUserGetById(ctx context.Context, user *User) error {
|
func PgUserGetById(ctx context.Context, user *types.User) error {
|
||||||
var rgba int64
|
var rgba int64
|
||||||
err := dbConn.QueryRow(ctx, `
|
err := dbConn.QueryRow(ctx, `
|
||||||
SELECT name, pass_hash, COALESCE(pronouns, ''), rgba, created_at FROM users WHERE id = $1
|
SELECT name, pass_hash, COALESCE(pronouns, ''), rgba, created_at FROM users WHERE id = $1
|
||||||
`, user.Id).Scan(&user.Name, &user.PasswordHash, &user.Pronouns, &rgba, &user.CreatedAt)
|
`, user.Id).Scan(&user.Name, &user.PasswordHash, &user.Pronouns, &rgba, &user.CreatedAt)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
user.Color = Uint32ToRgba(uint32(rgba))
|
user.Color = convertions.Uint32ToRgba(uint32(rgba))
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func PgUserSetColor(ctx context.Context, user *User) error {
|
func PgUserSetColor(ctx context.Context, user *types.User) error {
|
||||||
_, err := dbConn.Exec(ctx, `
|
_, err := dbConn.Exec(ctx, `
|
||||||
UPDATE users SET rgba = $1 WHERE id = $2
|
UPDATE users SET rgba = $1 WHERE id = $2
|
||||||
`, RgbaToUint32(user.Color), user.Id)
|
`, convertions.RgbaToUint32(user.Color), user.Id)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func PgUserSetPronouns(ctx context.Context, user *User) error {
|
func PgUserSetPronouns(ctx context.Context, user *types.User) error {
|
||||||
_, err := dbConn.Exec(ctx, `
|
_, err := dbConn.Exec(ctx, `
|
||||||
UPDATE users SET pronouns = $1 WHERE id = $2
|
UPDATE users SET pronouns = $1 WHERE id = $2
|
||||||
`, user.Pronouns, user.Id)
|
`, user.Pronouns, user.Id)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func PgGetWholeUser(ctx context.Context, user *User) error {
|
func PgGetWholeUser(ctx context.Context, user *types.User) error {
|
||||||
if err := PgUserGetById(ctx, user); err != nil {
|
if err := PgUserGetById(ctx, user); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -126,25 +130,25 @@ func PgGetWholeUser(ctx context.Context, user *User) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
CacheSaveUser(user)
|
cache.CacheSaveUser(user)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PgConnectionSave(ctx context.Context, conn *Connection) error {
|
func PgConnectionSave(ctx context.Context, conn *types.Connection) error {
|
||||||
return dbConn.QueryRow(ctx, `
|
return dbConn.QueryRow(ctx, `
|
||||||
INSERT INTO user_connections (requestor_id, recipient_id, state, created_at) VALUES ($1, $2, $3, $4)
|
INSERT INTO user_connections (requestor_id, recipient_id, state, created_at) VALUES ($1, $2, $3, $4)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
`, conn.RequestorId, conn.RecipientId, conn.State, conn.CreatedAt).Scan(&conn.Id)
|
`, conn.RequestorId, conn.RecipientId, conn.State, conn.CreatedAt).Scan(&conn.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PgConnectionDelete(ctx context.Context, conn *Connection) error {
|
func PgConnectionDelete(ctx context.Context, conn *types.Connection) error {
|
||||||
_, err := dbConn.Exec(ctx, `
|
_, err := dbConn.Exec(ctx, `
|
||||||
DELETE FROM user_connections WHERE id = $1
|
DELETE FROM user_connections WHERE id = $1
|
||||||
`, conn.Id)
|
`, conn.Id)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func PgConnectionsGetBelongingToUser(ctx context.Context, user *User) error {
|
func PgConnectionsGetBelongingToUser(ctx context.Context, user *types.User) error {
|
||||||
rows, err := dbConn.Query(ctx, `
|
rows, err := dbConn.Query(ctx, `
|
||||||
SELECT id, requestor_id, recipient_id, state, created_at
|
SELECT id, requestor_id, recipient_id, state, created_at
|
||||||
FROM user_connections
|
FROM user_connections
|
||||||
@@ -156,11 +160,11 @@ func PgConnectionsGetBelongingToUser(ctx context.Context, user *User) error {
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
if user.Connections == nil {
|
if user.Connections == nil {
|
||||||
user.Connections = make(map[uuid.UUID]*Connection)
|
user.Connections = make(map[uuid.UUID]*types.Connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
conn := &Connection{}
|
conn := &types.Connection{}
|
||||||
if err = rows.Scan(&conn.Id, &conn.RequestorId, &conn.RecipientId, &conn.State, &conn.CreatedAt); err != nil {
|
if err = rows.Scan(&conn.Id, &conn.RequestorId, &conn.RecipientId, &conn.State, &conn.CreatedAt); err != nil {
|
||||||
return fmt.Errorf("scanning connection row: %w", err)
|
return fmt.Errorf("scanning connection row: %w", err)
|
||||||
}
|
}
|
||||||
@@ -169,14 +173,14 @@ func PgConnectionsGetBelongingToUser(ctx context.Context, user *User) error {
|
|||||||
return rows.Err()
|
return rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func PgConnectionUpdateState(ctx context.Context, conn *Connection) error {
|
func PgConnectionUpdateState(ctx context.Context, conn *types.Connection) error {
|
||||||
_, err := dbConn.Exec(ctx, `
|
_, err := dbConn.Exec(ctx, `
|
||||||
UPDATE user_connections SET state = $1 WHERE id = $2
|
UPDATE user_connections SET state = $1 WHERE id = $2
|
||||||
`, conn.State, conn.Id)
|
`, conn.State, conn.Id)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func PgConnectionMessageSave(ctx context.Context, message *Message) error {
|
func PgConnectionMessageSave(ctx context.Context, message *types.Message) error {
|
||||||
if message.Id != (uuid.UUID{}) {
|
if message.Id != (uuid.UUID{}) {
|
||||||
_, err := dbConn.Exec(ctx, `
|
_, err := dbConn.Exec(ctx, `
|
||||||
INSERT INTO direct_messages (id, sender_id, receiver_id, created_at, content) VALUES ($1, $2, $3, $4, $5)
|
INSERT INTO direct_messages (id, sender_id, receiver_id, created_at, content) VALUES ($1, $2, $3, $4, $5)
|
||||||
@@ -189,7 +193,7 @@ func PgConnectionMessageSave(ctx context.Context, message *Message) error {
|
|||||||
`, message.Sender, message.Receiver, message.CreatedAt, message.Content).Scan(&message.Id)
|
`, message.Sender, message.Receiver, message.CreatedAt, message.Content).Scan(&message.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PgConnectionGetMessagesBefore(ctx context.Context, before time.Time, connection uuid.UUID, cap uint32) ([]*Message, error) {
|
func PgConnectionGetMessagesBefore(ctx context.Context, before time.Time, connection uuid.UUID, cap uint32) ([]*types.Message, error) {
|
||||||
rows, err := dbConn.Query(ctx, `
|
rows, err := dbConn.Query(ctx, `
|
||||||
SELECT id, sender_id, receiver_id, created_at, content
|
SELECT id, sender_id, receiver_id, created_at, content
|
||||||
FROM (
|
FROM (
|
||||||
@@ -207,9 +211,9 @@ func PgConnectionGetMessagesBefore(ctx context.Context, before time.Time, connec
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
messages := make([]*Message, 0, cap)
|
messages := make([]*types.Message, 0, cap)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
msg := &Message{}
|
msg := &types.Message{}
|
||||||
if err = rows.Scan(&msg.Id, &msg.Sender, &msg.Receiver, &msg.CreatedAt, &msg.Content); err != nil {
|
if err = rows.Scan(&msg.Id, &msg.Sender, &msg.Receiver, &msg.CreatedAt, &msg.Content); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package tokens
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
package main
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-socket/Enums/ConnectionState"
|
"go-socket/packages/Enums/ConnectionState"
|
||||||
"go-socket/Enums/WsEventType"
|
"go-socket/packages/Enums/WsEventType"
|
||||||
|
"go-socket/packages/globals"
|
||||||
|
|
||||||
"github.com/coder/websocket"
|
"github.com/coder/websocket"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -37,7 +38,7 @@ type Connection struct {
|
|||||||
Mu sync.RWMutex `json:"-"`
|
Mu sync.RWMutex `json:"-"`
|
||||||
Id uuid.UUID `json:"id"`
|
Id uuid.UUID `json:"id"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
MessagesBuff [MaxDirectMsgCache]*Message `json:"-"`
|
MessagesBuff [globals.MaxDirectMsgCache]*Message `json:"-"`
|
||||||
NextBuffIdx uint32 `json:"-"`
|
NextBuffIdx uint32 `json:"-"`
|
||||||
RequestorId uuid.UUID `json:"requestorId"`
|
RequestorId uuid.UUID `json:"requestorId"`
|
||||||
RecipientId uuid.UUID `json:"recipientId"`
|
RecipientId uuid.UUID `json:"recipientId"`
|
||||||
@@ -50,15 +51,15 @@ func (conn *Connection) AddMessageToBuff(message *Message) {
|
|||||||
conn.Mu.Lock()
|
conn.Mu.Lock()
|
||||||
defer conn.Mu.Unlock()
|
defer conn.Mu.Unlock()
|
||||||
|
|
||||||
conn.MessagesBuff[conn.NextBuffIdx%MaxDirectMsgCache] = message
|
conn.MessagesBuff[conn.NextBuffIdx%globals.MaxDirectMsgCache] = message
|
||||||
conn.NextBuffIdx++
|
conn.NextBuffIdx++
|
||||||
if conn.NextBuffIdx >= MaxDirectMsgCache {
|
if conn.NextBuffIdx >= globals.MaxDirectMsgCache {
|
||||||
conn.HaveOverflowed = true
|
conn.HaveOverflowed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSortedMessagesBuff returns arr, length
|
// GetSortedMessagesBuff returns arr, length
|
||||||
func (conn *Connection) GetSortedMessagesBuff() (*[MaxDirectMsgCache]*Message, uint32) {
|
func (conn *Connection) GetSortedMessagesBuff() (*[globals.MaxDirectMsgCache]*Message, uint32) {
|
||||||
conn.Mu.RLock()
|
conn.Mu.RLock()
|
||||||
defer conn.Mu.RUnlock()
|
defer conn.Mu.RUnlock()
|
||||||
|
|
||||||
@@ -66,11 +67,11 @@ func (conn *Connection) GetSortedMessagesBuff() (*[MaxDirectMsgCache]*Message, u
|
|||||||
return &conn.MessagesBuff, conn.NextBuffIdx
|
return &conn.MessagesBuff, conn.NextBuffIdx
|
||||||
}
|
}
|
||||||
|
|
||||||
sorted := new([MaxDirectMsgCache]*Message)
|
sorted := new([globals.MaxDirectMsgCache]*Message)
|
||||||
for i := uint32(0); i < MaxDirectMsgCache; i++ {
|
for i := uint32(0); i < globals.MaxDirectMsgCache; i++ {
|
||||||
sorted[i] = conn.MessagesBuff[(conn.NextBuffIdx+i)%MaxDirectMsgCache]
|
sorted[i] = conn.MessagesBuff[(conn.NextBuffIdx+i)%globals.MaxDirectMsgCache]
|
||||||
}
|
}
|
||||||
return sorted, MaxDirectMsgCache
|
return sorted, globals.MaxDirectMsgCache
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectionElevationData struct {
|
type ConnectionElevationData struct {
|
||||||
@@ -93,7 +94,7 @@ type LoginReturn struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WsEventMessage struct {
|
type WsEventMessage struct {
|
||||||
Type WsEventType.WsEventType `json:"type"`
|
Type WsEventType.WsEventType `json:"types"`
|
||||||
Event any `json:"event"`
|
Event any `json:"event"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package wsServer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -7,7 +7,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-socket/Enums/WsEventType"
|
"go-socket/packages/Enums/WsEventType"
|
||||||
|
"go-socket/packages/cache"
|
||||||
|
"go-socket/packages/tokens"
|
||||||
|
"go-socket/packages/types"
|
||||||
|
|
||||||
"github.com/coder/websocket"
|
"github.com/coder/websocket"
|
||||||
"github.com/coder/websocket/wsjson"
|
"github.com/coder/websocket/wsjson"
|
||||||
@@ -25,7 +28,7 @@ func ServeWsConnection(responseWriter http.ResponseWriter, request *http.Request
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var user = User{WsConn: connection}
|
var user = types.User{WsConn: connection}
|
||||||
var isAuthenticated bool
|
var isAuthenticated bool
|
||||||
var ignoreCache bool
|
var ignoreCache bool
|
||||||
|
|
||||||
@@ -54,7 +57,7 @@ func ServeWsConnection(responseWriter http.ResponseWriter, request *http.Request
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WsSendMessageCloseIfTimeout(user *User, message any) {
|
func WsSendMessageCloseIfTimeout(user *types.User, message any) {
|
||||||
if user.WsConn == nil {
|
if user.WsConn == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -72,29 +75,29 @@ func WsSendMessageCloseIfTimeout(user *User, message any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendToAllMessageCloseIfTimeout(message *map[string]any) {
|
func sendToAllMessageCloseIfTimeout(message *map[string]any) {
|
||||||
mu.RLock()
|
cache.Mu.RLock()
|
||||||
users := make([]*User, 0, len(CacheUsers))
|
users := make([]*types.User, 0, len(cache.Users))
|
||||||
for _, user := range CacheUsers {
|
for _, user := range cache.Users {
|
||||||
users = append(users, user)
|
users = append(users, user)
|
||||||
}
|
}
|
||||||
mu.RUnlock()
|
cache.Mu.RUnlock()
|
||||||
|
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
WsSendMessageCloseIfTimeout(user, message)
|
WsSendMessageCloseIfTimeout(user, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAuthenticatedMessage(user *User, userMessage *map[string]any) bool {
|
func handleAuthenticatedMessage(user *types.User, userMessage *map[string]any) bool {
|
||||||
WsSendMessageCloseIfTimeout(user, userMessage)
|
WsSendMessageCloseIfTimeout(user, userMessage)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUnauthenticatedMessage(ctx context.Context, user *User, userMessage *map[string]any) bool {
|
func handleUnauthenticatedMessage(ctx context.Context, user *types.User, userMessage *map[string]any) bool {
|
||||||
token, ok := (*userMessage)["token"].(string)
|
token, ok := (*userMessage)["token"].(string)
|
||||||
response := WsEventMessage{Type: WsEventType.Authentication}
|
response := types.WsEventMessage{Type: WsEventType.Authentication}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
response.Event = WsAuthMessage{
|
response.Event = types.WsAuthMessage{
|
||||||
Success: false,
|
Success: false,
|
||||||
Error: "no token in message",
|
Error: "no token in message",
|
||||||
}
|
}
|
||||||
@@ -102,9 +105,9 @@ func handleUnauthenticatedMessage(ctx context.Context, user *User, userMessage *
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
userId, err := TokenValidateGetId(token)
|
userId, err := tokens.TokenValidateGetId(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Event = WsAuthMessage{
|
response.Event = types.WsAuthMessage{
|
||||||
Success: false,
|
Success: false,
|
||||||
Error: "invalid token",
|
Error: "invalid token",
|
||||||
}
|
}
|
||||||
@@ -112,9 +115,9 @@ func handleUnauthenticatedMessage(ctx context.Context, user *User, userMessage *
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
userFromCache, err := CacheGetUserById(userId)
|
userFromCache, err := cache.CacheGetUserById(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Event = WsAuthMessage{
|
response.Event = types.WsAuthMessage{
|
||||||
Success: false,
|
Success: false,
|
||||||
Error: "user not found",
|
Error: "user not found",
|
||||||
}
|
}
|
||||||
@@ -125,7 +128,7 @@ func handleUnauthenticatedMessage(ctx context.Context, user *User, userMessage *
|
|||||||
userFromCache.WsConn = user.WsConn
|
userFromCache.WsConn = user.WsConn
|
||||||
*user = *userFromCache
|
*user = *userFromCache
|
||||||
|
|
||||||
response.Event = WsAuthMessage{
|
response.Event = types.WsAuthMessage{
|
||||||
Success: true,
|
Success: true,
|
||||||
Error: "",
|
Error: "",
|
||||||
}
|
}
|
||||||
@@ -133,9 +136,9 @@ func handleUnauthenticatedMessage(ctx context.Context, user *User, userMessage *
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeConnection(user *User, ignoreCache bool) {
|
func closeConnection(user *types.User, ignoreCache bool) {
|
||||||
if !ignoreCache {
|
if !ignoreCache {
|
||||||
CacheDeleteUser(user.Id)
|
cache.CacheDeleteUser(user.Id)
|
||||||
}
|
}
|
||||||
if user.WsConn != nil {
|
if user.WsConn != nil {
|
||||||
user.WsConn.CloseNow()
|
user.WsConn.CloseNow()
|
||||||
Reference in New Issue
Block a user