From 40cb7736c2925f9a92cd346d778537aef034b9ed Mon Sep 17 00:00:00 2001 From: Sisi Date: Sat, 2 May 2026 12:59:05 +0200 Subject: [PATCH] fix channel caches, add hub functions to api endpoints --- machine-client/.gitignore | 1 + machine-client/index.html | 47 +----------- machine-client/style.css | 44 +++++++++++ main.go | 22 ++++++ packages/cache/cache.go | 12 +++ packages/convertions/convertions.go | 2 +- packages/httpRequest/connectionsAndDms.go | 2 +- packages/httpRequest/hubs.go | 90 +++++++++++++++++------ packages/httpRequest/user.go | 34 +++++++++ 9 files changed, 184 insertions(+), 70 deletions(-) create mode 100644 machine-client/.gitignore create mode 100644 machine-client/style.css diff --git a/machine-client/.gitignore b/machine-client/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/machine-client/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/machine-client/index.html b/machine-client/index.html index 663f962..79c1a1d 100644 --- a/machine-client/index.html +++ b/machine-client/index.html @@ -3,52 +3,7 @@ go-socket debug - +

go-socket debug

diff --git a/machine-client/style.css b/machine-client/style.css new file mode 100644 index 0000000..49b9046 --- /dev/null +++ b/machine-client/style.css @@ -0,0 +1,44 @@ +body { font-family: monospace; background: #1a1a1a; color: #e0e0e0; margin: 20px; } +h1 { color: #aaa; font-size: 1.2em; margin-bottom: 12px; } + +.config { margin-bottom: 10px; } +.config label { color: #888; } +.config input { background: #2a2a2a; color: #e0e0e0; border: 1px solid #444; padding: 4px 8px; width: 300px; } + +#log-header { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; } +#log-header span { color: #888; font-size: 1em; } +#clear-log { background: #2a2a2a; color: #888; border: 1px solid #444; padding: 3px 10px; cursor: pointer; font-size: 0.85em; font-family: monospace; } +#log { background: #111; border: 1px solid #333; padding: 10px; height: 340px; overflow-y: auto; font-size: 0.85em; white-space: pre-wrap; word-break: break-all; margin-bottom: 14px; } +.log-ws { color: #8af; } +.log-http { color: #af8; } +.log-err { color: #f88; } +.log-info { color: #666; } + +#btn-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; } +#btn-row button { + background: #2a2a2a; color: #ccc; border: 1px solid #444; + padding: 5px 10px; cursor: pointer; font-family: monospace; font-size: 0.8em; +} +#btn-row button:hover { background: #333; border-color: #666; } +#btn-row button.active { background: #2a3a2a; border-color: #4a4; color: #af8; } +#btn-row button.active.ws { background: #2a2a3a; border-color: #44a; color: #88f; } +#btn-row button.warn { color: #f88; } + +#form-panel { display: none; background: #1e1e1e; border: 1px solid #333; padding: 12px; margin-bottom: 6px; } +#form-panel.open { display: block; } +#form-title { color: #888; font-size: 0.85em; margin-bottom: 10px; } +.form-content { display: none; } +.form-content.active { display: block; } +.field { margin-bottom: 7px; } +.field label { display: inline-block; width: 160px; color: #777; font-size: 0.88em; } +.field input { background: #2a2a2a; color: #e0e0e0; border: 1px solid #444; padding: 4px 8px; width: 260px; font-family: monospace; font-size: 0.88em; } +.hint { color: #555; font-size: 0.78em; margin-left: 5px; } +.form-actions { margin-top: 10px; display: flex; gap: 8px; align-items: center; } +button.send { background: #2a4a2a; color: #8f8; border: 1px solid #4a4; padding: 5px 16px; cursor: pointer; font-family: monospace; } +button.send:hover { background: #3a5a3a; } +button.ws-action { background: #2a2a4a; color: #88f; border: 1px solid #44a; padding: 5px 14px; cursor: pointer; font-family: monospace; } +button.ws-action:hover { background: #3a3a5a; } +button.ws-danger { background: #4a2a2a; color: #f88; border: 1px solid #a44; padding: 5px 14px; cursor: pointer; font-family: monospace; } +.ws-status { font-size: 0.82em; } +.ws-status.connected { color: #8f8; } +.ws-status.disconnected { color: #f88; } diff --git a/main.go b/main.go index 5fddb7f..b21698d 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ func main() { http.HandleFunc("POST /user", withCORS(httpRequest.HandleUserNew)) http.HandleFunc("DELETE /user", withCORS(httpRequest.HandleUserDelete)) http.HandleFunc("GET /user", withCORS(httpRequest.HandleUserGetUser)) + http.HandleFunc("GET /users", withCORS(httpRequest.HandleUserGetUsers)) http.HandleFunc("PATCH /user/profile", withCORS(httpRequest.HandleUserModProfile)) http.HandleFunc("PATCH /user/avatar", withCORS(httpRequest.HandleSetUserAvatar)) http.HandleFunc("PATCH /user/profilebg", withCORS(httpRequest.HandleSetUserProfileBg)) @@ -60,7 +61,28 @@ func main() { http.HandleFunc("POST /hub", withCORS(httpRequest.HandleHubCreate)) http.HandleFunc("POST /hub/message", withCORS(httpRequest.HandleHubMessage)) http.HandleFunc("GET /hubs", withCORS(httpRequest.HandleGetHubs)) + http.HandleFunc("GET /hubs/channels", withCORS(httpRequest.HandleGetChannels)) + http.HandleFunc("GET /hubs/users", withCORS(httpRequest.HandleGetHubUsers)) http.HandleFunc("PUT /hub/join", withCORS(httpRequest.HandleHubJoin)) + http.HandleFunc("PATCH /hub/name", withCORS(httpRequest.HandleHubSetName)) + http.HandleFunc("PATCH /hub/color", withCORS(httpRequest.HandleHubSetColor)) + http.HandleFunc("DELETE /hub", withCORS(httpRequest.HandleHubRemove)) + http.HandleFunc("PATCH /hub/usercolorallowed", withCORS(httpRequest.HandleHubToggleUserColorAllowed)) + http.HandleFunc("DELETE /hub/user", withCORS(httpRequest.HandleHubUserRemove)) + http.HandleFunc("PATCH /hub/user/name", withCORS(httpRequest.HandleHubRenameUser)) + http.HandleFunc("PATCH /hub/self/name", withCORS(httpRequest.HandleHubRenameSelf)) + http.HandleFunc("PATCH /hub/user/mute", withCORS(httpRequest.HandleHubToggleMuteUser)) + http.HandleFunc("PUT /hub/user/role", withCORS(httpRequest.HandleHubUserAddRole)) + http.HandleFunc("DELETE /hub/user/role", withCORS(httpRequest.HandleHubUserRemoveRole)) + http.HandleFunc("PUT /hub/role", httpRequest.HandleHubCreateRole) + http.HandleFunc("DELETE /hub/role", withCORS(httpRequest.HandleHubRemoveRole)) + http.HandleFunc("PATCH /hub/role/name", withCORS(httpRequest.HandleRoleSetName)) + http.HandleFunc("PATCH /hub/role/color", withCORS(httpRequest.HandleRoleSetColor)) + http.HandleFunc("PATCH /hub/role/permissions", withCORS(httpRequest.HandleRoleSetPermissions)) + http.HandleFunc("DELETE /hub/self/role", withCORS(httpRequest.HandleRoleSelfRemove)) + http.HandleFunc("PUT /hub/channel", withCORS(httpRequest.HandleChannelCreate)) + http.HandleFunc("DELETE /hub/channel", withCORS(httpRequest.HandleChannelRemove)) + http.HandleFunc("PATCH /hub/name", withCORS(httpRequest.HandleChannelSetName)) http.HandleFunc("POST /connection/message", withCORS(httpRequest.HandleDm)) http.HandleFunc("GET /ws", wsServer.ServeWsConnection) diff --git a/packages/cache/cache.go b/packages/cache/cache.go index 26550e8..7a2c3c0 100644 --- a/packages/cache/cache.go +++ b/packages/cache/cache.go @@ -26,6 +26,18 @@ func GetUserById(id uuid.UUID) (*types.User, error) { } return user, nil } +func GetUsersByIds(ids []uuid.UUID) (users []*types.User, err error) { + Mu.RLock() + defer Mu.RUnlock() + for _, id := range ids { + user, ok := Users[id] + if !ok { + return nil, fmt.Errorf("if %s not found", id) + } + users = append(users, user) + } + return users, nil +} func GetUserByName(name string) (*types.User, error) { Mu.RLock() defer Mu.RUnlock() diff --git a/packages/convertions/convertions.go b/packages/convertions/convertions.go index fc83e6a..65c7e5a 100644 --- a/packages/convertions/convertions.go +++ b/packages/convertions/convertions.go @@ -50,7 +50,7 @@ func StringToUuid(str string) (uuid.UUID, error) { return uuid.Parse(str) } -func StringToUuidSlice(uuidStr string) ([]uuid.UUID, error) { +func StringToUuids(uuidStr string) ([]uuid.UUID, error) { if uuidStr == "" { return nil, errors.New("empty string") } diff --git a/packages/httpRequest/connectionsAndDms.go b/packages/httpRequest/connectionsAndDms.go index 3fa8673..c6a8849 100644 --- a/packages/httpRequest/connectionsAndDms.go +++ b/packages/httpRequest/connectionsAndDms.go @@ -98,7 +98,7 @@ func HandleUserGetConnectionsUnreadMessages(response http.ResponseWriter, reques return } - connectionIds, err := convertions.StringToUuidSlice(request.URL.Query().Get("connections")) + connectionIds, err := convertions.StringToUuids(request.URL.Query().Get("connections")) if err != nil { http.Error(response, "invalid uuid format", http.StatusBadRequest) return diff --git a/packages/httpRequest/hubs.go b/packages/httpRequest/hubs.go index 4c0d9dd..3582e1b 100644 --- a/packages/httpRequest/hubs.go +++ b/packages/httpRequest/hubs.go @@ -54,31 +54,55 @@ func hubUserHighestRoleId(u *types.HubUser, h *types.Hub) (id uint8) { return id } -func updateUserChannelCache(u *types.HubUser, c *types.HubChannel) { +func updateChannelCacheForSpecUserAndChannel(u *types.HubUser, c *types.HubChannel) { c.Mu.Lock() defer c.Mu.Unlock() - if u.Roles.DoesIntersect(c.RolesCanView) { - c.UsersCachedPermissions[u.OriginalId] |= types.CachedUserCanView + + if u == nil { + delete(c.UsersCachedPermissions, c.Id) } else { - c.UsersCachedPermissions[u.OriginalId] &^= types.CachedUserCanView - } - if u.Roles.DoesIntersect(c.RolesCanReadHistory) { - c.UsersCachedPermissions[u.OriginalId] |= types.CachedUserCanReadHistory - } else { - c.UsersCachedPermissions[u.OriginalId] &^= types.CachedUserCanReadHistory - } - if u.Roles.DoesIntersect(c.RolesCanMessage) { - c.UsersCachedPermissions[u.OriginalId] |= types.CachedUserCanMessage - } else { - c.UsersCachedPermissions[u.OriginalId] &^= types.CachedUserCanMessage + if u.Roles.DoesIntersect(c.RolesCanView) { + c.UsersCachedPermissions[u.OriginalId] |= types.CachedUserCanView + } else { + c.UsersCachedPermissions[u.OriginalId] &^= types.CachedUserCanView + } + if u.Roles.DoesIntersect(c.RolesCanReadHistory) { + c.UsersCachedPermissions[u.OriginalId] |= types.CachedUserCanReadHistory + } else { + c.UsersCachedPermissions[u.OriginalId] &^= types.CachedUserCanReadHistory + } + if u.Roles.DoesIntersect(c.RolesCanMessage) { + c.UsersCachedPermissions[u.OriginalId] |= types.CachedUserCanMessage + } else { + c.UsersCachedPermissions[u.OriginalId] &^= types.CachedUserCanMessage + } } } -func updateChannelCache(c *types.HubChannel, h *types.Hub) { +func updateChannelCacheForSpecChannel(c *types.HubChannel, h *types.Hub) { h.Mu.RLock() defer h.Mu.RUnlock() for _, u := range h.Users { - updateUserChannelCache(u, c) + updateChannelCacheForSpecUserAndChannel(u, c) + } +} + +func updateChannelCacheForSpecUser(u *types.HubUser, h *types.Hub) { + h.Mu.RLock() + defer h.Mu.RUnlock() + for _, c := range h.Channels { + updateChannelCacheForSpecUserAndChannel(u, c) + } +} + +func updateChannelCacheForSpecRole(r *types.HubRole, h *types.Hub) { + h.Mu.Lock() + defer h.Mu.Unlock() + for _, u := range h.Users { + if !u.Roles.ContainsRoleId(r.Id) { + continue + } + updateChannelCacheForSpecUser(u, h) } } @@ -178,9 +202,12 @@ func HandleHubJoin(response http.ResponseWriter, request *http.Request) { hubUser := types.NewHubUser() hubUser.OriginalId = user.Id - hubUser.Roles.Add(hub.JoinRole.Id) + if hub.JoinRole != nil { + hubUser.Roles.Add(hub.JoinRole.Id) + } hubUser.CreatedAt = time.Now() hub.Users[hubUser.OriginalId] = hubUser + updateChannelCacheForSpecUser(hubUser, hub) } func HandleHubMessage(response http.ResponseWriter, request *http.Request) { @@ -307,7 +334,7 @@ func HandleGetHubUsers(response http.ResponseWriter, request *http.Request) { } ctx := request.Context() _, requestor, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request) - if { + if err != nil { return } users := make([]*types.HubUser, 0) @@ -423,6 +450,7 @@ func HandleHubUserRemove(response http.ResponseWriter, request *http.Request) { } hub.Mu.Lock() delete(hub.Users, targetId) + updateChannelCacheForSpecUser(target, hub) hub.Mu.Unlock() response.WriteHeader(http.StatusAccepted) } @@ -526,6 +554,7 @@ func HandleHubUserAddRole(response http.ResponseWriter, request *http.Request) { return } target.Roles.Add(roleId) + updateChannelCacheForSpecUser(target, hub) response.WriteHeader(http.StatusAccepted) } @@ -557,6 +586,7 @@ func HandleHubUserRemoveRole(response http.ResponseWriter, request *http.Request return } target.Roles.Remove(roleId) + updateChannelCacheForSpecUser(target, hub) response.WriteHeader(http.StatusAccepted) } @@ -709,6 +739,7 @@ func HandleRoleSetPermissions(response http.ResponseWriter, request *http.Reques return } + madeChange := false query := request.URL.Query() for key, values := range query { permission, ok := types.PermissionLookup(key) @@ -725,20 +756,35 @@ func HandleRoleSetPermissions(response http.ResponseWriter, request *http.Reques } if convertions.StringToBool(values[0]) { - targetRole.Permissions.Grant(permission) + if !targetRole.Permissions.Has(permission) { + madeChange = true + targetRole.Permissions.Grant(permission) + } } else { - targetRole.Permissions.Revoke(permission) + if targetRole.Permissions.Has(permission) { + madeChange = true + targetRole.Permissions.Revoke(permission) + } } } + + if !madeChange { + response.WriteHeader(http.StatusNoContent) + response.Write([]byte("no changes made")) + return + } + + updateChannelCacheForSpecRole(targetRole, hub) response.WriteHeader(http.StatusAccepted) } func HandleRoleSelfRemove(response http.ResponseWriter, request *http.Request) { - requestor, _, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionUserSelfRoleRemove) + requestor, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionUserSelfRoleRemove) if !ok { return } requestor.Roles.Remove(usedRoleId) + updateChannelCacheForSpecUser(requestor, hub) response.WriteHeader(http.StatusAccepted) } @@ -777,7 +823,7 @@ func HandleChannelCreate(response http.ResponseWriter, request *http.Request) { } hub.Mu.Unlock() - updateChannelCache(newHubChannel, hub) + updateChannelCacheForSpecChannel(newHubChannel, hub) response.WriteHeader(http.StatusAccepted) } diff --git a/packages/httpRequest/user.go b/packages/httpRequest/user.go index 6c31f41..9cb4ddf 100644 --- a/packages/httpRequest/user.go +++ b/packages/httpRequest/user.go @@ -252,3 +252,37 @@ func HandleUserGetUser(response http.ResponseWriter, request *http.Request) { response.WriteHeader(http.StatusAccepted) response.Write(userData) } + +func HandleUserGetUsers(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 + } + + targetIds, err := convertions.StringToUuids(request.URL.Query().Get("targetids")) + if err != nil { + http.Error(response, "invalid targetids", http.StatusBadRequest) + return + } + + users, err := cache.GetUsersByIds(targetIds) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + + userData, err := json.Marshal(users) + if err != nil { + http.Error(response, "json parse error", http.StatusInternalServerError) + return + } + + response.WriteHeader(http.StatusAccepted) + response.Write(userData) +}