working file sharing

This commit is contained in:
2026-04-16 20:38:16 +02:00
parent f0fdaedd8c
commit 84e3f852fe
14 changed files with 190 additions and 81 deletions
+382
View File
@@ -0,0 +1,382 @@
package httpRequest
import (
json2 "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/convertions"
"go-socket/packages/globals"
"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 !postValidCheckWithResponseOnFail(&response, request, false) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.FormValue("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
targetConnection, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
if err != nil {
http.Error(response, "invalid connectionid", http.StatusBadRequest)
return
}
conn, ok := cache.CacheGetConnection(user, targetConnection)
if !ok {
http.Error(response, "invalid connectionid", http.StatusBadRequest)
return
}
msgContent := request.FormValue("msgContent")
attachedFile := request.FormValue("attachedFile")
if msgContent == "" && attachedFile == "" {
http.Error(response, "empty msgContent", http.StatusBadRequest)
return
}
if attachedFile != "" && !strings.HasPrefix(attachedFile, targetConnection.String()+"/") {
http.Error(response, "invalid attachedFile", http.StatusBadRequest)
return
}
var target *types.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 := &types.Message{
Id: uuid.New(),
Content: msgContent,
AttachedFile: attachedFile,
CreatedAt: time.Now(),
Sender: user.Id,
Receiver: conn.Id,
}
wsServer.WsSendMessageCloseIfTimeout(target, types.WsEventMessage{
Type: WsEventType.DirectMessage,
Event: message,
})
err = postgresql.ConnectionMessageSave(ctx, message)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusAccepted)
}
func HandleUserGetConnectionMessages(response http.ResponseWriter, request *http.Request) {
if !postValidCheckWithResponseOnFail(&response, request, false) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.FormValue("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
connectionId, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
if err != nil {
http.Error(response, "invalid connectionid", http.StatusBadRequest)
return
}
before, err := convertions.ConvertStringTimestamp(request.FormValue("before"))
if err != nil {
before = time.Now()
}
messagesCap, err := convertions.StringToUint32(request.FormValue("messages"))
if err != nil {
messagesCap = globals.MaxDirectMsgCache
}
conn, ok := cache.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 []*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, connectionId, 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])
}
}
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 HandleUserNewConnection(response http.ResponseWriter, request *http.Request) {
if !postValidCheckWithResponseOnFail(&response, request, false) {
return
}
ctx := request.Context()
requestor, err := getUserByToken(ctx, request.FormValue("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
recipientId, err := convertions.ConvertStringUuid(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 := &types.Connection{
CreatedAt: time.Now(),
RequestorId: requestor.Id,
RecipientId: recipient.Id,
State: ConnectionState.Stranger,
}
err = postgresql.ConnectionSave(ctx, connection)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
cache.CacheAddConnection(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 !postValidCheckWithResponseOnFail(&response, request, false) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.FormValue("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
connectionId, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
if err != nil {
http.Error(response, "invalid connectionid", http.StatusBadRequest)
return
}
conn, ok := cache.CacheGetConnection(user, connectionId)
if !ok {
http.Error(response, "invalid connectionid", http.StatusBadRequest)
return
}
var user2 *types.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 = postgresql.ConnectionDelete(ctx, conn)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
cache.CacheDeleteConnection(user, user2, connectionId)
wsServer.WsSendMessageCloseIfTimeout(user2, types.WsEventMessage{
Type: WsEventType.ConnectionDeleted,
Event: connectionId,
})
response.WriteHeader(http.StatusAccepted)
}
func HandleUserElevateConnection(response http.ResponseWriter, request *http.Request) {
if !postValidCheckWithResponseOnFail(&response, request, false) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.FormValue("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
connectionId, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
if err != nil {
http.Error(response, "invalid connectionid", http.StatusBadRequest)
return
}
conn, ok := cache.CacheGetConnection(user, connectionId)
if !ok {
http.Error(response, "invalid connectionid", http.StatusBadRequest)
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
case ConnectionState.GroupFellow:
conn.State = ConnectionState.Stranger
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"))
var user2 *types.User
if conn.RequestorId == user.Id {
user2, err = getUserById(ctx, conn.RecipientId)
} else {
user2, err = getUserById(ctx, conn.RequestorId)
}
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
wsServer.WsSendMessageCloseIfTimeout(user2, types.WsEventMessage{
Type: WsEventType.ConnectionElevated,
Event: types.ConnectionElevationData{
Id: connectionId,
NewState: conn.State,
},
})
return
}
conn.UserWantingToElevate = user.Id
response.Write([]byte("waiting for second user to elevate"))
}
func HandleUserGetConnections(response http.ResponseWriter, request *http.Request) {
if !postValidCheckWithResponseOnFail(&response, request, false) {
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)
}
+100
View File
@@ -0,0 +1,100 @@
package httpRequest
import (
"net/http"
"strings"
"go-socket/packages/convertions"
"go-socket/packages/globals"
"go-socket/packages/minio"
)
func HandleFileUpload(response http.ResponseWriter, request *http.Request) {
if !postValidCheckWithResponseOnFail(&response, request, true) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
request.Body = http.MaxBytesReader(response, request.Body, int64(globals.MaxPostWithFileBytes))
if err = request.ParseMultipartForm(int64(globals.MaxPostBytes)); err != nil {
http.Error(response, "invalid multipart form", http.StatusBadRequest)
return
}
connectionId, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
if err != nil {
http.Error(response, "invalid connectionid", http.StatusBadRequest)
return
}
_, ok := user.Connections[connectionId]
if !ok {
http.Error(response, "no such connection", http.StatusUnauthorized)
return
}
file, header, err := request.FormFile("file")
if err != nil {
http.Error(response, "missing file", http.StatusBadRequest)
return
}
defer file.Close()
contentType := header.Header.Get("Content-Type")
key := minio.GetKey(connectionId, contentType)
if err = minio.Upload(ctx, key, file, header.Size, contentType, map[string]string{
"originalName": header.Filename,
"uploaderId": user.Id.String(),
}); err != nil {
http.Error(response, "upload failed", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusCreated)
response.Write([]byte(key))
}
func HandleFileDownload(response http.ResponseWriter, request *http.Request) {
if !postValidCheckWithResponseOnFail(&response, request, false) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
connectionId, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
if err != nil {
http.Error(response, "invalid connectionid", http.StatusBadRequest)
return
}
if _, ok := user.Connections[connectionId]; !ok {
http.Error(response, "no such connection", http.StatusUnauthorized)
return
}
key := request.FormValue("key")
if !strings.HasPrefix(key, connectionId.String()+"/") {
http.Error(response, "no such file", http.StatusUnauthorized)
return
}
url, err := minio.GetDownloadUrl(ctx, key)
if err != nil {
http.Error(response, "no such file", http.StatusUnauthorized)
return
}
response.WriteHeader(http.StatusOK)
response.Write([]byte(url.String()))
}
+33
View File
@@ -0,0 +1,33 @@
package httpRequest
import (
"context"
"go-socket/packages/cache"
"go-socket/packages/postgresql"
"go-socket/packages/tokens"
"go-socket/packages/types"
"github.com/google/uuid"
)
func getUserById(ctx context.Context, userId uuid.UUID) (*types.User, error) {
user, err := cache.CacheGetUserById(userId)
if err != nil {
user = &types.User{Id: userId}
err = postgresql.GetWholeUser(ctx, user)
if err != nil {
return nil, err
}
}
return user, nil
}
func getUserByToken(ctx context.Context, token string) (*types.User, error) {
userId, err := tokens.TokenValidateGetId(token)
if err != nil {
return nil, err
}
return getUserById(ctx, userId)
}
+21
View File
@@ -0,0 +1,21 @@
package httpRequest
import (
"net/http"
"go-socket/packages/globals"
)
func postValidCheckWithResponseOnFail(response *http.ResponseWriter, request *http.Request, withFile bool) bool {
if request.Method != http.MethodPost {
http.Error(*response, "POST only", http.StatusMethodNotAllowed)
return false
}
if withFile && request.ContentLength > int64(globals.MaxPostWithFileBytes) ||
!withFile && request.ContentLength > int64(globals.MaxPostBytes) {
http.Error(*response, "Request too large", http.StatusRequestEntityTooLarge)
return false
}
return true
}
+193
View File
@@ -0,0 +1,193 @@
package httpRequest
import (
json2 "encoding/json"
"net/http"
"time"
"go-socket/packages/cache"
"go-socket/packages/convertions"
"go-socket/packages/passwords"
"go-socket/packages/postgresql"
"go-socket/packages/tokens"
"go-socket/packages/types"
"golang.org/x/crypto/bcrypt"
)
func HandleUserNewToken(response http.ResponseWriter, request *http.Request) {
if !postValidCheckWithResponseOnFail(&response, request, false) {
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 passwords", http.StatusBadRequest)
return
}
var (
user *types.User
err error
ctx = request.Context()
)
user, err = cache.CacheGetUserByName(username)
if err != nil {
user = &types.User{Name: username}
if err = postgresql.UserGetStandardInfoByName(ctx, user); err != nil {
http.Error(response, "bad login", http.StatusUnauthorized)
return
}
if err = postgresql.GetWholeUser(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 := tokens.TokenCreate(user.Id)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
json, err := json2.Marshal(types.LoginReturn{Token: token, UserId: user.Id})
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusCreated)
response.Write(json)
}
func HandleUserNew(response http.ResponseWriter, request *http.Request) {
if !postValidCheckWithResponseOnFail(&response, request, false) {
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 passwords", http.StatusBadRequest)
return
}
hashedPassword, err := passwords.PasswordHash(password)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
newUser := &types.User{
Name: username,
PasswordHash: hashedPassword,
Color: types.Rgba{}.GetRandom(),
CreatedAt: time.Now(),
}
ctx := request.Context()
err = postgresql.UserSave(ctx, newUser)
if err != nil {
http.Error(response, "name taken", http.StatusUnauthorized)
return
}
response.WriteHeader(http.StatusCreated)
}
func HandleUserDelete(response http.ResponseWriter, request *http.Request) {
if !postValidCheckWithResponseOnFail(&response, request, false) {
return
}
ctx := request.Context()
userId, err := tokens.TokenValidateGetId(request.FormValue("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
err = postgresql.UserDelete(ctx, userId)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
cache.CacheDeleteUser(userId)
response.WriteHeader(http.StatusAccepted)
}
// HandleUserModifyAppearance currently just color
func HandleUserModifyAppearance(response http.ResponseWriter, request *http.Request) {
if !postValidCheckWithResponseOnFail(&response, request, false) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.FormValue("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
color, err := convertions.StringToRgba(request.FormValue("color"))
if err != nil {
http.Error(response, "invalid color", http.StatusBadRequest)
return
}
user.Color = color
err = postgresql.UserSetColor(ctx, user)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusAccepted)
}
// HandleUserModifyAbout currently just pronouns
func HandleUserModifyAbout(response http.ResponseWriter, request *http.Request) {
if !postValidCheckWithResponseOnFail(&response, request, false) {
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 = postgresql.UserSetPronouns(ctx, user)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusAccepted)
}