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) }