Files
2026-05-08 11:11:28 +02:00

695 lines
18 KiB
Go

package httpRequest
import (
"encoding/json"
"maps"
"net/http"
"slices"
"strings"
"go-socket/packages/Enums/WsEventType"
"go-socket/packages/postgresql"
"go-socket/packages/types"
"go-socket/packages/wsServer"
"go-socket/packages/config"
"go-socket/packages/convertions"
"go-socket/packages/minio"
)
func HandleAttachmentFileUpload(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, file) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
if err = request.ParseMultipartForm(int64(config.MaxRequestBytes)); err != nil {
http.Error(response, "invalid multipart form", http.StatusBadRequest)
return
}
target := request.FormValue("target_id")
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")
var key string
if conn, ok := getConnection(ctx, target, user); ok {
key = minio.GetKey(&minio.GetKeyOptions{
ConnectionId: conn.Id,
MimeType: contentType,
UploadType: minio.ConnectionFile,
})
} else if channel, ok := getChannelFromUser(user, target); ok {
channel.Mu.RLock()
perms := channel.UsersCachedPermissions[user.Id]
channel.Mu.RUnlock()
if !perms.CanMessage() {
http.Error(response, "forbidden", http.StatusForbidden)
return
}
key = minio.GetKey(&minio.GetKeyOptions{
ChannelId: channel.Id,
MimeType: contentType,
UploadType: minio.HubChannelFile,
})
} else {
http.Error(response, "cannot find target", http.StatusBadRequest)
return
}
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 HandleSetUserAvatar(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, avatar) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
file, header, err := request.FormFile("file")
if err != nil {
http.Error(response, "missing file", http.StatusBadRequest)
return
}
defer file.Close()
isImg, contentType, err := isImage(file)
if err != nil || !isImg {
http.Error(response, "invalid file", http.StatusBadRequest)
return
}
key := minio.GetKey(&minio.GetKeyOptions{
MimeType: contentType,
UploadType: minio.UserAvatar,
UserId: user.Id,
})
err = minio.Upload(ctx, key, file, header.Size, contentType, map[string]string{
"originalName": header.Filename,
"uploaderId": user.Id.String(),
})
if err != nil {
http.Error(response, "upload failed", http.StatusInternalServerError)
return
}
if user.AvatarKey != "" {
if err = minio.Delete(ctx, user.AvatarKey); err != nil {
minio.Delete(ctx, key)
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
}
user.AvatarKey = key
err = postgresql.UserUpdateProfile(ctx, user, &types.UserProfileUpdate{Avatar: true})
if err != nil {
http.Error(response, "failed to update user avatar", http.StatusInternalServerError)
minio.Delete(ctx, user.AvatarKey)
return
}
user.Mu.RLock()
connections := slices.Collect(maps.Values(user.Connections))
user.Mu.RUnlock()
for _, conn := range connections {
targetId := conn.GetSecondUser(user.Id)
target, err := getUserById(ctx, targetId)
if err != nil {
continue
}
wsServer.WsSendMessageCloseIfTimeout(target, types.WsEventMessage{
Type: WsEventType.UserAvatarChange,
Event: &map[string]any{
"userId": user.Id,
},
})
}
user.Mu.RLock()
hubs := slices.Collect(maps.Values(user.Hubs))
user.Mu.RUnlock()
for _, hub := range hubs {
hub.Mu.RLock()
hubUsers := slices.Collect(maps.Values(hub.Users))
hub.Mu.RUnlock()
for _, hubUser := range hubUsers {
if hubUser.OriginalId == user.Id {
continue
}
target, err := getUserById(ctx, hubUser.OriginalId)
if err != nil {
continue
}
wsServer.WsSendMessageCloseIfTimeout(target, &types.WsHubSpecificHubEventMessage{
Type: WsEventType.UserAvatarChange,
HubId: hub.Id,
Event: &map[string]any{
"userId": user.Id,
},
})
}
}
response.WriteHeader(http.StatusCreated)
}
func HandleGetUserAvatar(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
_, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
targetId, err := convertions.StringToUuid(request.URL.Query().Get("user_id"))
if err != nil {
http.Error(response, "invalid user_id", http.StatusBadRequest)
return
}
target, err := getUserById(ctx, targetId)
if err != nil {
http.Error(response, "user not found", http.StatusNotFound)
return
}
if target.AvatarKey == "" {
http.Error(response, "user have no avatar", http.StatusNoContent)
return
}
url, meta, err := minio.GetDownloadUrlAndMetadata(ctx, target.AvatarKey)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
avatarData, err := json.Marshal(map[string]any{
"url": url.String(),
"metadata": meta,
})
if err != nil {
http.Error(response, "json error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusOK)
response.Write(avatarData)
}
func HandleSetUserProfileBg(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, profileBg) {
return
}
ctx := request.Context()
user, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
file, header, err := request.FormFile("file")
if err != nil {
http.Error(response, "missing file", http.StatusBadRequest)
return
}
defer file.Close()
isImg, contentType, err := isImage(file)
if err != nil || !isImg {
http.Error(response, "invalid file", http.StatusBadRequest)
return
}
key := minio.GetKey(&minio.GetKeyOptions{
MimeType: contentType,
UploadType: minio.UserProfileBg,
UserId: user.Id,
})
err = minio.Upload(ctx, key, file, header.Size, contentType, map[string]string{
"originalName": header.Filename,
"uploaderId": user.Id.String(),
})
if err != nil {
http.Error(response, "upload failed", http.StatusInternalServerError)
return
}
if user.ProfileBgKey != "" {
if err = minio.Delete(ctx, user.ProfileBgKey); err != nil {
minio.Delete(ctx, key)
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
}
user.ProfileBgKey = key
err = postgresql.UserUpdateProfile(ctx, user, &types.UserProfileUpdate{ProfileBg: true})
if err != nil {
http.Error(response, "failed to update user profile background", http.StatusInternalServerError)
minio.Delete(ctx, user.ProfileBgKey)
return
}
user.Mu.RLock()
connections := slices.Collect(maps.Values(user.Connections))
user.Mu.RUnlock()
for _, conn := range connections {
target, err := getUserById(ctx, conn.GetSecondUser(user.Id))
if err != nil {
continue
}
wsServer.WsSendMessageCloseIfTimeout(target, types.WsEventMessage{
Type: WsEventType.UserProfileBgChange,
Event: &map[string]any{
"userId": user.Id,
},
})
}
user.Mu.RLock()
hubs := slices.Collect(maps.Values(user.Hubs))
user.Mu.RUnlock()
for _, hub := range hubs {
hub.Mu.RLock()
hubUsers := slices.Collect(maps.Values(hub.Users))
hub.Mu.RUnlock()
for _, hubUser := range hubUsers {
if hubUser.OriginalId == user.Id {
continue
}
target, err := getUserById(ctx, hubUser.OriginalId)
if err != nil {
continue
}
wsServer.WsSendMessageCloseIfTimeout(target, &types.WsHubSpecificHubEventMessage{
Type: WsEventType.UserProfileBgChange,
HubId: hub.Id,
Event: &map[string]any{
"userId": user.Id,
},
})
}
}
response.WriteHeader(http.StatusCreated)
}
func HandleGetUserProfileBg(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
_, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
targetId, err := convertions.StringToUuid(request.URL.Query().Get("user_id"))
if err != nil {
http.Error(response, "invalid user_id", http.StatusBadRequest)
return
}
target, err := getUserById(ctx, targetId)
if err != nil {
http.Error(response, "user not found", http.StatusNotFound)
return
}
if target.ProfileBgKey == "" {
http.Error(response, "user have no profile background", http.StatusNoContent)
return
}
url, meta, err := minio.GetDownloadUrlAndMetadata(ctx, target.ProfileBgKey)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
profileBgData, err := json.Marshal(map[string]any{
"url": url.String(),
"metadata": meta,
})
if err != nil {
http.Error(response, "json error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusOK)
response.Write(profileBgData)
}
func HandleHubSetIcon(response http.ResponseWriter, request *http.Request) {
_, hub, ctx, _, ok := hubPermissionContext(response, request, hubIcon, types.PermissionSetHubIcon)
if !ok {
return
}
file, header, err := request.FormFile("file")
if err != nil {
http.Error(response, "missing file", http.StatusBadRequest)
return
}
defer file.Close()
isImg, contentType, err := isImage(file)
if err != nil || !isImg {
http.Error(response, "invalid file", http.StatusBadRequest)
return
}
key := minio.GetKey(&minio.GetKeyOptions{
MimeType: contentType,
UploadType: minio.HubIcon,
HubId: hub.Id,
})
if err = minio.Upload(ctx, key, file, header.Size, contentType, map[string]string{
"originalName": header.Filename,
}); err != nil {
http.Error(response, "upload failed", http.StatusInternalServerError)
return
}
if hub.IconUrl != "" {
if err = minio.Delete(ctx, hub.IconUrl); err != nil {
minio.Delete(ctx, key)
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
}
hub.IconUrl = key
response.WriteHeader(http.StatusCreated)
}
func HandleHubSetBg(response http.ResponseWriter, request *http.Request) {
_, hub, ctx, _, ok := hubPermissionContext(response, request, hubBackground, types.PermissionSetHubBg)
if !ok {
return
}
file, header, err := request.FormFile("file")
if err != nil {
http.Error(response, "missing file", http.StatusBadRequest)
return
}
defer file.Close()
isImg, contentType, err := isImage(file)
if err != nil || !isImg {
http.Error(response, "invalid file", http.StatusBadRequest)
return
}
key := minio.GetKey(&minio.GetKeyOptions{
MimeType: contentType,
UploadType: minio.HubBackground,
HubId: hub.Id,
})
if err = minio.Upload(ctx, key, file, header.Size, contentType, map[string]string{
"originalName": header.Filename,
}); err != nil {
http.Error(response, "upload failed", http.StatusInternalServerError)
return
}
if hub.BgUrl != "" {
if err = minio.Delete(ctx, hub.BgUrl); err != nil {
minio.Delete(ctx, key)
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
}
hub.BgUrl = key
response.WriteHeader(http.StatusCreated)
}
func HandleGetHubIcon(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
_, _, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request)
if err != nil {
return
}
if hub.IconUrl == "" {
http.Error(response, "hub has no icon", http.StatusNoContent)
return
}
url, meta, err := minio.GetDownloadUrlAndMetadata(ctx, hub.IconUrl)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
iconData, err := json.Marshal(map[string]any{
"url": url.String(),
"metadata": meta,
})
if err != nil {
http.Error(response, "json error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusOK)
response.Write(iconData)
}
func HandleGetHubBg(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
_, _, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request)
if err != nil {
return
}
if hub.BgUrl == "" {
http.Error(response, "hub has no background", http.StatusNoContent)
return
}
url, meta, err := minio.GetDownloadUrlAndMetadata(ctx, hub.BgUrl)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
bgData, err := json.Marshal(map[string]any{
"url": url.String(),
"metadata": meta,
})
if err != nil {
http.Error(response, "json error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusOK)
response.Write(bgData)
}
func HandleChannelSetIcon(response http.ResponseWriter, request *http.Request) {
_, hub, ctx, _, ok := hubPermissionContext(response, request, channelIcon, types.PermissionSetChannelIcon)
if !ok {
return
}
channelId, err := convertions.StringToUuid(request.FormValue("channel_id"))
if err != nil {
http.Error(response, "invalid channel_id", http.StatusBadRequest)
return
}
hub.Mu.RLock()
channel, ok := hub.Channels[channelId]
hub.Mu.RUnlock()
if !ok {
http.Error(response, "channel not found", http.StatusNotFound)
return
}
file, header, err := request.FormFile("file")
if err != nil {
http.Error(response, "missing file", http.StatusBadRequest)
return
}
defer file.Close()
isImg, contentType, err := isImage(file)
if err != nil || !isImg {
http.Error(response, "invalid file", http.StatusBadRequest)
return
}
key := minio.GetKey(&minio.GetKeyOptions{
MimeType: contentType,
UploadType: minio.ChannelIcon,
ChannelId: channel.Id,
})
if err = minio.Upload(ctx, key, file, header.Size, contentType, map[string]string{
"originalName": header.Filename,
}); err != nil {
http.Error(response, "upload failed", http.StatusInternalServerError)
return
}
if channel.IconUrl != "" {
if err = minio.Delete(ctx, channel.IconUrl); err != nil {
minio.Delete(ctx, key)
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
}
channel.IconUrl = key
response.WriteHeader(http.StatusCreated)
}
func HandleGetChannelIcon(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
_, hubUser, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request)
if err != nil {
return
}
channelId, err := convertions.StringToUuid(request.URL.Query().Get("channel_id"))
if err != nil {
http.Error(response, "invalid channel_id", http.StatusBadRequest)
return
}
hub.Mu.RLock()
channel, ok := hub.Channels[channelId]
hub.Mu.RUnlock()
if !ok {
http.Error(response, "channel not found", http.StatusNotFound)
return
}
if !haveHubUserCachedPermissions(types.CachedUserCanView, hubUser, channel) {
http.Error(response, "forbidden", http.StatusForbidden)
return
}
if channel.IconUrl == "" {
http.Error(response, "channel has no icon", http.StatusNoContent)
return
}
url, meta, err := minio.GetDownloadUrlAndMetadata(ctx, channel.IconUrl)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
iconData, err := json.Marshal(map[string]any{
"url": url.String(),
"metadata": meta,
})
if err != nil {
http.Error(response, "json error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusOK)
response.Write(iconData)
}
func HandleAttachmentFileDownload(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
}
target := request.URL.Query().Get("target_id")
key := request.URL.Query().Get("key")
var validPrefix string
if conn, ok := getConnection(ctx, target, user); ok {
validPrefix = string(minio.ConnectionFilePrefix) + conn.Id.String() + "/"
} else if channel, ok := getChannelFromUser(user, target); ok {
channel.Mu.RLock()
perms := channel.UsersCachedPermissions[user.Id]
channel.Mu.RUnlock()
if !perms.CanReadHistory() {
http.Error(response, "forbidden", http.StatusForbidden)
return
}
validPrefix = string(minio.HubChannelFilePrefix) + channel.Id.String() + "/"
} else {
http.Error(response, "cannot find target", http.StatusBadRequest)
return
}
if !strings.HasPrefix(key, validPrefix) {
http.Error(response, "no such file", http.StatusUnauthorized)
return
}
url, meta, err := minio.GetDownloadUrlAndMetadata(ctx, key)
if err != nil {
http.Error(response, "no such file", http.StatusUnauthorized)
return
}
fileData, err := json.Marshal(map[string]string{
"url": url.String(),
"originalName": meta["originalName"],
})
if err != nil {
http.Error(response, "metadata error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusOK)
response.Write(fileData)
}