diff --git a/go-socket b/go-socket index 0635fe1..072fef0 100755 Binary files a/go-socket and b/go-socket differ diff --git a/machine-client/clientTest.py b/machine-client/clientTest.py index b7e5287..353c87a 100644 --- a/machine-client/clientTest.py +++ b/machine-client/clientTest.py @@ -34,6 +34,12 @@ shutdown_event: asyncio.Event = None def post(path, **data): return requests.post(f"{BASE_URL}{path}", data=data) +def post_auth(path, **data): + return requests.post(f"{BASE_URL}{path}", data={"token": token, **data}) + +def post_auth_header(path, files=None, **data): + return requests.post(f"{BASE_URL}{path}", headers={"token": token}, data=data, files=files) + def fmt_msg(m: dict) -> str: sender = m.get("sender", "?") content = m.get("content", "") @@ -47,12 +53,19 @@ def fmt_msg(m: dict) -> str: # ── commands ───────────────────────────────────────────────────────────────── -def cmd_login(args): +def cmd_new_user(args): + if len(args) < 2: + print("usage: /new/user ") + return + r = post("/new/user", username=args[0], password=args[1]) + print("registered" if r.ok else f"error: {r.text}") + +def cmd_new_token(args): global token, user_id if len(args) < 2: - print("usage: /login ") + print("usage: /new/token ") return - r = post("/token", username=args[0], password=args[1]) + r = post("/new/token", username=args[0], password=args[1]) if r.ok: data = r.json() token = data["token"] @@ -64,31 +77,31 @@ def cmd_login(args): else: print(f"login failed: {r.text}") -def cmd_send(args): +def cmd_msg_user(args): if not token: print("not logged in") return if len(args) < 2: - print("usage: /send ") + print("usage: /msg/user ") return conn_id = args[0] content = " ".join(args[1:]) - r = post("/message", token=token, connectionid=conn_id, msgContent=content) + r = post_auth("/msg/user", connectionid=conn_id, msgContent=content) print("sent" if r.ok else f"error: {r.text}") -def cmd_history(args): +def cmd_get_connection_messages(args): if not token: print("not logged in") return if not args: - print("usage: /history [count]") + print("usage: /get/connection/messages [count] [before]") return - data = {"token": token, "connectionid": args[0]} + data = {"connectionid": args[0]} if len(args) > 1: data["messages"] = args[1] if len(args) > 2: data["before"] = args[2] - r = requests.post(f"{BASE_URL}/get/connection/messages", data=data) + r = post_auth("/get/connection/messages", **data) if r.ok: msgs = r.json() or [] if not msgs: @@ -98,41 +111,198 @@ def cmd_history(args): else: print(f"error: {r.text}") -def cmd_connections(args): +def cmd_get_connections(args): if not token: print("not logged in") return - r = post("/get/connections", token=token) + r = post_auth("/get/connections") if r.ok: - for c in (r.json() or {}).values(): + for c in (r.json() or []): print(f" {c['id']} requestor={c['requestorId']} recipient={c['recipientId']} state={c['state']}") else: print(f"error: {r.text}") -def cmd_delconnection(args): +def cmd_new_connection(args): if not token: print("not logged in") return if not args: - print("usage: /delconnection ") + print("usage: /new/connection ") return - r = post("/del/connection", token=token, connectionid=args[0]) + r = post_auth("/new/connection", recipient=args[0]) + print("connection requested" if r.ok else f"error: {r.text}") + +def cmd_mod_connection_elevate(args): + if not token: + print("not logged in") + return + if not args: + print("usage: /mod/connection/elevate ") + return + r = post_auth("/mod/connection/elevate", connectionid=args[0]) + print(f"ok: {r.text}" if r.ok else f"error: {r.text}") + +def cmd_del_connection(args): + if not token: + print("not logged in") + return + if not args: + print("usage: /del/connection ") + return + r = post_auth("/del/connection", connectionid=args[0]) print("deleted" if r.ok else f"error: {r.text}") +def cmd_del_user(args): + if not token: + print("not logged in") + return + r = post_auth("/del/user") + print("user deleted" if r.ok else f"error: {r.text}") + +def cmd_get_user(args): + if not token: + print("not logged in") + return + if not args: + print("usage: /get/user ") + return + r = post_auth("/get/user", targetid=args[0]) + if r.ok: + print(json.dumps(r.json(), indent=2)) + else: + print(f"error: {r.text}") + +def cmd_mod_user_profile(args): + if not token: + print("not logged in") + return + data = {} + for arg in args: + if "=" in arg: + k, v = arg.split("=", 1) + data[k] = v + if not data: + print("usage: /mod/user/profile [pronouns=...] [description=...] [color=r,g,b,a]") + return + r = post_auth("/mod/user/profile", **data) + print("updated" if r.ok else f"error: {r.text}") + +def cmd_get_file(args): + if not token: + print("not logged in") + return + if len(args) < 2: + print("usage: /get/file ") + return + r = post_auth_header("/get/file", connectionid=args[0], key=args[1]) + if r.ok: + print(json.dumps(r.json(), indent=2)) + else: + print(f"error: {r.text}") + +def cmd_new_file(args): + if not token: + print("not logged in") + return + if len(args) < 2: + print("usage: /new/file ") + return + conn_id, filepath = args[0], args[1] + try: + with open(filepath, "rb") as f: + r = post_auth_header("/new/file", files={"file": f}, connectionid=conn_id) + print(f"uploaded: {r.text}" if r.ok else f"error: {r.text}") + except FileNotFoundError: + print(f"file not found: {filepath}") + +def cmd_mod_user_avatar(args): + if not token: + print("not logged in") + return + if not args: + print("usage: /mod/user/avatar ") + return + filepath = args[0] + try: + with open(filepath, "rb") as f: + r = post_auth_header("/mod/user/avatar", files={"file": f}) + print("avatar updated" if r.ok else f"error: {r.text}") + except FileNotFoundError: + print(f"file not found: {filepath}") + +def cmd_mod_user_profilebg(args): + if not token: + print("not logged in") + return + if not args: + print("usage: /mod/user/profilebg ") + return + filepath = args[0] + try: + with open(filepath, "rb") as f: + r = post_auth_header("/mod/user/profilebg", files={"file": f}) + print("profile background updated" if r.ok else f"error: {r.text}") + except FileNotFoundError: + print(f"file not found: {filepath}") + +def cmd_get_user_avatar(args): + if not token: + print("not logged in") + return + if not args: + print("usage: /get/user/avatar ") + return + r = post_auth_header("/get/user/avatar", userid=args[0]) + print(r.text if r.ok else f"error: {r.text}") + +def cmd_get_user_profilebg(args): + if not token: + print("not logged in") + return + if not args: + print("usage: /get/user/profilebg ") + return + r = post_auth_header("/get/user/profilebg", userid=args[0]) + print(r.text if r.ok else f"error: {r.text}") + COMMANDS = { - "/login": cmd_login, - "/send": cmd_send, - "/history": cmd_history, - "/connections": cmd_connections, - "/delconnection": cmd_delconnection, + "/new/user": cmd_new_user, + "/new/token": cmd_new_token, + "/new/connection": cmd_new_connection, + "/new/file": cmd_new_file, + "/mod/user/profile": cmd_mod_user_profile, + "/mod/user/avatar": cmd_mod_user_avatar, + "/mod/user/profilebg": cmd_mod_user_profilebg, + "/mod/connection/elevate": cmd_mod_connection_elevate, + "/get/user": cmd_get_user, + "/get/connections": cmd_get_connections, + "/get/connection/messages": cmd_get_connection_messages, + "/get/file": cmd_get_file, + "/get/user/avatar": cmd_get_user_avatar, + "/get/user/profilebg": cmd_get_user_profilebg, + "/del/user": cmd_del_user, + "/del/connection": cmd_del_connection, + "/msg/user": cmd_msg_user, } HELP = """ - /login – authenticate - /connections – list your connections - /send – send a DM - /history [count] [before] – fetch message history - /delconnection – delete a connection + /new/user – create account + /new/token – authenticate + /new/connection – send connection request + /new/file – upload attachment + /mod/user/profile [pronouns=...] [description=...] [color=r,g,b,a] – update profile + /mod/user/avatar – set avatar image + /mod/user/profilebg – set profile background + /mod/connection/elevate – elevate connection + /get/user – get user info + /get/connections – list your connections + /get/connection/messages [count] [before] – fetch message history + /get/file – download attachment URL + /get/user/avatar – get avatar download URL + /get/user/profilebg – get profile background URL + /del/user – delete your account + /del/connection – delete a connection + /msg/user – send a DM """ # ── websocket ───────────────────────────────────────────────────────────────── @@ -141,7 +311,6 @@ async def receiver(ws): async for raw in ws: try: data = json.loads(raw) - # pushed DM if "content" in data and "sender" in data: print(f"\n{fmt_msg(data)}", flush=True) else: @@ -171,7 +340,6 @@ async def run(): print("Alt+Enter = newline | Enter = send | /help | Ctrl+C = quit\n") input_thread.start() input_thread_started = True - # re-auth after reconnect if token: await ws.send(json.dumps({"token": token})) @@ -219,7 +387,6 @@ def input_loop(): if cmd in COMMANDS: COMMANDS[cmd](parts[1:]) else: - # raw JSON passthrough asyncio.run_coroutine_threadsafe(send_queue.put(text), loop) except (EOFError, KeyboardInterrupt): asyncio.run_coroutine_threadsafe(shutdown_event.set(), loop) diff --git a/machine-client/index.html b/machine-client/index.html index 9a962e3..3284a98 100644 --- a/machine-client/index.html +++ b/machine-client/index.html @@ -66,29 +66,24 @@
- - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + +
@@ -116,14 +111,6 @@ ], submit: () => httpPost('/new/user', { username:'nu-username', password:'nu-password', color:'nu-color' }) }, - 'new-connection': { - title: 'POST /new/connection — send connection request', - fields: [ - { id: 'nconn-token', label: 'token', ph: '' }, - { id: 'nconn-recipient', label: 'recipient', ph: 'uint32' }, - ], - submit: () => httpPost('/new/connection', { token:'nconn-token', recipient:'nconn-recipient' }) - }, 'new-token': { title: 'POST /new/token — login / get token', fields: [ @@ -132,82 +119,59 @@ ], submit: () => httpPost('/new/token', { username:'nt-username', password:'nt-password' }) }, - 'new-group': { - title: 'POST /new/group — create group', + 'new-connection': { + title: 'POST /new/connection — send connection request', fields: [ - { id: 'ng-token', label: 'token', ph: '' }, - { id: 'ng-name', label: 'name', ph: 'optional, default: Best group ever' }, - { id: 'ng-color', label: 'color', ph: 'R,G,B optional', hint: 'R,G,B' }, - { id: 'ng-enableUserColors', label: 'enableUserColors', ph: '1 to enable (optional)' }, + { id: 'nconn-token', label: 'token', ph: '' }, + { id: 'nconn-recipient', label: 'recipient', ph: 'uint32' }, ], - submit: () => httpPost('/new/group', { token:'ng-token', name:'ng-name', color:'ng-color', enableUserColors:'ng-enableUserColors' }) + submit: () => httpPost('/new/connection', { token:'nconn-token', recipient:'nconn-recipient' }) }, - 'mod-user-appearance': { - title: 'POST /mod/user/appearence — change user color', + 'mod-user-profile': { + title: 'POST /mod/user/profile — update profile', fields: [ - { id: 'mua-token', label: 'token', ph: '' }, - { id: 'mua-color', label: 'color', ph: '255,100,50', hint: 'R,G,B' }, + { id: 'mup-token', label: 'token', ph: '' }, + { id: 'mup-pronouns', label: 'pronouns', ph: 'optional' }, + { id: 'mup-description', label: 'description', ph: 'optional' }, + { id: 'mup-color', label: 'color', ph: '255,100,50 optional', hint: 'R,G,B' }, ], - submit: () => httpPost('/mod/user/appearence', { token:'mua-token', color:'mua-color' }) + submit: () => httpPost('/mod/user/profile', { token:'mup-token', pronouns:'mup-pronouns', description:'mup-description', color:'mup-color' }) }, - 'mod-user-about': { - title: 'POST /mod/user/about — change user pronouns', - fields: [ - { id: 'mub-token', label: 'token', ph: '' }, - { id: 'mub-pronouns', label: 'pronouns', ph: '2–25 chars' }, - ], - submit: () => httpPost('/mod/user/about', { token:'mub-token', pronouns:'mub-pronouns' }) + 'mod-user-avatar': { + title: 'POST /mod/user/avatar — set avatar image (token in header)', + renderCustom: () => ` +
+
+
+ +
+ ` }, - 'accept-connection': { - title: 'POST /mod/connection/accept — accept connection request', - fields: [ - { id: 'ca-token', label: 'token', ph: '' }, - { id: 'ca-connectionid', label: 'connectionid', ph: 'UUID' }, - ], - submit: () => httpPost('/mod/connection/accept', { token:'ca-token', connectionid:'ca-connectionid' }) + 'mod-user-profilebg': { + title: 'POST /mod/user/profilebg — set profile background (token in header)', + renderCustom: () => ` +
+
+
+ +
+ ` }, - 'add-users': { - title: 'POST /mod/group/addusers — add users (owner only)', + 'mod-connection-elevate': { + title: 'POST /mod/connection/elevate — elevate connection', fields: [ - { id: 'au-token', label: 'token', ph: '' }, - { id: 'au-groupid', label: 'groupid', ph: 'uint32' }, - { id: 'au-users', label: 'users', ph: 'id1,id2,id3', hint: 'csv of uint32' }, + { id: 'mce-token', label: 'token', ph: '' }, + { id: 'mce-connectionid', label: 'connectionid', ph: 'UUID' }, ], - submit: () => httpPost('/mod/group/addusers', { token:'au-token', groupid:'au-groupid', users:'au-users' }) + submit: () => httpPost('/mod/connection/elevate', { token:'mce-token', connectionid:'mce-connectionid' }) }, - 'remove-users': { - title: 'POST /mod/group/removeusers — remove users (owner only)', + 'get-user': { + title: 'POST /get/user — get user info', fields: [ - { id: 'ru-token', label: 'token', ph: '' }, - { id: 'ru-groupid', label: 'groupid', ph: 'uint32' }, - { id: 'ru-users', label: 'users', ph: 'id1,id2,id3', hint: 'csv of uint32' }, + { id: 'gu-token', label: 'token', ph: '' }, + { id: 'gu-targetid', label: 'targetid', ph: 'uint32' }, ], - submit: () => httpPost('/mod/group/removeusers', { token:'ru-token', groupid:'ru-groupid', users:'ru-users' }) - }, - 'group-color': { - title: 'POST /mod/group/color — change color (owner only)', - fields: [ - { id: 'gc-token', label: 'token', ph: '' }, - { id: 'gc-groupid', label: 'groupid', ph: 'uint32' }, - { id: 'gc-color', label: 'color', ph: '255,100,50', hint: 'R,G,B' }, - ], - submit: () => httpPost('/mod/group/color', { token:'gc-token', groupid:'gc-groupid', color:'gc-color' }) - }, - 'group-owner': { - title: 'POST /mod/group/owner — change owner (owner only)', - fields: [ - { id: 'go-token', label: 'token', ph: '' }, - { id: 'go-groupid', label: 'groupid', ph: 'uint32' }, - { id: 'go-newOwner', label: 'newOwner', ph: 'username of new owner' }, - ], - submit: () => httpPost('/mod/group/owner', { token:'go-token', groupid:'go-groupid', newOwner:'go-newOwner' }) - }, - 'get-groups': { - title: "POST /get/groups — get user's groups (no members)", - fields: [ - { id: 'gg-token', label: 'token', ph: '' }, - ], - submit: () => httpPost('/get/groups', { token:'gg-token' }) + submit: () => httpPost('/get/user', { token:'gu-token', targetid:'gu-targetid' }) }, 'get-connections': { title: "POST /get/connections — get user's connections", @@ -216,37 +180,6 @@ ], submit: () => httpPost('/get/connections', { token:'gconn-token' }) }, - 'get-members': { - title: 'POST /get/group/members — get group member IDs', - fields: [ - { id: 'gm-token', label: 'token', ph: '' }, - { id: 'gm-group', label: 'group', ph: 'group ID (uint32)' }, - ], - submit: () => httpPost('/get/group/members', { token:'gm-token', group:'gm-group' }) - }, - 'del-user': { - title: 'POST /del/user — delete own account', - fields: [ - { id: 'du-token', label: 'token', ph: '' }, - ], - submit: () => httpPost('/del/user', { token:'du-token' }) - }, - 'del-group': { - title: 'POST /del/group — delete group (owner only)', - fields: [ - { id: 'dg-token', label: 'token', ph: '' }, - { id: 'dg-groupid', label: 'groupid', ph: 'uint32' }, - ], - submit: () => httpPost('/del/group', { token:'dg-token', groupid:'dg-groupid' }) - }, - 'del-connection': { - title: 'POST /del/connection — delete a connection', - fields: [ - { id: 'dc-token', label: 'token', ph: '' }, - { id: 'dc-connectionid', label: 'connectionid', ph: 'UUID' }, - ], - submit: () => httpPost('/del/connection', { token:'dc-token', connectionid:'dc-connectionid' }) - }, 'get-connection-messages': { title: 'POST /get/connection/messages — fetch message history', fields: [ @@ -257,6 +190,41 @@ ], submit: () => httpPost('/get/connection/messages', { token:'gcm-token', connectionid:'gcm-connectionid', messages:'gcm-messages', before:'gcm-before' }) }, + 'get-user-avatar': { + title: 'POST /get/user/avatar — get avatar URL (token in header)', + renderCustom: () => ` +
+
+
+ +
+ ` + }, + 'get-user-profilebg': { + title: 'POST /get/user/profilebg — get profile background URL (token in header)', + renderCustom: () => ` +
+
+
+ +
+ ` + }, + 'del-user': { + title: 'POST /del/user — delete own account', + fields: [ + { id: 'du-token', label: 'token', ph: '' }, + ], + submit: () => httpPost('/del/user', { token:'du-token' }) + }, + 'del-connection': { + title: 'POST /del/connection — delete a connection', + fields: [ + { id: 'dc-token', label: 'token', ph: '' }, + { id: 'dc-connectionid', label: 'connectionid', ph: 'UUID' }, + ], + submit: () => httpPost('/del/connection', { token:'dc-token', connectionid:'dc-connectionid' }) + }, 'msg-user': { title: 'POST /msg/user — send direct message to user', fields: [ @@ -267,15 +235,6 @@ ], submit: () => httpPost('/msg/user', { token:'mu-token', connectionid:'mu-connectionid', msgContent:'mu-msgContent', attachedFile:'mu-attachedFile' }) }, - 'msg-group': { - title: 'POST /msg/group — send message to group', - fields: [ - { id: 'mg-token', label: 'token', ph: '' }, - { id: 'mg-groupid', label: 'groupid', ph: 'uint32' }, - { id: 'mg-content', label: 'content', ph: 'message text' }, - ], - submit: () => httpPost('/msg/group', { token:'mg-token', groupid:'mg-groupid', content:'mg-content' }) - }, 'file-upload': { title: 'POST /new/file — upload file (multipart, token in header)', renderCustom: () => ` @@ -339,10 +298,10 @@ const def = formDefs[name]; // activate clicked button - const idx = Object.keys(formDefs).indexOf(name); - if (idx >= 0) { - buttons[idx].classList.add('active'); - if (name === 'websocket') buttons[idx].classList.add('ws'); + const btn = document.querySelector(`#btn-row button[data-form="${name}"]`); + if (btn) { + btn.classList.add('active'); + if (name === 'websocket') btn.classList.add('ws'); } titleEl.textContent = def.title; @@ -485,6 +444,58 @@ } } + async function submitAvatarUpload() { + const token = document.getElementById('mua-token').value; + const fileInput = document.getElementById('mua-file'); + if (!fileInput.files.length) { log('HTTP ERR', 'no file selected', 'log-err'); return; } + const form = new FormData(); + form.append('file', fileInput.files[0]); + log('HTTP /mod/user/avatar', `→ file=${fileInput.files[0].name}`, 'log-info'); + try { + const resp = await fetch(baseUrl() + '/mod/user/avatar', { method: 'POST', headers: { token }, body: form }); + 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 submitProfileBgUpload() { + const token = document.getElementById('mpb-token').value; + const fileInput = document.getElementById('mpb-file'); + if (!fileInput.files.length) { log('HTTP ERR', 'no file selected', 'log-err'); return; } + const form = new FormData(); + form.append('file', fileInput.files[0]); + log('HTTP /mod/user/profilebg', `→ file=${fileInput.files[0].name}`, 'log-info'); + try { + const resp = await fetch(baseUrl() + '/mod/user/profilebg', { method: 'POST', headers: { token }, body: form }); + 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 submitGetUserAvatar() { + const token = document.getElementById('gua-token').value; + const userid = document.getElementById('gua-userid').value; + const params = new URLSearchParams({ userid }); + log('HTTP /get/user/avatar', `→ ${params.toString()}`, 'log-info'); + try { + const resp = await fetch(baseUrl() + '/get/user/avatar', { method: 'POST', headers: { token }, body: params }); + 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 params = new URLSearchParams({ userid }); + log('HTTP /get/user/profilebg', `→ ${params.toString()}`, 'log-info'); + try { + const resp = await fetch(baseUrl() + '/get/user/profilebg', { method: 'POST', headers: { token }, body: params }); + 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'); } + } + function setWsStatus(connected) { const el = document.getElementById('ws-status'); if (!el) return; diff --git a/main.go b/main.go index 28560d3..f85e219 100644 --- a/main.go +++ b/main.go @@ -32,16 +32,17 @@ func main() { http.HandleFunc("/new/connection", withCORS(httpRequest.HandleUserNewConnection)) http.HandleFunc("/new/token", withCORS(httpRequest.HandleUserNewToken)) http.HandleFunc("/new/file", withCORS(httpRequest.HandleAttachmentFileUpload)) - http.HandleFunc("/mod/user/appearence", withCORS(httpRequest.HandleUserModProfile)) + http.HandleFunc("/mod/user/profile", withCORS(httpRequest.HandleUserModProfile)) http.HandleFunc("/mod/user/avatar", withCORS(httpRequest.HandleUserModAvatar)) http.HandleFunc("/mod/user/profilebg", withCORS(httpRequest.HandleUserModProfileBg)) - http.HandleFunc("/mod/user/about", withCORS(httpRequest.HandleUserModProfile)) - http.HandleFunc("/mod/connection/accept", withCORS(httpRequest.HandleUserElevateConnection)) + http.HandleFunc("/mod/connection/elevate", withCORS(httpRequest.HandleUserElevateConnection)) http.HandleFunc("/get/user", withCORS(httpRequest.HandleUserGetUser)) http.HandleFunc("/get/connections", withCORS(httpRequest.HandleUserGetConnections)) http.HandleFunc("/get/connection/messages", withCORS(httpRequest.HandleUserGetConnectionMessages)) http.HandleFunc("/get/file", withCORS(httpRequest.HandleAttachmentFileDownload)) + http.HandleFunc("/get/user/avatar", withCORS(httpRequest.HandleGetUserAvatar)) + http.HandleFunc("/get/user/profilebg", withCORS(httpRequest.HandleGetUserProfileBg)) http.HandleFunc("/del/user", withCORS(httpRequest.HandleUserDelete)) http.HandleFunc("/del/connection", withCORS(httpRequest.HandleUserDeleteConnection)) diff --git a/packages/httpRequest/connectionsAndDms.go b/packages/httpRequest/connectionsAndDms.go index 1844039..73a5fd3 100644 --- a/packages/httpRequest/connectionsAndDms.go +++ b/packages/httpRequest/connectionsAndDms.go @@ -266,6 +266,7 @@ func HandleUserDeleteConnection(response http.ResponseWriter, request *http.Requ }) response.WriteHeader(http.StatusAccepted) + response.Write(conn.Id[:]) } func HandleUserElevateConnection(response http.ResponseWriter, request *http.Request) { diff --git a/packages/httpRequest/files.go b/packages/httpRequest/files.go index 1d2a576..65d221e 100644 --- a/packages/httpRequest/files.go +++ b/packages/httpRequest/files.go @@ -42,7 +42,11 @@ func HandleAttachmentFileUpload(response http.ResponseWriter, request *http.Requ defer file.Close() contentType := header.Header.Get("Content-Type") - key := minio.GetKey(conn.Id, contentType, minio.File) + key := minio.GetKey(minio.GetKeyOptions{ + ConnectionId: conn.Id, + MimeType: contentType, + UploadType: minio.File, + }) if err = minio.Upload(ctx, key, file, header.Size, contentType, map[string]string{ "originalName": header.Filename, @@ -85,7 +89,7 @@ func HandleGetUserAvatar(response http.ResponseWriter, request *http.Request) { return } - url, _, err := minio.GetDownloadUrlAndMetadata(ctx, string(minio.UserAvatarPrefix)+target.Avatar) + url, _, err := minio.GetDownloadUrlAndMetadata(ctx, target.Avatar) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return @@ -123,7 +127,7 @@ func HandleGetUserProfileBg(response http.ResponseWriter, request *http.Request) return } - url, _, err := minio.GetDownloadUrlAndMetadata(ctx, string(minio.UserProfileBgPrefix)+target.ProfileBg) + url, _, err := minio.GetDownloadUrlAndMetadata(ctx, target.ProfileBg) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return diff --git a/packages/httpRequest/user.go b/packages/httpRequest/user.go index 54ac9c1..c1870fd 100644 --- a/packages/httpRequest/user.go +++ b/packages/httpRequest/user.go @@ -209,11 +209,6 @@ func HandleUserModAvatar(response http.ResponseWriter, request *http.Request) { return } - conn, ok := getConnectionWithResponseOnFail(&response, request, user) - if !ok { - return - } - file, header, err := request.FormFile("file") if err != nil { http.Error(response, "missing file", http.StatusBadRequest) @@ -228,14 +223,18 @@ func HandleUserModAvatar(response http.ResponseWriter, request *http.Request) { } if user.Avatar != "" { - err = minio.Delete(ctx, string(minio.UserAvatarPrefix)+user.Avatar) + err = minio.Delete(ctx, user.Avatar) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } } - key := minio.GetKey(conn.Id, contentType, minio.File) + key := minio.GetKey(minio.GetKeyOptions{ + UserId: user.Id, + MimeType: contentType, + UploadType: minio.UserAvatar, + }) if err = minio.Upload(ctx, key, file, header.Size, contentType, map[string]string{ "originalName": header.Filename, "uploaderId": user.Id.String(), @@ -244,7 +243,7 @@ func HandleUserModAvatar(response http.ResponseWriter, request *http.Request) { return } - user.Avatar = key[len(minio.UserAvatarPrefix):] + user.Avatar = key err = postgresql.UserUpdateProfile(ctx, user, types.UserProfileUpdateList{Avatar: true}) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) @@ -273,11 +272,6 @@ func HandleUserModProfileBg(response http.ResponseWriter, request *http.Request) return } - conn, ok := getConnectionWithResponseOnFail(&response, request, user) - if !ok { - return - } - file, header, err := request.FormFile("file") if err != nil { http.Error(response, "missing file", http.StatusBadRequest) @@ -292,14 +286,18 @@ func HandleUserModProfileBg(response http.ResponseWriter, request *http.Request) } if user.ProfileBg != "" { - err = minio.Delete(ctx, string(minio.UserProfileBgPrefix)+user.ProfileBg) + err = minio.Delete(ctx, user.ProfileBg) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } } - key := minio.GetKey(conn.Id, contentType, minio.UserProfileBg) + key := minio.GetKey(minio.GetKeyOptions{ + UserId: user.Id, + MimeType: contentType, + UploadType: minio.UserProfileBg, + }) if err = minio.Upload(ctx, key, file, header.Size, contentType, map[string]string{ "originalName": header.Filename, "uploaderId": user.Id.String(), @@ -308,7 +306,7 @@ func HandleUserModProfileBg(response http.ResponseWriter, request *http.Request) return } - user.ProfileBg = key[len(minio.UserProfileBgPrefix):] + user.ProfileBg = key err = postgresql.UserUpdateProfile(ctx, user, types.UserProfileUpdateList{ProfileBg: true}) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) diff --git a/packages/minio/minio.go b/packages/minio/minio.go index 1a44e5c..5f3d3c9 100644 --- a/packages/minio/minio.go +++ b/packages/minio/minio.go @@ -33,20 +33,29 @@ const ( UserProfileBgPrefix DataTypePrefix = "userProfileBg/" ) -func GetKey(connectionId uuid.UUID, mimeType string, uploadType DataType) string { - extensions, err := mime.ExtensionsByType(mimeType) +type GetKeyOptions struct { + UserId uuid.UUID + ConnectionId uuid.UUID + MimeType string + UploadType DataType +} + +func GetKey(opts GetKeyOptions) string { + extensions, err := mime.ExtensionsByType(opts.MimeType) if err != nil || len(extensions) == 0 { extensions = []string{".unknown"} } - key := connectionId.String() + "/" + strconv.FormatInt(time.Now().UnixMilli(), 10) + extensions[0] + key := "/" + strconv.FormatInt(time.Now().UnixMilli(), 10) + extensions[0] - if uploadType == UserAvatar { - return string(UserAvatarPrefix) + key - } else if uploadType == UserProfileBg { - return string(UserProfileBgPrefix) + key + switch opts.UploadType { + case UserAvatar: + return string(UserAvatarPrefix) + opts.UserId.String() + key + case UserProfileBg: + return string(UserProfileBgPrefix) + opts.UserId.String() + key + default: + return string(FilePrefix) + opts.ConnectionId.String() + key } - return string(FilePrefix) + key } func Init(ctx context.Context) { diff --git a/packages/types/types.go b/packages/types/types.go index 3d5345d..7e925f3 100644 --- a/packages/types/types.go +++ b/packages/types/types.go @@ -30,8 +30,8 @@ type User struct { Pronouns string `json:"pronouns"` Description string `json:"description"` Avatar string `json:"avatar"` - ProfileBg string `json:"profileBg"` - PasswordHash string `json:"passwordHash"` + ProfileBg string `json:"profileBackground"` + PasswordHash string `json:"-"` CreatedAt time.Time `json:"createdAt"` WsConn *websocket.Conn `json:"-"` Id uuid.UUID `json:"-"`