diff --git a/go-socket b/go-socket index 32219c6..be5b708 100755 Binary files a/go-socket and b/go-socket differ diff --git a/machine-client/index.html b/machine-client/index.html index 79c1a1d..94868c7 100644 --- a/machine-client/index.html +++ b/machine-client/index.html @@ -12,6 +12,7 @@ +
@@ -41,9 +42,38 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -199,7 +229,7 @@
-
+
sent as header
sent as header
@@ -219,6 +249,245 @@
+ +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
key=bool pairs
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
+
sent as query
+
+
+ + +
+
+
+
+
+
sent as query
+
+
+ + +
+
+
+
+
+
sent as query
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
@@ -242,6 +511,7 @@ let activeForm = null; let currentToken = ''; let currentUserId = ''; + let currentHubId = ''; // method, path, which field ids go where // dest: 'header' | 'body' | 'query' @@ -250,19 +520,48 @@ 'new-token': { method:'POST', path:'/token', title:'POST /token — login / get token', fields:[{id:'nt-username',dest:'body',name:'username'},{id:'nt-password',dest:'body',name:'password'}] }, 'new-connection': { method:'POST', path:'/connection', title:'POST /connection — send connection request', fields:[{id:'nconn-token',dest:'header',name:'token'},{id:'nconn-recipient',dest:'body',name:'recipient'}] }, 'mod-user-profile': { method:'PATCH', path:'/user/profile', title:'PATCH /user/profile — update profile', fields:[{id:'mup-token',dest:'header',name:'token'},{id:'mup-pronouns',dest:'body',name:'pronouns'},{id:'mup-description',dest:'body',name:'description'},{id:'mup-color',dest:'body',name:'color'}] }, - 'mod-connection-elevate': { method:'POST', path:'/connection/elevate', title:'POST /connection/elevate — elevate', fields:[{id:'mce-token',dest:'header',name:'token'},{id:'mce-connectionid',dest:'body',name:'connectionid'}] }, - 'mod-connection-deelevate':{ method:'POST', path:'/connection/deelevate', title:'POST /connection/deelevate — de-elevate', fields:[{id:'mcde-token',dest:'header',name:'token'},{id:'mcde-connectionid',dest:'body',name:'connectionid'}] }, - 'get-user': { method:'GET', path:'/user', title:'GET /user — get user info', fields:[{id:'gu-token',dest:'header',name:'token'},{id:'gu-targetid',dest:'query',name:'targetid'}] }, + 'mod-connection-elevate': { method:'POST', path:'/connection/elevate', title:'POST /connection/elevate — elevate', fields:[{id:'mce-token',dest:'header',name:'token'},{id:'mce-connectionid',dest:'body',name:'connection_id'}] }, + 'mod-connection-deelevate':{ method:'POST', path:'/connection/deelevate', title:'POST /connection/deelevate — de-elevate', fields:[{id:'mcde-token',dest:'header',name:'token'},{id:'mcde-connectionid',dest:'body',name:'connection_id'}] }, + 'get-user': { method:'GET', path:'/user', title:'GET /user — get user info', fields:[{id:'gu-token',dest:'header',name:'token'},{id:'gu-targetid',dest:'query',name:'target_id'}] }, 'get-connections': { method:'GET', path:'/connections', title:'GET /connections — get connections', fields:[{id:'gconn-token',dest:'header',name:'token'}] }, 'get-connections-unread': { method:'GET', path:'/connections/unreadmessages', title:'GET /connections/unreadmessages — unread counts for given connections (returns []uint32 in same order)', fields:[{id:'gcur-token',dest:'header',name:'token'},{id:'gcur-connections',dest:'query',name:'connections'}] }, - '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'}] }, + '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:'connection_id'},{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:'/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-message': { method:'POST', path:'/hub/message', title:'POST /hub/message — send hub channel message', fields:[{id:'hm-token',dest:'header',name:'token'},{id:'hm-hubid',dest:'body',name:'hubid'},{id:'hm-channelid',dest:'header',name:'channelid'},{id:'hm-msgContent',dest:'body',name:'msgContent'},{id:'hm-attachedFile',dest:'body',name:'attachedFile'}] }, - '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'}] }, + '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:'connection_id'}] }, + '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:'connection_id'},{id:'mu-msgContent',dest:'body',name:'msg_content'},{id:'mu-attachedFile',dest:'body',name:'attached_file'}] }, + '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:'hub_name'}] }, + 'hub-message': { method:'POST', path:'/hub/channel/message', title:'POST /hub/channel/message — send hub channel message', fields:[{id:'hm-token',dest:'header',name:'token'},{id:'hm-hubid',dest:'header',name:'hub_id'},{id:'hm-channelid',dest:'header',name:'channel_id'},{id:'hm-msgContent',dest:'body',name:'msg_content'},{id:'hm-attachedFile',dest:'body',name:'attached_file'}] }, + '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:'hub_id'}] }, 'get-hubs': { method:'GET', path:'/hubs', title:'GET /hubs — get own hubs', fields:[{id:'gh-token',dest:'header',name:'token'}] }, + 'get-hub-data': { method:'GET', path:'/hub', title:'GET /hub — get hub data', fields:[{id:'ghd-token',dest:'header',name:'token'},{id:'ghd-hubid',dest:'header',name:'hub_id'}] }, + 'get-hub-channel-data': { method:'GET', path:'/hub/channel', title:'GET /hub/channel — get channel data', fields:[{id:'ghcd-token',dest:'header',name:'token'},{id:'ghcd-hubid',dest:'header',name:'hub_id'},{id:'ghcd-channelid',dest:'query',name:'channel_id'}] }, + 'get-hub-channels': { method:'GET', path:'/hubs/channels', title:'GET /hubs/channels — get hub channels', fields:[{id:'ghc-token',dest:'header',name:'token'},{id:'ghc-hubid',dest:'header',name:'hub_id'}] }, + 'get-hub-users': { method:'GET', path:'/hubs/users', title:'GET /hubs/users — get hub users (excludes self)', fields:[{id:'ghu-token',dest:'header',name:'token'},{id:'ghu-hubid',dest:'header',name:'hub_id'}] }, + 'get-hub-roles': { method:'GET', path:'/hubs/roles', title:'GET /hubs/roles — get hub roles', fields:[{id:'ghr-token',dest:'header',name:'token'},{id:'ghr-hubid',dest:'header',name:'hub_id'}] }, + 'get-users': { method:'GET', path:'/users', title:'GET /users — get multiple users by IDs', fields:[{id:'gus-token',dest:'header',name:'token'},{id:'gus-targetids',dest:'query',name:'target_ids'}] }, + 'hub-set-name': { method:'PATCH', path:'/hub/name', title:'PATCH /hub/name — set hub name', fields:[{id:'hsn-token',dest:'header',name:'token'},{id:'hsn-hubid',dest:'header',name:'hub_id'},{id:'hsn-newname',dest:'body',name:'new_name'}] }, + 'hub-set-color': { method:'PATCH', path:'/hub/color', title:'PATCH /hub/color — set hub color (R,G,B,A)', fields:[{id:'hsc-token',dest:'header',name:'token'},{id:'hsc-hubid',dest:'header',name:'hub_id'},{id:'hsc-newcolor',dest:'body',name:'new_color'}] }, + 'hub-remove': { method:'DELETE', path:'/hub', title:'DELETE /hub — remove hub', fields:[{id:'hr-token',dest:'header',name:'token'},{id:'hr-hubid',dest:'header',name:'hub_id'}] }, + 'hub-toggle-color': { method:'PATCH', path:'/hub/usercolorallowed', title:'PATCH /hub/usercolorallowed — toggle user color allowed', fields:[{id:'htc-token',dest:'header',name:'token'},{id:'htc-hubid',dest:'header',name:'hub_id'}] }, + 'hub-user-remove': { method:'DELETE', path:'/hub/user', title:'DELETE /hub/user — remove user from hub', fields:[{id:'hur-token',dest:'header',name:'token'},{id:'hur-hubid',dest:'header',name:'hub_id'},{id:'hur-targetid',dest:'query',name:'target_id'}] }, + 'hub-user-rename': { method:'PATCH', path:'/hub/user/name', title:'PATCH /hub/user/name — rename hub user', fields:[{id:'hun-token',dest:'header',name:'token'},{id:'hun-hubid',dest:'header',name:'hub_id'},{id:'hun-targetid',dest:'body',name:'target_id'},{id:'hun-newname',dest:'body',name:'new_name'}] }, + 'hub-self-rename': { method:'PATCH', path:'/hub/self/name', title:'PATCH /hub/self/name — rename self in hub', fields:[{id:'hsr-token',dest:'header',name:'token'},{id:'hsr-hubid',dest:'header',name:'hub_id'},{id:'hsr-newname',dest:'body',name:'new_name'}] }, + 'hub-user-mute': { method:'PATCH', path:'/hub/user/mute', title:'PATCH /hub/user/mute — toggle mute user', fields:[{id:'hum-token',dest:'header',name:'token'},{id:'hum-hubid',dest:'header',name:'hub_id'},{id:'hum-targetid',dest:'body',name:'target_id'}] }, + 'hub-user-add-role': { method:'PUT', path:'/hub/user/role', title:'PUT /hub/user/role — add role to user', fields:[{id:'huar-token',dest:'header',name:'token'},{id:'huar-hubid',dest:'header',name:'hub_id'},{id:'huar-targetid',dest:'body',name:'target_id'},{id:'huar-roleid',dest:'body',name:'role_id'}] }, + 'hub-user-remove-role': { method:'DELETE', path:'/hub/user/role', title:'DELETE /hub/user/role — remove role from user', fields:[{id:'hurr-token',dest:'header',name:'token'},{id:'hurr-hubid',dest:'header',name:'hub_id'},{id:'hurr-targetid',dest:'query',name:'target_id'},{id:'hurr-roleid',dest:'query',name:'role_id'}] }, + 'hub-role-create': { method:'PUT', path:'/hub/role', title:'PUT /hub/role — create role', fields:[{id:'hrc-token',dest:'header',name:'token'},{id:'hrc-hubid',dest:'header',name:'hub_id'},{id:'hrc-name',dest:'body',name:'name'}] }, + 'hub-role-remove': { method:'DELETE', path:'/hub/role', title:'DELETE /hub/role — remove role', fields:[{id:'hrr-token',dest:'header',name:'token'},{id:'hrr-hubid',dest:'header',name:'hub_id'},{id:'hrr-roleid',dest:'query',name:'role_id'}] }, + 'hub-role-set-name': { method:'PATCH', path:'/hub/role/name', title:'PATCH /hub/role/name — set role name', fields:[{id:'hrsn-token',dest:'header',name:'token'},{id:'hrsn-hubid',dest:'header',name:'hub_id'},{id:'hrsn-roleid',dest:'body',name:'role_id'},{id:'hrsn-newname',dest:'body',name:'new_name'}] }, + 'hub-role-set-color': { method:'PATCH', path:'/hub/role/color', title:'PATCH /hub/role/color — set role color (R,G,B,A)', fields:[{id:'hrsc-token',dest:'header',name:'token'},{id:'hrsc-hubid',dest:'header',name:'hub_id'},{id:'hrsc-roleid',dest:'body',name:'role_id'},{id:'hrsc-newcolor',dest:'body',name:'new_color'}] }, + 'hub-role-set-perms': { title:'PATCH /hub/role/permissions — set role permissions (roleid in body, perm=bool in query)' }, + 'hub-self-remove-role': { method:'DELETE', path:'/hub/self/role', title:'DELETE /hub/self/role — remove own role', fields:[{id:'hsrr-token',dest:'header',name:'token'},{id:'hsrr-hubid',dest:'header',name:'hub_id'},{id:'hsrr-roleid',dest:'body',name:'role_id'}] }, + 'hub-channel-create': { method:'PUT', path:'/hub/channel', title:'PUT /hub/channel — create channel', fields:[{id:'hcc-token',dest:'header',name:'token'},{id:'hcc-hubid',dest:'header',name:'hub_id'},{id:'hcc-name',dest:'body',name:'name'}] }, + 'hub-channel-remove': { method:'DELETE', path:'/hub/channel', title:'DELETE /hub/channel — remove channel', fields:[{id:'hcr-token',dest:'header',name:'token'},{id:'hcr-hubid',dest:'header',name:'hub_id'},{id:'hcr-channel-id',dest:'query',name:'channel_id'}] }, + 'hub-channel-set-name': { method:'PATCH', path:'/hub/channel/name', title:'PATCH /hub/channel/name — set channel name', fields:[{id:'hcsn-token',dest:'header',name:'token'},{id:'hcsn-hubid',dest:'header',name:'hub_id'},{id:'hcsn-channel-id',dest:'body',name:'channel_id'},{id:'hcsn-newname',dest:'body',name:'new_name'}] }, + 'hub-channel-role-view': { method:'PATCH', path:'/hub/channel/roles/view', title:'PATCH /hub/channel/roles/view — set role can view channel', fields:[{id:'hcrv-token',dest:'header',name:'token'},{id:'hcrv-hubid',dest:'header',name:'hub_id'},{id:'hcrv-channelid',dest:'body',name:'channel_id'},{id:'hcrv-roleid',dest:'body',name:'role_id'},{id:'hcrv-allow',dest:'query',name:'allow'}] }, + 'hub-channel-role-send': { method:'PATCH', path:'/hub/channel/roles/send', title:'PATCH /hub/channel/roles/send — set role can send in channel', fields:[{id:'hcrs-token',dest:'header',name:'token'},{id:'hcrs-hubid',dest:'header',name:'hub_id'},{id:'hcrs-channelid',dest:'body',name:'channel_id'},{id:'hcrs-roleid',dest:'body',name:'role_id'},{id:'hcrs-allow',dest:'query',name:'allow'}] }, + 'hub-channel-role-history':{ method:'PATCH', path:'/hub/channel/roles/history', title:'PATCH /hub/channel/roles/history — set role can read history', fields:[{id:'hcrh-token',dest:'header',name:'token'},{id:'hcrh-hubid',dest:'header',name:'hub_id'},{id:'hcrh-channelid',dest:'body',name:'channel_id'},{id:'hcrh-roleid',dest:'body',name:'role_id'},{id:'hcrh-allow',dest:'query',name:'allow'}] }, + 'hub-channel-set-desc': { method:'PATCH', path:'/hub/channel/description',title:'PATCH /hub/channel/description — set channel description', fields:[{id:'hcsd-token',dest:'header',name:'token'},{id:'hcsd-hubid',dest:'header',name:'hub_id'},{id:'hcsd-channel-id',dest:'body',name:'channel_id'},{id:'hcsd-description',dest:'body',name:'description'}] }, '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)' }, @@ -277,6 +576,13 @@ document.querySelectorAll('input[id$="-token"]').forEach(el => { el.value = currentToken; }); } + function autofillHubIds() { + if (!currentHubId) return; + document.querySelectorAll('input[id$="-hubid"]').forEach(el => { el.value = currentHubId; }); + const hubEl = document.getElementById('current-hub'); + if (hubEl) hubEl.textContent = 'hubId: ' + currentHubId; + } + function showForm(name) { const panel = document.getElementById('form-panel'); const titleEl = document.getElementById('form-title'); @@ -312,6 +618,7 @@ panel.classList.add('open'); autofillTokens(); + autofillHubIds(); } function submit(name) { @@ -379,6 +686,19 @@ wsConnectAndAuth(); } + if (method === 'POST' && path === '/hub' && resp.ok) { + currentHubId = text.trim(); + autofillHubIds(); + } + + if (method === 'PUT' && path === '/hub/join' && resp.ok) { + const el = document.getElementById('hj-hubid'); + if (el && el.value) { + currentHubId = el.value; + autofillHubIds(); + } + } + return { ok: resp.ok, text, status: resp.status }; } catch(e) { log('HTTP ERR', e.message, 'log-err'); @@ -440,9 +760,9 @@ const fileInput = document.getElementById('fu-file'); if (!fileInput.files.length) { log('HTTP ERR', 'no file selected', 'log-err'); return; } const form = new FormData(); - form.append('connectionid', connectionid); + form.append('connection_id', connectionid); form.append('file', fileInput.files[0]); - log('POST /file', 'connectionid=' + connectionid + ' file=' + fileInput.files[0].name, 'log-info'); + log('POST /file', 'connection_id=' + connectionid + ' file=' + fileInput.files[0].name, 'log-info'); try { const resp = await fetch(baseUrl() + '/file', { method: 'POST', headers: { token }, body: form }); const text = await resp.text(); @@ -460,7 +780,7 @@ const token = document.getElementById('fd-token').value; const connectionid = document.getElementById('fd-connectionid').value; const key = document.getElementById('fd-key').value; - const query = new URLSearchParams({ connectionid, key }); + const query = new URLSearchParams({ connection_id: connectionid, key }); log('GET /file', query.toString(), 'log-info'); try { const resp = await fetch(baseUrl() + '/file?' + query.toString(), { method: 'GET', headers: { token } }); @@ -500,7 +820,7 @@ async function submitGetUserAvatar() { const token = document.getElementById('gua-token').value; const userid = document.getElementById('gua-userid').value; - const query = new URLSearchParams({ userid }); + const query = new URLSearchParams({ user_id: userid }); log('GET /user/avatar', query.toString(), 'log-info'); try { const resp = await fetch(baseUrl() + '/user/avatar?' + query.toString(), { method: 'GET', headers: { token } }); @@ -509,10 +829,26 @@ } catch(e) { log('HTTP ERR', e.message, 'log-err'); } } + async function submitHubRoleSetPerms() { + const token = document.getElementById('hrsp-token').value; + const hubid = document.getElementById('hrsp-hubid').value; + const roleid = document.getElementById('hrsp-roleid').value; + const permsRaw = document.getElementById('hrsp-permissions').value; + const body = new URLSearchParams({ role_id: roleid }); + let queryStr = permsRaw; + log('PATCH /hub/role/permissions', 'role_id=' + roleid + ' ' + permsRaw, 'log-info'); + try { + const url = baseUrl() + '/hub/role/permissions' + (queryStr ? '?' + queryStr : ''); + const resp = await fetch(url, { method: 'PATCH', headers: { token, hub_id: hubid }, body }); + const text = await resp.text(); + log('HTTP ' + resp.status, text, resp.ok ? 'log-http' : 'log-err'); + } catch(e) { log('HTTP ERR', e.message, 'log-err'); } + } + async function submitGetUserProfileBg() { const token = document.getElementById('gpb-token').value; const userid = document.getElementById('gpb-userid').value; - const query = new URLSearchParams({ userid }); + const query = new URLSearchParams({ user_id: userid }); log('GET /user/profilebg', query.toString(), 'log-info'); try { const resp = await fetch(baseUrl() + '/user/profilebg?' + query.toString(), { method: 'GET', headers: { token } }); diff --git a/main.go b/main.go index 2fa8da5..057ec06 100644 --- a/main.go +++ b/main.go @@ -59,10 +59,13 @@ func main() { http.HandleFunc("GET /file", withCORS(httpRequest.HandleAttachmentFileDownload)) http.HandleFunc("POST /hub", withCORS(httpRequest.HandleHubCreate)) - http.HandleFunc("POST /hub/message", withCORS(httpRequest.HandleHubMessage)) + http.HandleFunc("GET /hub", withCORS(httpRequest.GetHubData)) + http.HandleFunc("POST /hub/channel/message", withCORS(httpRequest.HandleHubMessage)) + http.HandleFunc("GET /hub/channel", withCORS(httpRequest.GetChannelData)) 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("GET /hubs/roles", withCORS(httpRequest.GetRoles)) http.HandleFunc("PUT /hub/join", withCORS(httpRequest.HandleHubJoin)) http.HandleFunc("PATCH /hub/name", withCORS(httpRequest.HandleHubSetName)) http.HandleFunc("PATCH /hub/color", withCORS(httpRequest.HandleHubSetColor)) @@ -82,8 +85,11 @@ func main() { 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("PATCH /hub/description", withCORS(httpRequest.HandleChannelSetDescription)) + http.HandleFunc("PATCH /hub/channel/name", withCORS(httpRequest.HandleChannelSetName)) + http.HandleFunc("PATCH /hub/channel/description", withCORS(httpRequest.HandleChannelSetDescription)) + 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("POST /connection/message", withCORS(httpRequest.HandleDm)) http.HandleFunc("GET /ws", wsServer.ServeWsConnection) diff --git a/packages/httpRequest/connectionsAndDms.go b/packages/httpRequest/connectionsAndDms.go index c6a8849..9d0a00b 100644 --- a/packages/httpRequest/connectionsAndDms.go +++ b/packages/httpRequest/connectionsAndDms.go @@ -33,21 +33,21 @@ func HandleDm(response http.ResponseWriter, request *http.Request) { return } - conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connectionid"), user) + conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user) if !ok { return } - msgContent := request.FormValue("msgContent") - attachedFile := request.FormValue("attachedFile") + msgContent := request.FormValue("msg_content") + attachedFile := request.FormValue("attached_file") if msgContent == "" && attachedFile == "" { - http.Error(response, "empty msgContent", http.StatusBadRequest) + http.Error(response, "empty msg_content", http.StatusBadRequest) return } if attachedFile != "" && !strings.HasPrefix(attachedFile, string(minio.ConnectionFilePrefix)+conn.Id.String()+"/") { - http.Error(response, "invalid attachedFile", http.StatusBadRequest) + http.Error(response, "invalid attached_file", http.StatusBadRequest) return } @@ -140,7 +140,7 @@ func HandleUserGetConnectionMessages(response http.ResponseWriter, request *http return } - conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connectionid"), user) + conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user) if !ok { return } @@ -268,7 +268,7 @@ func HandleUserDeleteConnection(response http.ResponseWriter, request *http.Requ return } - conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connectionid"), user) + conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user) if !ok { return } @@ -305,7 +305,7 @@ func HandleUserElevateConnection(response http.ResponseWriter, request *http.Req http.Error(response, "invalid token", http.StatusUnauthorized) return } - conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connectionid"), user) + conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user) if !ok { return } @@ -374,7 +374,7 @@ func HandleUserDeElevateConnection(response http.ResponseWriter, request *http.R return } - conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connectionid"), user) + conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user) if !ok { return } diff --git a/packages/httpRequest/files.go b/packages/httpRequest/files.go index e8290d3..4a50a0b 100644 --- a/packages/httpRequest/files.go +++ b/packages/httpRequest/files.go @@ -153,9 +153,9 @@ func HandleGetUserAvatar(response http.ResponseWriter, request *http.Request) { return } - targetId, err := convertions.StringToUuid(request.URL.Query().Get("userid")) + targetId, err := convertions.StringToUuid(request.URL.Query().Get("user_id")) if err != nil { - http.Error(response, "invalid userid", http.StatusBadRequest) + http.Error(response, "invalid user_id", http.StatusBadRequest) return } @@ -274,9 +274,9 @@ func HandleGetUserProfileBg(response http.ResponseWriter, request *http.Request) return } - targetId, err := convertions.StringToUuid(request.URL.Query().Get("userid")) + targetId, err := convertions.StringToUuid(request.URL.Query().Get("user_id")) if err != nil { - http.Error(response, "invalid userid", http.StatusBadRequest) + http.Error(response, "invalid user_id", http.StatusBadRequest) return } diff --git a/packages/httpRequest/get.go b/packages/httpRequest/get.go index e0fe09b..eeb5014 100644 --- a/packages/httpRequest/get.go +++ b/packages/httpRequest/get.go @@ -73,9 +73,9 @@ func getHubUserIfValidWithResponseOnFail(ctx context.Context, response http.Resp return nil, nil, nil, errors.New("invalid token") } - hub, err := getHubByIdStr(ctx, request.Header.Get("hubid")) + hub, err := getHubByIdStr(ctx, request.Header.Get("hub_id")) if err != nil { - http.Error(response, "invalid hubid", http.StatusBadRequest) + http.Error(response, "invalid hub_id", http.StatusBadRequest) return nil, nil, nil, errors.New("no such hub") } @@ -83,8 +83,8 @@ func getHubUserIfValidWithResponseOnFail(ctx context.Context, response http.Resp hubUser, ok := hub.Users[user.Id] hub.Mu.RUnlock() if !ok { - http.Error(response, "invalid hubid", http.StatusUnauthorized) - return nil, nil, nil, errors.New("invalid hubid") + http.Error(response, "invalid hub_id", http.StatusUnauthorized) + return nil, nil, nil, errors.New("invalid hub_id") } return user, hubUser, hub, nil @@ -94,13 +94,16 @@ func getHubChannelIfValidWithResponseOnFail(ctx context.Context, response http.R *types.HubChannel, error) { channelUuid, err := convertions.StringToUuid(channelId) if err != nil { - http.Error(response, "invalid channelid", http.StatusBadRequest) - return nil, errors.New("invalid channelid") + http.Error(response, "invalid channel_id", http.StatusBadRequest) + return nil, errors.New("invalid channel_id") } + + hub.Mu.RLock() channel, ok := hub.Channels[channelUuid] + hub.Mu.RUnlock() if !ok { - http.Error(response, "invalid channelid", http.StatusBadRequest) - return nil, errors.New("invalid channelid") + http.Error(response, "channel not found", http.StatusNotFound) + return nil, errors.New("channel not found") } if !haveHubUserCachedPermissions(types.CachedUserCanView, hubUser, channel) { diff --git a/packages/httpRequest/hubs.go b/packages/httpRequest/hubs.go index a44eafa..9b575fe 100644 --- a/packages/httpRequest/hubs.go +++ b/packages/httpRequest/hubs.go @@ -3,9 +3,7 @@ package httpRequest import ( "context" "encoding/json" - "maps" "net/http" - "slices" "strings" "time" @@ -57,25 +55,24 @@ func hubUserHighestRoleId(u *types.HubUser, h *types.Hub) (id uint8) { func updateChannelCacheForSpecUserAndChannel(u *types.HubUser, c *types.HubChannel) { c.Mu.Lock() defer c.Mu.Unlock() - if u == nil { delete(c.UsersCachedPermissions, c.Id) + return + } + if u.Roles.DoesIntersect(c.RolesCanView) { + c.UsersCachedPermissions[u.OriginalId] |= types.CachedUserCanView } else { - 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 - } + 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 } } @@ -91,17 +88,23 @@ func updateChannelCacheForSpecUser(u *types.HubUser, h *types.Hub) { h.Mu.RLock() defer h.Mu.RUnlock() for _, c := range h.Channels { + if c == nil { + continue + } updateChannelCacheForSpecUserAndChannel(u, c) } } func updateChannelCacheForSpecRole(r *types.HubRole, h *types.Hub) { - h.Mu.Lock() - defer h.Mu.Unlock() + h.Mu.RLock() + var users []*types.HubUser for _, u := range h.Users { - if !u.Roles.ContainsRoleId(r.Id) { - continue + if u.Roles.ContainsRoleId(r.Id) { + users = append(users, u) } + } + h.Mu.RUnlock() + for _, u := range users { updateChannelCacheForSpecUser(u, h) } } @@ -128,7 +131,7 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) { return } - hubName := request.FormValue("hubname") + hubName := request.FormValue("hub_name") if hubName == "" { http.Error(response, "hub name is required", http.StatusBadRequest) return @@ -140,7 +143,7 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) { hub.Id = uuid.New() hub.Creator = user.Id hub.CreatedAt = time.Now() - user.Hubs[user.Id] = hub + user.Hubs[hub.Id] = hub creator := types.NewHubUser() creator.OriginalId = user.Id @@ -148,7 +151,7 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) { hub.Users[creator.OriginalId] = creator rootRole := &types.HubRole{ - Id: uint8(0), + Id: types.HubBoundRolesMax, Permissions: types.PermissionAll(), Name: "root", Color: types.RandomRgba(), @@ -158,7 +161,7 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) { creator.Roles.Add(rootRole.Id) memberRole := &types.HubRole{ - Id: types.HubBoundRolesMax, + Id: uint8(0), Name: "member", Color: types.RandomRgba(), CreatedAt: hub.CreatedAt, @@ -179,9 +182,13 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) { channel.RolesCanReadHistory.Add(rootRole.Id) channel.RolesCanReadHistory.Add(memberRole.Id) channel.UsersCachedPermissions[creator.OriginalId] = types.CachedUserPermissionsAll - hub.Channels[0] = channel + channel.Position = 0 + hub.Channels[channel.Id] = channel cache.SaveHub(hub) + + response.WriteHeader(http.StatusCreated) + response.Write([]byte(hub.Id.String())) } func HandleHubJoin(response http.ResponseWriter, request *http.Request) { @@ -194,12 +201,19 @@ func HandleHubJoin(response http.ResponseWriter, request *http.Request) { http.Error(response, "invalid token", http.StatusUnauthorized) return } - hub, err := getHubByIdStr(ctx, request.Header.Get("hubid")) + hub, err := getHubByIdStr(ctx, request.Header.Get("hub_id")) if err != nil { - http.Error(response, "invalid hubid", http.StatusBadRequest) + http.Error(response, "invalid hub_id", http.StatusBadRequest) return } + _, ok := hub.Users[user.Id] + if ok { + http.Error(response, "hub already joined", http.StatusBadRequest) + return + } + + user.Hubs[hub.Id] = hub hubUser := types.NewHubUser() hubUser.OriginalId = user.Id if hub.JoinRole != nil { @@ -208,6 +222,8 @@ func HandleHubJoin(response http.ResponseWriter, request *http.Request) { hubUser.CreatedAt = time.Now() hub.Users[hubUser.OriginalId] = hubUser updateChannelCacheForSpecUser(hubUser, hub) + + response.WriteHeader(http.StatusCreated) } func HandleHubMessage(response http.ResponseWriter, request *http.Request) { @@ -219,7 +235,7 @@ func HandleHubMessage(response http.ResponseWriter, request *http.Request) { if err != nil { return } - channel, err := getHubChannelIfValidWithResponseOnFail(ctx, response, hub, hubUser, request.Header.Get("channelid")) + channel, err := getHubChannelIfValidWithResponseOnFail(ctx, response, hub, hubUser, request.Header.Get("channel_id")) if err != nil { return } @@ -229,8 +245,8 @@ func HandleHubMessage(response http.ResponseWriter, request *http.Request) { return } - msgContent := request.FormValue("msgContent") - attachedFile := request.FormValue("attachedFile") + msgContent := request.FormValue("msg_content") + attachedFile := request.FormValue("attached_file") if msgContent == "" && attachedFile == "" { http.Error(response, "empty msgContent", http.StatusBadRequest) @@ -266,69 +282,10 @@ func HandleHubMessage(response http.ResponseWriter, request *http.Request) { } wsServer.WsSendMessageCloseIfTimeout(target, msg) } -} - -func HandleGetHubs(response http.ResponseWriter, request *http.Request) { - if !validCheckWithResponseOnFail(response, request, normal) { - return - } - ctx := request.Context() - user, err := getUserByToken(ctx, request.Header.Get("token")) - if err != nil { - http.Error(response, "invalid token", http.StatusBadRequest) - return - } - - user.Mu.RLock() - hubs := slices.Collect(maps.Values(user.Hubs)) - user.Mu.RUnlock() - if len(hubs) == 0 { - response.WriteHeader(http.StatusNoContent) - response.Write([]byte("no hubs found")) - return - } - converted, err := json.Marshal(hubs) - if err != nil { - http.Error(response, "json error", http.StatusInternalServerError) - return - } - response.WriteHeader(http.StatusOK) - response.Write(converted) + response.WriteHeader(http.StatusCreated) } func HandleGetChannels(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 - } - channelMap := make(map[uint8]*types.HubChannel) - hub.Mu.RLock() - for i, channel := range hub.Channels { - if channel == nil { - continue - } - channelMap[uint8(i)] = channel - } - hub.Mu.Unlock() - if len(channelMap) == 0 { - response.WriteHeader(http.StatusNoContent) - return - } - - channels, err := json.Marshal(channelMap) - if err != nil { - http.Error(response, "json error", http.StatusInternalServerError) - return - } - response.WriteHeader(http.StatusOK) - response.Write(channels) -} - -func HandleGetHubUsers(response http.ResponseWriter, request *http.Request) { if !validCheckWithResponseOnFail(response, request, normal) { return } @@ -337,27 +294,129 @@ func HandleGetHubUsers(response http.ResponseWriter, request *http.Request) { if err != nil { return } - users := make([]*types.HubUser, 0) + channels := make([]uuid.UUID, 0) hub.Mu.RLock() - for userId, user := range hub.Users { - if userId == requestor.OriginalId { + for _, channel := range hub.Channels { + if channel == nil || !haveHubUserCachedPermissions(types.CachedUserCanView, requestor, channel) { continue } - users = append(users, user) + channels = append(channels, channel.Id) } - hub.Mu.Unlock() - if len(users) == 0 { + hub.Mu.RUnlock() + if len(channels) == 0 { response.WriteHeader(http.StatusNoContent) return } - channels, err := json.Marshal(users) + marshal, err := json.Marshal(channels) if err != nil { http.Error(response, "json error", http.StatusInternalServerError) return } response.WriteHeader(http.StatusOK) - response.Write(channels) + response.Write(marshal) +} + +func HandleGetHubUsers(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 + } + users := make([]*types.HubUser, 0) + hub.Mu.RLock() + for _, user := range hub.Users { + users = append(users, user) + } + hub.Mu.RUnlock() + if len(users) == 0 { + response.WriteHeader(http.StatusNoContent) + return + } + + marshal, err := json.Marshal(users) + if err != nil { + http.Error(response, "json error", http.StatusInternalServerError) + return + } + response.WriteHeader(http.StatusOK) + response.Write(marshal) +} + +func GetRoles(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 + } + hub.Mu.RLock() + roles := make([]*types.HubRole, 0) + for _, role := range hub.Roles { + if role == nil { + continue + } + roles = append(roles, role) + } + hub.Mu.RUnlock() + marshal, err := json.Marshal(roles) + if err != nil { + http.Error(response, "json error", http.StatusInternalServerError) + return + } + response.WriteHeader(http.StatusOK) + response.Write(marshal) +} + +func GetHubData(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 + } + marshal, err := json.Marshal(hub) + if err != nil { + http.Error(response, "json error", http.StatusInternalServerError) + } + response.WriteHeader(http.StatusOK) + response.Write(marshal) +} +func GetChannelData(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 + } + 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, "no such channel", http.StatusNotFound) + return + } + marshal, err := json.Marshal(channel) + if err != nil { + http.Error(response, "json error", http.StatusInternalServerError) + return + } + response.WriteHeader(http.StatusOK) + response.Write(marshal) } func hubPermissionContext(response http.ResponseWriter, request *http.Request, rt bodyLimit, needed types.Permissions) (*types.HubUser, *types.Hub, context.Context, uint8, bool) { @@ -384,7 +443,7 @@ func HandleHubSetName(response http.ResponseWriter, request *http.Request) { if !ok { return } - newName := request.FormValue("newname") + newName := request.FormValue("new_name") if newName == "" { http.Error(response, "empty name", http.StatusBadRequest) return @@ -398,7 +457,7 @@ func HandleHubSetColor(response http.ResponseWriter, request *http.Request) { if !ok { return } - color, err := convertions.StringToRgba(request.FormValue("newname")) + color, err := convertions.StringToRgba(request.FormValue("new_color")) if err != nil { http.Error(response, "bad color", http.StatusBadRequest) return @@ -430,28 +489,37 @@ func HandleHubUserRemove(response http.ResponseWriter, request *http.Request) { if !ok { return } - targetId, err := convertions.StringToUuid(request.FormValue("targetid")) + targetId, err := convertions.StringToUuid(request.FormValue("target_id")) if err != nil { - http.Error(response, "bad targetid", http.StatusBadRequest) + http.Error(response, "bad target_id", http.StatusBadRequest) return } hub.Mu.RLock() - target, exists := hub.Users[targetId] + hubTarget, exists := hub.Users[targetId] + hub.Mu.RUnlock() if !exists { - hub.Mu.Unlock() http.Error(response, "user not found", http.StatusNotFound) return } - hub.Mu.RUnlock() - if usedRoleId <= hubUserHighestRoleId(target, hub) { + if usedRoleId <= hubUserHighestRoleId(hubTarget, hub) { http.Error(response, "target higher in hierarchy", http.StatusForbidden) return } + + target, err := cache.GetUserById(targetId) + if err != nil { + http.Error(response, "target not found", http.StatusInternalServerError) + return + } + target.Mu.Lock() + delete(target.Hubs, hub.Id) + target.Mu.Unlock() + hub.Mu.Lock() delete(hub.Users, targetId) - updateChannelCacheForSpecUser(target, hub) hub.Mu.Unlock() + updateChannelCacheForSpecUser(hubTarget, hub) response.WriteHeader(http.StatusAccepted) } @@ -460,27 +528,25 @@ func HandleHubRenameUser(response http.ResponseWriter, request *http.Request) { if !ok { return } - newName := request.FormValue("newname") - targetId, err := convertions.StringToUuid(request.FormValue("targetid")) + newName := request.FormValue("new_name") + targetId, err := convertions.StringToUuid(request.FormValue("target_id")) if err != nil { - http.Error(response, "bad targetid", http.StatusBadRequest) + http.Error(response, "bad target_id", http.StatusBadRequest) return } hub.Mu.RLock() target, exists := hub.Users[targetId] + hub.Mu.RUnlock() if !exists { - hub.Mu.Unlock() http.Error(response, "user not found", http.StatusNotFound) return } - hub.Mu.RUnlock() if usedRoleId <= hubUserHighestRoleId(target, hub) { http.Error(response, "target higher in hierarchy", http.StatusForbidden) return } target.Name = newName - response.WriteHeader(http.StatusAccepted) } @@ -489,7 +555,7 @@ func HandleHubRenameSelf(response http.ResponseWriter, request *http.Request) { if !ok { return } - requestor.Name = request.FormValue("newname") + requestor.Name = request.FormValue("new_name") response.WriteHeader(http.StatusAccepted) } @@ -498,25 +564,23 @@ func HandleHubToggleMuteUser(response http.ResponseWriter, request *http.Request if !ok { return } - targetId, err := convertions.StringToUuid(request.FormValue("targetid")) + targetId, err := convertions.StringToUuid(request.FormValue("target_id")) if err != nil { - http.Error(response, "bad targetid", http.StatusBadRequest) + http.Error(response, "bad target_id", http.StatusBadRequest) return } hub.Mu.RLock() target, exists := hub.Users[targetId] + hub.Mu.RUnlock() if !exists { - hub.Mu.Unlock() http.Error(response, "user not found", http.StatusNotFound) return } - hub.Mu.RUnlock() if usedRoleId <= hubUserHighestRoleId(target, hub) { http.Error(response, "target higher in hierarchy", http.StatusForbidden) return } - hub.Mu.RLock() target.IsMuted = !target.IsMuted response.WriteHeader(http.StatusAccepted) } @@ -526,14 +590,14 @@ func HandleHubUserAddRole(response http.ResponseWriter, request *http.Request) { if !ok { return } - targetId, err := convertions.StringToUuid(request.FormValue("targetid")) + targetId, err := convertions.StringToUuid(request.FormValue("target_id")) if err != nil { - http.Error(response, "bad targetid", http.StatusBadRequest) + http.Error(response, "bad target_id", http.StatusBadRequest) return } - roleId, err := convertions.StringToUint8(request.FormValue("roleid")) + roleId, err := convertions.StringToUint8(request.FormValue("role_id")) if err != nil { - http.Error(response, "bad roleid", http.StatusBadRequest) + http.Error(response, "bad role_id", http.StatusBadRequest) return } if roleId > usedRoleId { @@ -563,14 +627,14 @@ func HandleHubUserRemoveRole(response http.ResponseWriter, request *http.Request if !ok { return } - targetId, err := convertions.StringToUuid(request.FormValue("targetid")) + targetId, err := convertions.StringToUuid(request.FormValue("target_id")) if err != nil { - http.Error(response, "bad targetid", http.StatusBadRequest) + http.Error(response, "bad target_id", http.StatusBadRequest) return } - roleId, err := convertions.StringToUint8(request.FormValue("roleid")) + roleId, err := convertions.StringToUint8(request.FormValue("role_id")) if err != nil { - http.Error(response, "bad roleid", http.StatusBadRequest) + http.Error(response, "bad role_id", http.StatusBadRequest) return } if roleId > usedRoleId { @@ -630,9 +694,9 @@ func HandleHubRemoveRole(response http.ResponseWriter, request *http.Request) { if !ok { return } - targetRoleId, err := convertions.StringToUint8(request.FormValue("roleid")) + targetRoleId, err := convertions.StringToUint8(request.FormValue("role_id")) if err != nil { - http.Error(response, "bad roleid", http.StatusBadRequest) + http.Error(response, "bad role_id", http.StatusBadRequest) return } if targetRoleId == 0 { @@ -660,16 +724,16 @@ func HandleRoleSetName(response http.ResponseWriter, request *http.Request) { if !ok { return } - roleId, err := convertions.StringToUint8(request.FormValue("roleid")) + roleId, err := convertions.StringToUint8(request.FormValue("role_id")) if err != nil { - http.Error(response, "bad roleid", http.StatusBadRequest) + http.Error(response, "bad role_id", http.StatusBadRequest) return } if roleId > usedRoleId { http.Error(response, "target role higher in hierarchy", http.StatusForbidden) return } - newName := request.FormValue("newname") + newName := request.FormValue("new_name") if newName == "" { http.Error(response, "name empty", http.StatusBadRequest) return @@ -678,6 +742,7 @@ func HandleRoleSetName(response http.ResponseWriter, request *http.Request) { hub.Mu.Lock() role := hub.Roles[roleId] if role == nil { + hub.Mu.Unlock() http.Error(response, "no such role", http.StatusNotFound) return } @@ -691,16 +756,16 @@ func HandleRoleSetColor(response http.ResponseWriter, request *http.Request) { if !ok { return } - roleId, err := convertions.StringToUint8(request.FormValue("roleid")) + roleId, err := convertions.StringToUint8(request.FormValue("role_id")) if err != nil { - http.Error(response, "bad roleid", http.StatusBadRequest) + http.Error(response, "bad role_id", http.StatusBadRequest) return } if roleId > usedRoleId { http.Error(response, "target role higher in hierarchy", http.StatusForbidden) return } - color, err := convertions.StringToRgba(request.FormValue("newcolor")) + color, err := convertions.StringToRgba(request.FormValue("new_color")) if err != nil { http.Error(response, "invalid newcolor", http.StatusBadRequest) return @@ -709,6 +774,7 @@ func HandleRoleSetColor(response http.ResponseWriter, request *http.Request) { hub.Mu.Lock() role := hub.Roles[roleId] if role == nil { + hub.Mu.Unlock() http.Error(response, "no such role", http.StatusNotFound) return } @@ -722,9 +788,9 @@ func HandleRoleSetPermissions(response http.ResponseWriter, request *http.Reques if !ok { return } - targetRoleId, err := convertions.StringToUint8(request.FormValue("roleid")) + targetRoleId, err := convertions.StringToUint8(request.FormValue("role_id")) if err != nil { - http.Error(response, "bad roleid", http.StatusBadRequest) + http.Error(response, "bad role_id", http.StatusBadRequest) return } if targetRoleId > usedRoleId { @@ -814,13 +880,16 @@ func HandleChannelCreate(response http.ResponseWriter, request *http.Request) { newHubChannel.RolesCanReadHistory.Add(types.HubBoundRolesMax) } hub.Mu.Lock() - for i, channel := range hub.Channels { - if channel != nil { - continue - } - hub.Channels[i] = newHubChannel - break + usedPositions := make(map[uint8]bool, len(hub.Channels)) + for _, ch := range hub.Channels { + usedPositions[ch.Position] = true } + var position uint8 + for usedPositions[position] { + position++ + } + newHubChannel.Position = position + hub.Channels[newHubChannel.Id] = newHubChannel hub.Mu.Unlock() updateChannelCacheForSpecChannel(newHubChannel, hub) @@ -832,14 +901,19 @@ func HandleChannelRemove(response http.ResponseWriter, request *http.Request) { if !ok { return } - channelId, err := convertions.StringToUint8(request.FormValue("id")) + channelId, err := convertions.StringToUuid(request.FormValue("channel_id")) if err != nil { - http.Error(response, "invalid channel id", http.StatusBadRequest) + http.Error(response, "invalid channel_id", http.StatusBadRequest) return } hub.Mu.Lock() - hub.Channels[channelId] = nil + 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() response.WriteHeader(http.StatusAccepted) } @@ -848,26 +922,26 @@ func HandleChannelSetName(response http.ResponseWriter, request *http.Request) { if !ok { return } - newName := request.FormValue("newname") + newName := request.FormValue("new_name") if newName == "" { http.Error(response, "newname empty", http.StatusBadRequest) return } - channelId, err := convertions.StringToUint8(request.FormValue("id")) + channelId, err := convertions.StringToUuid(request.FormValue("channel_id")) if err != nil { - http.Error(response, "invalid channel id", http.StatusBadRequest) + http.Error(response, "invalid channel_id", http.StatusBadRequest) return } hub.Mu.Lock() - channel := hub.Channels[channelId] - if channel == nil { + channel, ok := hub.Channels[channelId] + if !ok { + hub.Mu.Unlock() http.Error(response, "no such channel", http.StatusNotFound) return } channel.Name = newName hub.Mu.Unlock() - response.WriteHeader(http.StatusAccepted) } @@ -877,20 +951,85 @@ func HandleChannelSetDescription(response http.ResponseWriter, request *http.Req return } newDescription := request.FormValue("description") - channelId, err := convertions.StringToUint8(request.FormValue("id")) + channelId, err := convertions.StringToUuid(request.FormValue("channel_id")) if err != nil { - http.Error(response, "invalid channel id", http.StatusBadRequest) + http.Error(response, "invalid channel_id", http.StatusBadRequest) return } hub.Mu.Lock() - channel := hub.Channels[channelId] - if channel == nil { + channel, ok := hub.Channels[channelId] + if !ok { + hub.Mu.Unlock() http.Error(response, "no such channel", http.StatusNotFound) return } channel.Description = newDescription hub.Mu.Unlock() - 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) + if !ok { + return + } + channelId, err := convertions.StringToUuid(request.FormValue("channel_id")) + if err != nil { + http.Error(response, "invalid channel_id", http.StatusBadRequest) + return + } + roleId, err := convertions.StringToUint8(request.FormValue("role_id")) + if err != nil { + http.Error(response, "invalid role_id", http.StatusBadRequest) + return + } + if roleId > usedRoleId { + http.Error(response, "target role higher in hierarchy", http.StatusForbidden) + return + } + allow := convertions.StringToBool(request.URL.Query().Get("allow")) + + hub.Mu.Lock() + channel, ok := hub.Channels[channelId] + if !ok { + hub.Mu.Unlock() + http.Error(response, "channel not found", http.StatusNotFound) + return + } + modify(channel, roleId, allow) + hub.Mu.Unlock() + + updateChannelCacheForSpecChannel(channel, hub) + 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) { + if allow { + c.RolesCanView.Add(roleId) + } else { + c.RolesCanView.Remove(roleId) + } + }) +} + +func HandleChannelSetPermittedSendRole(response http.ResponseWriter, request *http.Request) { + handleChannelRolePermission(response, request, types.PermissionSetChannelPermittedSendMessageRoles, func(c *types.HubChannel, roleId uint8, allow bool) { + if allow { + c.RolesCanMessage.Add(roleId) + } else { + c.RolesCanMessage.Remove(roleId) + } + }) +} + +func HandleChannelSetPermittedHistoryRole(response http.ResponseWriter, request *http.Request) { + handleChannelRolePermission(response, request, types.PermissionSetChannelPermittedReadHistoryRoles, func(c *types.HubChannel, roleId uint8, allow bool) { + if allow { + c.RolesCanReadHistory.Add(roleId) + } else { + c.RolesCanReadHistory.Remove(roleId) + } + }) +} diff --git a/packages/httpRequest/user.go b/packages/httpRequest/user.go index 9cb4ddf..8515efe 100644 --- a/packages/httpRequest/user.go +++ b/packages/httpRequest/user.go @@ -232,9 +232,9 @@ func HandleUserGetUser(response http.ResponseWriter, request *http.Request) { return } - targetId, err := convertions.StringToUuid(request.URL.Query().Get("targetid")) + targetId, err := convertions.StringToUuid(request.URL.Query().Get("target_id")) if err != nil { - http.Error(response, "invalid userid", http.StatusBadRequest) + http.Error(response, "invalid target_id", http.StatusBadRequest) return } target, err := getUserById(ctx, targetId) @@ -265,7 +265,7 @@ func HandleUserGetUsers(response http.ResponseWriter, request *http.Request) { return } - targetIds, err := convertions.StringToUuids(request.URL.Query().Get("targetids")) + targetIds, err := convertions.StringToUuids(request.URL.Query().Get("target_ids")) if err != nil { http.Error(response, "invalid targetids", http.StatusBadRequest) return @@ -286,3 +286,31 @@ func HandleUserGetUsers(response http.ResponseWriter, request *http.Request) { response.WriteHeader(http.StatusAccepted) response.Write(userData) } + +func HandleGetHubs(response http.ResponseWriter, request *http.Request) { + if !validCheckWithResponseOnFail(response, request, normal) { + return + } + ctx := request.Context() + user, err := getUserByToken(ctx, request.Header.Get("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusBadRequest) + return + } + + user.Mu.RLock() + hubs := slices.Collect(maps.Keys(user.Hubs)) + user.Mu.RUnlock() + if len(hubs) == 0 { + response.WriteHeader(http.StatusNoContent) + response.Write([]byte("no hubs found")) + return + } + converted, err := json.Marshal(hubs) + if err != nil { + http.Error(response, "json error", http.StatusInternalServerError) + return + } + response.WriteHeader(http.StatusOK) + response.Write(converted) +} diff --git a/packages/types/types.go b/packages/types/types.go index 18d3fa3..f03e29e 100644 --- a/packages/types/types.go +++ b/packages/types/types.go @@ -264,24 +264,25 @@ func (p *CachedUserPermissions) ClearCanMessage() { *p &^= CachedUserCanMessage func (p CachedUserPermissions) CanMessage() bool { return p&CachedUserCanMessage != 0 } type Hub struct { - Mu sync.RWMutex `json:"-"` - CreatedAt time.Time `json:"createdAt"` - Roles [256]*HubRole `json:"-"` - Users map[uuid.UUID]*HubUser `json:"-"` - Channels [256]*HubChannel `json:"-"` - Name string `json:"name"` - IconUrl string `json:"iconUrl"` - BgUrl string `json:"backgroundUrl"` - JoinRole *HubRole `json:"joinRole"` - Id uuid.UUID `json:"id"` - Creator uuid.UUID `json:"creator"` - Color Rgba `json:"color"` - UserColorAllowed bool `json:"userColorAllowed"` + Mu sync.RWMutex `json:"-"` + CreatedAt time.Time `json:"createdAt"` + Roles [256]*HubRole `json:"-"` + Users map[uuid.UUID]*HubUser `json:"-"` + Channels map[uuid.UUID]*HubChannel `json:"-"` + Name string `json:"name"` + IconUrl string `json:"iconUrl"` + BgUrl string `json:"backgroundUrl"` + JoinRole *HubRole `json:"joinRole"` + Id uuid.UUID `json:"id"` + Creator uuid.UUID `json:"creator"` + Color Rgba `json:"color"` + UserColorAllowed bool `json:"userColorAllowed"` } func NewHub() *Hub { return &Hub{ - Users: make(map[uuid.UUID]*HubUser), + Users: make(map[uuid.UUID]*HubUser), + Channels: map[uuid.UUID]*HubChannel{}, } } @@ -305,9 +306,9 @@ func (h *HubRole) HasPermission(r Permissions) bool { } type HubUser struct { - Mu sync.RWMutex `json:"mu"` + Mu sync.RWMutex `json:"-"` CreatedAt time.Time `json:"createdAt"` - Roles HubBoundRoles `json:"-"` + Roles HubBoundRoles `json:"roles"` Name string `json:"name"` // Name empty = original name OriginalId uuid.UUID `json:"originalId"` IsMuted bool `json:"isMuted"` @@ -330,6 +331,7 @@ type HubChannel struct { UsersCachedPermissions map[uuid.UUID]CachedUserPermissions `json:"-"` NextBuffIdx uint32 `json:"-"` Id uuid.UUID `json:"id"` + Position uint8 `json:"position"` HaveOverflowed bool `json:"-"` }