use uuid for everything

This commit is contained in:
2026-04-12 14:19:46 +02:00
parent 18ebfd0416
commit 3be9619cca
9 changed files with 72 additions and 68 deletions
+10 -10
View File
@@ -9,17 +9,17 @@ import (
var ( var (
mu sync.RWMutex mu sync.RWMutex
CacheUsers = make(map[uint32]*User) CacheUsers = make(map[uuid.UUID]*User)
CacheGroups = make(map[uint32]*Group) CacheGroups = make(map[uuid.UUID]*Group)
) )
func CacheGetUserById(id uint32) (*User, error) { func CacheGetUserById(id uuid.UUID) (*User, error) {
mu.RLock() mu.RLock()
defer mu.RUnlock() defer mu.RUnlock()
user, ok := CacheUsers[id] user, ok := CacheUsers[id]
if !ok { if !ok {
return nil, fmt.Errorf("user %d not found", id) return nil, fmt.Errorf("user %s not found", id)
} }
return user, nil return user, nil
} }
@@ -43,7 +43,7 @@ func CacheSaveUser(user *User) {
CacheUsers[user.Id] = user CacheUsers[user.Id] = user
} }
func CacheDeleteUser(id uint32) { func CacheDeleteUser(id uuid.UUID) {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
@@ -57,7 +57,7 @@ func CacheSaveGroup(group *Group) {
CacheGroups[group.Id] = group CacheGroups[group.Id] = group
} }
func CacheDeleteGroup(id uint32) { func CacheDeleteGroup(id uuid.UUID) {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
delete(CacheGroups, id) delete(CacheGroups, id)
@@ -65,7 +65,7 @@ func CacheDeleteGroup(id uint32) {
func CacheAddConnection(a, b *User, conn *Connection) { func CacheAddConnection(a, b *User, conn *Connection) {
first, second := a, b first, second := a, b
if a.Id > b.Id { if a.Id.String() > b.Id.String() {
first, second = b, a first, second = b, a
} }
first.Mu.Lock() first.Mu.Lock()
@@ -78,7 +78,7 @@ func CacheAddConnection(a, b *User, conn *Connection) {
func CacheDeleteConnection(a, b *User, id uuid.UUID) { func CacheDeleteConnection(a, b *User, id uuid.UUID) {
first, second := a, b first, second := a, b
if a.Id > b.Id { if a.Id.String() > b.Id.String() {
first, second = b, a first, second = b, a
} }
first.Mu.Lock() first.Mu.Lock()
@@ -96,13 +96,13 @@ func CacheGetConnection(user *User, id uuid.UUID) (*Connection, bool) {
return conn, ok return conn, ok
} }
func CacheGetGroup(id uint32) (*Group, error) { func CacheGetGroup(id uuid.UUID) (*Group, error) {
mu.RLock() mu.RLock()
defer mu.RUnlock() defer mu.RUnlock()
group, ok := CacheGroups[id] group, ok := CacheGroups[id]
if !ok { if !ok {
return nil, fmt.Errorf("group %d not found", id) return nil, fmt.Errorf("group %s not found", id)
} }
return group, nil return group, nil
+18 -18
View File
@@ -26,7 +26,7 @@ func DbInit(ctx context.Context) {
_, err = dbConn.Exec(ctx, ` _, err = dbConn.Exec(ctx, `
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY, id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT UNIQUE NOT NULL, name TEXT UNIQUE NOT NULL,
pass_hash TEXT NOT NULL, pass_hash TEXT NOT NULL,
pronouns TEXT DEFAULT NULL, pronouns TEXT DEFAULT NULL,
@@ -43,8 +43,8 @@ func DbInit(ctx context.Context) {
_, err = dbConn.Exec(ctx, ` _, err = dbConn.Exec(ctx, `
CREATE TABLE IF NOT EXISTS user_connections ( CREATE TABLE IF NOT EXISTS user_connections (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
requestor_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, requestor_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
recipient_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, recipient_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
state SMALLINT NOT NULL DEFAULT 0, state SMALLINT NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT NOW() created_at TIMESTAMP NOT NULL DEFAULT NOW()
) )
@@ -56,7 +56,7 @@ func DbInit(ctx context.Context) {
_, err = dbConn.Exec(ctx, ` _, err = dbConn.Exec(ctx, `
CREATE TABLE IF NOT EXISTS direct_messages ( CREATE TABLE IF NOT EXISTS direct_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sender_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, sender_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
receiver_id UUID NOT NULL REFERENCES user_connections(id) ON DELETE CASCADE, receiver_id UUID NOT NULL REFERENCES user_connections(id) ON DELETE CASCADE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(), created_at TIMESTAMP NOT NULL DEFAULT NOW(),
content TEXT NOT NULL content TEXT NOT NULL
@@ -68,10 +68,10 @@ func DbInit(ctx context.Context) {
_, err = dbConn.Exec(ctx, ` _, err = dbConn.Exec(ctx, `
CREATE TABLE IF NOT EXISTS chat_groups ( CREATE TABLE IF NOT EXISTS chat_groups (
id SERIAL PRIMARY KEY, id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL, name TEXT NOT NULL,
creator_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, creator_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
enable_client_colors BOOLEAN NOT NULL DEFAULT true, enable_client_colors BOOLEAN NOT NULL DEFAULT true,
color_red SMALLINT DEFAULT NULL, color_red SMALLINT DEFAULT NULL,
color_green SMALLINT DEFAULT NULL, color_green SMALLINT DEFAULT NULL,
@@ -85,8 +85,8 @@ func DbInit(ctx context.Context) {
_, err = dbConn.Exec(ctx, ` _, err = dbConn.Exec(ctx, `
CREATE TABLE IF NOT EXISTS chat_group_members ( CREATE TABLE IF NOT EXISTS chat_group_members (
group_id INTEGER NOT NULL REFERENCES chat_groups(id) ON DELETE CASCADE, group_id UUID NOT NULL REFERENCES chat_groups(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
joined_at TIMESTAMP NOT NULL DEFAULT NOW(), joined_at TIMESTAMP NOT NULL DEFAULT NOW(),
PRIMARY KEY (group_id, user_id) PRIMARY KEY (group_id, user_id)
) )
@@ -106,7 +106,7 @@ func DbUserSave(ctx context.Context, user *User) error {
return err return err
} }
func DbUserDelete(ctx context.Context, id uint32) error { func DbUserDelete(ctx context.Context, id uuid.UUID) error {
_, err := dbConn.Exec(ctx, ` _, err := dbConn.Exec(ctx, `
DELETE FROM users WHERE id = $1 DELETE FROM users WHERE id = $1
`, id) `, id)
@@ -136,9 +136,9 @@ func DbUserGetGroups(ctx context.Context, user *User) error {
} }
defer rows.Close() defer rows.Close()
user.Groups = make(map[uint32]struct{}) user.Groups = make(map[uuid.UUID]struct{})
for rows.Next() { for rows.Next() {
var groupId uint32 var groupId uuid.UUID
if err := rows.Scan(&groupId); err != nil { if err := rows.Scan(&groupId); err != nil {
return err return err
} }
@@ -312,9 +312,9 @@ func DbGroupGetMembers(ctx context.Context, group *Group) error {
} }
defer rows.Close() defer rows.Close()
group.Users = make(map[uint32]struct{}) group.Users = make(map[uuid.UUID]struct{})
for rows.Next() { for rows.Next() {
var userId uint32 var userId uuid.UUID
if err := rows.Scan(&userId); err != nil { if err := rows.Scan(&userId); err != nil {
return err return err
} }
@@ -323,12 +323,12 @@ func DbGroupGetMembers(ctx context.Context, group *Group) error {
return rows.Err() return rows.Err()
} }
func DbGroupAddUsers(ctx context.Context, groupId uint32, userIds *[MaxUsersInGroup]uint32) error { func DbGroupAddUsers(ctx context.Context, groupId uuid.UUID, userIds *[MaxUsersInGroup]uuid.UUID) error {
batch := &pgx.Batch{} batch := &pgx.Batch{}
now := time.Now() now := time.Now()
var count int var count int
for _, uid := range userIds { for _, uid := range userIds {
if uid == 0 { if uid == (uuid.UUID{}) {
continue continue
} }
batch.Queue(` batch.Queue(`
@@ -348,11 +348,11 @@ func DbGroupAddUsers(ctx context.Context, groupId uint32, userIds *[MaxUsersInGr
return nil return nil
} }
func DbGroupRemoveUsers(ctx context.Context, groupId uint32, userIds *[MaxUsersInGroup]uint32) (int, error) { func DbGroupRemoveUsers(ctx context.Context, groupId uuid.UUID, userIds *[MaxUsersInGroup]uuid.UUID) (int, error) {
batch := &pgx.Batch{} batch := &pgx.Batch{}
var count int var count int
for _, uid := range userIds { for _, uid := range userIds {
if uid == 0 { if uid == (uuid.UUID{}) {
continue continue
} }
batch.Queue(` batch.Queue(`
+7 -3
View File
@@ -1,8 +1,12 @@
package main package main
import "context" import (
"context"
func GetUserById(ctx context.Context, userId uint32) (*User, error) { "github.com/google/uuid"
)
func GetUserById(ctx context.Context, userId uuid.UUID) (*User, error) {
user, err := CacheGetUserById(userId) user, err := CacheGetUserById(userId)
if err != nil { if err != nil {
user = &User{Id: userId} user = &User{Id: userId}
@@ -23,7 +27,7 @@ func GetUserByToken(ctx context.Context, token string) (*User, error) {
return GetUserById(ctx, userId) return GetUserById(ctx, userId)
} }
func GetGroup(ctx context.Context, groupId uint32) (*Group, error) { func GetGroup(ctx context.Context, groupId uuid.UUID) (*Group, error) {
group, err := CacheGetGroup(groupId) group, err := CacheGetGroup(groupId)
if err != nil { if err != nil {
group = &Group{Id: groupId} group = &Group{Id: groupId}
BIN
View File
Binary file not shown.
+2 -3
View File
@@ -165,7 +165,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 := ConvertStringUint32(request.FormValue("recipient")) recipientId, err := 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
@@ -296,8 +296,7 @@ func HttpHandleUserElevateConnection(response http.ResponseWriter, request *http
response.WriteHeader(http.StatusAccepted) response.WriteHeader(http.StatusAccepted)
// TODO change to != "" after user id via uuid if conn.UserWantingToElevate != (uuid.UUID{}) && conn.UserWantingToElevate != user.Id {
if conn.UserWantingToElevate != 0 && conn.UserWantingToElevate != user.Id {
switch conn.State { switch conn.State {
case ConnectionState.Stranger: case ConnectionState.Stranger:
conn.State = ConnectionState.Friend conn.State = ConnectionState.Friend
+13 -11
View File
@@ -11,6 +11,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/google/uuid"
) )
func isOwnerOfGroup(user *User, group *Group) bool { func isOwnerOfGroup(user *User, group *Group) bool {
@@ -27,7 +29,7 @@ func getIfOwnerUserAndGroup(ctx context.Context, response *http.ResponseWriter,
return nil, nil, err return nil, nil, err
} }
affectedGroupId, err := ConvertStringUint32(request.FormValue("groupid")) affectedGroupId, err := ConvertStringUuid(request.FormValue("groupid"))
if err != nil { if err != nil {
http.Error(*response, "no such group", http.StatusUnauthorized) http.Error(*response, "no such group", http.StatusUnauthorized)
return nil, nil, err return nil, nil, err
@@ -77,7 +79,7 @@ func HttpHandeGroupCreate(response http.ResponseWriter, request *http.Request) {
OwnerId: user.Id, OwnerId: user.Id,
CreatorId: user.Id, CreatorId: user.Id,
Color: color, Color: color,
Users: map[uint32]struct{}{user.Id: {}}, Users: map[uuid.UUID]struct{}{user.Id: {}},
} }
enableUserColors := request.FormValue("enableUserColors") enableUserColors := request.FormValue("enableUserColors")
@@ -91,7 +93,7 @@ func HttpHandeGroupCreate(response http.ResponseWriter, request *http.Request) {
return return
} }
response.WriteHeader(http.StatusCreated) response.WriteHeader(http.StatusCreated)
fmt.Fprintf(response, "%d", group.Id) fmt.Fprintf(response, "%s", group.Id)
} }
func HttpHandleGroupDelete(response http.ResponseWriter, request *http.Request) { func HttpHandleGroupDelete(response http.ResponseWriter, request *http.Request) {
@@ -140,13 +142,13 @@ func HttpHandleGroupAddUsers(response http.ResponseWriter, request *http.Request
return return
} }
var ids [MaxUsersInGroup]uint32 var ids [MaxUsersInGroup]uuid.UUID
var idx uint32 = 0 var idx uint32 = 0
for _, s := range usersStringSlice { for _, s := range usersStringSlice {
if idx >= MaxUsersInGroup { if idx >= MaxUsersInGroup {
break break
} }
id, err := ConvertStringUint32(strings.TrimSpace(s)) id, err := ConvertStringUuid(strings.TrimSpace(s))
if err != nil { if err != nil {
continue continue
} }
@@ -192,13 +194,13 @@ func HttpHandleGroupRemoveUser(response http.ResponseWriter, request *http.Reque
usersStringSlice := strings.SplitN(usersString, ",", int(MaxUsersInGroup)+1) usersStringSlice := strings.SplitN(usersString, ",", int(MaxUsersInGroup)+1)
var ids [MaxUsersInGroup]uint32 var ids [MaxUsersInGroup]uuid.UUID
var idx uint32 = 0 var idx uint32 = 0
for _, s := range usersStringSlice { for _, s := range usersStringSlice {
if idx >= MaxUsersInGroup { if idx >= MaxUsersInGroup {
break break
} }
id, err := ConvertStringUint32(strings.TrimSpace(s)) id, err := ConvertStringUuid(strings.TrimSpace(s))
if err != nil { if err != nil {
continue continue
} }
@@ -314,7 +316,7 @@ func HttpHandleGroupMessage(response http.ResponseWriter, request *http.Request)
return return
} }
groupIdStr := request.FormValue("groupid") groupIdStr := request.FormValue("groupid")
groupId, err := ConvertStringUint32(groupIdStr) groupId, err := ConvertStringUuid(groupIdStr)
if err != nil { if err != nil {
http.Error(response, "no such group", http.StatusUnauthorized) http.Error(response, "no such group", http.StatusUnauthorized)
return return
@@ -360,13 +362,13 @@ func HttpHandleGroupsGetWithoutMembers(response http.ResponseWriter, request *ht
} }
user.Mu.RLock() user.Mu.RLock()
groupIds := make([]uint32, 0, len(user.Groups)) groupIds := make([]uuid.UUID, 0, len(user.Groups))
for groupId := range user.Groups { for groupId := range user.Groups {
groupIds = append(groupIds, groupId) groupIds = append(groupIds, groupId)
} }
user.Mu.RUnlock() user.Mu.RUnlock()
groups := make(map[uint32]*Group, len(groupIds)) groups := make(map[uuid.UUID]*Group, len(groupIds))
for _, groupId := range groupIds { for _, groupId := range groupIds {
group, err := GetGroup(ctx, groupId) group, err := GetGroup(ctx, groupId)
if err != nil { if err != nil {
@@ -398,7 +400,7 @@ func HttpHandleGroupMembersGet(response http.ResponseWriter, request *http.Reque
} }
groupStr := request.FormValue("group") groupStr := request.FormValue("group")
groupId, err := ConvertStringUint32(groupStr) groupId, err := ConvertStringUuid(groupStr)
if err != nil { if err != nil {
http.Error(response, "invalid group", http.StatusBadRequest) http.Error(response, "invalid group", http.StatusBadRequest)
return return
+11 -11
View File
@@ -18,8 +18,8 @@ type User struct {
PasswordHash string PasswordHash string
CreatedAt time.Time CreatedAt time.Time
WsConn *websocket.Conn WsConn *websocket.Conn
Id uint32 Id uuid.UUID
Groups map[uint32]struct{} Groups map[uuid.UUID]struct{}
Connections map[uuid.UUID]*Connection Connections map[uuid.UUID]*Connection
Color [3]uint8 Color [3]uint8
} }
@@ -30,9 +30,9 @@ type Connection struct {
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
MessagesBuff [MaxDirectMsgCache]*Message `json:"-"` MessagesBuff [MaxDirectMsgCache]*Message `json:"-"`
NextBuffIdx uint32 `json:"-"` NextBuffIdx uint32 `json:"-"`
RequestorId uint32 `json:"requestorId"` RequestorId uuid.UUID `json:"requestorId"`
RecipientId uint32 `json:"recipientId"` RecipientId uuid.UUID `json:"recipientId"`
UserWantingToElevate uint32 `json:"userWantingToElevate"` // TODO add to database UserWantingToElevate uuid.UUID `json:"userWantingToElevate"` // TODO add to database
HaveOverflowed bool `json:"-"` HaveOverflowed bool `json:"-"`
State ConnectionState.ConnectionState `json:"state"` State ConnectionState.ConnectionState `json:"state"`
} }
@@ -73,7 +73,7 @@ type Message struct {
Id uuid.UUID `json:"id"` Id uuid.UUID `json:"id"`
Content string `json:"content"` Content string `json:"content"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
Sender uint32 `json:"sender"` Sender uuid.UUID `json:"sender"`
Receiver uuid.UUID `json:"receiver"` Receiver uuid.UUID `json:"receiver"`
} }
@@ -83,10 +83,10 @@ type Group struct {
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
MessagesBuff [MaxDirectMsgCache]*Message `json:"-"` MessagesBuff [MaxDirectMsgCache]*Message `json:"-"`
NextBuffIdx uint32 `json:"-"` NextBuffIdx uint32 `json:"-"`
Id uint32 `json:"-"` Id uuid.UUID `json:"-"`
CreatorId uint32 `json:"creatorId"` CreatorId uuid.UUID `json:"creatorId"`
OwnerId uint32 `json:"ownerId"` OwnerId uuid.UUID `json:"ownerId"`
Users map[uint32]struct{} `json:"-"` Users map[uuid.UUID]struct{} `json:"-"`
Color [3]uint8 `json:"color"` Color [3]uint8 `json:"color"`
EnableUserColors bool `json:"enableUserColors"` EnableUserColors bool `json:"enableUserColors"`
HaveMessageBuffOverflowed bool `json:"-"` HaveMessageBuffOverflowed bool `json:"-"`
@@ -94,7 +94,7 @@ type Group struct {
type LoginReturn struct { type LoginReturn struct {
Token string `json:"token"` Token string `json:"token"`
UserId uint32 `json:"userId"` UserId uuid.UUID `json:"userId"`
} }
type WsEventMessage struct { type WsEventMessage struct {
-1
View File
@@ -1,5 +1,4 @@
group messages history group messages history
rewrite to use uuid
media support media support
fix color saving to use INT (002255255035) rgba fix color saving to use INT (002255255035) rgba
+11 -11
View File
@@ -2,26 +2,26 @@ package main
import ( import (
"fmt" "fmt"
"strconv"
"time" "time"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
) )
const tokenSecret = "tmp" // TODO delete in production const tokenSecret = "tmp" // TODO delete in production
const tokenExpiration = time.Hour const tokenExpiration = time.Hour
func TokenCreate(userId uint32) (string, error) { func TokenCreate(userId uuid.UUID) (string, error) {
now := time.Now() now := time.Now()
signedToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{ signedToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
Subject: strconv.FormatUint(uint64(userId), 10), Subject: userId.String(),
IssuedAt: jwt.NewNumericDate(now), IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(tokenExpiration)), ExpiresAt: jwt.NewNumericDate(now.Add(tokenExpiration)),
}).SignedString([]byte(tokenSecret)) }).SignedString([]byte(tokenSecret))
return signedToken, err return signedToken, err
} }
func TokenValidateGetId(tokenString string) (uint32, error) { func TokenValidateGetId(tokenString string) (uuid.UUID, error) {
token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) { token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
@@ -29,28 +29,28 @@ func TokenValidateGetId(tokenString string) (uint32, error) {
return []byte(tokenSecret), nil return []byte(tokenSecret), nil
}) })
if err != nil { if err != nil {
return 0, err return uuid.UUID{}, err
} }
claims, ok := token.Claims.(jwt.MapClaims) claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid { if !ok || !token.Valid {
return 0, fmt.Errorf("invalid token") return uuid.UUID{}, fmt.Errorf("invalid token")
} }
exp, ok := claims["exp"].(float64) exp, ok := claims["exp"].(float64)
if !ok || time.Now().Unix() > int64(exp) { if !ok || time.Now().Unix() > int64(exp) {
return 0, fmt.Errorf("token expired") return uuid.UUID{}, fmt.Errorf("token expired")
} }
sub, ok := claims["sub"].(string) sub, ok := claims["sub"].(string)
if !ok { if !ok {
return 0, fmt.Errorf("invalid subject claim") return uuid.UUID{}, fmt.Errorf("invalid subject claim")
} }
id, err := strconv.ParseUint(sub, 10, 32) id, err := uuid.Parse(sub)
if err != nil { if err != nil {
return 0, fmt.Errorf("invalid subject claim") return uuid.UUID{}, fmt.Errorf("invalid subject claim")
} }
return uint32(id), nil return id, nil
} }