Files
admin c0d4483154 fix hub bugs, add channel role permission endpoints
Covers: snake_case param renames, mutex RLock/Unlock mismatches, user.Hubs keyed by hub.Id, hub color using new_name, DELETE params sent as query, delete(target.Hubs, hub.Id), root/member role Id swap, and the three new PATCH
  /hub/channel/roles/* handlers.
2026-05-03 15:51:57 +02:00

435 lines
12 KiB
Go

package httpRequest
import (
"encoding/json"
"maps"
"net/http"
"slices"
"strings"
"time"
"go-socket/packages/Enums/ConnectionState"
"go-socket/packages/Enums/WsEventType"
"go-socket/packages/cache"
"go-socket/packages/config"
"go-socket/packages/convertions"
"go-socket/packages/minio"
"go-socket/packages/postgresql"
"go-socket/packages/types"
"go-socket/packages/wsServer"
"github.com/google/uuid"
)
func HandleDm(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user)
if !ok {
return
}
msgContent := request.FormValue("msg_content")
attachedFile := request.FormValue("attached_file")
if msgContent == "" && attachedFile == "" {
http.Error(response, "empty msg_content", http.StatusBadRequest)
return
}
if attachedFile != "" && !strings.HasPrefix(attachedFile, string(minio.ConnectionFilePrefix)+conn.Id.String()+"/") {
http.Error(response, "invalid attached_file", http.StatusBadRequest)
return
}
var target *types.User
target, err = getUserById(ctx, conn.GetSecondUser(user.Id))
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
message := &types.Message{
Id: uuid.New(),
Content: msgContent,
AttachedFile: attachedFile,
CreatedAt: time.Now(),
Sender: user.Id,
Receiver: conn.Id,
}
if target.WsConn != nil {
wsServer.WsSendMessageCloseIfTimeout(target, types.WsEventMessage{
Type: WsEventType.DirectMessage,
Event: message,
})
} else {
cache.IncrementConnectionsUnreadMessages(target.Id, conn.Id)
}
err = postgresql.ConnectionMessageSave(ctx, message)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusAccepted)
}
func HandleUserGetConnectionsUnreadMessages(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
connectionIds, err := convertions.StringToUuids(request.URL.Query().Get("connections"))
if err != nil {
http.Error(response, "invalid uuid format", http.StatusBadRequest)
return
}
result := make([]uint32, 0, len(connectionIds))
for _, connId := range connectionIds {
_, ok := cache.GetConnection(user, connId)
if !ok {
http.Error(response, "no such connection: "+connId.String(), http.StatusUnauthorized)
return
}
}
for _, connId := range connectionIds {
count, _ := cache.GetConnectionsUnreadMessages(user.Id, connId)
cache.DeleteConnectionsUnreadMessages(user.Id, connId)
result = append(result, count)
}
counts, err := json.Marshal(result)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusAccepted)
response.Write(counts)
}
func HandleUserGetConnectionMessages(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user)
if !ok {
return
}
before, err := convertions.StringToTimestamp(request.URL.Query().Get("before"))
if err != nil {
before = time.Now()
}
messagesCap, err := convertions.StringToUint32(request.URL.Query().Get("messages"))
if err != nil {
messagesCap = config.MaxDirectMsgCache
}
buffer, bufferSize := conn.GetSortedMessagesBuff()
var validBufCount uint32
for validBufCount < bufferSize && buffer[validBufCount].CreatedAt.Before(before) {
validBufCount++
}
var messages []*types.Message
if validBufCount >= messagesCap {
start := validBufCount - messagesCap
messages = make([]*types.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 := postgresql.ConnectionGetMessagesBefore(ctx, cutoff, conn.Id, remaining)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
messages = make([]*types.Message, 0, uint32(len(dbMessages))+validBufCount)
messages = append(messages, dbMessages...)
for i := uint32(0); i < validBufCount; i++ {
messages = append(messages, buffer[i])
}
}
data, err := json.Marshal(messages)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusOK)
response.Write(data)
}
func HandleUserNewConnection(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
requestor, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
recipientId, err := convertions.StringToUuid(request.FormValue("recipient"))
if err != nil {
http.Error(response, "invalid recipient", http.StatusBadRequest)
return
}
recipient, err := getUserById(ctx, recipientId)
if err != nil {
http.Error(response, "no such user", http.StatusNotFound)
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 := types.NewConn()
connection.CreatedAt = time.Now()
connection.RequestorId = requestor.Id
connection.RecipientId = recipient.Id
connection.State = ConnectionState.Stranger
err = postgresql.ConnectionSave(ctx, connection)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
cache.AddConnection(requestor, recipient, connection)
wsServer.WsSendMessageCloseIfTimeout(recipient, types.WsEventMessage{
Type: WsEventType.ConnectionCreated,
Event: connection,
})
response.WriteHeader(http.StatusCreated)
return
}
func HandleUserDeleteConnection(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user)
if !ok {
return
}
user2, err := getUserById(ctx, conn.GetSecondUser(user.Id))
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
err = postgresql.ConnectionDelete(ctx, conn)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
cache.DeleteConnection(user, user2, conn.Id)
wsServer.WsSendMessageCloseIfTimeout(user2, types.WsEventMessage{
Type: WsEventType.ConnectionDeleted,
Event: conn.Id,
})
response.WriteHeader(http.StatusAccepted)
response.Write(conn.Id[:])
}
func HandleUserElevateConnection(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user)
if !ok {
return
}
response.WriteHeader(http.StatusAccepted)
if conn.UserWantingToElevate != (uuid.UUID{}) && conn.UserWantingToElevate != user.Id {
switch conn.State {
case ConnectionState.Stranger:
conn.State = ConnectionState.Friend
break
default:
http.Error(response, "cannot elevate further", http.StatusBadRequest)
return
}
err = postgresql.ConnectionUpdateState(ctx, conn)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.Write([]byte("elevated"))
user2, err := getUserById(ctx, conn.GetSecondUser(user.Id))
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
wsServer.WsSendMessageCloseIfTimeout(user2, types.WsEventMessage{
Type: WsEventType.ConnectionElevated,
Event: types.ConnectionStatusSetData{
Id: conn.Id,
NewState: conn.State,
},
})
return
}
conn.UserWantingToElevate = user.Id
user2, err := getUserById(ctx, conn.GetSecondUser(user.Id))
if err == nil {
wsServer.WsSendMessageCloseIfTimeout(user2, types.WsEventMessage{
Type: WsEventType.ConnectionElevatePending,
Event: types.ConnectionElevatePendingData{
Id: conn.Id,
UserWantingToElevate: user.Id,
},
})
}
response.Write([]byte("waiting for second user to elevate"))
}
func HandleUserDeElevateConnection(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user)
if !ok {
return
}
if conn.State != ConnectionState.Stranger {
conn.State = ConnectionState.Stranger
err = postgresql.ConnectionUpdateState(ctx, conn)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
user2, err := getUserById(ctx, conn.GetSecondUser(user.Id))
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
wsServer.WsSendMessageCloseIfTimeout(user2, types.WsEventMessage{
Type: WsEventType.ConnectionDeElevated,
Event: types.ConnectionStatusSetData{
Id: conn.Id,
NewState: conn.State,
},
})
response.Write([]byte("de elevated"))
return
}
response.WriteHeader(http.StatusConflict)
response.Write([]byte("already lowest possible"))
}
func HandleUserGetConnections(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.Header.Get("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()
data, err := json.Marshal(connections)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusOK)
response.Write(data)
}