From 59e693dacd433d663f76f650aefbf67d75cff44d Mon Sep 17 00:00:00 2001 From: Sisi Date: Sat, 11 Apr 2026 23:04:50 +0200 Subject: [PATCH] split http across files --- database.go | 36 +- getter.go | 39 ++ http.go | 994 +---------------------------------------- httpConnectionAndDm.go | 331 ++++++++++++++ httpGroup.go | 431 ++++++++++++++++++ httpUser.go | 192 ++++++++ structs.go | 1 - 7 files changed, 1019 insertions(+), 1005 deletions(-) create mode 100644 getter.go create mode 100644 httpConnectionAndDm.go create mode 100644 httpGroup.go create mode 100644 httpUser.go diff --git a/database.go b/database.go index ccb1154..1178040 100644 --- a/database.go +++ b/database.go @@ -54,13 +54,12 @@ func DbInit(ctx context.Context) { } _, err = dbConn.Exec(ctx, ` - CREATE TABLE IF NOT EXISTS messages ( + CREATE TABLE IF NOT EXISTS direct_messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), sender_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, receiver_id UUID NOT NULL REFERENCES user_connections(id) ON DELETE CASCADE, created_at TIMESTAMP NOT NULL DEFAULT NOW(), - content TEXT NOT NULL, - is_group_message BOOLEAN DEFAULT FALSE + content TEXT NOT NULL ) `) if err != nil { @@ -162,6 +161,21 @@ func DbUserSetPronouns(ctx context.Context, user *User) error { return err } +func DbGetWholeUser(ctx context.Context, user *User) error { + if err := DbUserGetById(ctx, user); err != nil { + return err + } + if err := DbUserGetGroups(ctx, user); err != nil { + return err + } + if err := DbConnectionsGetBelongingToUser(ctx, user); err != nil { + return err + } + + CacheSaveUser(user) + return nil +} + func DbGroupSetColor(ctx context.Context, group *Group) error { _, err := dbConn.Exec(ctx, ` UPDATE chat_groups SET color_red = $1, color_green = $2, color_blue = $3 WHERE id = $4 @@ -219,22 +233,22 @@ func DbConnectionUpdateState(ctx context.Context, conn *Connection) error { func DbMessageSave(ctx context.Context, message *Message) error { if message.Id != (uuid.UUID{}) { _, err := dbConn.Exec(ctx, ` - INSERT INTO messages (id, sender_id, receiver_id, created_at, content, is_group_message) VALUES ($1, $2, $3, $4, $5, $6) - `, message.Id, message.Sender, message.Receiver, message.CreatedAt, message.Content, message.IsGroupMessage) + INSERT INTO direct_messages (id, sender_id, receiver_id, created_at, content) VALUES ($1, $2, $3, $4, $5) + `, message.Id, message.Sender, message.Receiver, message.CreatedAt, message.Content) return err } return dbConn.QueryRow(ctx, ` - INSERT INTO messages (sender_id, receiver_id, created_at, content, is_group_message) VALUES ($1, $2, $3, $4, $5) + INSERT INTO direct_messages (sender_id, receiver_id, created_at, content) VALUES ($1, $2, $3, $4) RETURNING id - `, message.Sender, message.Receiver, message.CreatedAt, message.Content, message.IsGroupMessage).Scan(&message.Id) + `, message.Sender, message.Receiver, message.CreatedAt, message.Content).Scan(&message.Id) } func DbConnectionGetMessagesBefore(ctx context.Context, before time.Time, connection uuid.UUID, cap uint32) ([]*Message, error) { rows, err := dbConn.Query(ctx, ` - SELECT id, sender_id, receiver_id, created_at, content, is_group_message + SELECT id, sender_id, receiver_id, created_at, content FROM ( - SELECT id, sender_id, receiver_id, created_at, content, is_group_message - FROM messages + SELECT id, sender_id, receiver_id, created_at, content + FROM direct_messages WHERE receiver_id = $1 AND created_at < $2 ORDER BY created_at DESC @@ -250,7 +264,7 @@ func DbConnectionGetMessagesBefore(ctx context.Context, before time.Time, connec messages := make([]*Message, 0, cap) for rows.Next() { msg := &Message{} - if err = rows.Scan(&msg.Id, &msg.Sender, &msg.Receiver, &msg.CreatedAt, &msg.Content, &msg.IsGroupMessage); err != nil { + if err = rows.Scan(&msg.Id, &msg.Sender, &msg.Receiver, &msg.CreatedAt, &msg.Content); err != nil { return nil, err } messages = append(messages, msg) diff --git a/getter.go b/getter.go new file mode 100644 index 0000000..9de508e --- /dev/null +++ b/getter.go @@ -0,0 +1,39 @@ +package main + +import "context" + +func GetUserById(ctx context.Context, userId uint32) (*User, error) { + user, err := CacheGetUserById(userId) + if err != nil { + user = &User{Id: userId} + err = DbGetWholeUser(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) +} + +func GetGroup(ctx context.Context, groupId uint32) (*Group, error) { + group, err := CacheGetGroup(groupId) + if err != nil { + group = &Group{Id: groupId} + if err = DbGroupGetById(ctx, group); err != nil { + return nil, err + } + if err = DbGroupGetMembers(ctx, group); err != nil { + return nil, err + } + CacheSaveGroup(group) + } + return group, nil +} diff --git a/http.go b/http.go index d6df415..8611f61 100644 --- a/http.go +++ b/http.go @@ -1,1005 +1,13 @@ package main import ( - "context" - json2 "encoding/json" - "errors" - "fmt" - "maps" "net/http" - "slices" - "strconv" - "strings" - "time" - - "go-socket/Enums/ConnectionState" - - "github.com/google/uuid" - "golang.org/x/crypto/bcrypt" ) -func isMethodAllowed(response *http.ResponseWriter, request *http.Request) bool { +func HttpMethodAllowed(response *http.ResponseWriter, request *http.Request) bool { if request.Method != http.MethodPost { http.Error(*response, "POST only", http.StatusMethodNotAllowed) return false } return true } - -func getWholeUserFromDb(ctx context.Context, user *User) error { - if err := DbUserGetById(ctx, user); err != nil { - return err - } - if err := DbUserGetGroups(ctx, user); err != nil { - return err - } - if err := DbConnectionsGetBelongingToUser(ctx, user); err != nil { - return err - } - - CacheSaveUser(user) - return nil -} - -func getUserById(ctx context.Context, userId uint32) (*User, error) { - user, err := CacheGetUserById(userId) - if err != nil { - user = &User{Id: userId} - err = getWholeUserFromDb(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) -} - -func getGroup(ctx context.Context, groupId uint32) (*Group, error) { - group, err := CacheGetGroup(groupId) - if err != nil { - group = &Group{Id: groupId} - if err = DbGroupGetById(ctx, group); err != nil { - return nil, err - } - if err = DbGroupGetMembers(ctx, group); err != nil { - return nil, err - } - CacheSaveGroup(group) - } - return group, nil -} - -func isOwner(user *User, group *Group) bool { - if group.OwnerId == user.Id { - return true - } - return false -} - -func getIfOwnerUserAndGroup(ctx context.Context, response *http.ResponseWriter, request *http.Request) (*User, *Group, error) { - user, err := getUserByToken(ctx, request.FormValue("token")) - if err != nil { - http.Error(*response, "invalid token", http.StatusUnauthorized) - return nil, nil, err - } - - affectedGroupId, err := ConvertStringUint32(request.FormValue("groupid")) - if err != nil { - http.Error(*response, "no such group", http.StatusUnauthorized) - return nil, nil, err - } - - group, err := getGroup(ctx, affectedGroupId) - if err != nil { - http.Error(*response, "no such group", http.StatusUnauthorized) - return nil, nil, err - } - - if !isOwner(user, group) { - http.Error(*response, "no such group", http.StatusUnauthorized) - return nil, nil, errors.New("not an owner") - } - return user, group, nil -} - -func getUserByTokenGetUserByIdStrHandleHttp(ctx context.Context, response *http.ResponseWriter, token string, userIdStr string) (*User, *User, error) { - user, err := getUserByToken(ctx, token) - if err != nil { - http.Error(*response, "invalid token", http.StatusUnauthorized) - return nil, nil, err - } - affectedUserId, err := ConvertStringUint32(userIdStr) - if err != nil { - http.Error(*response, "no such user", http.StatusUnauthorized) - return nil, nil, err - } - user2, err := getUserById(ctx, affectedUserId) - if err != nil { - http.Error(*response, "no such user", http.StatusUnauthorized) - return nil, nil, err - } - return user, user2, nil -} - -func HttpHandleUserNew(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - username := request.FormValue("username") - if len(username) < 4 { - http.Error(response, "no or short username", http.StatusBadRequest) - return - } - - password := request.FormValue("password") - if len(password) < 8 { - http.Error(response, "no or short password", http.StatusBadRequest) - return - } - - color, err := ConvertStringToRgb(request.FormValue("color")) - if err != nil { - http.Error(response, "bad color", http.StatusBadRequest) - return - } - - hashedPassword, err := PasswordHash(password) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - - newUser := &User{ - Name: username, - PasswordHash: hashedPassword, - Color: color, - CreatedAt: time.Now(), - } - - ctx := request.Context() - - err = DbUserSave(ctx, newUser) - if err != nil { - http.Error(response, "name taken", http.StatusUnauthorized) - return - } - - response.WriteHeader(http.StatusCreated) -} - -func HttpHandleUserDelete(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - ctx := request.Context() - - userId, err := TokenValidateGetId(request.FormValue("token")) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - - err = DbUserDelete(ctx, userId) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - - CacheDeleteUser(userId) - response.WriteHeader(http.StatusAccepted) -} - -// HttpHandleUserModifyAppearance currently just color -func HttpHandleUserModifyAppearance(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - ctx := request.Context() - user, err := getUserByToken(ctx, request.FormValue("token")) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - - color, err := ConvertStringToRgb(request.FormValue("color")) - if err != nil { - http.Error(response, "invalid color", http.StatusBadRequest) - return - } - user.Color = color - err = DbUserSetColor(ctx, user) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - response.WriteHeader(http.StatusAccepted) -} - -// HttpHandleUserModifyAbout currently just pronouns -func HttpHandleUserModifyAbout(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - ctx := request.Context() - user, err := getUserByToken(ctx, request.FormValue("token")) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - - pronouns := request.FormValue("pronouns") - if len(pronouns) > 25 || len(pronouns) < 2 { - http.Error(response, "invalid pronouns", http.StatusBadRequest) - return - } - - user.Pronouns = pronouns - err = DbUserSetPronouns(ctx, user) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - response.WriteHeader(http.StatusAccepted) -} - -func HttpHandleUserMessage(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - ctx := request.Context() - user, err := getUserByToken(ctx, request.FormValue("token")) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - - targetConnection, err := ConvertStringUuid(request.FormValue("connectionid")) - if err != nil { - http.Error(response, "invalid connectionid", http.StatusBadRequest) - return - } - conn, ok := CacheGetConnection(user, targetConnection) - if !ok { - http.Error(response, "invalid connectionid", http.StatusBadRequest) - return - } - - msgContent := request.FormValue("msgContent") - if msgContent == "" { - http.Error(response, "empty msgContent", http.StatusBadRequest) - return - } - - var target *User - - if user.Id == conn.RequestorId { - target, err = getUserById(ctx, conn.RecipientId) - } else if user.Id == conn.RecipientId { - target, err = getUserById(ctx, conn.RequestorId) - } else { - http.Error(response, "invalid connectionid", http.StatusBadRequest) - return - } - - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - message := &Message{ - Id: uuid.New(), - Content: msgContent, - CreatedAt: time.Now(), - Sender: user.Id, - Receiver: conn.Id, - IsGroupMessage: false, - } - - WsMessageSendToUser(target, message) - - err = DbMessageSave(ctx, message) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - - response.WriteHeader(http.StatusAccepted) -} - -func HttpHandleUserGetConnectionMessages(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - ctx := request.Context() - user, err := getUserByToken(ctx, request.FormValue("token")) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - - connectionId, err := ConvertStringUuid(request.FormValue("connectionid")) - if err != nil { - http.Error(response, "invalid connectionid", http.StatusBadRequest) - return - } - - before, err := ConvertStringTimestamp(request.FormValue("before")) - if err != nil { - before = time.Now() - } - - messagesCap, err := ConvertStringUint32(request.FormValue("messages")) - if err != nil { - messagesCap = MaxDirectMsgCache - } - - conn, ok := CacheGetConnection(user, connectionId) - if !ok { - http.Error(response, "invalid connectionid", http.StatusBadRequest) - return - } - - buffer, bufferSize := conn.GetSortedMessagesBuff() - - var validBufCount uint32 - for validBufCount < bufferSize && buffer[validBufCount].CreatedAt.Before(before) { - validBufCount++ - } - - var messages []*Message - - if validBufCount >= messagesCap { - start := validBufCount - messagesCap - messages = make([]*Message, messagesCap) - for i := uint32(0); i < messagesCap; i++ { - messages[i] = buffer[start+i] - } - } else { - remaining := messagesCap - validBufCount - cutoff := before - if validBufCount > 0 { - cutoff = buffer[0].CreatedAt - } - dbMessages, err := DbConnectionGetMessagesBefore(ctx, cutoff, connectionId, remaining) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - messages = make([]*Message, 0, uint32(len(dbMessages))+validBufCount) - messages = append(messages, dbMessages...) - for i := uint32(0); i < validBufCount; i++ { - messages = append(messages, buffer[i]) - } - } - - json, err := json2.Marshal(messages) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - - response.WriteHeader(http.StatusOK) - response.Write(json) -} - -func HttpHandleUserNewConnection(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - ctx := request.Context() - requestor, recipient, err := getUserByTokenGetUserByIdStrHandleHttp(ctx, &response, request.FormValue("token"), request.FormValue("recipient")) - if err != nil { - return - } - - if requestor.Id == recipient.Id { - http.Error(response, "cannot connect to yourself", http.StatusBadRequest) - return - } - - requestor.Mu.RLock() - for _, connection := range requestor.Connections { - if (connection.RequestorId == requestor.Id && connection.RecipientId == recipient.Id) || - (connection.RecipientId == requestor.Id && connection.RequestorId == recipient.Id) { - requestor.Mu.RUnlock() - http.Error(response, "connection already exists", http.StatusBadRequest) - return - } - } - requestor.Mu.RUnlock() - - connection := &Connection{ - CreatedAt: time.Now(), - RequestorId: requestor.Id, - RecipientId: recipient.Id, - State: ConnectionState.Stranger, - } - err = DbConnectionSave(ctx, connection) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - CacheAddConnection(requestor, recipient, connection) - - response.WriteHeader(http.StatusCreated) - return -} - -func HttpHandleUserDeleteConnection(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - ctx := request.Context() - - user, err := getUserByToken(ctx, request.FormValue("token")) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - - connectionId, err := ConvertStringUuid(request.FormValue("connectionid")) - if err != nil { - http.Error(response, "invalid connectionid", http.StatusBadRequest) - return - } - - conn, ok := CacheGetConnection(user, connectionId) - if !ok { - http.Error(response, "invalid connectionid", http.StatusBadRequest) - return - } - - var user2 *User - if conn.RequestorId == user.Id { - recipient, err := getUserById(ctx, conn.RecipientId) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - user2 = recipient - } else if conn.RecipientId == user.Id { - requestor, err := getUserById(ctx, conn.RequestorId) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - user2 = requestor - } else { - http.Error(response, "invalid connectionid", http.StatusBadRequest) - return - } - - err = DbConnectionDelete(ctx, conn) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - - CacheDeleteConnection(user, user2, connectionId) - - response.WriteHeader(http.StatusAccepted) -} - -func HttpHandleUserElevateConnection(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - ctx := request.Context() - user, err := getUserByToken(ctx, request.FormValue("token")) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - connectionId, err := ConvertStringUuid(request.FormValue("connectionid")) - if err != nil { - http.Error(response, "invalid connectionid", http.StatusBadRequest) - return - } - conn, ok := CacheGetConnection(user, connectionId) - if !ok { - http.Error(response, "invalid connectionid", http.StatusBadRequest) - return - } - - if conn.RecipientId != user.Id { - http.Error(response, "invalid connectionid", http.StatusBadRequest) - return - } - - switch conn.State { - case ConnectionState.Stranger: - conn.State = ConnectionState.Friend - break - case ConnectionState.GroupFellow: - conn.State = ConnectionState.Stranger - break - default: - http.Error(response, "cannot elevate further", http.StatusBadRequest) - return - } - - err = DbConnectionUpdateState(ctx, conn) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - response.WriteHeader(http.StatusAccepted) -} - -func HttpHandleUserGetConnections(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - ctx := request.Context() - user, err := getUserByToken(ctx, request.FormValue("token")) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - - user.Mu.RLock() - connections := slices.Collect(maps.Values(user.Connections)) - user.Mu.RUnlock() - json, err := json2.Marshal(connections) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - - response.WriteHeader(http.StatusOK) - response.Write(json) -} - -func HttpHandleTokenNew(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - username := request.FormValue("username") - if len(username) < 4 { - http.Error(response, "no or short username", http.StatusBadRequest) - return - } - - password := request.FormValue("password") - - if len(password) < 8 { - http.Error(response, "no or short password", http.StatusBadRequest) - return - } - - var ( - user *User - err error - ctx = request.Context() - ) - - user, err = CacheGetUserByName(username) - if err != nil { - user = &User{Name: username} - if err = DbUserGetStandardInfoByName(ctx, user); err != nil { - http.Error(response, "bad login", http.StatusUnauthorized) - return - } - if err = getWholeUserFromDb(ctx, user); err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - } - - err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)) - if err != nil { - http.Error(response, "bad login", http.StatusUnauthorized) - return - } - - token, err := TokenCreate(user.Id) - if err != nil { - http.Error(response, "internal server error2", http.StatusInternalServerError) - return - } - - json, err := json2.Marshal(LoginReturn{Token: token, UserId: user.Id}) - if err != nil { - http.Error(response, "internal server error3", http.StatusInternalServerError) - return - } - - response.WriteHeader(http.StatusCreated) - response.Write(json) -} - -func HttpHandeGroupCreate(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - ctx := request.Context() - - user, err := getUserByToken(ctx, request.FormValue("token")) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - - name := request.FormValue("name") - if name == "" { - name = "Best group ever" - } - - colorString := request.FormValue("color") - color, err := ConvertStringToRgb(colorString) - if err != nil { - http.Error(response, "invalid color", http.StatusBadRequest) - return - } - - group := Group{ - Name: name, - CreatedAt: time.Now(), - OwnerId: user.Id, - CreatorId: user.Id, - Color: color, - Users: map[uint32]struct{}{user.Id: {}}, - } - - enableUserColors := request.FormValue("enableUserColors") - if enableUserColors == "1" { - group.EnableUserColors = true - } - - err = DbGroupSave(ctx, &group) - if err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - response.WriteHeader(http.StatusCreated) - fmt.Fprintf(response, "%d", group.Id) -} - -func HttpHandleGroupDelete(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - ctx := request.Context() - _, group, err := getIfOwnerUserAndGroup(ctx, &response, request) - if err != nil { - return - } - - err = DbGroupDelete(ctx, group) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - CacheDeleteGroup(group.Id) - - response.WriteHeader(http.StatusAccepted) -} - -func HttpHandleGroupAddUsers(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - ctx := request.Context() - - _, group, err := getIfOwnerUserAndGroup(ctx, &response, request) - if err != nil { - return - } - - usersString := request.FormValue("users") - var remainingUsersCount = int(MaxUsersInGroup) - len(group.Users) - if remainingUsersCount < 1 { - http.Error(response, "max users", http.StatusUnauthorized) - return - } - - usersStringSlice := strings.SplitN(usersString, ",", remainingUsersCount+1) - if len(usersStringSlice) == 0 { - http.Error(response, "no users to add", http.StatusBadRequest) - return - } - - var ids [MaxUsersInGroup]uint32 - var idx uint32 = 0 - for _, s := range usersStringSlice { - if idx >= MaxUsersInGroup { - break - } - id, err := ConvertStringUint32(strings.TrimSpace(s)) - if err != nil { - continue - } - ids[idx] = id - idx++ - } - if idx == 0 { - http.Error(response, "no valid users", http.StatusBadRequest) - return - } - - err = DbGroupAddUsers(ctx, group.Id, &ids) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - - for i := uint32(0); i < idx; i++ { - group.Users[ids[i]] = struct{}{} - if cachedUser, err := CacheGetUserById(ids[i]); err == nil { - cachedUser.Mu.Lock() - cachedUser.Groups[group.Id] = struct{}{} - cachedUser.Mu.Unlock() - } - } - - response.WriteHeader(http.StatusAccepted) -} - -func HttpHandleGroupRemoveUser(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - ctx := request.Context() - - _, group, err := getIfOwnerUserAndGroup(ctx, &response, request) - if err != nil { - return - } - - usersString := request.FormValue("users") - - usersStringSlice := strings.SplitN(usersString, ",", int(MaxUsersInGroup)+1) - - var ids [MaxUsersInGroup]uint32 - var idx uint32 = 0 - for _, s := range usersStringSlice { - if idx >= MaxUsersInGroup { - break - } - id, err := ConvertStringUint32(strings.TrimSpace(s)) - if err != nil { - continue - } - ids[idx] = id - idx++ - } - if idx == 0 { - http.Error(response, "no valid users", http.StatusBadRequest) - return - } - - count, err := DbGroupRemoveUsers(ctx, group.Id, &ids) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - - for i := uint32(0); i < idx; i++ { - delete(group.Users, ids[i]) - if cachedUser, err := CacheGetUserById(ids[i]); err == nil { - cachedUser.Mu.Lock() - delete(cachedUser.Groups, group.Id) - cachedUser.Mu.Unlock() - } - } - - response.WriteHeader(http.StatusAccepted) - response.Write([]byte(strconv.Itoa(count))) -} - -func HttpHandleGroupChangeColor(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - ctx := request.Context() - _, group, err := getIfOwnerUserAndGroup(ctx, &response, request) - if err != nil { - return - } - - color, err := ConvertStringToRgb(request.FormValue("color")) - if err != nil { - http.Error(response, "invalid color", http.StatusBadRequest) - return - } - - group.Color = color - err = DbGroupSetColor(ctx, group) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - - response.WriteHeader(http.StatusAccepted) - response.Write([]byte("changed")) -} - -func HttpHandleGroupChangeOwner(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - ctx := request.Context() - _, group, err := getIfOwnerUserAndGroup(ctx, &response, request) - if err != nil { - return - } - - newOwnerName := request.FormValue("newOwner") - - newOwner, err := CacheGetUserByName(newOwnerName) - if err != nil { - newOwner = &User{Name: newOwnerName} - err = DbUserGetStandardInfoByName(ctx, newOwner) - if err != nil { - http.Error(response, "user not in group", http.StatusBadRequest) - return - } - - if err = getWholeUserFromDb(ctx, newOwner); err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - } - - _, ok := group.Users[newOwner.Id] - if !ok { - http.Error(response, "user not in group", http.StatusBadRequest) - return - } - - group.OwnerId = newOwner.Id - err = DbGroupSetOwnerId(ctx, group) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - - response.WriteHeader(http.StatusAccepted) -} - -func HttpHandleGroupMessage(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - ctx := request.Context() - - user, err := getUserByToken(ctx, request.FormValue("token")) - if err != nil { - http.Error(response, "invalid token", http.StatusBadRequest) - return - } - groupIdStr := request.FormValue("groupid") - groupId, err := ConvertStringUint32(groupIdStr) - if err != nil { - http.Error(response, "no such group", http.StatusUnauthorized) - return - } - - group, err := getGroup(ctx, groupId) - if err != nil { - http.Error(response, "no such group", http.StatusUnauthorized) - return - } - - content := request.FormValue("content") - if content == "" { - http.Error(response, "empty message", http.StatusBadRequest) - return - } - - _, ok := group.Users[user.Id] - if !ok { - http.Error(response, "no such group", http.StatusUnauthorized) - return - } - - err = WsSendToGroup(group, user, content) - if err != nil { - http.Error(response, err.Error(), http.StatusBadRequest) - return - } - response.WriteHeader(http.StatusAccepted) -} - -func HttpHandleGroupsGetWithoutMembers(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - ctx := request.Context() - - user, err := getUserByToken(ctx, request.FormValue("token")) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - - user.Mu.RLock() - groupIds := make([]uint32, 0, len(user.Groups)) - for groupId := range user.Groups { - groupIds = append(groupIds, groupId) - } - user.Mu.RUnlock() - - groups := make(map[uint32]*Group, len(groupIds)) - for _, groupId := range groupIds { - group, err := getGroup(ctx, groupId) - if err != nil { - continue - } - groups[groupId] = group - } - - json, err := json2.Marshal(groups) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - - response.WriteHeader(http.StatusAccepted) - response.Write(json) -} - -func HttpHandleGroupMembersGet(response http.ResponseWriter, request *http.Request) { - if !isMethodAllowed(&response, request) { - return - } - - ctx := request.Context() - user, err := getUserByToken(ctx, request.FormValue("token")) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - - groupStr := request.FormValue("group") - groupId, err := ConvertStringUint32(groupStr) - if err != nil { - http.Error(response, "invalid group", http.StatusBadRequest) - return - } - - user.Mu.RLock() - _, ok := user.Groups[groupId] - user.Mu.RUnlock() - if !ok { - http.Error(response, "no such group", http.StatusUnauthorized) - return - } - - group, err := getGroup(ctx, groupId) - if err != nil { - http.Error(response, "no such group", http.StatusUnauthorized) - return - } - - groupMembers := slices.Collect(maps.Keys(group.Users)) - - json, err := json2.Marshal(groupMembers) - if err != nil { - http.Error(response, "internal server error", http.StatusInternalServerError) - return - } - - response.WriteHeader(http.StatusAccepted) - response.Write(json) -} diff --git a/httpConnectionAndDm.go b/httpConnectionAndDm.go new file mode 100644 index 0000000..74ccb82 --- /dev/null +++ b/httpConnectionAndDm.go @@ -0,0 +1,331 @@ +package main + +import ( + json2 "encoding/json" + "maps" + "net/http" + "slices" + "time" + + "go-socket/Enums/ConnectionState" + + "github.com/google/uuid" +) + +func HttpHandleUserMessage(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + user, err := GetUserByToken(ctx, request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + + targetConnection, err := ConvertStringUuid(request.FormValue("connectionid")) + if err != nil { + http.Error(response, "invalid connectionid", http.StatusBadRequest) + return + } + conn, ok := CacheGetConnection(user, targetConnection) + if !ok { + http.Error(response, "invalid connectionid", http.StatusBadRequest) + return + } + + msgContent := request.FormValue("msgContent") + if msgContent == "" { + http.Error(response, "empty msgContent", http.StatusBadRequest) + return + } + + var target *User + + if user.Id == conn.RequestorId { + target, err = GetUserById(ctx, conn.RecipientId) + } else if user.Id == conn.RecipientId { + target, err = GetUserById(ctx, conn.RequestorId) + } else { + http.Error(response, "invalid connectionid", http.StatusBadRequest) + return + } + + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + message := &Message{ + Id: uuid.New(), + Content: msgContent, + CreatedAt: time.Now(), + Sender: user.Id, + Receiver: conn.Id, + } + + WsMessageSendToUser(target, message) + + err = DbMessageSave(ctx, message) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + response.WriteHeader(http.StatusAccepted) +} + +func HttpHandleUserGetConnectionMessages(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + ctx := request.Context() + user, err := GetUserByToken(ctx, request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + + connectionId, err := ConvertStringUuid(request.FormValue("connectionid")) + if err != nil { + http.Error(response, "invalid connectionid", http.StatusBadRequest) + return + } + + before, err := ConvertStringTimestamp(request.FormValue("before")) + if err != nil { + before = time.Now() + } + + messagesCap, err := ConvertStringUint32(request.FormValue("messages")) + if err != nil { + messagesCap = MaxDirectMsgCache + } + + conn, ok := CacheGetConnection(user, connectionId) + if !ok { + http.Error(response, "invalid connectionid", http.StatusBadRequest) + return + } + + buffer, bufferSize := conn.GetSortedMessagesBuff() + + var validBufCount uint32 + for validBufCount < bufferSize && buffer[validBufCount].CreatedAt.Before(before) { + validBufCount++ + } + + var messages []*Message + + if validBufCount >= messagesCap { + start := validBufCount - messagesCap + messages = make([]*Message, messagesCap) + for i := uint32(0); i < messagesCap; i++ { + messages[i] = buffer[start+i] + } + } else { + remaining := messagesCap - validBufCount + cutoff := before + if validBufCount > 0 { + cutoff = buffer[0].CreatedAt + } + dbMessages, err := DbConnectionGetMessagesBefore(ctx, cutoff, connectionId, remaining) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + messages = make([]*Message, 0, uint32(len(dbMessages))+validBufCount) + messages = append(messages, dbMessages...) + for i := uint32(0); i < validBufCount; i++ { + messages = append(messages, buffer[i]) + } + } + + json, err := json2.Marshal(messages) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + response.WriteHeader(http.StatusOK) + response.Write(json) +} + +func HttpHandleUserNewConnection(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + ctx := request.Context() + requestor, err := GetUserByToken(ctx, request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + recipientId, err := ConvertStringUint32(request.FormValue("recipient")) + if err != nil { + http.Error(response, "no such user", http.StatusUnauthorized) + return + } + recipient, err := GetUserById(ctx, recipientId) + if err != nil { + http.Error(response, "no such user", http.StatusUnauthorized) + return + } + + if requestor.Id == recipient.Id { + http.Error(response, "cannot connect to yourself", http.StatusBadRequest) + return + } + + requestor.Mu.RLock() + for _, connection := range requestor.Connections { + if (connection.RequestorId == requestor.Id && connection.RecipientId == recipient.Id) || + (connection.RecipientId == requestor.Id && connection.RequestorId == recipient.Id) { + requestor.Mu.RUnlock() + http.Error(response, "connection already exists", http.StatusBadRequest) + return + } + } + requestor.Mu.RUnlock() + + connection := &Connection{ + CreatedAt: time.Now(), + RequestorId: requestor.Id, + RecipientId: recipient.Id, + State: ConnectionState.Stranger, + } + err = DbConnectionSave(ctx, connection) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + CacheAddConnection(requestor, recipient, connection) + + response.WriteHeader(http.StatusCreated) + return +} + +func HttpHandleUserDeleteConnection(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + ctx := request.Context() + + user, err := GetUserByToken(ctx, request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + + connectionId, err := ConvertStringUuid(request.FormValue("connectionid")) + if err != nil { + http.Error(response, "invalid connectionid", http.StatusBadRequest) + return + } + + conn, ok := CacheGetConnection(user, connectionId) + if !ok { + http.Error(response, "invalid connectionid", http.StatusBadRequest) + return + } + + var user2 *User + if conn.RequestorId == user.Id { + recipient, err := GetUserById(ctx, conn.RecipientId) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + user2 = recipient + } else if conn.RecipientId == user.Id { + requestor, err := GetUserById(ctx, conn.RequestorId) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + user2 = requestor + } else { + http.Error(response, "invalid connectionid", http.StatusBadRequest) + return + } + + err = DbConnectionDelete(ctx, conn) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + CacheDeleteConnection(user, user2, connectionId) + + response.WriteHeader(http.StatusAccepted) +} + +func HttpHandleUserElevateConnection(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + ctx := request.Context() + user, err := GetUserByToken(ctx, request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + connectionId, err := ConvertStringUuid(request.FormValue("connectionid")) + if err != nil { + http.Error(response, "invalid connectionid", http.StatusBadRequest) + return + } + conn, ok := CacheGetConnection(user, connectionId) + if !ok { + http.Error(response, "invalid connectionid", http.StatusBadRequest) + return + } + + if conn.RecipientId != user.Id { + http.Error(response, "invalid connectionid", http.StatusBadRequest) + return + } + + switch conn.State { + case ConnectionState.Stranger: + conn.State = ConnectionState.Friend + break + case ConnectionState.GroupFellow: + conn.State = ConnectionState.Stranger + break + default: + http.Error(response, "cannot elevate further", http.StatusBadRequest) + return + } + + err = DbConnectionUpdateState(ctx, conn) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + response.WriteHeader(http.StatusAccepted) +} + +func HttpHandleUserGetConnections(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + ctx := request.Context() + user, err := GetUserByToken(ctx, request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + + user.Mu.RLock() + connections := slices.Collect(maps.Values(user.Connections)) + user.Mu.RUnlock() + json, err := json2.Marshal(connections) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + response.WriteHeader(http.StatusOK) + response.Write(json) +} diff --git a/httpGroup.go b/httpGroup.go new file mode 100644 index 0000000..46ea877 --- /dev/null +++ b/httpGroup.go @@ -0,0 +1,431 @@ +package main + +import ( + "context" + json2 "encoding/json" + "errors" + "fmt" + "maps" + "net/http" + "slices" + "strconv" + "strings" + "time" +) + +func isOwnerOfGroup(user *User, group *Group) bool { + if group.OwnerId == user.Id { + return true + } + return false +} + +func getIfOwnerUserAndGroup(ctx context.Context, response *http.ResponseWriter, request *http.Request) (*User, *Group, error) { + user, err := GetUserByToken(ctx, request.FormValue("token")) + if err != nil { + http.Error(*response, "invalid token", http.StatusUnauthorized) + return nil, nil, err + } + + affectedGroupId, err := ConvertStringUint32(request.FormValue("groupid")) + if err != nil { + http.Error(*response, "no such group", http.StatusUnauthorized) + return nil, nil, err + } + + group, err := GetGroup(ctx, affectedGroupId) + if err != nil { + http.Error(*response, "no such group", http.StatusUnauthorized) + return nil, nil, err + } + + if !isOwnerOfGroup(user, group) { + http.Error(*response, "no such group", http.StatusUnauthorized) + return nil, nil, errors.New("not an owner") + } + return user, group, nil +} + +func HttpHandeGroupCreate(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + + user, err := GetUserByToken(ctx, request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + + name := request.FormValue("name") + if name == "" { + name = "Best group ever" + } + + colorString := request.FormValue("color") + color, err := ConvertStringToRgb(colorString) + if err != nil { + http.Error(response, "invalid color", http.StatusBadRequest) + return + } + + group := Group{ + Name: name, + CreatedAt: time.Now(), + OwnerId: user.Id, + CreatorId: user.Id, + Color: color, + Users: map[uint32]struct{}{user.Id: {}}, + } + + enableUserColors := request.FormValue("enableUserColors") + if enableUserColors == "1" { + group.EnableUserColors = true + } + + err = DbGroupSave(ctx, &group) + if err != nil { + http.Error(response, err.Error(), http.StatusInternalServerError) + return + } + response.WriteHeader(http.StatusCreated) + fmt.Fprintf(response, "%d", group.Id) +} + +func HttpHandleGroupDelete(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + _, group, err := getIfOwnerUserAndGroup(ctx, &response, request) + if err != nil { + return + } + + err = DbGroupDelete(ctx, group) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + CacheDeleteGroup(group.Id) + + response.WriteHeader(http.StatusAccepted) +} + +func HttpHandleGroupAddUsers(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + + _, group, err := getIfOwnerUserAndGroup(ctx, &response, request) + if err != nil { + return + } + + usersString := request.FormValue("users") + var remainingUsersCount = int(MaxUsersInGroup) - len(group.Users) + if remainingUsersCount < 1 { + http.Error(response, "max users", http.StatusUnauthorized) + return + } + + usersStringSlice := strings.SplitN(usersString, ",", remainingUsersCount+1) + if len(usersStringSlice) == 0 { + http.Error(response, "no users to add", http.StatusBadRequest) + return + } + + var ids [MaxUsersInGroup]uint32 + var idx uint32 = 0 + for _, s := range usersStringSlice { + if idx >= MaxUsersInGroup { + break + } + id, err := ConvertStringUint32(strings.TrimSpace(s)) + if err != nil { + continue + } + ids[idx] = id + idx++ + } + if idx == 0 { + http.Error(response, "no valid users", http.StatusBadRequest) + return + } + + err = DbGroupAddUsers(ctx, group.Id, &ids) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + for i := uint32(0); i < idx; i++ { + group.Users[ids[i]] = struct{}{} + if cachedUser, err := CacheGetUserById(ids[i]); err == nil { + cachedUser.Mu.Lock() + cachedUser.Groups[group.Id] = struct{}{} + cachedUser.Mu.Unlock() + } + } + + response.WriteHeader(http.StatusAccepted) +} + +func HttpHandleGroupRemoveUser(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + + _, group, err := getIfOwnerUserAndGroup(ctx, &response, request) + if err != nil { + return + } + + usersString := request.FormValue("users") + + usersStringSlice := strings.SplitN(usersString, ",", int(MaxUsersInGroup)+1) + + var ids [MaxUsersInGroup]uint32 + var idx uint32 = 0 + for _, s := range usersStringSlice { + if idx >= MaxUsersInGroup { + break + } + id, err := ConvertStringUint32(strings.TrimSpace(s)) + if err != nil { + continue + } + ids[idx] = id + idx++ + } + if idx == 0 { + http.Error(response, "no valid users", http.StatusBadRequest) + return + } + + count, err := DbGroupRemoveUsers(ctx, group.Id, &ids) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + for i := uint32(0); i < idx; i++ { + delete(group.Users, ids[i]) + if cachedUser, err := CacheGetUserById(ids[i]); err == nil { + cachedUser.Mu.Lock() + delete(cachedUser.Groups, group.Id) + cachedUser.Mu.Unlock() + } + } + + response.WriteHeader(http.StatusAccepted) + response.Write([]byte(strconv.Itoa(count))) +} + +func HttpHandleGroupChangeColor(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + _, group, err := getIfOwnerUserAndGroup(ctx, &response, request) + if err != nil { + return + } + + color, err := ConvertStringToRgb(request.FormValue("color")) + if err != nil { + http.Error(response, "invalid color", http.StatusBadRequest) + return + } + + group.Color = color + err = DbGroupSetColor(ctx, group) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + response.WriteHeader(http.StatusAccepted) + response.Write([]byte("changed")) +} + +func HttpHandleGroupChangeOwner(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + _, group, err := getIfOwnerUserAndGroup(ctx, &response, request) + if err != nil { + return + } + + newOwnerName := request.FormValue("newOwner") + + newOwner, err := CacheGetUserByName(newOwnerName) + if err != nil { + newOwner = &User{Name: newOwnerName} + err = DbUserGetStandardInfoByName(ctx, newOwner) + if err != nil { + http.Error(response, "user not in group", http.StatusBadRequest) + return + } + + if err = DbGetWholeUser(ctx, newOwner); err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + } + + _, ok := group.Users[newOwner.Id] + if !ok { + http.Error(response, "user not in group", http.StatusBadRequest) + return + } + + group.OwnerId = newOwner.Id + err = DbGroupSetOwnerId(ctx, group) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + response.WriteHeader(http.StatusAccepted) +} + +func HttpHandleGroupMessage(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + + user, err := GetUserByToken(ctx, request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusBadRequest) + return + } + groupIdStr := request.FormValue("groupid") + groupId, err := ConvertStringUint32(groupIdStr) + if err != nil { + http.Error(response, "no such group", http.StatusUnauthorized) + return + } + + group, err := GetGroup(ctx, groupId) + if err != nil { + http.Error(response, "no such group", http.StatusUnauthorized) + return + } + + content := request.FormValue("content") + if content == "" { + http.Error(response, "empty message", http.StatusBadRequest) + return + } + + _, ok := group.Users[user.Id] + if !ok { + http.Error(response, "no such group", http.StatusUnauthorized) + return + } + + err = WsSendToGroup(group, user, content) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + response.WriteHeader(http.StatusAccepted) +} + +func HttpHandleGroupsGetWithoutMembers(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + + user, err := GetUserByToken(ctx, request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + + user.Mu.RLock() + groupIds := make([]uint32, 0, len(user.Groups)) + for groupId := range user.Groups { + groupIds = append(groupIds, groupId) + } + user.Mu.RUnlock() + + groups := make(map[uint32]*Group, len(groupIds)) + for _, groupId := range groupIds { + group, err := GetGroup(ctx, groupId) + if err != nil { + continue + } + groups[groupId] = group + } + + json, err := json2.Marshal(groups) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + response.WriteHeader(http.StatusAccepted) + response.Write(json) +} + +func HttpHandleGroupMembersGet(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + user, err := GetUserByToken(ctx, request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + + groupStr := request.FormValue("group") + groupId, err := ConvertStringUint32(groupStr) + if err != nil { + http.Error(response, "invalid group", http.StatusBadRequest) + return + } + + user.Mu.RLock() + _, ok := user.Groups[groupId] + user.Mu.RUnlock() + if !ok { + http.Error(response, "no such group", http.StatusUnauthorized) + return + } + + group, err := GetGroup(ctx, groupId) + if err != nil { + http.Error(response, "no such group", http.StatusUnauthorized) + return + } + + groupMembers := slices.Collect(maps.Keys(group.Users)) + + json, err := json2.Marshal(groupMembers) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + response.WriteHeader(http.StatusAccepted) + response.Write(json) +} diff --git a/httpUser.go b/httpUser.go new file mode 100644 index 0000000..e12281a --- /dev/null +++ b/httpUser.go @@ -0,0 +1,192 @@ +package main + +import ( + json2 "encoding/json" + "net/http" + "time" + + "golang.org/x/crypto/bcrypt" +) + +func HttpHandleTokenNew(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + username := request.FormValue("username") + if len(username) < 4 { + http.Error(response, "no or short username", http.StatusBadRequest) + return + } + + password := request.FormValue("password") + + if len(password) < 8 { + http.Error(response, "no or short password", http.StatusBadRequest) + return + } + + var ( + user *User + err error + ctx = request.Context() + ) + + user, err = CacheGetUserByName(username) + if err != nil { + user = &User{Name: username} + if err = DbUserGetStandardInfoByName(ctx, user); err != nil { + http.Error(response, "bad login", http.StatusUnauthorized) + return + } + if err = DbGetWholeUser(ctx, user); err != nil { + http.Error(response, err.Error(), http.StatusInternalServerError) + return + } + } + + err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)) + if err != nil { + http.Error(response, "bad login", http.StatusUnauthorized) + return + } + + token, err := TokenCreate(user.Id) + if err != nil { + http.Error(response, "internal server error2", http.StatusInternalServerError) + return + } + + json, err := json2.Marshal(LoginReturn{Token: token, UserId: user.Id}) + if err != nil { + http.Error(response, "internal server error3", http.StatusInternalServerError) + return + } + + response.WriteHeader(http.StatusCreated) + response.Write(json) +} + +func HttpHandleUserNew(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + username := request.FormValue("username") + if len(username) < 4 { + http.Error(response, "no or short username", http.StatusBadRequest) + return + } + + password := request.FormValue("password") + if len(password) < 8 { + http.Error(response, "no or short password", http.StatusBadRequest) + return + } + + color, err := ConvertStringToRgb(request.FormValue("color")) + if err != nil { + http.Error(response, "bad color", http.StatusBadRequest) + return + } + + hashedPassword, err := PasswordHash(password) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + newUser := &User{ + Name: username, + PasswordHash: hashedPassword, + Color: color, + CreatedAt: time.Now(), + } + + ctx := request.Context() + + err = DbUserSave(ctx, newUser) + if err != nil { + http.Error(response, "name taken", http.StatusUnauthorized) + return + } + + response.WriteHeader(http.StatusCreated) +} + +func HttpHandleUserDelete(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + ctx := request.Context() + + userId, err := TokenValidateGetId(request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + + err = DbUserDelete(ctx, userId) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + CacheDeleteUser(userId) + response.WriteHeader(http.StatusAccepted) +} + +// HttpHandleUserModifyAppearance currently just color +func HttpHandleUserModifyAppearance(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + user, err := GetUserByToken(ctx, request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + + color, err := ConvertStringToRgb(request.FormValue("color")) + if err != nil { + http.Error(response, "invalid color", http.StatusBadRequest) + return + } + user.Color = color + err = DbUserSetColor(ctx, user) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + response.WriteHeader(http.StatusAccepted) +} + +// HttpHandleUserModifyAbout currently just pronouns +func HttpHandleUserModifyAbout(response http.ResponseWriter, request *http.Request) { + if !HttpMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + user, err := GetUserByToken(ctx, request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + + pronouns := request.FormValue("pronouns") + if len(pronouns) > 25 || len(pronouns) < 2 { + http.Error(response, "invalid pronouns", http.StatusBadRequest) + return + } + + user.Pronouns = pronouns + err = DbUserSetPronouns(ctx, user) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + response.WriteHeader(http.StatusAccepted) +} diff --git a/structs.go b/structs.go index ea80f56..184eb56 100644 --- a/structs.go +++ b/structs.go @@ -68,7 +68,6 @@ type Message struct { CreatedAt time.Time `json:"createdAt"` Sender uint32 `json:"sender"` Receiver uuid.UUID `json:"receiver"` - IsGroupMessage bool `json:"isGroupMessage"` } type Group struct {