From ba319130d25ea5655afccfc6fe8cb4ed7da887cf Mon Sep 17 00:00:00 2001 From: trisua Date: Sun, 10 Aug 2025 22:26:13 -0400 Subject: [PATCH] add: allow mods to remove associations --- crates/app/src/public/html/components.lisp | 4 +- crates/app/src/public/html/mod/profile.lisp | 20 +++++---- crates/app/src/routes/api/v1/auth/profile.rs | 46 +++++++++++++++++++- crates/app/src/routes/api/v1/mod.rs | 6 ++- 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/crates/app/src/public/html/components.lisp b/crates/app/src/public/html/components.lisp index a0e89bb..609732d 100644 --- a/crates/app/src/public/html/components.lisp +++ b/crates/app/src/public/html/components.lisp @@ -1350,9 +1350,9 @@ (text "Listening to ")) (text "{{ other_user.connections.Spotify[1].data.artist }}"))) -(text "{%- endif %} {%- endmacro %} {% macro user_plate(user, show_menu=false, show_kick=false, secondary=false) -%}") +(text "{%- endif %} {%- endmacro %} {% macro user_plate(user, show_menu=false, show_kick=false, secondary=false, full=false) -%}") (div - ("class" "flex gap_2 items_center card tiny user_plate {% if secondary -%}secondary{%- endif %}") + ("class" "flex gap_2 items_center card tiny user_plate {% if secondary -%}secondary{%- endif %} {% if full -%} w_full {%- endif %}") (a ("href" "/@{{ user.username }}") (text "{{ self::avatar(username=user.username, size=\"42px\", selector_type=\"username\") }}")) diff --git a/crates/app/src/public/html/mod/profile.lisp b/crates/app/src/public/html/mod/profile.lisp index 8dcf616..7e12f8c 100644 --- a/crates/app/src/public/html/mod/profile.lisp +++ b/crates/app/src/public/html/mod/profile.lisp @@ -84,7 +84,7 @@ const ui = await ns(\"ui\"); const element = document.getElementById(\"mod_options\"); - globalThis.profile_request = async (do_confirm, path, body) => { + globalThis.profile_request = async (do_confirm, path, body = null, headers = { \"Content-Type\": \"application/json\" }, method = \"POST\") => { if (do_confirm) { if ( !(await trigger(\"atto::confirm\", [ @@ -96,11 +96,9 @@ } fetch(`/api/v1/auth/user/{{ profile.id }}/${path}`, { - method: \"POST\", - headers: { - \"Content-Type\": \"application/json\", - }, - body: JSON.stringify(body), + method, + headers: headers != null ? headers : undefined, + body: body != null ? JSON.stringify(body) : undefined, }) .then((res) => res.json()) .then((res) => { @@ -265,9 +263,15 @@ (span (text "{{ text \"mod_panel:label.associations\" }}")))) (div - ("class" "card lowered flex flex_wrap gap_2") + ("class" "card flex flex_wrap gap_4 flex_collapse") (text "{% for user in associations -%}") - (text "{{ components::user_plate(user=user, show_menu=false) }}") + (div + ("class" "flex flex_row gap_2 items_center card small secondary") + (text "{{ components::user_plate(user=user, show_menu=false, secondary=true, full=true) }}") + (button + ("class" "small square red lowered") + ("onclick" "profile_request(true, 'associations/{{ user.id }}', null, null, 'DELETE')") + (icon (text "x")))) (text "{%- endfor %}"))) (text "{% if invite -%}") (div diff --git a/crates/app/src/routes/api/v1/auth/profile.rs b/crates/app/src/routes/api/v1/auth/profile.rs index cb4955d..5323b33 100644 --- a/crates/app/src/routes/api/v1/auth/profile.rs +++ b/crates/app/src/routes/api/v1/auth/profile.rs @@ -283,7 +283,7 @@ pub async fn remove_applied_configuration_request( } /// Append associations to the current user. -pub async fn append_associations_request( +pub async fn append_association_request( jar: CookieJar, Extension(data): Extension, Json(req): Json, @@ -331,6 +331,50 @@ pub async fn append_associations_request( } } +/// Remove an association from the given user. +pub async fn remove_association_request( + jar: CookieJar, + Extension(data): Extension, + Path((uid, association)): Path<(usize, usize)>, +) -> impl IntoResponse { + let data = &(data.read().await).0; + let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageProfile) { + Some(ua) => ua, + None => return Json(Error::NotAllowed.into()), + }; + + if !user.permissions.check(FinePermission::MANAGE_USERS) { + return Json(Error::NotAllowed.into()); + } + + // get user + let mut other_user = match data.get_user_by_id(uid).await { + Ok(x) => x, + Err(e) => return Json(e.into()), + }; + + // find association and remove + other_user.associated.remove( + match other_user.associated.iter().position(|x| x == &association) { + Some(x) => x, + None => return Json(Error::GeneralNotFound("association".to_string()).into()), + }, + ); + + // ... + match data + .update_user_associated(other_user.id, other_user.associated) + .await + { + Ok(_) => Json(ApiReturn { + ok: true, + message: "Associations updated".to_string(), + payload: (), + }), + Err(e) => Json(e.into()), + } +} + /// Update the password of the given user. /// /// Does not support third-party grants. diff --git a/crates/app/src/routes/api/v1/mod.rs b/crates/app/src/routes/api/v1/mod.rs index 4a64b2f..e6df9df 100644 --- a/crates/app/src/routes/api/v1/mod.rs +++ b/crates/app/src/routes/api/v1/mod.rs @@ -397,6 +397,10 @@ pub fn routes() -> Router { "/auth/user/{id}/totp/codes", post(auth::profile::refresh_totp_codes_request), ) + .route( + "/auth/user/{id}/associations/{association}", + delete(auth::profile::remove_association_request), + ) .route( "/auth/user/{username}/totp/check", get(auth::profile::has_totp_enabled_request), @@ -404,7 +408,7 @@ pub fn routes() -> Router { .route("/auth/user/me/seen", post(auth::profile::seen_request)) .route( "/auth/user/me/append_associations", - put(auth::profile::append_associations_request), + put(auth::profile::append_association_request), ) .route("/auth/user/find/{id}", get(auth::profile::redirect_from_id)) .route(