From 2a77c61bf235689390f8aa530c906d93a8997634 Mon Sep 17 00:00:00 2001 From: trisua Date: Sun, 22 Jun 2025 21:07:35 -0400 Subject: [PATCH] add: ability to add user to stack through block list ui --- crates/app/src/assets.rs | 2 + crates/app/src/langs/en-US.toml | 1 + crates/app/src/public/html/body.lisp | 15 +----- .../app/src/public/html/profile/settings.lisp | 19 ++++--- .../app/src/public/html/stacks/add_user.lisp | 49 +++++++++++++++++++ crates/app/src/public/html/stacks/manage.lisp | 2 +- crates/app/src/public/js/me.js | 19 +++++++ crates/app/src/routes/api/v1/stacks.rs | 4 ++ crates/app/src/routes/pages/mod.rs | 1 + crates/app/src/routes/pages/stacks.rs | 38 ++++++++++++++ 10 files changed, 130 insertions(+), 20 deletions(-) create mode 100644 crates/app/src/public/html/stacks/add_user.lisp diff --git a/crates/app/src/assets.rs b/crates/app/src/assets.rs index 130b4fc..a556acc 100644 --- a/crates/app/src/assets.rs +++ b/crates/app/src/assets.rs @@ -117,6 +117,7 @@ pub const CHATS_CHANNELS: &str = include_str!("./public/html/chats/channels.lisp pub const STACKS_LIST: &str = include_str!("./public/html/stacks/list.lisp"); pub const STACKS_FEED: &str = include_str!("./public/html/stacks/feed.lisp"); pub const STACKS_MANAGE: &str = include_str!("./public/html/stacks/manage.lisp"); +pub const STACKS_ADD_USER: &str = include_str!("./public/html/stacks/add_user.lisp"); pub const FORGE_HOME: &str = include_str!("./public/html/forge/home.lisp"); pub const FORGE_BASE: &str = include_str!("./public/html/forge/base.lisp"); @@ -408,6 +409,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD { write_template!(html_path->"stacks/list.html"(crate::assets::STACKS_LIST) -d "stacks" --config=config --lisp plugins); write_template!(html_path->"stacks/feed.html"(crate::assets::STACKS_FEED) --config=config --lisp plugins); write_template!(html_path->"stacks/manage.html"(crate::assets::STACKS_MANAGE) --config=config --lisp plugins); + write_template!(html_path->"stacks/add_user.html"(crate::assets::STACKS_ADD_USER) --config=config --lisp plugins); write_template!(html_path->"forge/home.html"(crate::assets::FORGE_HOME) -d "forge" --config=config --lisp plugins); write_template!(html_path->"forge/base.html"(crate::assets::FORGE_BASE) --config=config --lisp plugins); diff --git a/crates/app/src/langs/en-US.toml b/crates/app/src/langs/en-US.toml index 27c4b8f..2ad3e9d 100644 --- a/crates/app/src/langs/en-US.toml +++ b/crates/app/src/langs/en-US.toml @@ -169,6 +169,7 @@ version = "1.0.0" "settings:label.manage_blocks" = "Manage blocks" "settings:label.users" = "Users" "settings:label.generate_invite" = "Generate invite" +"settings:label.add_to_stack" = "Add to stack" "settings:tab.security" = "Security" "settings:tab.blocks" = "Blocks" "settings:tab.billing" = "Billing" diff --git a/crates/app/src/public/html/body.lisp b/crates/app/src/public/html/body.lisp index 370a608..1aeddea 100644 --- a/crates/app/src/public/html/body.lisp +++ b/crates/app/src/public/html/body.lisp @@ -312,22 +312,11 @@ if (playing.error) { // refresh token - const [new_token, new_refresh_token, expires_in] = - await trigger(\"spotify::refresh_token\", [ - client_id, + const [new_token, new_refresh_token] = + await trigger(\"spotify::refresh\", [ refresh_token, ]); - await trigger(\"connections::push_con_data\", [ - \"Spotify\", - { - token: new_token, - refresh_token: new_refresh_token, - expires_in: expires_in.toString(), - name: profile.display_name, - }, - ]); - token = new_token; refresh_token = new_refresh_token; return; diff --git a/crates/app/src/public/html/profile/settings.lisp b/crates/app/src/public/html/profile/settings.lisp index 489805c..b0db773 100644 --- a/crates/app/src/public/html/profile/settings.lisp +++ b/crates/app/src/public/html/profile/settings.lisp @@ -433,12 +433,19 @@ (div ("class" "flex gap-2") (text "{{ components::avatar(username=user.username) }} {{ components::full_username(user=user) }}")) - (a - ("href" "/@{{ user.username }}") - ("class" "button lowered small") - (text "{{ icon \"external-link\" }}") - (span - (text "{{ text \"requests:action.view_profile\" }}")))) + (div + ("class" "flex gap-2") + (a + ("href" "/stacks/add_user/{{ user.id }}") + ("target" "_blank") + ("class" "button lowered small") + (icon (text "plus")) + (span (str (text "settings:label.add_to_stack")))) + (a + ("href" "/@{{ user.username }}") + ("class" "button lowered small") + (icon (text "external-link")) + (span (str (text "requests:action.view_profile")))))) (text "{% endfor %}"))))) (div ("class" "w-full flex flex-col gap-2 hidden") diff --git a/crates/app/src/public/html/stacks/add_user.lisp b/crates/app/src/public/html/stacks/add_user.lisp new file mode 100644 index 0000000..214decd --- /dev/null +++ b/crates/app/src/public/html/stacks/add_user.lisp @@ -0,0 +1,49 @@ +(text "{% extends \"root.html\" %} {% block head %}") +(title + (text "Add user to stack - {{ config.name }}")) + +(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"\") }}") +(main + ("class" "flex flex-col gap-2") + (div + ("class" "card-nest") + (div + ("class" "card small flex items-center gap-2") + (text "{{ components::avatar(username=add_user.username, size=\"24px\") }}") + (text "{{ components::full_username(user=add_user) }}")) + (div + ("class" "card flex flex-col gap-2") + (span (text "Select a stack to add this user to:")) + (text "{% for stack in stacks %}") + (button + ("class" "justify-start lowered w-full") + ("onclick" "choose_stack('{{ stack.id }}')") + (icon (text "layers")) + (text "{{ stack.name }}")) + (text "{% endfor %}")))) + +(script + (text "function choose_stack(id) { + fetch(`/api/v1/stacks/${id}/users`, { + method: \"POST\", + headers: { + \"Content-Type\": \"application/json\", + }, + body: JSON.stringify({ + username: \"{{ add_user.username }}\", + }), + }) + .then((res) => res.json()) + .then((res) => { + trigger(\"atto::toast\", [ + res.ok ? \"success\" : \"error\", + res.message, + ]); + + if (res.ok) { + window.close(); + } + }); + }")) + +(text "{% endblock %}") diff --git a/crates/app/src/public/html/stacks/manage.lisp b/crates/app/src/public/html/stacks/manage.lisp index 85c4251..450c027 100644 --- a/crates/app/src/public/html/stacks/manage.lisp +++ b/crates/app/src/public/html/stacks/manage.lisp @@ -173,7 +173,7 @@ return; } - fetch(`/api/v1/stacks/{{ stack.id }}/users`, { + fetch(\"/api/v1/stacks/{{ stack.id }}/users\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\", diff --git a/crates/app/src/public/js/me.js b/crates/app/src/public/js/me.js index fc99248..5dbc322 100644 --- a/crates/app/src/public/js/me.js +++ b/crates/app/src/public/js/me.js @@ -806,6 +806,25 @@ return [access_token, refresh_token, expires_in]; }); + self.define("refresh", async (_, refresh_token) => { + const [new_token, new_refresh_token, expires_in] = await trigger( + "spotify::refresh_token", + [client_id, refresh_token], + ); + + await trigger("connections::push_con_data", [ + "Spotify", + { + token: new_token, + refresh_token: new_refresh_token, + expires_in: expires_in.toString(), + name: profile.display_name, + }, + ]); + + return [new_token, refresh_token]; + }); + self.define("profile", async (_, token) => { return await ( await fetch("https://api.spotify.com/v1/me", { diff --git a/crates/app/src/routes/api/v1/stacks.rs b/crates/app/src/routes/api/v1/stacks.rs index d3979a2..1fe5c87 100644 --- a/crates/app/src/routes/api/v1/stacks.rs +++ b/crates/app/src/routes/api/v1/stacks.rs @@ -211,6 +211,10 @@ pub async fn add_user_request( Err(e) => return Json(e.into()), }; + if stack.users.contains(&other_user.id) { + return Json(Error::MiscError("This user is already in this stack".to_string()).into()); + } + stack.users.push(other_user.id); // check number of stacks diff --git a/crates/app/src/routes/pages/mod.rs b/crates/app/src/routes/pages/mod.rs index 9115f65..b59dce8 100644 --- a/crates/app/src/routes/pages/mod.rs +++ b/crates/app/src/routes/pages/mod.rs @@ -131,6 +131,7 @@ pub fn routes() -> Router { .route("/stacks", get(stacks::list_request)) .route("/stacks/{id}", get(stacks::feed_request)) .route("/stacks/{id}/manage", get(stacks::manage_request)) + .route("/stacks/add_user/{id}", get(stacks::add_user_request)) // journals .route("/journals", get(journals::redirect_request)) .route("/journals/{journal}/{note}", get(journals::app_request)) diff --git a/crates/app/src/routes/pages/stacks.rs b/crates/app/src/routes/pages/stacks.rs index a7b33d8..e8285e9 100644 --- a/crates/app/src/routes/pages/stacks.rs +++ b/crates/app/src/routes/pages/stacks.rs @@ -157,3 +157,41 @@ pub async fn manage_request( // return Ok(Html(data.1.render("stacks/manage.html", &context).unwrap())) } + +/// `/stacks/add_user` +pub async fn add_user_request( + jar: CookieJar, + Path(id): Path, + Extension(data): Extension, +) -> impl IntoResponse { + let data = data.read().await; + let user = match get_user_from_token!(jar, data.0) { + Some(ua) => ua, + None => { + return Err(Html( + render_error(Error::NotAllowed, &jar, &data, &None).await, + )); + } + }; + + let add_user = match data.0.get_user_by_id(id).await { + Ok(ua) => ua, + Err(e) => return Err(Html(render_error(e, &jar, &data, &None).await)), + }; + + let stacks = match data.0.get_stacks_by_user(user.id).await { + Ok(p) => p, + Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)), + }; + + let lang = get_lang!(jar, data.0); + let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await; + + context.insert("stacks", &stacks); + context.insert("add_user", &add_user); + + // return + Ok(Html( + data.1.render("stacks/add_user.html", &context).unwrap(), + )) +}