diff --git a/go.mod b/go.mod
index 7f83720..cae97cc 100644
--- a/go.mod
+++ b/go.mod
@@ -3,13 +3,13 @@ module go-socket
go 1.26
require (
+ github.com/BurntSushi/toml v1.6.0
github.com/coder/websocket v1.8.14
github.com/jackc/pgx/v5 v5.8.0
golang.org/x/crypto v0.49.0
)
require (
- github.com/BurntSushi/toml v1.6.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/google/uuid v1.6.0 // indirect
diff --git a/machine-client/index.html b/machine-client/index.html
index 35558ae..4ffd848 100644
--- a/machine-client/index.html
+++ b/machine-client/index.html
@@ -83,7 +83,10 @@
-
+
+
+
+
@@ -220,7 +223,7 @@
@@ -267,7 +294,10 @@
'get-connection-messages': { method:'GET', path:'/connection/messages', title:'GET /connection/messages — message history', fields:[{id:'gcm-token',dest:'header',name:'token'},{id:'gcm-connectionid',dest:'query',name:'connectionid'},{id:'gcm-messages',dest:'query',name:'messages'},{id:'gcm-before',dest:'query',name:'before'}] },
'del-user': { method:'DELETE', path:'/user', title:'DELETE /user — delete own account', fields:[{id:'du-token',dest:'header',name:'token'}] },
'del-connection': { method:'DELETE', path:'/connection', title:'DELETE /connection — delete a connection', fields:[{id:'dc-token',dest:'header',name:'token'},{id:'dc-connectionid',dest:'query',name:'connectionid'}] },
- 'msg-user': { method:'POST', path:'/message', title:'POST /message — send direct message', fields:[{id:'mu-token',dest:'header',name:'token'},{id:'mu-connectionid',dest:'body',name:'connectionid'},{id:'mu-msgContent',dest:'body',name:'msgContent'},{id:'mu-attachedFile',dest:'body',name:'attachedFile'}] },
+ 'msg-user': { method:'POST', path:'/connection/message', title:'POST /connection/message — send direct message', fields:[{id:'mu-token',dest:'header',name:'token'},{id:'mu-connectionid',dest:'body',name:'connectionid'},{id:'mu-msgContent',dest:'body',name:'msgContent'},{id:'mu-attachedFile',dest:'body',name:'attachedFile'}] },
+ 'hub-create': { method:'POST', path:'/hub', title:'POST /hub — create a new hub', fields:[{id:'hc-token',dest:'header',name:'token'},{id:'hc-hubname',dest:'body',name:'hubname'}] },
+ 'hub-join': { method:'PUT', path:'/hub/join', title:'PUT /hub/join — join hub (hubid as header)', fields:[{id:'hj-token',dest:'header',name:'token'},{id:'hj-hubid',dest:'header',name:'hubid'}] },
+ 'channel-message': { method:'POST', path:'/channel/message', title:'POST /channel/message — send hub channel msg',fields:[{id:'cm-token',dest:'header',name:'token'},{id:'cm-hubid',dest:'body',name:'hubid'},{id:'cm-channelid',dest:'body',name:'channelid'},{id:'cm-msgContent',dest:'body',name:'msgContent'},{id:'cm-attachedFile',dest:'body',name:'attachedFile'}] },
'mod-user-avatar': { title:'PATCH /user/avatar — set avatar image' },
'mod-user-profilebg': { title:'PATCH /user/profilebg — set profile background' },
'file-upload': { title:'POST /file — upload file (multipart)' },
diff --git a/main.go b/main.go
index caffed1..4054f15 100644
--- a/main.go
+++ b/main.go
@@ -38,8 +38,8 @@ func main() {
http.HandleFunc("DELETE /user", withCORS(httpRequest.HandleUserDelete))
http.HandleFunc("GET /user", withCORS(httpRequest.HandleUserGetUser))
http.HandleFunc("PATCH /user/profile", withCORS(httpRequest.HandleUserModProfile))
- http.HandleFunc("PATCH /user/avatar", withCORS(httpRequest.HandleUserModAvatar))
- http.HandleFunc("PATCH /user/profilebg", withCORS(httpRequest.HandleUserModProfileBg))
+ http.HandleFunc("PATCH /user/avatar", withCORS(httpRequest.HandleSetUserAvatar))
+ http.HandleFunc("PATCH /user/profilebg", withCORS(httpRequest.HandleSetUserProfileBg))
http.HandleFunc("GET /user/avatar", withCORS(httpRequest.HandleGetUserAvatar))
http.HandleFunc("GET /user/profilebg", withCORS(httpRequest.HandleGetUserProfileBg))
@@ -56,7 +56,10 @@ func main() {
http.HandleFunc("POST /file", withCORS(httpRequest.HandleAttachmentFileUpload))
http.HandleFunc("GET /file", withCORS(httpRequest.HandleAttachmentFileDownload))
- http.HandleFunc("POST /message", withCORS(httpRequest.HandleDm))
+ http.HandleFunc("POST /hub", withCORS(httpRequest.HandleHubCreate))
+ http.HandleFunc("PUT /hub/join", withCORS(httpRequest.HandleHubJoin))
+
+ http.HandleFunc("POST /connection/message", withCORS(httpRequest.HandleDm))
http.HandleFunc("GET /ws", wsServer.ServeWsConnection)
log.Println("beep boop; server server started")
diff --git a/packages/config/config.go b/packages/config/config.go
index 4a60011..ea45354 100644
--- a/packages/config/config.go
+++ b/packages/config/config.go
@@ -33,6 +33,7 @@ type configFile struct {
MaxRequestWithProfileBgBytes uint32 `toml:"max_request_with_profile_bg_bytes"`
FileProcessingPartBytes uint64 `toml:"file_processing_part_bytes"`
FileProcessingThreads uint `toml:"file_processing_threads"`
+ FileStorageBucketName string `toml:"file_storage_bucket_name"`
FileDownloadLinkTtl time.Duration `toml:"file_download_link_ttl"`
}
diff --git a/packages/httpRequest/files.go b/packages/httpRequest/files.go
index 81ace0f..94e0027 100644
--- a/packages/httpRequest/files.go
+++ b/packages/httpRequest/files.go
@@ -2,6 +2,8 @@ package httpRequest
import (
json2 "encoding/json"
+ "go-socket/packages/postgresql"
+ "go-socket/packages/types"
"net/http"
"strings"
@@ -42,7 +44,7 @@ func HandleAttachmentFileUpload(response http.ResponseWriter, request *http.Requ
defer file.Close()
contentType := header.Header.Get("Content-Type")
- key := minio.GetKey(minio.GetKeyOptions{
+ key := minio.GetKey(&minio.GetKeyOptions{
ConnectionId: conn.Id,
MimeType: contentType,
UploadType: minio.ConnectionFile,
@@ -60,6 +62,62 @@ func HandleAttachmentFileUpload(response http.ResponseWriter, request *http.Requ
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.AvatarUrl != "" {
+ if err = minio.Delete(ctx, user.AvatarUrl); err != nil {
+ minio.Delete(ctx, key)
+ http.Error(response, "internal server error", http.StatusInternalServerError)
+ return
+ }
+ }
+ user.AvatarUrl = key
+ err = postgresql.UserUpdateProfile(ctx, user, &types.UserProfileUpdateList{Avatar: true})
+ if err != nil {
+ http.Error(response, "failed to update user avatar", http.StatusInternalServerError)
+ minio.Delete(ctx, user.AvatarUrl)
+ return
+ }
+
+ response.WriteHeader(http.StatusCreated)
+}
+
func HandleGetUserAvatar(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(&response, request, normal) {
return
@@ -85,18 +143,82 @@ func HandleGetUserAvatar(response http.ResponseWriter, request *http.Request) {
}
if target.AvatarUrl == "" {
- http.Error(response, "no avatar", http.StatusNotFound)
+ http.Error(response, "user have no avatar", http.StatusNoContent)
return
}
- url, _, err := minio.GetDownloadUrlAndMetadata(ctx, target.AvatarUrl)
+ url, meta, err := minio.GetDownloadUrlAndMetadata(ctx, target.AvatarUrl)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
+ json, err := json2.Marshal(map[string]any{
+ "url": url.String(),
+ "metadata": meta,
+ })
+ if err != nil {
+ http.Error(response, "json error", http.StatusInternalServerError)
+ }
+
response.WriteHeader(http.StatusOK)
- response.Write([]byte(url.String()))
+ response.Write(json)
+}
+
+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.ProfileBgUrl != "" {
+ if err = minio.Delete(ctx, user.ProfileBgUrl); err != nil {
+ minio.Delete(ctx, key)
+ http.Error(response, "internal server error", http.StatusInternalServerError)
+ return
+ }
+ }
+ user.ProfileBgUrl = key
+ err = postgresql.UserUpdateProfile(ctx, user, &types.UserProfileUpdateList{ProfileBg: true})
+ if err != nil {
+ http.Error(response, "failed to update user profile background", http.StatusInternalServerError)
+ minio.Delete(ctx, user.ProfileBgUrl)
+ return
+ }
+
+ response.WriteHeader(http.StatusCreated)
}
func HandleGetUserProfileBg(response http.ResponseWriter, request *http.Request) {
@@ -105,7 +227,8 @@ func HandleGetUserProfileBg(response http.ResponseWriter, request *http.Request)
}
ctx := request.Context()
- if _, err := getUserByToken(ctx, request.Header.Get("token")); err != nil {
+ _, err := getUserByToken(ctx, request.Header.Get("token"))
+ if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
@@ -123,18 +246,26 @@ func HandleGetUserProfileBg(response http.ResponseWriter, request *http.Request)
}
if target.ProfileBgUrl == "" {
- http.Error(response, "no profile background", http.StatusNotFound)
+ http.Error(response, "user have no profile background", http.StatusNoContent)
return
}
- url, _, err := minio.GetDownloadUrlAndMetadata(ctx, target.ProfileBgUrl)
+ url, meta, err := minio.GetDownloadUrlAndMetadata(ctx, target.ProfileBgUrl)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
+ json, err := json2.Marshal(map[string]any{
+ "url": url.String(),
+ "metadata": meta,
+ })
+ if err != nil {
+ http.Error(response, "json error", http.StatusInternalServerError)
+ }
+
response.WriteHeader(http.StatusOK)
- response.Write([]byte(url.String()))
+ response.Write(json)
}
func HandleAttachmentFileDownload(response http.ResponseWriter, request *http.Request) {
diff --git a/packages/httpRequest/get.go b/packages/httpRequest/get.go
index cc152d9..0745ee7 100644
--- a/packages/httpRequest/get.go
+++ b/packages/httpRequest/get.go
@@ -90,22 +90,22 @@ func getHubUserIfValidWithResponseOnFail(ctx context.Context, response http.Resp
return user, hubUser, hub, nil
}
-func getHubChannelIfValidWithResponseOnFail(ctx context.Context, response http.ResponseWriter, hub *types.Hub, hubUser *types.HubUser, channelId string) (
- *types.HubChannel, error) {
- channelUuid, err := convertions.StringToUuid(channelId)
- if err != nil {
- http.Error(response, "invalid channelid", http.StatusBadRequest)
- return nil, errors.New("invalid channelid")
- }
- channel, ok := hub.Channels[channelUuid]
- if !ok {
- http.Error(response, "invalid channelid", http.StatusBadRequest)
- return nil, errors.New("invalid channelid")
- }
-
- if !haveHubUserPermissionsOnChannel(types.CachedUserCanView, hubUser, channel) {
- return nil, errors.New("invalid channelid")
- }
-
- return channel, nil
-}
+//func getHubChannelIfValidWithResponseOnFail(ctx context.Context, response http.ResponseWriter, hub *types.Hub, hubUser *types.HubUser, channelId string) (
+// *types.HubChannel, error) {
+// channelUuid, err := convertions.StringToUuid(channelId)
+// if err != nil {
+// http.Error(response, "invalid channelid", http.StatusBadRequest)
+// return nil, errors.New("invalid channelid")
+// }
+// channel, ok := hub.Channels[channelUuid]
+// if !ok {
+// http.Error(response, "invalid channelid", http.StatusBadRequest)
+// return nil, errors.New("invalid channelid")
+// }
+//
+// if !haveHubUserPermissionsOnChannel(types.CachedUserCanView, hubUser, channel) {
+// return nil, errors.New("invalid channelid")
+// }
+//
+// return channel, nil
+//}
diff --git a/packages/httpRequest/hubs.go b/packages/httpRequest/hubs.go
index 3381788..cbb227d 100644
--- a/packages/httpRequest/hubs.go
+++ b/packages/httpRequest/hubs.go
@@ -4,10 +4,8 @@ import (
"net/http"
"time"
- "go-socket/packages/Enums/WsEventType"
"go-socket/packages/cache"
"go-socket/packages/types"
- "go-socket/packages/wsServer"
"github.com/google/uuid"
)
@@ -134,65 +132,6 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) {
cache.SaveHub(hub)
}
-func HandleChannelSendMessage(response http.ResponseWriter, request *http.Request) {
- if !validCheckWithResponseOnFail(&response, request, normal) {
- return
- }
- ctx := request.Context()
- user, hubUser, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request.Header.Get("token"), request.FormValue("hubid"))
- if err != nil {
- return
- }
-
- msgContent := request.FormValue("msgContent")
- attachedFile := request.FormValue("attachedFile")
-
- if msgContent == "" && attachedFile == "" {
- http.Error(response, "empty msgContent", http.StatusBadRequest)
- return
- }
-
- channel, err := getHubChannelIfValidWithResponseOnFail(ctx, response, hub, hubUser, request.FormValue("channelid"))
- if err != nil {
- return
- }
-
- if !haveHubUserPermissionsOnChannel(types.CachedUserCanMessage, hubUser, channel) {
- http.Error(response, "cannot send messages here", http.StatusUnauthorized)
- return
- }
-
- message := &types.Message{
- Id: uuid.New(),
- AttachedFile: "",
- Content: msgContent,
- Sender: user.Id,
- Receiver: channel.Id,
- CreatedAt: time.Now(),
- }
-
- channel.Mu.RLock()
- recipients := make([]uuid.UUID, 0, len(channel.UsersCachedPermissions))
- for id, userCachedPerms := range channel.UsersCachedPermissions {
- if userCachedPerms.CanReadHistory() && id != user.Id {
- recipients = append(recipients, id)
- }
- }
- channel.Mu.RUnlock()
-
- for _, id := range recipients {
- targetUser, err := cache.GetUserById(id)
- if err != nil {
- // todo Add to postgres in future
- continue
- }
- wsServer.WsSendMessageCloseIfTimeout(targetUser, types.WsEventMessage{
- Type: WsEventType.HubMessage,
- Event: message,
- })
- }
-}
-
func HandleHubJoin(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(&response, request, normal) {
return
diff --git a/packages/httpRequest/user.go b/packages/httpRequest/user.go
index b2644a4..aef2487 100644
--- a/packages/httpRequest/user.go
+++ b/packages/httpRequest/user.go
@@ -5,11 +5,8 @@ import (
"net/http"
"time"
- "go-socket/packages/config"
- "go-socket/packages/convertions"
- "go-socket/packages/minio"
-
"go-socket/packages/cache"
+ "go-socket/packages/convertions"
"go-socket/packages/passwords"
"go-socket/packages/postgresql"
"go-socket/packages/tokens"
@@ -152,7 +149,7 @@ func HandleUserModProfile(response http.ResponseWriter, request *http.Request) {
return
}
- var updateList types.UserProfileUpdateList
+ updateList := &types.UserProfileUpdateList{}
if pronouns := request.FormValue("pronouns"); pronouns != "" {
if len(pronouns) > 32 {
@@ -190,132 +187,6 @@ func HandleUserModProfile(response http.ResponseWriter, request *http.Request) {
response.WriteHeader(http.StatusAccepted)
}
-func HandleUserModAvatar(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
- }
-
- request.Body = http.MaxBytesReader(response, request.Body, int64(config.MaxRequestWithAvatarBytes))
-
- if err = request.ParseMultipartForm(int64(config.MaxRequestBytes)); err != nil {
- http.Error(response, "invalid multipart form", http.StatusBadRequest)
- 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
- }
-
- if user.AvatarUrl != "" {
- err = minio.Delete(ctx, user.AvatarUrl)
- if err != nil {
- http.Error(response, "internal server error", http.StatusInternalServerError)
- return
- }
- }
-
- key := minio.GetKey(minio.GetKeyOptions{
- UserId: user.Id,
- MimeType: contentType,
- UploadType: minio.UserAvatar,
- })
- 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
- }
-
- user.AvatarUrl = key
- err = postgresql.UserUpdateProfile(ctx, user, types.UserProfileUpdateList{Avatar: true})
- if err != nil {
- http.Error(response, "internal server error", http.StatusInternalServerError)
- return
- }
-
- response.WriteHeader(http.StatusAccepted)
-}
-
-func HandleUserModProfileBg(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
- }
-
- request.Body = http.MaxBytesReader(response, request.Body, int64(config.MaxRequestWithProfileBgBytes))
-
- if err = request.ParseMultipartForm(int64(config.MaxRequestBytes)); err != nil {
- http.Error(response, "invalid multipart form", http.StatusBadRequest)
- 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
- }
-
- if user.ProfileBgUrl != "" {
- err = minio.Delete(ctx, user.ProfileBgUrl)
- if err != nil {
- http.Error(response, "internal server error", http.StatusInternalServerError)
- return
- }
- }
-
- key := minio.GetKey(minio.GetKeyOptions{
- UserId: user.Id,
- MimeType: contentType,
- UploadType: minio.UserProfileBg,
- })
- 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
- }
-
- user.ProfileBgUrl = key
- err = postgresql.UserUpdateProfile(ctx, user, types.UserProfileUpdateList{ProfileBg: true})
- if err != nil {
- http.Error(response, "internal server error", http.StatusInternalServerError)
- return
- }
-
- response.WriteHeader(http.StatusAccepted)
-}
-
func HandleUserGetUser(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(&response, request, normal) {
return
diff --git a/packages/minio/minio.go b/packages/minio/minio.go
index cb35440..8908323 100644
--- a/packages/minio/minio.go
+++ b/packages/minio/minio.go
@@ -43,7 +43,7 @@ type GetKeyOptions struct {
UploadType DataType
}
-func GetKey(opts GetKeyOptions) string {
+func GetKey(opts *GetKeyOptions) string {
extensions, err := mime.ExtensionsByType(opts.MimeType)
if err != nil || len(extensions) == 0 {
extensions = []string{".unknown"}
diff --git a/packages/postgresql/postgresql.go b/packages/postgresql/postgresql.go
index 129eed7..4220870 100644
--- a/packages/postgresql/postgresql.go
+++ b/packages/postgresql/postgresql.go
@@ -113,7 +113,7 @@ func UserGetById(ctx context.Context, user *types.User) error {
return err
}
-func UserUpdateProfile(ctx context.Context, user *types.User, updateList types.UserProfileUpdateList) error {
+func UserUpdateProfile(ctx context.Context, user *types.User, updateList *types.UserProfileUpdateList) error {
setClauses := make([]string, 0, 3)
args := make([]any, 0, 4)
argIdx := 1