fix hub bugs, add channel role permission endpoints

Covers: snake_case param renames, mutex RLock/Unlock mismatches, user.Hubs keyed by hub.Id, hub color using new_name, DELETE params sent as query, delete(target.Hubs, hub.Id), root/member role Id swap, and the three new PATCH
  /hub/channel/roles/* handlers.
This commit is contained in:
2026-05-03 15:51:57 +02:00
parent cb003d235f
commit c0d4483154
9 changed files with 732 additions and 218 deletions
BIN
View File
Binary file not shown.
+352 -16
View File
@@ -12,6 +12,7 @@
<label>Base URL: </label> <label>Base URL: </label>
<input id="baseUrl" value="http://localhost:8080"> <input id="baseUrl" value="http://localhost:8080">
<span id="current-user" style="margin-left:14px;color:#666;font-size:0.85em"></span> <span id="current-user" style="margin-left:14px;color:#666;font-size:0.85em"></span>
<span id="current-hub" style="margin-left:14px;color:#666;font-size:0.85em"></span>
</div> </div>
<div id="log-header"> <div id="log-header">
@@ -41,9 +42,38 @@
<button data-form="del-connection" class="warn" onclick="showForm('del-connection')">DELETE /connection</button> <button data-form="del-connection" class="warn" onclick="showForm('del-connection')">DELETE /connection</button>
<button data-form="msg-user" onclick="showForm('msg-user')">POST /connection/message</button> <button data-form="msg-user" onclick="showForm('msg-user')">POST /connection/message</button>
<button data-form="hub-create" onclick="showForm('hub-create')">POST /hub</button> <button data-form="hub-create" onclick="showForm('hub-create')">POST /hub</button>
<button data-form="hub-message" onclick="showForm('hub-message')">POST /hub/message</button> <button data-form="hub-message" onclick="showForm('hub-message')">POST /hub/channel/message</button>
<button data-form="hub-join" onclick="showForm('hub-join')">PUT /hub/join</button> <button data-form="hub-join" onclick="showForm('hub-join')">PUT /hub/join</button>
<button data-form="get-hubs" onclick="showForm('get-hubs')">GET /hubs</button> <button data-form="get-hubs" onclick="showForm('get-hubs')">GET /hubs</button>
<button data-form="get-hub-data" onclick="showForm('get-hub-data')">GET /hub</button>
<button data-form="get-hub-channel-data" onclick="showForm('get-hub-channel-data')">GET /hub/channel</button>
<button data-form="get-hub-channels" onclick="showForm('get-hub-channels')">GET /hubs/channels</button>
<button data-form="get-hub-users" onclick="showForm('get-hub-users')">GET /hubs/users</button>
<button data-form="get-hub-roles" onclick="showForm('get-hub-roles')">GET /hubs/roles</button>
<button data-form="get-users" onclick="showForm('get-users')">GET /users</button>
<button data-form="hub-set-name" onclick="showForm('hub-set-name')">PATCH /hub/name</button>
<button data-form="hub-set-color" onclick="showForm('hub-set-color')">PATCH /hub/color</button>
<button data-form="hub-toggle-color" onclick="showForm('hub-toggle-color')">PATCH /hub/usercolorallowed</button>
<button data-form="hub-user-rename" onclick="showForm('hub-user-rename')">PATCH /hub/user/name</button>
<button data-form="hub-self-rename" onclick="showForm('hub-self-rename')">PATCH /hub/self/name</button>
<button data-form="hub-user-mute" onclick="showForm('hub-user-mute')">PATCH /hub/user/mute</button>
<button data-form="hub-role-set-name" onclick="showForm('hub-role-set-name')">PATCH /hub/role/name</button>
<button data-form="hub-role-set-color" onclick="showForm('hub-role-set-color')">PATCH /hub/role/color</button>
<button data-form="hub-role-set-perms" onclick="showForm('hub-role-set-perms')">PATCH /hub/role/permissions</button>
<button data-form="hub-channel-set-name" onclick="showForm('hub-channel-set-name')">PATCH /hub/channel/name</button>
<button data-form="hub-channel-set-desc" onclick="showForm('hub-channel-set-desc')">PATCH /hub/channel/description</button>
<button data-form="hub-channel-role-view" onclick="showForm('hub-channel-role-view')">PATCH /hub/channel/roles/view</button>
<button data-form="hub-channel-role-send" onclick="showForm('hub-channel-role-send')">PATCH /hub/channel/roles/send</button>
<button data-form="hub-channel-role-history" onclick="showForm('hub-channel-role-history')">PATCH /hub/channel/roles/history</button>
<button data-form="hub-user-add-role" onclick="showForm('hub-user-add-role')">PUT /hub/user/role</button>
<button data-form="hub-role-create" onclick="showForm('hub-role-create')">PUT /hub/role</button>
<button data-form="hub-channel-create" onclick="showForm('hub-channel-create')">PUT /hub/channel</button>
<button data-form="hub-remove" class="warn" onclick="showForm('hub-remove')">DELETE /hub</button>
<button data-form="hub-user-remove" class="warn" onclick="showForm('hub-user-remove')">DELETE /hub/user</button>
<button data-form="hub-user-remove-role" class="warn" onclick="showForm('hub-user-remove-role')">DELETE /hub/user/role</button>
<button data-form="hub-role-remove" class="warn" onclick="showForm('hub-role-remove')">DELETE /hub/role</button>
<button data-form="hub-self-remove-role" class="warn" onclick="showForm('hub-self-remove-role')">DELETE /hub/self/role</button>
<button data-form="hub-channel-remove" class="warn" onclick="showForm('hub-channel-remove')">DELETE /hub/channel</button>
<button data-form="websocket" onclick="showForm('websocket')">WS /ws</button> <button data-form="websocket" onclick="showForm('websocket')">WS /ws</button>
</div> </div>
@@ -199,7 +229,7 @@
<!-- POST /hub/message --> <!-- POST /hub/message -->
<div class="form-content" id="fc-hub-message"> <div class="form-content" id="fc-hub-message">
<div class="field"><label>token</label><input id="hm-token" placeholder=""></div> <div class="field"><label>token</label><input id="hm-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hm-hubid" placeholder="UUID"></div> <div class="field"><label>hubid</label><input id="hm-hubid" placeholder="UUID"><span class="hint">sent as header</span></div>
<div class="field"><label>channelid</label><input id="hm-channelid" placeholder="UUID"><span class="hint">sent as header</span></div> <div class="field"><label>channelid</label><input id="hm-channelid" placeholder="UUID"><span class="hint">sent as header</span></div>
<div class="field"><label>msgContent</label><input id="hm-msgContent" placeholder="message text (optional if file set)"></div> <div class="field"><label>msgContent</label><input id="hm-msgContent" placeholder="message text (optional if file set)"></div>
<div class="field"><label>attachedFile</label><input id="hm-attachedFile" placeholder="key from POST /file (optional)"></div> <div class="field"><label>attachedFile</label><input id="hm-attachedFile" placeholder="key from POST /file (optional)"></div>
@@ -219,6 +249,245 @@
<div class="form-actions"><button class="send" onclick="submit('get-hubs')">Send</button></div> <div class="form-actions"><button class="send" onclick="submit('get-hubs')">Send</button></div>
</div> </div>
<!-- GET /users -->
<div class="form-content" id="fc-get-users">
<div class="field"><label>token</label><input id="gus-token" placeholder=""></div>
<div class="field"><label>targetids</label><input id="gus-targetids" placeholder="UUID,UUID,..."></div>
<div class="form-actions"><button class="send" onclick="submit('get-users')">Send</button></div>
</div>
<!-- GET /hubs/channels -->
<div class="form-content" id="fc-get-hub-channels">
<div class="field"><label>token</label><input id="ghc-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="ghc-hubid" placeholder="UUID"></div>
<div class="form-actions"><button class="send" onclick="submit('get-hub-channels')">Send</button></div>
</div>
<!-- GET /hubs/users -->
<div class="form-content" id="fc-get-hub-users">
<div class="field"><label>token</label><input id="ghu-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="ghu-hubid" placeholder="UUID"></div>
<div class="form-actions"><button class="send" onclick="submit('get-hub-users')">Send</button></div>
</div>
<!-- PATCH /hub/name -->
<div class="form-content" id="fc-hub-set-name">
<div class="field"><label>token</label><input id="hsn-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hsn-hubid" placeholder="UUID"></div>
<div class="field"><label>newname</label><input id="hsn-newname" placeholder=""></div>
<div class="form-actions"><button class="send" onclick="submit('hub-set-name')">Send</button></div>
</div>
<!-- PATCH /hub/color -->
<div class="form-content" id="fc-hub-set-color">
<div class="field"><label>token</label><input id="hsc-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hsc-hubid" placeholder="UUID"></div>
<div class="field"><label>new_color (R,G,B,A)</label><input id="hsc-newcolor" placeholder="255,100,50,255"></div>
<div class="form-actions"><button class="send" onclick="submit('hub-set-color')">Send</button></div>
</div>
<!-- DELETE /hub -->
<div class="form-content" id="fc-hub-remove">
<div class="field"><label>token</label><input id="hr-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hr-hubid" placeholder="UUID"></div>
<div class="form-actions"><button class="send" onclick="submit('hub-remove')">Send</button></div>
</div>
<!-- PATCH /hub/usercolorallowed -->
<div class="form-content" id="fc-hub-toggle-color">
<div class="field"><label>token</label><input id="htc-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="htc-hubid" placeholder="UUID"></div>
<div class="form-actions"><button class="send" onclick="submit('hub-toggle-color')">Send</button></div>
</div>
<!-- DELETE /hub/user -->
<div class="form-content" id="fc-hub-user-remove">
<div class="field"><label>token</label><input id="hur-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hur-hubid" placeholder="UUID"></div>
<div class="field"><label>targetid</label><input id="hur-targetid" placeholder="UUID"></div>
<div class="form-actions"><button class="send" onclick="submit('hub-user-remove')">Send</button></div>
</div>
<!-- PATCH /hub/user/name -->
<div class="form-content" id="fc-hub-user-rename">
<div class="field"><label>token</label><input id="hun-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hun-hubid" placeholder="UUID"></div>
<div class="field"><label>targetid</label><input id="hun-targetid" placeholder="UUID"></div>
<div class="field"><label>newname</label><input id="hun-newname" placeholder=""></div>
<div class="form-actions"><button class="send" onclick="submit('hub-user-rename')">Send</button></div>
</div>
<!-- PATCH /hub/self/name -->
<div class="form-content" id="fc-hub-self-rename">
<div class="field"><label>token</label><input id="hsr-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hsr-hubid" placeholder="UUID"></div>
<div class="field"><label>newname</label><input id="hsr-newname" placeholder=""></div>
<div class="form-actions"><button class="send" onclick="submit('hub-self-rename')">Send</button></div>
</div>
<!-- PATCH /hub/user/mute -->
<div class="form-content" id="fc-hub-user-mute">
<div class="field"><label>token</label><input id="hum-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hum-hubid" placeholder="UUID"></div>
<div class="field"><label>targetid</label><input id="hum-targetid" placeholder="UUID"></div>
<div class="form-actions"><button class="send" onclick="submit('hub-user-mute')">Send</button></div>
</div>
<!-- PUT /hub/user/role -->
<div class="form-content" id="fc-hub-user-add-role">
<div class="field"><label>token</label><input id="huar-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="huar-hubid" placeholder="UUID"></div>
<div class="field"><label>targetid</label><input id="huar-targetid" placeholder="UUID"></div>
<div class="field"><label>roleid</label><input id="huar-roleid" placeholder="uint8"></div>
<div class="form-actions"><button class="send" onclick="submit('hub-user-add-role')">Send</button></div>
</div>
<!-- DELETE /hub/user/role -->
<div class="form-content" id="fc-hub-user-remove-role">
<div class="field"><label>token</label><input id="hurr-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hurr-hubid" placeholder="UUID"></div>
<div class="field"><label>targetid</label><input id="hurr-targetid" placeholder="UUID"></div>
<div class="field"><label>roleid</label><input id="hurr-roleid" placeholder="uint8"></div>
<div class="form-actions"><button class="send" onclick="submit('hub-user-remove-role')">Send</button></div>
</div>
<!-- PUT /hub/role -->
<div class="form-content" id="fc-hub-role-create">
<div class="field"><label>token</label><input id="hrc-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hrc-hubid" placeholder="UUID"></div>
<div class="field"><label>name</label><input id="hrc-name" placeholder=""></div>
<div class="form-actions"><button class="send" onclick="submit('hub-role-create')">Send</button></div>
</div>
<!-- DELETE /hub/role -->
<div class="form-content" id="fc-hub-role-remove">
<div class="field"><label>token</label><input id="hrr-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hrr-hubid" placeholder="UUID"></div>
<div class="field"><label>roleid</label><input id="hrr-roleid" placeholder="uint8"></div>
<div class="form-actions"><button class="send" onclick="submit('hub-role-remove')">Send</button></div>
</div>
<!-- PATCH /hub/role/name -->
<div class="form-content" id="fc-hub-role-set-name">
<div class="field"><label>token</label><input id="hrsn-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hrsn-hubid" placeholder="UUID"></div>
<div class="field"><label>roleid</label><input id="hrsn-roleid" placeholder="uint8"></div>
<div class="field"><label>newname</label><input id="hrsn-newname" placeholder=""></div>
<div class="form-actions"><button class="send" onclick="submit('hub-role-set-name')">Send</button></div>
</div>
<!-- PATCH /hub/role/color -->
<div class="form-content" id="fc-hub-role-set-color">
<div class="field"><label>token</label><input id="hrsc-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hrsc-hubid" placeholder="UUID"></div>
<div class="field"><label>roleid</label><input id="hrsc-roleid" placeholder="uint8"></div>
<div class="field"><label>newcolor (R,G,B,A)</label><input id="hrsc-newcolor" placeholder="255,100,50,255"></div>
<div class="form-actions"><button class="send" onclick="submit('hub-role-set-color')">Send</button></div>
</div>
<!-- PATCH /hub/role/permissions -->
<div class="form-content" id="fc-hub-role-set-perms">
<div class="field"><label>token</label><input id="hrsp-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hrsp-hubid" placeholder="UUID"></div>
<div class="field"><label>roleid</label><input id="hrsp-roleid" placeholder="uint8"></div>
<div class="field"><label>permissions</label><input id="hrsp-permissions" placeholder="set_hub_name=true&amp;remove_hub=false"><span class="hint">key=bool pairs</span></div>
<div class="form-actions"><button class="send" onclick="submitHubRoleSetPerms()">Send</button></div>
</div>
<!-- DELETE /hub/self/role -->
<div class="form-content" id="fc-hub-self-remove-role">
<div class="field"><label>token</label><input id="hsrr-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hsrr-hubid" placeholder="UUID"></div>
<div class="field"><label>roleid</label><input id="hsrr-roleid" placeholder="uint8"></div>
<div class="form-actions"><button class="send" onclick="submit('hub-self-remove-role')">Send</button></div>
</div>
<!-- PUT /hub/channel -->
<div class="form-content" id="fc-hub-channel-create">
<div class="field"><label>token</label><input id="hcc-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hcc-hubid" placeholder="UUID"></div>
<div class="field"><label>name</label><input id="hcc-name" placeholder=""></div>
<div class="form-actions"><button class="send" onclick="submit('hub-channel-create')">Send</button></div>
</div>
<!-- DELETE /hub/channel -->
<div class="form-content" id="fc-hub-channel-remove">
<div class="field"><label>token</label><input id="hcr-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hcr-hubid" placeholder="UUID"></div>
<div class="field"><label>channel_id</label><input id="hcr-channel-id" placeholder="UUID"></div>
<div class="form-actions"><button class="send" onclick="submit('hub-channel-remove')">Send</button></div>
</div>
<!-- PATCH /hub/channel/name -->
<div class="form-content" id="fc-hub-channel-set-name">
<div class="field"><label>token</label><input id="hcsn-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hcsn-hubid" placeholder="UUID"></div>
<div class="field"><label>channel_id</label><input id="hcsn-channel-id" placeholder="UUID"></div>
<div class="field"><label>new_name</label><input id="hcsn-newname" placeholder=""></div>
<div class="form-actions"><button class="send" onclick="submit('hub-channel-set-name')">Send</button></div>
</div>
<!-- PATCH /hub/channel/description -->
<div class="form-content" id="fc-hub-channel-set-desc">
<div class="field"><label>token</label><input id="hcsd-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hcsd-hubid" placeholder="UUID"></div>
<div class="field"><label>channel_id</label><input id="hcsd-channel-id" placeholder="UUID"></div>
<div class="field"><label>description</label><input id="hcsd-description" placeholder=""></div>
<div class="form-actions"><button class="send" onclick="submit('hub-channel-set-desc')">Send</button></div>
</div>
<!-- PATCH /hub/channel/roles/view -->
<div class="form-content" id="fc-hub-channel-role-view">
<div class="field"><label>token</label><input id="hcrv-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hcrv-hubid" placeholder="UUID"></div>
<div class="field"><label>channel_id</label><input id="hcrv-channelid" placeholder="UUID"></div>
<div class="field"><label>role_id</label><input id="hcrv-roleid" placeholder="uint8"></div>
<div class="field"><label>allow</label><input id="hcrv-allow" placeholder="true / false"><span class="hint">sent as query</span></div>
<div class="form-actions"><button class="send" onclick="submit('hub-channel-role-view')">Send</button></div>
</div>
<!-- PATCH /hub/channel/roles/send -->
<div class="form-content" id="fc-hub-channel-role-send">
<div class="field"><label>token</label><input id="hcrs-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hcrs-hubid" placeholder="UUID"></div>
<div class="field"><label>channel_id</label><input id="hcrs-channelid" placeholder="UUID"></div>
<div class="field"><label>role_id</label><input id="hcrs-roleid" placeholder="uint8"></div>
<div class="field"><label>allow</label><input id="hcrs-allow" placeholder="true / false"><span class="hint">sent as query</span></div>
<div class="form-actions"><button class="send" onclick="submit('hub-channel-role-send')">Send</button></div>
</div>
<!-- PATCH /hub/channel/roles/history -->
<div class="form-content" id="fc-hub-channel-role-history">
<div class="field"><label>token</label><input id="hcrh-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="hcrh-hubid" placeholder="UUID"></div>
<div class="field"><label>channel_id</label><input id="hcrh-channelid" placeholder="UUID"></div>
<div class="field"><label>role_id</label><input id="hcrh-roleid" placeholder="uint8"></div>
<div class="field"><label>allow</label><input id="hcrh-allow" placeholder="true / false"><span class="hint">sent as query</span></div>
<div class="form-actions"><button class="send" onclick="submit('hub-channel-role-history')">Send</button></div>
</div>
<!-- GET /hub -->
<div class="form-content" id="fc-get-hub-data">
<div class="field"><label>token</label><input id="ghd-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="ghd-hubid" placeholder="UUID"></div>
<div class="form-actions"><button class="send" onclick="submit('get-hub-data')">Send</button></div>
</div>
<!-- GET /hub/channel -->
<div class="form-content" id="fc-get-hub-channel-data">
<div class="field"><label>token</label><input id="ghcd-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="ghcd-hubid" placeholder="UUID"></div>
<div class="field"><label>channel_id</label><input id="ghcd-channelid" placeholder="UUID"></div>
<div class="form-actions"><button class="send" onclick="submit('get-hub-channel-data')">Send</button></div>
</div>
<!-- GET /hubs/roles -->
<div class="form-content" id="fc-get-hub-roles">
<div class="field"><label>token</label><input id="ghr-token" placeholder=""></div>
<div class="field"><label>hubid</label><input id="ghr-hubid" placeholder="UUID"></div>
<div class="form-actions"><button class="send" onclick="submit('get-hub-roles')">Send</button></div>
</div>
<!-- WS /ws --> <!-- WS /ws -->
<div class="form-content" id="fc-websocket"> <div class="form-content" id="fc-websocket">
<div class="form-actions" style="margin-bottom:10px"> <div class="form-actions" style="margin-bottom:10px">
@@ -242,6 +511,7 @@
let activeForm = null; let activeForm = null;
let currentToken = ''; let currentToken = '';
let currentUserId = ''; let currentUserId = '';
let currentHubId = '';
// method, path, which field ids go where // method, path, which field ids go where
// dest: 'header' | 'body' | 'query' // 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-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'}] }, '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-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-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:'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:'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:'targetid'}] }, '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': { 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-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-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'}] }, '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:'connectionid'},{id:'mu-msgContent',dest:'body',name:'msgContent'},{id:'mu-attachedFile',dest:'body',name:'attachedFile'}] }, 'msg-user': { method:'POST', path:'/connection/message', title:'POST /connection/message — send direct message', fields:[{id:'mu-token',dest:'header',name:'token'},{id:'mu-connectionid',dest:'body',name:'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:'hubname'}] }, '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/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-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:'hubid'}] }, '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-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-avatar': { title:'PATCH /user/avatar — set avatar image' },
'mod-user-profilebg': { title:'PATCH /user/profilebg — set profile background' }, 'mod-user-profilebg': { title:'PATCH /user/profilebg — set profile background' },
'file-upload': { title:'POST /file — upload file (multipart)' }, 'file-upload': { title:'POST /file — upload file (multipart)' },
@@ -277,6 +576,13 @@
document.querySelectorAll('input[id$="-token"]').forEach(el => { el.value = currentToken; }); 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) { function showForm(name) {
const panel = document.getElementById('form-panel'); const panel = document.getElementById('form-panel');
const titleEl = document.getElementById('form-title'); const titleEl = document.getElementById('form-title');
@@ -312,6 +618,7 @@
panel.classList.add('open'); panel.classList.add('open');
autofillTokens(); autofillTokens();
autofillHubIds();
} }
function submit(name) { function submit(name) {
@@ -379,6 +686,19 @@
wsConnectAndAuth(); 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 }; return { ok: resp.ok, text, status: resp.status };
} catch(e) { } catch(e) {
log('HTTP ERR', e.message, 'log-err'); log('HTTP ERR', e.message, 'log-err');
@@ -440,9 +760,9 @@
const fileInput = document.getElementById('fu-file'); const fileInput = document.getElementById('fu-file');
if (!fileInput.files.length) { log('HTTP ERR', 'no file selected', 'log-err'); return; } if (!fileInput.files.length) { log('HTTP ERR', 'no file selected', 'log-err'); return; }
const form = new FormData(); const form = new FormData();
form.append('connectionid', connectionid); form.append('connection_id', connectionid);
form.append('file', fileInput.files[0]); 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 { try {
const resp = await fetch(baseUrl() + '/file', { method: 'POST', headers: { token }, body: form }); const resp = await fetch(baseUrl() + '/file', { method: 'POST', headers: { token }, body: form });
const text = await resp.text(); const text = await resp.text();
@@ -460,7 +780,7 @@
const token = document.getElementById('fd-token').value; const token = document.getElementById('fd-token').value;
const connectionid = document.getElementById('fd-connectionid').value; const connectionid = document.getElementById('fd-connectionid').value;
const key = document.getElementById('fd-key').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'); log('GET /file', query.toString(), 'log-info');
try { try {
const resp = await fetch(baseUrl() + '/file?' + query.toString(), { method: 'GET', headers: { token } }); const resp = await fetch(baseUrl() + '/file?' + query.toString(), { method: 'GET', headers: { token } });
@@ -500,7 +820,7 @@
async function submitGetUserAvatar() { async function submitGetUserAvatar() {
const token = document.getElementById('gua-token').value; const token = document.getElementById('gua-token').value;
const userid = document.getElementById('gua-userid').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'); log('GET /user/avatar', query.toString(), 'log-info');
try { try {
const resp = await fetch(baseUrl() + '/user/avatar?' + query.toString(), { method: 'GET', headers: { token } }); 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'); } } 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() { async function submitGetUserProfileBg() {
const token = document.getElementById('gpb-token').value; const token = document.getElementById('gpb-token').value;
const userid = document.getElementById('gpb-userid').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'); log('GET /user/profilebg', query.toString(), 'log-info');
try { try {
const resp = await fetch(baseUrl() + '/user/profilebg?' + query.toString(), { method: 'GET', headers: { token } }); const resp = await fetch(baseUrl() + '/user/profilebg?' + query.toString(), { method: 'GET', headers: { token } });
+9 -3
View File
@@ -59,10 +59,13 @@ func main() {
http.HandleFunc("GET /file", withCORS(httpRequest.HandleAttachmentFileDownload)) http.HandleFunc("GET /file", withCORS(httpRequest.HandleAttachmentFileDownload))
http.HandleFunc("POST /hub", withCORS(httpRequest.HandleHubCreate)) 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", withCORS(httpRequest.HandleGetHubs))
http.HandleFunc("GET /hubs/channels", withCORS(httpRequest.HandleGetChannels)) http.HandleFunc("GET /hubs/channels", withCORS(httpRequest.HandleGetChannels))
http.HandleFunc("GET /hubs/users", withCORS(httpRequest.HandleGetHubUsers)) 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("PUT /hub/join", withCORS(httpRequest.HandleHubJoin))
http.HandleFunc("PATCH /hub/name", withCORS(httpRequest.HandleHubSetName)) http.HandleFunc("PATCH /hub/name", withCORS(httpRequest.HandleHubSetName))
http.HandleFunc("PATCH /hub/color", withCORS(httpRequest.HandleHubSetColor)) 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("DELETE /hub/self/role", withCORS(httpRequest.HandleRoleSelfRemove))
http.HandleFunc("PUT /hub/channel", withCORS(httpRequest.HandleChannelCreate)) http.HandleFunc("PUT /hub/channel", withCORS(httpRequest.HandleChannelCreate))
http.HandleFunc("DELETE /hub/channel", withCORS(httpRequest.HandleChannelRemove)) http.HandleFunc("DELETE /hub/channel", withCORS(httpRequest.HandleChannelRemove))
http.HandleFunc("PATCH /hub/name", withCORS(httpRequest.HandleChannelSetName)) http.HandleFunc("PATCH /hub/channel/name", withCORS(httpRequest.HandleChannelSetName))
http.HandleFunc("PATCH /hub/description", withCORS(httpRequest.HandleChannelSetDescription)) 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("POST /connection/message", withCORS(httpRequest.HandleDm))
http.HandleFunc("GET /ws", wsServer.ServeWsConnection) http.HandleFunc("GET /ws", wsServer.ServeWsConnection)
+9 -9
View File
@@ -33,21 +33,21 @@ func HandleDm(response http.ResponseWriter, request *http.Request) {
return return
} }
conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connectionid"), user) conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user)
if !ok { if !ok {
return return
} }
msgContent := request.FormValue("msgContent") msgContent := request.FormValue("msg_content")
attachedFile := request.FormValue("attachedFile") attachedFile := request.FormValue("attached_file")
if msgContent == "" && attachedFile == "" { if msgContent == "" && attachedFile == "" {
http.Error(response, "empty msgContent", http.StatusBadRequest) http.Error(response, "empty msg_content", http.StatusBadRequest)
return return
} }
if attachedFile != "" && !strings.HasPrefix(attachedFile, string(minio.ConnectionFilePrefix)+conn.Id.String()+"/") { 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 return
} }
@@ -140,7 +140,7 @@ func HandleUserGetConnectionMessages(response http.ResponseWriter, request *http
return return
} }
conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connectionid"), user) conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user)
if !ok { if !ok {
return return
} }
@@ -268,7 +268,7 @@ func HandleUserDeleteConnection(response http.ResponseWriter, request *http.Requ
return return
} }
conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connectionid"), user) conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user)
if !ok { if !ok {
return return
} }
@@ -305,7 +305,7 @@ func HandleUserElevateConnection(response http.ResponseWriter, request *http.Req
http.Error(response, "invalid token", http.StatusUnauthorized) http.Error(response, "invalid token", http.StatusUnauthorized)
return return
} }
conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connectionid"), user) conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user)
if !ok { if !ok {
return return
} }
@@ -374,7 +374,7 @@ func HandleUserDeElevateConnection(response http.ResponseWriter, request *http.R
return return
} }
conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connectionid"), user) conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connection_id"), user)
if !ok { if !ok {
return return
} }
+4 -4
View File
@@ -153,9 +153,9 @@ func HandleGetUserAvatar(response http.ResponseWriter, request *http.Request) {
return return
} }
targetId, err := convertions.StringToUuid(request.URL.Query().Get("userid")) targetId, err := convertions.StringToUuid(request.URL.Query().Get("user_id"))
if err != nil { if err != nil {
http.Error(response, "invalid userid", http.StatusBadRequest) http.Error(response, "invalid user_id", http.StatusBadRequest)
return return
} }
@@ -274,9 +274,9 @@ func HandleGetUserProfileBg(response http.ResponseWriter, request *http.Request)
return return
} }
targetId, err := convertions.StringToUuid(request.URL.Query().Get("userid")) targetId, err := convertions.StringToUuid(request.URL.Query().Get("user_id"))
if err != nil { if err != nil {
http.Error(response, "invalid userid", http.StatusBadRequest) http.Error(response, "invalid user_id", http.StatusBadRequest)
return return
} }
+11 -8
View File
@@ -73,9 +73,9 @@ func getHubUserIfValidWithResponseOnFail(ctx context.Context, response http.Resp
return nil, nil, nil, errors.New("invalid token") 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 { 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") 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] hubUser, ok := hub.Users[user.Id]
hub.Mu.RUnlock() hub.Mu.RUnlock()
if !ok { if !ok {
http.Error(response, "invalid hubid", http.StatusUnauthorized) http.Error(response, "invalid hub_id", http.StatusUnauthorized)
return nil, nil, nil, errors.New("invalid hubid") return nil, nil, nil, errors.New("invalid hub_id")
} }
return user, hubUser, hub, nil return user, hubUser, hub, nil
@@ -94,13 +94,16 @@ func getHubChannelIfValidWithResponseOnFail(ctx context.Context, response http.R
*types.HubChannel, error) { *types.HubChannel, error) {
channelUuid, err := convertions.StringToUuid(channelId) channelUuid, err := convertions.StringToUuid(channelId)
if err != nil { if err != nil {
http.Error(response, "invalid channelid", http.StatusBadRequest) http.Error(response, "invalid channel_id", http.StatusBadRequest)
return nil, errors.New("invalid channelid") return nil, errors.New("invalid channel_id")
} }
hub.Mu.RLock()
channel, ok := hub.Channels[channelUuid] channel, ok := hub.Channels[channelUuid]
hub.Mu.RUnlock()
if !ok { if !ok {
http.Error(response, "invalid channelid", http.StatusBadRequest) http.Error(response, "channel not found", http.StatusNotFound)
return nil, errors.New("invalid channelid") return nil, errors.New("channel not found")
} }
if !haveHubUserCachedPermissions(types.CachedUserCanView, hubUser, channel) { if !haveHubUserCachedPermissions(types.CachedUserCanView, hubUser, channel) {
+284 -145
View File
@@ -3,9 +3,7 @@ package httpRequest
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"maps"
"net/http" "net/http"
"slices"
"strings" "strings"
"time" "time"
@@ -57,10 +55,10 @@ func hubUserHighestRoleId(u *types.HubUser, h *types.Hub) (id uint8) {
func updateChannelCacheForSpecUserAndChannel(u *types.HubUser, c *types.HubChannel) { func updateChannelCacheForSpecUserAndChannel(u *types.HubUser, c *types.HubChannel) {
c.Mu.Lock() c.Mu.Lock()
defer c.Mu.Unlock() defer c.Mu.Unlock()
if u == nil { if u == nil {
delete(c.UsersCachedPermissions, c.Id) delete(c.UsersCachedPermissions, c.Id)
} else { return
}
if u.Roles.DoesIntersect(c.RolesCanView) { if u.Roles.DoesIntersect(c.RolesCanView) {
c.UsersCachedPermissions[u.OriginalId] |= types.CachedUserCanView c.UsersCachedPermissions[u.OriginalId] |= types.CachedUserCanView
} else { } else {
@@ -76,7 +74,6 @@ func updateChannelCacheForSpecUserAndChannel(u *types.HubUser, c *types.HubChann
} else { } else {
c.UsersCachedPermissions[u.OriginalId] &^= types.CachedUserCanMessage c.UsersCachedPermissions[u.OriginalId] &^= types.CachedUserCanMessage
} }
}
} }
func updateChannelCacheForSpecChannel(c *types.HubChannel, h *types.Hub) { func updateChannelCacheForSpecChannel(c *types.HubChannel, h *types.Hub) {
@@ -91,17 +88,23 @@ func updateChannelCacheForSpecUser(u *types.HubUser, h *types.Hub) {
h.Mu.RLock() h.Mu.RLock()
defer h.Mu.RUnlock() defer h.Mu.RUnlock()
for _, c := range h.Channels { for _, c := range h.Channels {
if c == nil {
continue
}
updateChannelCacheForSpecUserAndChannel(u, c) updateChannelCacheForSpecUserAndChannel(u, c)
} }
} }
func updateChannelCacheForSpecRole(r *types.HubRole, h *types.Hub) { func updateChannelCacheForSpecRole(r *types.HubRole, h *types.Hub) {
h.Mu.Lock() h.Mu.RLock()
defer h.Mu.Unlock() var users []*types.HubUser
for _, u := range h.Users { for _, u := range h.Users {
if !u.Roles.ContainsRoleId(r.Id) { if u.Roles.ContainsRoleId(r.Id) {
continue users = append(users, u)
} }
}
h.Mu.RUnlock()
for _, u := range users {
updateChannelCacheForSpecUser(u, h) updateChannelCacheForSpecUser(u, h)
} }
} }
@@ -128,7 +131,7 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) {
return return
} }
hubName := request.FormValue("hubname") hubName := request.FormValue("hub_name")
if hubName == "" { if hubName == "" {
http.Error(response, "hub name is required", http.StatusBadRequest) http.Error(response, "hub name is required", http.StatusBadRequest)
return return
@@ -140,7 +143,7 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) {
hub.Id = uuid.New() hub.Id = uuid.New()
hub.Creator = user.Id hub.Creator = user.Id
hub.CreatedAt = time.Now() hub.CreatedAt = time.Now()
user.Hubs[user.Id] = hub user.Hubs[hub.Id] = hub
creator := types.NewHubUser() creator := types.NewHubUser()
creator.OriginalId = user.Id creator.OriginalId = user.Id
@@ -148,7 +151,7 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) {
hub.Users[creator.OriginalId] = creator hub.Users[creator.OriginalId] = creator
rootRole := &types.HubRole{ rootRole := &types.HubRole{
Id: uint8(0), Id: types.HubBoundRolesMax,
Permissions: types.PermissionAll(), Permissions: types.PermissionAll(),
Name: "root", Name: "root",
Color: types.RandomRgba(), Color: types.RandomRgba(),
@@ -158,7 +161,7 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) {
creator.Roles.Add(rootRole.Id) creator.Roles.Add(rootRole.Id)
memberRole := &types.HubRole{ memberRole := &types.HubRole{
Id: types.HubBoundRolesMax, Id: uint8(0),
Name: "member", Name: "member",
Color: types.RandomRgba(), Color: types.RandomRgba(),
CreatedAt: hub.CreatedAt, CreatedAt: hub.CreatedAt,
@@ -179,9 +182,13 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) {
channel.RolesCanReadHistory.Add(rootRole.Id) channel.RolesCanReadHistory.Add(rootRole.Id)
channel.RolesCanReadHistory.Add(memberRole.Id) channel.RolesCanReadHistory.Add(memberRole.Id)
channel.UsersCachedPermissions[creator.OriginalId] = types.CachedUserPermissionsAll channel.UsersCachedPermissions[creator.OriginalId] = types.CachedUserPermissionsAll
hub.Channels[0] = channel channel.Position = 0
hub.Channels[channel.Id] = channel
cache.SaveHub(hub) cache.SaveHub(hub)
response.WriteHeader(http.StatusCreated)
response.Write([]byte(hub.Id.String()))
} }
func HandleHubJoin(response http.ResponseWriter, request *http.Request) { 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) http.Error(response, "invalid token", http.StatusUnauthorized)
return return
} }
hub, err := getHubByIdStr(ctx, request.Header.Get("hubid")) hub, err := getHubByIdStr(ctx, request.Header.Get("hub_id"))
if err != nil { if err != nil {
http.Error(response, "invalid hubid", http.StatusBadRequest) http.Error(response, "invalid hub_id", http.StatusBadRequest)
return 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 := types.NewHubUser()
hubUser.OriginalId = user.Id hubUser.OriginalId = user.Id
if hub.JoinRole != nil { if hub.JoinRole != nil {
@@ -208,6 +222,8 @@ func HandleHubJoin(response http.ResponseWriter, request *http.Request) {
hubUser.CreatedAt = time.Now() hubUser.CreatedAt = time.Now()
hub.Users[hubUser.OriginalId] = hubUser hub.Users[hubUser.OriginalId] = hubUser
updateChannelCacheForSpecUser(hubUser, hub) updateChannelCacheForSpecUser(hubUser, hub)
response.WriteHeader(http.StatusCreated)
} }
func HandleHubMessage(response http.ResponseWriter, request *http.Request) { func HandleHubMessage(response http.ResponseWriter, request *http.Request) {
@@ -219,7 +235,7 @@ func HandleHubMessage(response http.ResponseWriter, request *http.Request) {
if err != nil { if err != nil {
return 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 { if err != nil {
return return
} }
@@ -229,8 +245,8 @@ func HandleHubMessage(response http.ResponseWriter, request *http.Request) {
return return
} }
msgContent := request.FormValue("msgContent") msgContent := request.FormValue("msg_content")
attachedFile := request.FormValue("attachedFile") attachedFile := request.FormValue("attached_file")
if msgContent == "" && attachedFile == "" { if msgContent == "" && attachedFile == "" {
http.Error(response, "empty msgContent", http.StatusBadRequest) http.Error(response, "empty msgContent", http.StatusBadRequest)
@@ -266,69 +282,10 @@ func HandleHubMessage(response http.ResponseWriter, request *http.Request) {
} }
wsServer.WsSendMessageCloseIfTimeout(target, msg) wsServer.WsSendMessageCloseIfTimeout(target, msg)
} }
} response.WriteHeader(http.StatusCreated)
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)
} }
func HandleGetChannels(response http.ResponseWriter, request *http.Request) { 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) { if !validCheckWithResponseOnFail(response, request, normal) {
return return
} }
@@ -337,27 +294,129 @@ func HandleGetHubUsers(response http.ResponseWriter, request *http.Request) {
if err != nil { if err != nil {
return return
} }
users := make([]*types.HubUser, 0) channels := make([]uuid.UUID, 0)
hub.Mu.RLock() hub.Mu.RLock()
for userId, user := range hub.Users { for _, channel := range hub.Channels {
if userId == requestor.OriginalId { if channel == nil || !haveHubUserCachedPermissions(types.CachedUserCanView, requestor, channel) {
continue continue
} }
users = append(users, user) channels = append(channels, channel.Id)
} }
hub.Mu.Unlock() hub.Mu.RUnlock()
if len(users) == 0 { if len(channels) == 0 {
response.WriteHeader(http.StatusNoContent) response.WriteHeader(http.StatusNoContent)
return return
} }
channels, err := json.Marshal(users) marshal, err := json.Marshal(channels)
if err != nil { if err != nil {
http.Error(response, "json error", http.StatusInternalServerError) http.Error(response, "json error", http.StatusInternalServerError)
return return
} }
response.WriteHeader(http.StatusOK) 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) { 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 { if !ok {
return return
} }
newName := request.FormValue("newname") newName := request.FormValue("new_name")
if newName == "" { if newName == "" {
http.Error(response, "empty name", http.StatusBadRequest) http.Error(response, "empty name", http.StatusBadRequest)
return return
@@ -398,7 +457,7 @@ func HandleHubSetColor(response http.ResponseWriter, request *http.Request) {
if !ok { if !ok {
return return
} }
color, err := convertions.StringToRgba(request.FormValue("newname")) color, err := convertions.StringToRgba(request.FormValue("new_color"))
if err != nil { if err != nil {
http.Error(response, "bad color", http.StatusBadRequest) http.Error(response, "bad color", http.StatusBadRequest)
return return
@@ -430,28 +489,37 @@ func HandleHubUserRemove(response http.ResponseWriter, request *http.Request) {
if !ok { if !ok {
return return
} }
targetId, err := convertions.StringToUuid(request.FormValue("targetid")) targetId, err := convertions.StringToUuid(request.FormValue("target_id"))
if err != nil { if err != nil {
http.Error(response, "bad targetid", http.StatusBadRequest) http.Error(response, "bad target_id", http.StatusBadRequest)
return return
} }
hub.Mu.RLock() hub.Mu.RLock()
target, exists := hub.Users[targetId] hubTarget, exists := hub.Users[targetId]
hub.Mu.RUnlock()
if !exists { if !exists {
hub.Mu.Unlock()
http.Error(response, "user not found", http.StatusNotFound) http.Error(response, "user not found", http.StatusNotFound)
return return
} }
hub.Mu.RUnlock()
if usedRoleId <= hubUserHighestRoleId(target, hub) { if usedRoleId <= hubUserHighestRoleId(hubTarget, hub) {
http.Error(response, "target higher in hierarchy", http.StatusForbidden) http.Error(response, "target higher in hierarchy", http.StatusForbidden)
return 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() hub.Mu.Lock()
delete(hub.Users, targetId) delete(hub.Users, targetId)
updateChannelCacheForSpecUser(target, hub)
hub.Mu.Unlock() hub.Mu.Unlock()
updateChannelCacheForSpecUser(hubTarget, hub)
response.WriteHeader(http.StatusAccepted) response.WriteHeader(http.StatusAccepted)
} }
@@ -460,27 +528,25 @@ func HandleHubRenameUser(response http.ResponseWriter, request *http.Request) {
if !ok { if !ok {
return return
} }
newName := request.FormValue("newname") newName := request.FormValue("new_name")
targetId, err := convertions.StringToUuid(request.FormValue("targetid")) targetId, err := convertions.StringToUuid(request.FormValue("target_id"))
if err != nil { if err != nil {
http.Error(response, "bad targetid", http.StatusBadRequest) http.Error(response, "bad target_id", http.StatusBadRequest)
return return
} }
hub.Mu.RLock() hub.Mu.RLock()
target, exists := hub.Users[targetId] target, exists := hub.Users[targetId]
hub.Mu.RUnlock()
if !exists { if !exists {
hub.Mu.Unlock()
http.Error(response, "user not found", http.StatusNotFound) http.Error(response, "user not found", http.StatusNotFound)
return return
} }
hub.Mu.RUnlock()
if usedRoleId <= hubUserHighestRoleId(target, hub) { if usedRoleId <= hubUserHighestRoleId(target, hub) {
http.Error(response, "target higher in hierarchy", http.StatusForbidden) http.Error(response, "target higher in hierarchy", http.StatusForbidden)
return return
} }
target.Name = newName target.Name = newName
response.WriteHeader(http.StatusAccepted) response.WriteHeader(http.StatusAccepted)
} }
@@ -489,7 +555,7 @@ func HandleHubRenameSelf(response http.ResponseWriter, request *http.Request) {
if !ok { if !ok {
return return
} }
requestor.Name = request.FormValue("newname") requestor.Name = request.FormValue("new_name")
response.WriteHeader(http.StatusAccepted) response.WriteHeader(http.StatusAccepted)
} }
@@ -498,25 +564,23 @@ func HandleHubToggleMuteUser(response http.ResponseWriter, request *http.Request
if !ok { if !ok {
return return
} }
targetId, err := convertions.StringToUuid(request.FormValue("targetid")) targetId, err := convertions.StringToUuid(request.FormValue("target_id"))
if err != nil { if err != nil {
http.Error(response, "bad targetid", http.StatusBadRequest) http.Error(response, "bad target_id", http.StatusBadRequest)
return return
} }
hub.Mu.RLock() hub.Mu.RLock()
target, exists := hub.Users[targetId] target, exists := hub.Users[targetId]
hub.Mu.RUnlock()
if !exists { if !exists {
hub.Mu.Unlock()
http.Error(response, "user not found", http.StatusNotFound) http.Error(response, "user not found", http.StatusNotFound)
return return
} }
hub.Mu.RUnlock()
if usedRoleId <= hubUserHighestRoleId(target, hub) { if usedRoleId <= hubUserHighestRoleId(target, hub) {
http.Error(response, "target higher in hierarchy", http.StatusForbidden) http.Error(response, "target higher in hierarchy", http.StatusForbidden)
return return
} }
hub.Mu.RLock()
target.IsMuted = !target.IsMuted target.IsMuted = !target.IsMuted
response.WriteHeader(http.StatusAccepted) response.WriteHeader(http.StatusAccepted)
} }
@@ -526,14 +590,14 @@ func HandleHubUserAddRole(response http.ResponseWriter, request *http.Request) {
if !ok { if !ok {
return return
} }
targetId, err := convertions.StringToUuid(request.FormValue("targetid")) targetId, err := convertions.StringToUuid(request.FormValue("target_id"))
if err != nil { if err != nil {
http.Error(response, "bad targetid", http.StatusBadRequest) http.Error(response, "bad target_id", http.StatusBadRequest)
return return
} }
roleId, err := convertions.StringToUint8(request.FormValue("roleid")) roleId, err := convertions.StringToUint8(request.FormValue("role_id"))
if err != nil { if err != nil {
http.Error(response, "bad roleid", http.StatusBadRequest) http.Error(response, "bad role_id", http.StatusBadRequest)
return return
} }
if roleId > usedRoleId { if roleId > usedRoleId {
@@ -563,14 +627,14 @@ func HandleHubUserRemoveRole(response http.ResponseWriter, request *http.Request
if !ok { if !ok {
return return
} }
targetId, err := convertions.StringToUuid(request.FormValue("targetid")) targetId, err := convertions.StringToUuid(request.FormValue("target_id"))
if err != nil { if err != nil {
http.Error(response, "bad targetid", http.StatusBadRequest) http.Error(response, "bad target_id", http.StatusBadRequest)
return return
} }
roleId, err := convertions.StringToUint8(request.FormValue("roleid")) roleId, err := convertions.StringToUint8(request.FormValue("role_id"))
if err != nil { if err != nil {
http.Error(response, "bad roleid", http.StatusBadRequest) http.Error(response, "bad role_id", http.StatusBadRequest)
return return
} }
if roleId > usedRoleId { if roleId > usedRoleId {
@@ -630,9 +694,9 @@ func HandleHubRemoveRole(response http.ResponseWriter, request *http.Request) {
if !ok { if !ok {
return return
} }
targetRoleId, err := convertions.StringToUint8(request.FormValue("roleid")) targetRoleId, err := convertions.StringToUint8(request.FormValue("role_id"))
if err != nil { if err != nil {
http.Error(response, "bad roleid", http.StatusBadRequest) http.Error(response, "bad role_id", http.StatusBadRequest)
return return
} }
if targetRoleId == 0 { if targetRoleId == 0 {
@@ -660,16 +724,16 @@ func HandleRoleSetName(response http.ResponseWriter, request *http.Request) {
if !ok { if !ok {
return return
} }
roleId, err := convertions.StringToUint8(request.FormValue("roleid")) roleId, err := convertions.StringToUint8(request.FormValue("role_id"))
if err != nil { if err != nil {
http.Error(response, "bad roleid", http.StatusBadRequest) http.Error(response, "bad role_id", http.StatusBadRequest)
return return
} }
if roleId > usedRoleId { if roleId > usedRoleId {
http.Error(response, "target role higher in hierarchy", http.StatusForbidden) http.Error(response, "target role higher in hierarchy", http.StatusForbidden)
return return
} }
newName := request.FormValue("newname") newName := request.FormValue("new_name")
if newName == "" { if newName == "" {
http.Error(response, "name empty", http.StatusBadRequest) http.Error(response, "name empty", http.StatusBadRequest)
return return
@@ -678,6 +742,7 @@ func HandleRoleSetName(response http.ResponseWriter, request *http.Request) {
hub.Mu.Lock() hub.Mu.Lock()
role := hub.Roles[roleId] role := hub.Roles[roleId]
if role == nil { if role == nil {
hub.Mu.Unlock()
http.Error(response, "no such role", http.StatusNotFound) http.Error(response, "no such role", http.StatusNotFound)
return return
} }
@@ -691,16 +756,16 @@ func HandleRoleSetColor(response http.ResponseWriter, request *http.Request) {
if !ok { if !ok {
return return
} }
roleId, err := convertions.StringToUint8(request.FormValue("roleid")) roleId, err := convertions.StringToUint8(request.FormValue("role_id"))
if err != nil { if err != nil {
http.Error(response, "bad roleid", http.StatusBadRequest) http.Error(response, "bad role_id", http.StatusBadRequest)
return return
} }
if roleId > usedRoleId { if roleId > usedRoleId {
http.Error(response, "target role higher in hierarchy", http.StatusForbidden) http.Error(response, "target role higher in hierarchy", http.StatusForbidden)
return return
} }
color, err := convertions.StringToRgba(request.FormValue("newcolor")) color, err := convertions.StringToRgba(request.FormValue("new_color"))
if err != nil { if err != nil {
http.Error(response, "invalid newcolor", http.StatusBadRequest) http.Error(response, "invalid newcolor", http.StatusBadRequest)
return return
@@ -709,6 +774,7 @@ func HandleRoleSetColor(response http.ResponseWriter, request *http.Request) {
hub.Mu.Lock() hub.Mu.Lock()
role := hub.Roles[roleId] role := hub.Roles[roleId]
if role == nil { if role == nil {
hub.Mu.Unlock()
http.Error(response, "no such role", http.StatusNotFound) http.Error(response, "no such role", http.StatusNotFound)
return return
} }
@@ -722,9 +788,9 @@ func HandleRoleSetPermissions(response http.ResponseWriter, request *http.Reques
if !ok { if !ok {
return return
} }
targetRoleId, err := convertions.StringToUint8(request.FormValue("roleid")) targetRoleId, err := convertions.StringToUint8(request.FormValue("role_id"))
if err != nil { if err != nil {
http.Error(response, "bad roleid", http.StatusBadRequest) http.Error(response, "bad role_id", http.StatusBadRequest)
return return
} }
if targetRoleId > usedRoleId { if targetRoleId > usedRoleId {
@@ -814,13 +880,16 @@ func HandleChannelCreate(response http.ResponseWriter, request *http.Request) {
newHubChannel.RolesCanReadHistory.Add(types.HubBoundRolesMax) newHubChannel.RolesCanReadHistory.Add(types.HubBoundRolesMax)
} }
hub.Mu.Lock() hub.Mu.Lock()
for i, channel := range hub.Channels { usedPositions := make(map[uint8]bool, len(hub.Channels))
if channel != nil { for _, ch := range hub.Channels {
continue usedPositions[ch.Position] = true
} }
hub.Channels[i] = newHubChannel var position uint8
break for usedPositions[position] {
position++
} }
newHubChannel.Position = position
hub.Channels[newHubChannel.Id] = newHubChannel
hub.Mu.Unlock() hub.Mu.Unlock()
updateChannelCacheForSpecChannel(newHubChannel, hub) updateChannelCacheForSpecChannel(newHubChannel, hub)
@@ -832,14 +901,19 @@ func HandleChannelRemove(response http.ResponseWriter, request *http.Request) {
if !ok { if !ok {
return return
} }
channelId, err := convertions.StringToUint8(request.FormValue("id")) channelId, err := convertions.StringToUuid(request.FormValue("channel_id"))
if err != nil { if err != nil {
http.Error(response, "invalid channel id", http.StatusBadRequest) http.Error(response, "invalid channel_id", http.StatusBadRequest)
return return
} }
hub.Mu.Lock() 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() hub.Mu.Unlock()
response.WriteHeader(http.StatusAccepted) response.WriteHeader(http.StatusAccepted)
} }
@@ -848,26 +922,26 @@ func HandleChannelSetName(response http.ResponseWriter, request *http.Request) {
if !ok { if !ok {
return return
} }
newName := request.FormValue("newname") newName := request.FormValue("new_name")
if newName == "" { if newName == "" {
http.Error(response, "newname empty", http.StatusBadRequest) http.Error(response, "newname empty", http.StatusBadRequest)
return return
} }
channelId, err := convertions.StringToUint8(request.FormValue("id")) channelId, err := convertions.StringToUuid(request.FormValue("channel_id"))
if err != nil { if err != nil {
http.Error(response, "invalid channel id", http.StatusBadRequest) http.Error(response, "invalid channel_id", http.StatusBadRequest)
return return
} }
hub.Mu.Lock() hub.Mu.Lock()
channel := hub.Channels[channelId] channel, ok := hub.Channels[channelId]
if channel == nil { if !ok {
hub.Mu.Unlock()
http.Error(response, "no such channel", http.StatusNotFound) http.Error(response, "no such channel", http.StatusNotFound)
return return
} }
channel.Name = newName channel.Name = newName
hub.Mu.Unlock() hub.Mu.Unlock()
response.WriteHeader(http.StatusAccepted) response.WriteHeader(http.StatusAccepted)
} }
@@ -877,20 +951,85 @@ func HandleChannelSetDescription(response http.ResponseWriter, request *http.Req
return return
} }
newDescription := request.FormValue("description") newDescription := request.FormValue("description")
channelId, err := convertions.StringToUint8(request.FormValue("id")) channelId, err := convertions.StringToUuid(request.FormValue("channel_id"))
if err != nil { if err != nil {
http.Error(response, "invalid channel id", http.StatusBadRequest) http.Error(response, "invalid channel_id", http.StatusBadRequest)
return return
} }
hub.Mu.Lock() hub.Mu.Lock()
channel := hub.Channels[channelId] channel, ok := hub.Channels[channelId]
if channel == nil { if !ok {
hub.Mu.Unlock()
http.Error(response, "no such channel", http.StatusNotFound) http.Error(response, "no such channel", http.StatusNotFound)
return return
} }
channel.Description = newDescription channel.Description = newDescription
hub.Mu.Unlock() hub.Mu.Unlock()
response.WriteHeader(http.StatusAccepted) 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)
}
})
}
+31 -3
View File
@@ -232,9 +232,9 @@ func HandleUserGetUser(response http.ResponseWriter, request *http.Request) {
return return
} }
targetId, err := convertions.StringToUuid(request.URL.Query().Get("targetid")) targetId, err := convertions.StringToUuid(request.URL.Query().Get("target_id"))
if err != nil { if err != nil {
http.Error(response, "invalid userid", http.StatusBadRequest) http.Error(response, "invalid target_id", http.StatusBadRequest)
return return
} }
target, err := getUserById(ctx, targetId) target, err := getUserById(ctx, targetId)
@@ -265,7 +265,7 @@ func HandleUserGetUsers(response http.ResponseWriter, request *http.Request) {
return return
} }
targetIds, err := convertions.StringToUuids(request.URL.Query().Get("targetids")) targetIds, err := convertions.StringToUuids(request.URL.Query().Get("target_ids"))
if err != nil { if err != nil {
http.Error(response, "invalid targetids", http.StatusBadRequest) http.Error(response, "invalid targetids", http.StatusBadRequest)
return return
@@ -286,3 +286,31 @@ func HandleUserGetUsers(response http.ResponseWriter, request *http.Request) {
response.WriteHeader(http.StatusAccepted) response.WriteHeader(http.StatusAccepted)
response.Write(userData) 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)
}
+5 -3
View File
@@ -268,7 +268,7 @@ type Hub struct {
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
Roles [256]*HubRole `json:"-"` Roles [256]*HubRole `json:"-"`
Users map[uuid.UUID]*HubUser `json:"-"` Users map[uuid.UUID]*HubUser `json:"-"`
Channels [256]*HubChannel `json:"-"` Channels map[uuid.UUID]*HubChannel `json:"-"`
Name string `json:"name"` Name string `json:"name"`
IconUrl string `json:"iconUrl"` IconUrl string `json:"iconUrl"`
BgUrl string `json:"backgroundUrl"` BgUrl string `json:"backgroundUrl"`
@@ -282,6 +282,7 @@ type Hub struct {
func NewHub() *Hub { func NewHub() *Hub {
return &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 { type HubUser struct {
Mu sync.RWMutex `json:"mu"` Mu sync.RWMutex `json:"-"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
Roles HubBoundRoles `json:"-"` Roles HubBoundRoles `json:"roles"`
Name string `json:"name"` // Name empty = original name Name string `json:"name"` // Name empty = original name
OriginalId uuid.UUID `json:"originalId"` OriginalId uuid.UUID `json:"originalId"`
IsMuted bool `json:"isMuted"` IsMuted bool `json:"isMuted"`
@@ -330,6 +331,7 @@ type HubChannel struct {
UsersCachedPermissions map[uuid.UUID]CachedUserPermissions `json:"-"` UsersCachedPermissions map[uuid.UUID]CachedUserPermissions `json:"-"`
NextBuffIdx uint32 `json:"-"` NextBuffIdx uint32 `json:"-"`
Id uuid.UUID `json:"id"` Id uuid.UUID `json:"id"`
Position uint8 `json:"position"`
HaveOverflowed bool `json:"-"` HaveOverflowed bool `json:"-"`
} }