diff --git a/go-socket b/go-socket index 1134ef2..4bd5ac9 100755 Binary files a/go-socket and b/go-socket differ diff --git a/main.go b/main.go index 17316f0..f036b87 100644 --- a/main.go +++ b/main.go @@ -72,6 +72,7 @@ func main() { http.HandleFunc("PATCH /hub/icon", withCORS(httpRequest.HandleHubSetIcon)) http.HandleFunc("GET /hub/icon", withCORS(httpRequest.HandleGetHubIcon)) http.HandleFunc("PATCH /hub/bg", withCORS(httpRequest.HandleHubSetBg)) + http.HandleFunc("PATCH /hub/joinrole", withCORS(httpRequest.HandleSetHubJoinRole)) http.HandleFunc("GET /hub/bg", withCORS(httpRequest.HandleGetHubBg)) http.HandleFunc("DELETE /hub", withCORS(httpRequest.HandleHubRemove)) http.HandleFunc("PATCH /hub/usercolorallowed", withCORS(httpRequest.HandleHubToggleUserColorAllowed)) @@ -94,6 +95,8 @@ func main() { http.HandleFunc("PATCH /hub/channel/roles/view", withCORS(httpRequest.HandleChannelSetPermittedVisibleRole)) http.HandleFunc("PATCH /hub/channel/roles/send", withCORS(httpRequest.HandleChannelSetPermittedSendRole)) http.HandleFunc("PATCH /hub/channel/roles/history", withCORS(httpRequest.HandleChannelSetPermittedHistoryRole)) + http.HandleFunc("PATCH /hub/channel/icon", withCORS(httpRequest.HandleChannelSetIcon)) + http.HandleFunc("GET /hub/channel/icon", withCORS(httpRequest.HandleGetChannelIcon)) http.HandleFunc("POST /connection/message", withCORS(httpRequest.HandleDm)) http.HandleFunc("GET /ws", wsServer.ServeWsConnection) diff --git a/packages/httpRequest/files.go b/packages/httpRequest/files.go index 9f2c97d..1dcaf5b 100644 --- a/packages/httpRequest/files.go +++ b/packages/httpRequest/files.go @@ -127,7 +127,7 @@ func HandleSetUserAvatar(response http.ResponseWriter, request *http.Request) { } } user.AvatarKey = key - err = postgresql.UserUpdateProfile(ctx, user, &types.UserProfileUpdateList{Avatar: true}) + 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) @@ -249,7 +249,7 @@ func HandleSetUserProfileBg(response http.ResponseWriter, request *http.Request) } } user.ProfileBgKey = key - err = postgresql.UserUpdateProfile(ctx, user, &types.UserProfileUpdateList{ProfileBg: true}) + 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) @@ -324,6 +324,267 @@ func HandleGetUserProfileBg(response http.ResponseWriter, request *http.Request) 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 diff --git a/packages/httpRequest/get.go b/packages/httpRequest/get.go index 1ed2682..a38dd1a 100644 --- a/packages/httpRequest/get.go +++ b/packages/httpRequest/get.go @@ -19,7 +19,7 @@ func getUserById(ctx context.Context, userId uuid.UUID) (*types.User, error) { user, err := cache.GetUserById(userId) if err != nil { user = &types.User{Id: userId, Hubs: make(map[uuid.UUID]*types.Hub)} - err = postgresql.GetWholeUser(ctx, user) + err = postgresql.UserGetWhole(ctx, user) if err != nil { return nil, err } @@ -96,9 +96,12 @@ func getHubByIdStr(ctx context.Context, hubId string) (*types.Hub, error) { } hub, ok := cache.GetHubById(hubUuid) - if !ok { - return nil, errors.New("hub not found") + hub = &types.Hub{Id: hubUuid} + err = postgresql.HubGetWhole(ctx, hub) + if err != nil { + return nil, err + } } return hub, nil diff --git a/packages/httpRequest/helper.go b/packages/httpRequest/helper.go index 9653f2b..c4669e9 100644 --- a/packages/httpRequest/helper.go +++ b/packages/httpRequest/helper.go @@ -17,6 +17,7 @@ const ( profileBg hubIcon hubBackground + channelIcon ) func validCheckWithResponseOnFail(response http.ResponseWriter, request *http.Request, pt bodyLimit) bool { @@ -37,6 +38,9 @@ func validCheckWithResponseOnFail(response http.ResponseWriter, request *http.Re case hubBackground: maxSize = int64(config.MaxRequestWithHubBackgroundBytes) break + case channelIcon: + maxSize = int64(config.MaxRequestWithHubIconBytes) + break default: maxSize = int64(config.MaxRequestBytes) } diff --git a/packages/httpRequest/hubs.go b/packages/httpRequest/hubs.go index 1ecfc06..8df6c90 100644 --- a/packages/httpRequest/hubs.go +++ b/packages/httpRequest/hubs.go @@ -8,6 +8,7 @@ import ( "time" "go-socket/packages/convertions" + "go-socket/packages/postgresql" "go-socket/packages/cache" "go-socket/packages/minio" @@ -187,6 +188,31 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) { hub.Channels[channel.Id] = channel cache.SaveHub(hub) + err = postgresql.HubSave(ctx, hub) + if err != nil { + http.Error(response, "failed to save hub", http.StatusInternalServerError) + return + } + err = postgresql.HubUserSave(ctx, hub.Id, creator) + if err != nil { + http.Error(response, "failed to save hub user", http.StatusInternalServerError) + return + } + err = postgresql.HubRoleSave(ctx, hub.Id, rootRole) + if err != nil { + http.Error(response, "failed to save hub role", http.StatusInternalServerError) + return + } + err = postgresql.HubRoleSave(ctx, hub.Id, memberRole) + if err != nil { + http.Error(response, "failed to save hub role", http.StatusInternalServerError) + return + } + err = postgresql.HubChannelSave(ctx, hub.Id, channel) + if err != nil { + http.Error(response, "failed to save hub channel", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusCreated) response.Write([]byte(hub.Id.String())) @@ -224,6 +250,10 @@ func HandleHubJoin(response http.ResponseWriter, request *http.Request) { hub.Users[hubUser.OriginalId] = hubUser updateChannelCacheForSpecUser(hubUser, hub) + if err = postgresql.HubUserSave(ctx, hub.Id, hubUser); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusCreated) } @@ -254,7 +284,7 @@ func HandleHubMessage(response http.ResponseWriter, request *http.Request) { return } - if attachedFile != "" && !strings.HasPrefix(attachedFile, channel.Id.String()+"/") { + if attachedFile != "" && !strings.HasPrefix(attachedFile, string(minio.HubChannelFilePrefix)+channel.Id.String()+"/") { http.Error(response, "invalid attachedFile", http.StatusBadRequest) return } @@ -440,7 +470,7 @@ func hubPermissionContext(response http.ResponseWriter, request *http.Request, r } func HandleHubSetName(response http.ResponseWriter, request *http.Request) { - _, hub, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetHubName) + _, hub, ctx, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetHubName) if !ok { return } @@ -450,11 +480,15 @@ func HandleHubSetName(response http.ResponseWriter, request *http.Request) { return } hub.Name = newName + if err := postgresql.HubUpdate(ctx, hub, &types.HubUpdate{Name: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleHubSetColor(response http.ResponseWriter, request *http.Request) { - _, hub, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetHubColor) + _, hub, ctx, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetHubColor) if !ok { return } @@ -464,181 +498,64 @@ func HandleHubSetColor(response http.ResponseWriter, request *http.Request) { return } hub.Color = color + if err = postgresql.HubUpdate(ctx, hub, &types.HubUpdate{Color: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } -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 HandleHubRemove(response http.ResponseWriter, request *http.Request) { - _, hub, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveHub) + _, hub, ctx, _, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveHub) if !ok { return } + if err := postgresql.HubDelete(ctx, hub.Id); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } cache.DeleteHub(hub) response.WriteHeader(http.StatusAccepted) } func HandleHubToggleUserColorAllowed(response http.ResponseWriter, request *http.Request) { - _, hub, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetUserColorAllowed) + _, hub, ctx, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetUserColorAllowed) if !ok { return } hub.UserColorAllowed = !hub.UserColorAllowed + if err := postgresql.HubUpdate(ctx, hub, &types.HubUpdate{UserColorAllowed: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } + response.WriteHeader(http.StatusAccepted) +} + +func HandleSetHubJoinRole(response http.ResponseWriter, request *http.Request) { + _, hub, ctx, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetHubJoinRole) + if !ok { + return + } + newRoleId, err := convertions.StringToUint8(request.FormValue("new_role_id")) + if err != nil { + http.Error(response, "bad role_id", http.StatusBadRequest) + return + } + newRole := hub.Roles[newRoleId] + if newRole == nil { + http.Error(response, "bad role_id", http.StatusNotFound) + return + } + hub.JoinRole = newRole + if err = postgresql.HubUpdate(ctx, hub, &types.HubUpdate{JoinRole: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleHubUserRemove(response http.ResponseWriter, request *http.Request) { - _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveUser) + _, hub, ctx, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveUser) if !ok { return } @@ -669,6 +586,10 @@ func HandleHubUserRemove(response http.ResponseWriter, request *http.Request) { delete(target.Hubs, hub.Id) target.Mu.Unlock() + if err = postgresql.HubUserDelete(ctx, hub.Id, targetId); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } hub.Mu.Lock() delete(hub.Users, targetId) hub.Mu.Unlock() @@ -677,7 +598,7 @@ func HandleHubUserRemove(response http.ResponseWriter, request *http.Request) { } func HandleHubRenameUser(response http.ResponseWriter, request *http.Request) { - _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionRenameUser) + _, hub, ctx, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionRenameUser) if !ok { return } @@ -700,20 +621,28 @@ func HandleHubRenameUser(response http.ResponseWriter, request *http.Request) { return } target.Name = newName + if err = postgresql.HubUserUpdate(ctx, hub.Id, target, &types.HubUserUpdate{Name: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleHubRenameSelf(response http.ResponseWriter, request *http.Request) { - requestor, _, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionSelfRename) + requestor, hub, ctx, _, ok := hubPermissionContext(response, request, normal, types.PermissionSelfRename) if !ok { return } requestor.Name = request.FormValue("new_name") + if err := postgresql.HubUserUpdate(ctx, hub.Id, requestor, &types.HubUserUpdate{Name: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleHubToggleMuteUser(response http.ResponseWriter, request *http.Request) { - _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionMuteUser) + _, hub, ctx, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionMuteUser) if !ok { return } @@ -735,11 +664,15 @@ func HandleHubToggleMuteUser(response http.ResponseWriter, request *http.Request return } target.IsMuted = !target.IsMuted + if err = postgresql.HubUserUpdate(ctx, hub.Id, target, &types.HubUserUpdate{IsMuted: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleHubUserAddRole(response http.ResponseWriter, request *http.Request) { - _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionUserAddRole) + _, hub, ctx, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionUserAddRole) if !ok { return } @@ -772,11 +705,15 @@ func HandleHubUserAddRole(response http.ResponseWriter, request *http.Request) { } target.Roles.Add(roleId) updateChannelCacheForSpecUser(target, hub) + if err = postgresql.HubUserUpdate(ctx, hub.Id, target, &types.HubUserUpdate{Roles: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleHubUserRemoveRole(response http.ResponseWriter, request *http.Request) { - _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionUserRemoveRole) + _, hub, ctx, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionUserRemoveRole) if !ok { return } @@ -804,11 +741,15 @@ func HandleHubUserRemoveRole(response http.ResponseWriter, request *http.Request } target.Roles.Remove(roleId) updateChannelCacheForSpecUser(target, hub) + if err = postgresql.HubUserUpdate(ctx, hub.Id, target, &types.HubUserUpdate{Roles: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleHubCreateRole(response http.ResponseWriter, request *http.Request) { - _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionCreateRole) + _, hub, ctx, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionCreateRole) if !ok { return } @@ -832,18 +773,23 @@ func HandleHubCreateRole(response http.ResponseWriter, request *http.Request) { http.Error(response, "no role slots available", http.StatusConflict) return } - hub.Roles[freeId] = &types.HubRole{ + newRole := &types.HubRole{ Id: freeId, Name: name, Color: types.RandomRgba(), CreatedAt: time.Now(), } + hub.Roles[freeId] = newRole hub.Mu.Unlock() + if err := postgresql.HubRoleSave(ctx, hub.Id, newRole); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusCreated) } func HandleHubRemoveRole(response http.ResponseWriter, request *http.Request) { - _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveRole) + _, hub, ctx, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveRole) if !ok { return } @@ -869,11 +815,15 @@ func HandleHubRemoveRole(response http.ResponseWriter, request *http.Request) { } hub.Roles[targetRoleId] = nil hub.Mu.Unlock() + if err := postgresql.HubRoleDelete(ctx, hub.Id, targetRoleId); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleRoleSetName(response http.ResponseWriter, request *http.Request) { - _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionSetRoleName) + _, hub, ctx, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionSetRoleName) if !ok { return } @@ -901,11 +851,15 @@ func HandleRoleSetName(response http.ResponseWriter, request *http.Request) { } role.Name = newName hub.Mu.Unlock() + if err = postgresql.HubRoleUpdate(ctx, hub.Id, role, &types.HubRoleUpdate{Name: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleRoleSetColor(response http.ResponseWriter, request *http.Request) { - _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionSetRoleColor) + _, hub, ctx, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionSetRoleColor) if !ok { return } @@ -933,11 +887,15 @@ func HandleRoleSetColor(response http.ResponseWriter, request *http.Request) { } role.Color = color hub.Mu.Unlock() + if err = postgresql.HubRoleUpdate(ctx, hub.Id, role, &types.HubRoleUpdate{Color: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleRoleSetPermissions(response http.ResponseWriter, request *http.Request) { - requestor, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionSetRolePermissions) + requestor, hub, ctx, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionSetRolePermissions) if !ok { return } @@ -994,21 +952,29 @@ func HandleRoleSetPermissions(response http.ResponseWriter, request *http.Reques } updateChannelCacheForSpecRole(targetRole, hub) + if err := postgresql.HubRoleUpdate(ctx, hub.Id, targetRole, &types.HubRoleUpdate{Permissions: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleRoleSelfRemove(response http.ResponseWriter, request *http.Request) { - requestor, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionUserSelfRoleRemove) + requestor, hub, ctx, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionUserSelfRoleRemove) if !ok { return } requestor.Roles.Remove(usedRoleId) updateChannelCacheForSpecUser(requestor, hub) + if err := postgresql.HubUserUpdate(ctx, hub.Id, requestor, &types.HubUserUpdate{Roles: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleChannelCreate(response http.ResponseWriter, request *http.Request) { - requestor, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionCreateChannel) + requestor, hub, ctx, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionCreateChannel) if !ok { return } @@ -1046,11 +1012,15 @@ func HandleChannelCreate(response http.ResponseWriter, request *http.Request) { hub.Mu.Unlock() updateChannelCacheForSpecChannel(newHubChannel, hub) + if err := postgresql.HubChannelSave(ctx, hub.Id, newHubChannel); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleChannelRemove(response http.ResponseWriter, request *http.Request) { - _, hub, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveChannel) + _, hub, ctx, _, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveChannel) if !ok { return } @@ -1061,17 +1031,21 @@ func HandleChannelRemove(response http.ResponseWriter, request *http.Request) { } hub.Mu.Lock() - if _, ok := hub.Channels[channelId]; !ok { + if _, ok = hub.Channels[channelId]; !ok { hub.Mu.Unlock() http.Error(response, "no such channel", http.StatusNotFound) return } delete(hub.Channels, channelId) hub.Mu.Unlock() + if err = postgresql.HubChannelDelete(ctx, channelId); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleChannelSetName(response http.ResponseWriter, request *http.Request) { - _, hub, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetChannelName) + _, hub, ctx, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetChannelName) if !ok { return } @@ -1095,11 +1069,15 @@ func HandleChannelSetName(response http.ResponseWriter, request *http.Request) { } channel.Name = newName hub.Mu.Unlock() + if err = postgresql.HubChannelUpdate(ctx, channel, &types.HubChannelUpdate{Name: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleChannelSetDescription(response http.ResponseWriter, request *http.Request) { - _, hub, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetChannelDescription) + _, hub, ctx, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetChannelDescription) if !ok { return } @@ -1119,11 +1097,15 @@ func HandleChannelSetDescription(response http.ResponseWriter, request *http.Req } channel.Description = newDescription hub.Mu.Unlock() + if err = postgresql.HubChannelUpdate(ctx, channel, &types.HubChannelUpdate{Description: true}); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } -func handleChannelRolePermission(response http.ResponseWriter, request *http.Request, perm types.Permissions, modify func(*types.HubChannel, uint8, bool)) { - _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, perm) +func handleChannelRolePermission(response http.ResponseWriter, request *http.Request, perm types.Permissions, updateList *types.HubChannelUpdate, modify func(*types.HubChannel, uint8, bool)) { + _, hub, ctx, usedRoleId, ok := hubPermissionContext(response, request, normal, perm) if !ok { return } @@ -1154,11 +1136,15 @@ func handleChannelRolePermission(response http.ResponseWriter, request *http.Req hub.Mu.Unlock() updateChannelCacheForSpecChannel(channel, hub) + if err = postgresql.HubChannelUpdate(ctx, channel, updateList); err != nil { + http.Error(response, "db error", http.StatusInternalServerError) + return + } response.WriteHeader(http.StatusAccepted) } func HandleChannelSetPermittedVisibleRole(response http.ResponseWriter, request *http.Request) { - handleChannelRolePermission(response, request, types.PermissionSetChannelPermittedVisibleRoles, func(c *types.HubChannel, roleId uint8, allow bool) { + handleChannelRolePermission(response, request, types.PermissionSetChannelPermittedVisibleRoles, &types.HubChannelUpdate{RolesCanView: true}, func(c *types.HubChannel, roleId uint8, allow bool) { if allow { c.RolesCanView.Add(roleId) } else { @@ -1168,7 +1154,7 @@ func HandleChannelSetPermittedVisibleRole(response http.ResponseWriter, request } func HandleChannelSetPermittedSendRole(response http.ResponseWriter, request *http.Request) { - handleChannelRolePermission(response, request, types.PermissionSetChannelPermittedSendMessageRoles, func(c *types.HubChannel, roleId uint8, allow bool) { + handleChannelRolePermission(response, request, types.PermissionSetChannelPermittedSendMessageRoles, &types.HubChannelUpdate{RolesCanMessage: true}, func(c *types.HubChannel, roleId uint8, allow bool) { if allow { c.RolesCanMessage.Add(roleId) } else { @@ -1178,7 +1164,7 @@ func HandleChannelSetPermittedSendRole(response http.ResponseWriter, request *ht } func HandleChannelSetPermittedHistoryRole(response http.ResponseWriter, request *http.Request) { - handleChannelRolePermission(response, request, types.PermissionSetChannelPermittedReadHistoryRoles, func(c *types.HubChannel, roleId uint8, allow bool) { + handleChannelRolePermission(response, request, types.PermissionSetChannelPermittedReadHistoryRoles, &types.HubChannelUpdate{RolesCanReadHistory: true}, func(c *types.HubChannel, roleId uint8, allow bool) { if allow { c.RolesCanReadHistory.Add(roleId) } else { diff --git a/packages/httpRequest/user.go b/packages/httpRequest/user.go index 8515efe..1051bf6 100644 --- a/packages/httpRequest/user.go +++ b/packages/httpRequest/user.go @@ -52,7 +52,7 @@ func HandleUserNewToken(response http.ResponseWriter, request *http.Request) { http.Error(response, "bad login", http.StatusUnauthorized) return } - if err = postgresql.GetWholeUser(ctx, user); err != nil { + if err = postgresql.UserGetWhole(ctx, user); err != nil { http.Error(response, err.Error(), http.StatusInternalServerError) return } @@ -64,6 +64,8 @@ func HandleUserNewToken(response http.ResponseWriter, request *http.Request) { return } + cache.SaveUser(user) + token, err := tokens.TokenCreate(user.Id) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) @@ -155,7 +157,7 @@ func HandleUserModProfile(response http.ResponseWriter, request *http.Request) { return } - updateList := &types.UserProfileUpdateList{} + updateList := &types.UserProfileUpdate{} updatedValues := map[string]any{} if pronouns := request.FormValue("pronouns"); pronouns != "" { diff --git a/packages/minio/minio.go b/packages/minio/minio.go index 58d256a..5c1d708 100644 --- a/packages/minio/minio.go +++ b/packages/minio/minio.go @@ -69,6 +69,8 @@ func GetKey(opts *GetKeyOptions) string { return string(HubIconPrefix) + opts.HubId.String() + key case HubBackground: return string(HubBackgroundPrefix) + opts.HubId.String() + key + case ChannelIcon: + return string(ChannelIconPrefix) + opts.ChannelId.String() + key default: return string(ConnectionFilePrefix) + opts.ConnectionId.String() + key } diff --git a/packages/postgresql/postgresql.go b/packages/postgresql/postgresql.go index 9b18469..0a15b50 100644 --- a/packages/postgresql/postgresql.go +++ b/packages/postgresql/postgresql.go @@ -6,7 +6,6 @@ import ( "strings" "time" - "go-socket/packages/cache" "go-socket/packages/convertions" "go-socket/packages/types" @@ -79,10 +78,10 @@ func Init(ctx context.Context) { icon_url TEXT, background_url TEXT, creator UUID NOT NULL REFERENCES users(id), - join_role, // TODO set role uuid + join_role SMALLINT, rgba BIGINT NOT NULL DEFAULT 0 CHECK (rgba BETWEEN 0 AND 4294967295), - user_color_allowed BOOLEAN DEAFAULT FALSE, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), + user_color_allowed BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMP NOT NULL DEFAULT NOW() ) `) if err != nil { @@ -91,12 +90,13 @@ func Init(ctx context.Context) { _, err = dbConn.Exec(ctx, ` CREATE TABLE IF NOT EXISTS hub_roles ( - id TINYINT PRIMARY KEY, - parent_id UUID NOT NULL REFERACES hubs(id) ON DELETE CASCADE + id SMALLINT NOT NULL, + hub_id UUID NOT NULL REFERENCES hubs(id) ON DELETE CASCADE, name TEXT NOT NULL, - permissions INT NOT NULL DEFAULT 0, + permissions BIGINT NOT NULL DEFAULT 0, rgba BIGINT NOT NULL DEFAULT 0 CHECK (rgba BETWEEN 0 AND 4294967295), created_at TIMESTAMP NOT NULL DEFAULT NOW(), + PRIMARY KEY (id, hub_id) ) `) if err != nil { @@ -105,11 +105,53 @@ func Init(ctx context.Context) { _, err = dbConn.Exec(ctx, ` CREATE TABLE IF NOT EXISTS hub_channel ( - id TINYINT PRIMARY KEY, + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + hub_id UUID NOT NULL REFERENCES hubs(id) ON DELETE CASCADE, name TEXT NOT NULL, - permissions INT NOT NULL DEFAULT 0, - rgba BIGINT NOT NULL DEFAULT 0 CHECK (rgba BETWEEN 0 AND 4294967295), + description TEXT, + icon_url TEXT, + position SMALLINT NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT NOW(), + roles_can_view_0 BIGINT NOT NULL DEFAULT 0, + roles_can_view_1 BIGINT NOT NULL DEFAULT 0, + roles_can_view_2 BIGINT NOT NULL DEFAULT 0, + roles_can_message_0 BIGINT NOT NULL DEFAULT 0, + roles_can_message_1 BIGINT NOT NULL DEFAULT 0, + roles_can_message_2 BIGINT NOT NULL DEFAULT 0, + roles_can_read_history_0 BIGINT NOT NULL DEFAULT 0, + roles_can_read_history_1 BIGINT NOT NULL DEFAULT 0, + roles_can_read_history_2 BIGINT NOT NULL DEFAULT 0 + ) + `) + if err != nil { + panic(err) + } + + _, err = dbConn.Exec(ctx, ` + CREATE TABLE IF NOT EXISTS hub_users ( + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + hub_id UUID NOT NULL REFERENCES hubs(id) ON DELETE CASCADE, + name TEXT NOT NULL DEFAULT '', + roles_0 BIGINT NOT NULL DEFAULT 0, + roles_1 BIGINT NOT NULL DEFAULT 0, + roles_2 BIGINT NOT NULL DEFAULT 0, + is_muted BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + PRIMARY KEY (user_id, hub_id) + ) + `) + if err != nil { + panic(err) + } + + _, err = dbConn.Exec(ctx, ` + CREATE TABLE IF NOT EXISTS hub_channel_messages ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + sender_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + receiver_id UUID NOT NULL REFERENCES hub_channel(id) ON DELETE CASCADE, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + content TEXT NOT NULL, + attached_file TEXT NOT NULL DEFAULT '' ) `) if err != nil { @@ -156,7 +198,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.UserProfileUpdate) error { setClauses := make([]string, 0, 3) args := make([]any, 0, 4) argIdx := 1 @@ -198,15 +240,49 @@ func UserUpdateProfile(ctx context.Context, user *types.User, updateList *types. return err } -func GetWholeUser(ctx context.Context, user *types.User) error { +func UserGetBelongingHubs(ctx context.Context, user *types.User) error { + rows, err := dbConn.Query(ctx, ` + SELECT h.id, h.name, COALESCE(h.icon_url, ''), COALESCE(h.background_url, ''), h.creator, h.join_role, h.rgba, h.user_color_allowed, h.created_at + FROM hubs h + INNER JOIN hub_users hu ON hu.hub_id = h.id + WHERE hu.user_id = $1 + `, user.Id) + if err != nil { + return err + } + defer rows.Close() + + if user.Hubs == nil { + user.Hubs = make(map[uuid.UUID]*types.Hub) + } + + for rows.Next() { + hub := types.NewHub() + var joinRoleId *int16 + var rgba int64 + if err = rows.Scan(&hub.Id, &hub.Name, &hub.IconUrl, &hub.BgUrl, &hub.Creator, &joinRoleId, &rgba, &hub.UserColorAllowed, &hub.CreatedAt); err != nil { + return fmt.Errorf("scanning hub row: %w", err) + } + hub.Color = convertions.Uint32ToRgba(uint32(rgba)) + if joinRoleId != nil { + hub.JoinRole = &types.HubRole{Id: uint8(*joinRoleId)} + } + user.Hubs[hub.Id] = hub + } + return rows.Err() +} + +func UserGetWhole(ctx context.Context, user *types.User) error { if err := UserGetById(ctx, user); err != nil { return err } if err := ConnectionsGetBelongingToUser(ctx, user); err != nil { return err } + if err := UserGetBelongingHubs(ctx, user); err != nil { + return err + } - cache.SaveUser(user) return nil } @@ -310,3 +386,378 @@ func ConnectionGetMessagesBefore(ctx context.Context, before time.Time, connecti } return messages, rows.Err() } + +func HubGet(ctx context.Context, hub *types.Hub) error { + var joinRoleId *int16 + var rgba int64 + err := dbConn.QueryRow(ctx, ` + SELECT name, COALESCE(icon_url, ''), COALESCE(background_url, ''), creator, join_role, rgba, user_color_allowed, created_at + FROM hubs WHERE id = $1 + `, hub.Id).Scan(&hub.Name, &hub.IconUrl, &hub.BgUrl, &hub.Creator, &joinRoleId, &rgba, &hub.UserColorAllowed, &hub.CreatedAt) + if err != nil { + return err + } + hub.Color = convertions.Uint32ToRgba(uint32(rgba)) + if joinRoleId != nil { + hub.JoinRole = &types.HubRole{Id: uint8(*joinRoleId)} + } + return nil +} + +func HubGetWhole(ctx context.Context, hub *types.Hub) error { + if err := HubGet(ctx, hub); err != nil { + return err + } + if err := HubRolesGet(ctx, hub); err != nil { + return err + } + if err := HubChannelsGet(ctx, hub); err != nil { + return err + } + if err := HubUsersGet(ctx, hub); err != nil { + return err + } + return nil +} + +func HubRolesGet(ctx context.Context, hub *types.Hub) error { + rows, err := dbConn.Query(ctx, ` + SELECT id, name, permissions, rgba, created_at FROM hub_roles WHERE hub_id = $1 + `, hub.Id) + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + role := &types.HubRole{} + var id int16 + var permissions, rgba int64 + if err = rows.Scan(&id, &role.Name, &permissions, &rgba, &role.CreatedAt); err != nil { + return fmt.Errorf("scanning hub_role row: %w", err) + } + role.Id = uint8(id) + role.Permissions = types.Permissions(uint32(permissions)) + role.Color = convertions.Uint32ToRgba(uint32(rgba)) + hub.Roles[role.Id] = role + } + return rows.Err() +} + +func HubUsersGet(ctx context.Context, hub *types.Hub) error { + rows, err := dbConn.Query(ctx, ` + SELECT user_id, name, roles_0, roles_1, roles_2, is_muted, created_at FROM hub_users WHERE hub_id = $1 + `, hub.Id) + if err != nil { + return err + } + defer rows.Close() + + if hub.Users == nil { + hub.Users = make(map[uuid.UUID]*types.HubUser) + } + + for rows.Next() { + user := types.NewHubUser() + var r0, r1, r2 int64 + if err = rows.Scan(&user.OriginalId, &user.Name, &r0, &r1, &r2, &user.IsMuted, &user.CreatedAt); err != nil { + return fmt.Errorf("scanning hub_user row: %w", err) + } + user.Roles[0] = uint64(r0) + user.Roles[1] = uint64(r1) + user.Roles[2] = uint64(r2) + hub.Users[user.OriginalId] = user + } + return rows.Err() +} + +func HubChannelsGet(ctx context.Context, hub *types.Hub) error { + rows, err := dbConn.Query(ctx, ` + SELECT id, name, COALESCE(description, ''), COALESCE(icon_url, ''), position, created_at, + roles_can_view_0, roles_can_view_1, roles_can_view_2, + roles_can_message_0, roles_can_message_1, roles_can_message_2, + roles_can_read_history_0, roles_can_read_history_1, roles_can_read_history_2 + FROM hub_channel WHERE hub_id = $1 + `, hub.Id) + if err != nil { + return err + } + defer rows.Close() + + if hub.Channels == nil { + hub.Channels = make(map[uuid.UUID]*types.HubChannel) + } + + for rows.Next() { + channel := types.NewHubChannel() + var pos int16 + var v0, v1, v2, m0, m1, m2, rh0, rh1, rh2 int64 + if err = rows.Scan( + &channel.Id, &channel.Name, &channel.Description, &channel.IconUrl, &pos, &channel.CreatedAt, + &v0, &v1, &v2, &m0, &m1, &m2, &rh0, &rh1, &rh2, + ); err != nil { + return fmt.Errorf("scanning hub_channel row: %w", err) + } + channel.Position = uint8(pos) + channel.RolesCanView = types.HubBoundRoles{uint64(v0), uint64(v1), uint64(v2)} + channel.RolesCanMessage = types.HubBoundRoles{uint64(m0), uint64(m1), uint64(m2)} + channel.RolesCanReadHistory = types.HubBoundRoles{uint64(rh0), uint64(rh1), uint64(rh2)} + hub.Channels[channel.Id] = channel + } + return rows.Err() +} + +func HubSave(ctx context.Context, hub *types.Hub) error { + var joinRoleId *int16 + if hub.JoinRole != nil { + id := int16(hub.JoinRole.Id) + joinRoleId = &id + } + _, err := dbConn.Exec(ctx, ` + INSERT INTO hubs (id, name, icon_url, background_url, creator, join_role, rgba, user_color_allowed, created_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + `, hub.Id, hub.Name, hub.IconUrl, hub.BgUrl, hub.Creator, joinRoleId, + convertions.RgbaToUint32(hub.Color), hub.UserColorAllowed, hub.CreatedAt) + return err +} + +func HubRoleSave(ctx context.Context, hubId uuid.UUID, role *types.HubRole) error { + _, err := dbConn.Exec(ctx, ` + INSERT INTO hub_roles (id, hub_id, name, permissions, rgba, created_at) + VALUES ($1, $2, $3, $4, $5, $6) + `, int16(role.Id), hubId, role.Name, int64(role.Permissions), + convertions.RgbaToUint32(role.Color), role.CreatedAt) + return err +} + +func HubChannelSave(ctx context.Context, hubId uuid.UUID, channel *types.HubChannel) error { + _, err := dbConn.Exec(ctx, ` + INSERT INTO hub_channel ( + id, hub_id, name, description, icon_url, position, created_at, + roles_can_view_0, roles_can_view_1, roles_can_view_2, + roles_can_message_0, roles_can_message_1, roles_can_message_2, + roles_can_read_history_0, roles_can_read_history_1, roles_can_read_history_2 + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) + `, channel.Id, hubId, channel.Name, channel.Description, channel.IconUrl, channel.Position, channel.CreatedAt, + int64(channel.RolesCanView[0]), int64(channel.RolesCanView[1]), int64(channel.RolesCanView[2]), + int64(channel.RolesCanMessage[0]), int64(channel.RolesCanMessage[1]), int64(channel.RolesCanMessage[2]), + int64(channel.RolesCanReadHistory[0]), int64(channel.RolesCanReadHistory[1]), int64(channel.RolesCanReadHistory[2])) + return err +} + +func HubUserSave(ctx context.Context, hubId uuid.UUID, hubUser *types.HubUser) error { + _, err := dbConn.Exec(ctx, ` + INSERT INTO hub_users (user_id, hub_id, name, roles_0, roles_1, roles_2, is_muted, created_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + `, hubUser.OriginalId, hubId, hubUser.Name, + int64(hubUser.Roles[0]), int64(hubUser.Roles[1]), int64(hubUser.Roles[2]), + hubUser.IsMuted, hubUser.CreatedAt) + return err +} + +func HubChannelMessageSave(ctx context.Context, message *types.Message) error { + if message.Id != (uuid.UUID{}) { + _, err := dbConn.Exec(ctx, ` + INSERT INTO hub_channel_messages (id, sender_id, receiver_id, created_at, content, attached_file) + VALUES ($1, $2, $3, $4, $5, $6) + `, message.Id, message.Sender, message.Receiver, message.CreatedAt, message.Content, message.AttachedFile) + return err + } + return dbConn.QueryRow(ctx, ` + INSERT INTO hub_channel_messages (sender_id, receiver_id, created_at, content, attached_file) + VALUES ($1, $2, $3, $4, $5) + RETURNING id + `, message.Sender, message.Receiver, message.CreatedAt, message.Content, message.AttachedFile).Scan(&message.Id) +} + +func HubChannelMessageGet(ctx context.Context, message *types.Message) error { + return dbConn.QueryRow(ctx, ` + SELECT sender_id, receiver_id, created_at, content, attached_file + FROM hub_channel_messages + WHERE id = $1 + `, message.Id).Scan(&message.Sender, &message.Receiver, &message.CreatedAt, &message.Content, &message.AttachedFile) +} + +func HubUpdate(ctx context.Context, hub *types.Hub, updateList *types.HubUpdate) error { + setClauses := make([]string, 0, 6) + args := make([]any, 0, 7) + argIdx := 1 + + if updateList.Name { + setClauses = append(setClauses, fmt.Sprintf("name = $%d", argIdx)) + args = append(args, hub.Name) + argIdx++ + } + if updateList.IconUrl { + setClauses = append(setClauses, fmt.Sprintf("icon_url = $%d", argIdx)) + args = append(args, hub.IconUrl) + argIdx++ + } + if updateList.BgUrl { + setClauses = append(setClauses, fmt.Sprintf("background_url = $%d", argIdx)) + args = append(args, hub.BgUrl) + argIdx++ + } + if updateList.JoinRole { + var joinRoleId *int16 + if hub.JoinRole != nil { + id := int16(hub.JoinRole.Id) + joinRoleId = &id + } + setClauses = append(setClauses, fmt.Sprintf("join_role = $%d", argIdx)) + args = append(args, joinRoleId) + argIdx++ + } + if updateList.Color { + setClauses = append(setClauses, fmt.Sprintf("rgba = $%d", argIdx)) + args = append(args, convertions.RgbaToUint32(hub.Color)) + argIdx++ + } + if updateList.UserColorAllowed { + setClauses = append(setClauses, fmt.Sprintf("user_color_allowed = $%d", argIdx)) + args = append(args, hub.UserColorAllowed) + argIdx++ + } + + if len(setClauses) == 0 { + return nil + } + + query := "UPDATE hubs SET " + strings.Join(setClauses, ", ") + fmt.Sprintf(" WHERE id = $%d", argIdx) + args = append(args, hub.Id) + _, err := dbConn.Exec(ctx, query, args...) + return err +} + +func HubRoleUpdate(ctx context.Context, hubId uuid.UUID, role *types.HubRole, updateList *types.HubRoleUpdate) error { + setClauses := make([]string, 0, 3) + args := make([]any, 0, 5) + argIdx := 1 + + if updateList.Name { + setClauses = append(setClauses, fmt.Sprintf("name = $%d", argIdx)) + args = append(args, role.Name) + argIdx++ + } + if updateList.Permissions { + setClauses = append(setClauses, fmt.Sprintf("permissions = $%d", argIdx)) + args = append(args, int64(role.Permissions)) + argIdx++ + } + if updateList.Color { + setClauses = append(setClauses, fmt.Sprintf("rgba = $%d", argIdx)) + args = append(args, convertions.RgbaToUint32(role.Color)) + argIdx++ + } + + if len(setClauses) == 0 { + return nil + } + + query := "UPDATE hub_roles SET " + strings.Join(setClauses, ", ") + fmt.Sprintf(" WHERE id = $%d AND hub_id = $%d", argIdx, argIdx+1) + args = append(args, int16(role.Id), hubId) + _, err := dbConn.Exec(ctx, query, args...) + return err +} + +func HubChannelUpdate(ctx context.Context, channel *types.HubChannel, updateList *types.HubChannelUpdate) error { + setClauses := make([]string, 0, 13) + args := make([]any, 0, 15) + argIdx := 1 + + if updateList.Name { + setClauses = append(setClauses, fmt.Sprintf("name = $%d", argIdx)) + args = append(args, channel.Name) + argIdx++ + } + if updateList.Description { + setClauses = append(setClauses, fmt.Sprintf("description = $%d", argIdx)) + args = append(args, channel.Description) + argIdx++ + } + if updateList.IconUrl { + setClauses = append(setClauses, fmt.Sprintf("icon_url = $%d", argIdx)) + args = append(args, channel.IconUrl) + argIdx++ + } + if updateList.Position { + setClauses = append(setClauses, fmt.Sprintf("position = $%d", argIdx)) + args = append(args, channel.Position) + argIdx++ + } + if updateList.RolesCanView { + setClauses = append(setClauses, fmt.Sprintf("roles_can_view_0 = $%d, roles_can_view_1 = $%d, roles_can_view_2 = $%d", argIdx, argIdx+1, argIdx+2)) + args = append(args, int64(channel.RolesCanView[0]), int64(channel.RolesCanView[1]), int64(channel.RolesCanView[2])) + argIdx += 3 + } + if updateList.RolesCanMessage { + setClauses = append(setClauses, fmt.Sprintf("roles_can_message_0 = $%d, roles_can_message_1 = $%d, roles_can_message_2 = $%d", argIdx, argIdx+1, argIdx+2)) + args = append(args, int64(channel.RolesCanMessage[0]), int64(channel.RolesCanMessage[1]), int64(channel.RolesCanMessage[2])) + argIdx += 3 + } + if updateList.RolesCanReadHistory { + setClauses = append(setClauses, fmt.Sprintf("roles_can_read_history_0 = $%d, roles_can_read_history_1 = $%d, roles_can_read_history_2 = $%d", argIdx, argIdx+1, argIdx+2)) + args = append(args, int64(channel.RolesCanReadHistory[0]), int64(channel.RolesCanReadHistory[1]), int64(channel.RolesCanReadHistory[2])) + argIdx += 3 + } + + if len(setClauses) == 0 { + return nil + } + + query := "UPDATE hub_channel SET " + strings.Join(setClauses, ", ") + fmt.Sprintf(" WHERE id = $%d", argIdx) + args = append(args, channel.Id) + _, err := dbConn.Exec(ctx, query, args...) + return err +} + +func HubUserUpdate(ctx context.Context, hubId uuid.UUID, hubUser *types.HubUser, updateList *types.HubUserUpdate) error { + setClauses := make([]string, 0, 5) + args := make([]any, 0, 7) + argIdx := 1 + + if updateList.Name { + setClauses = append(setClauses, fmt.Sprintf("name = $%d", argIdx)) + args = append(args, hubUser.Name) + argIdx++ + } + if updateList.Roles { + setClauses = append(setClauses, fmt.Sprintf("roles_0 = $%d, roles_1 = $%d, roles_2 = $%d", argIdx, argIdx+1, argIdx+2)) + args = append(args, int64(hubUser.Roles[0]), int64(hubUser.Roles[1]), int64(hubUser.Roles[2])) + argIdx += 3 + } + if updateList.IsMuted { + setClauses = append(setClauses, fmt.Sprintf("is_muted = $%d", argIdx)) + args = append(args, hubUser.IsMuted) + argIdx++ + } + + if len(setClauses) == 0 { + return nil + } + + query := "UPDATE hub_users SET " + strings.Join(setClauses, ", ") + fmt.Sprintf(" WHERE user_id = $%d AND hub_id = $%d", argIdx, argIdx+1) + args = append(args, hubUser.OriginalId, hubId) + _, err := dbConn.Exec(ctx, query, args...) + return err +} + +func HubDelete(ctx context.Context, hubId uuid.UUID) error { + _, err := dbConn.Exec(ctx, `DELETE FROM hubs WHERE id = $1`, hubId) + return err +} + +func HubUserDelete(ctx context.Context, hubId uuid.UUID, userId uuid.UUID) error { + _, err := dbConn.Exec(ctx, `DELETE FROM hub_users WHERE user_id = $1 AND hub_id = $2`, userId, hubId) + return err +} + +func HubRoleDelete(ctx context.Context, hubId uuid.UUID, roleId uint8) error { + _, err := dbConn.Exec(ctx, `DELETE FROM hub_roles WHERE id = $1 AND hub_id = $2`, int16(roleId), hubId) + return err +} + +func HubChannelDelete(ctx context.Context, channelId uuid.UUID) error { + _, err := dbConn.Exec(ctx, `DELETE FROM hub_channel WHERE id = $1`, channelId) + return err +} diff --git a/packages/types/types.go b/packages/types/types.go index f9a8976..8770acb 100644 --- a/packages/types/types.go +++ b/packages/types/types.go @@ -42,7 +42,7 @@ type User struct { Color Rgba `json:"color"` } -type UserProfileUpdateList struct { +type UserProfileUpdate struct { Pronouns bool Description bool Color bool @@ -148,6 +148,7 @@ const ( PermissionSetHubColor PermissionRemoveHub PermissionSetUserColorAllowed + PermissionSetHubJoinRole // User permissions PermissionInviteUser @@ -184,6 +185,7 @@ var permissionRegistry = map[string]Permissions{ "set_hub_color": PermissionSetHubColor, "remove_hub": PermissionRemoveHub, "set_user_color_allowed": PermissionSetUserColorAllowed, + "set_hub_join_role": PermissionSetHubJoinRole, "invite_user": PermissionInviteUser, "remove_user": PermissionRemoveUser, "rename_user": PermissionRenameUser, @@ -263,6 +265,17 @@ func (p *CachedUserPermissions) SetCanMessage() { *p |= CachedUserCanMessage } func (p *CachedUserPermissions) ClearCanMessage() { *p &^= CachedUserCanMessage } func (p CachedUserPermissions) CanMessage() bool { return p&CachedUserCanMessage != 0 } +type HubUpdate struct { + Name bool + IconUrl bool + BgUrl bool + JoinRole bool + Id bool + Creator bool + Color bool + UserColorAllowed bool +} + type Hub struct { Mu sync.RWMutex `json:"-"` CreatedAt time.Time `json:"createdAt"` @@ -286,6 +299,13 @@ func NewHub() *Hub { } } +type HubRoleUpdate struct { + Name bool + Permissions bool + Color bool + Id bool +} + type HubRole struct { Name string `json:"role"` CreatedAt time.Time `json:"createdAt"` @@ -304,6 +324,12 @@ func (h *HubRole) HasPermission(r Permissions) bool { return h.Permissions&r != 0 } +type HubUserUpdate struct { + Roles bool + Name bool + IsMuted bool +} + type HubUser struct { Mu sync.RWMutex `json:"-"` CreatedAt time.Time `json:"createdAt"` @@ -317,6 +343,17 @@ func NewHubUser() *HubUser { return &HubUser{} } +type HubChannelUpdate struct { + Name bool + Description bool + IconUrl bool + CreatedAt bool + RolesCanView bool + RolesCanMessage bool + RolesCanReadHistory bool + Position bool +} + type HubChannel struct { Mu sync.RWMutex `json:"-"` MessagesBuff []*Message `json:"-"` diff --git a/test-client/index.html b/test-client/index.html index b4d5140..f3b1857 100644 --- a/test-client/index.html +++ b/test-client/index.html @@ -69,6 +69,8 @@ + + @@ -108,7 +110,7 @@