add: ability to add user to stack through block list ui

This commit is contained in:
trisua 2025-06-22 21:07:35 -04:00
parent 8c969cd56f
commit 2a77c61bf2
10 changed files with 130 additions and 20 deletions

View file

@ -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_LIST: &str = include_str!("./public/html/stacks/list.lisp");
pub const STACKS_FEED: &str = include_str!("./public/html/stacks/feed.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_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_HOME: &str = include_str!("./public/html/forge/home.lisp");
pub const FORGE_BASE: &str = include_str!("./public/html/forge/base.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/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/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/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/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); write_template!(html_path->"forge/base.html"(crate::assets::FORGE_BASE) --config=config --lisp plugins);

View file

@ -169,6 +169,7 @@ version = "1.0.0"
"settings:label.manage_blocks" = "Manage blocks" "settings:label.manage_blocks" = "Manage blocks"
"settings:label.users" = "Users" "settings:label.users" = "Users"
"settings:label.generate_invite" = "Generate invite" "settings:label.generate_invite" = "Generate invite"
"settings:label.add_to_stack" = "Add to stack"
"settings:tab.security" = "Security" "settings:tab.security" = "Security"
"settings:tab.blocks" = "Blocks" "settings:tab.blocks" = "Blocks"
"settings:tab.billing" = "Billing" "settings:tab.billing" = "Billing"

View file

@ -312,22 +312,11 @@
if (playing.error) { if (playing.error) {
// refresh token // refresh token
const [new_token, new_refresh_token, expires_in] = const [new_token, new_refresh_token] =
await trigger(\"spotify::refresh_token\", [ await trigger(\"spotify::refresh\", [
client_id,
refresh_token, 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; token = new_token;
refresh_token = new_refresh_token; refresh_token = new_refresh_token;
return; return;

View file

@ -433,12 +433,19 @@
(div (div
("class" "flex gap-2") ("class" "flex gap-2")
(text "{{ components::avatar(username=user.username) }} {{ components::full_username(user=user) }}")) (text "{{ components::avatar(username=user.username) }} {{ components::full_username(user=user) }}"))
(a (div
("href" "/@{{ user.username }}") ("class" "flex gap-2")
("class" "button lowered small") (a
(text "{{ icon \"external-link\" }}") ("href" "/stacks/add_user/{{ user.id }}")
(span ("target" "_blank")
(text "{{ text \"requests:action.view_profile\" }}")))) ("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 %}"))))) (text "{% endfor %}")))))
(div (div
("class" "w-full flex flex-col gap-2 hidden") ("class" "w-full flex flex-col gap-2 hidden")

View file

@ -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 %}")

View file

@ -173,7 +173,7 @@
return; return;
} }
fetch(`/api/v1/stacks/{{ stack.id }}/users`, { fetch(\"/api/v1/stacks/{{ stack.id }}/users\", {
method: \"POST\", method: \"POST\",
headers: { headers: {
\"Content-Type\": \"application/json\", \"Content-Type\": \"application/json\",

View file

@ -806,6 +806,25 @@
return [access_token, refresh_token, expires_in]; 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) => { self.define("profile", async (_, token) => {
return await ( return await (
await fetch("https://api.spotify.com/v1/me", { await fetch("https://api.spotify.com/v1/me", {

View file

@ -211,6 +211,10 @@ pub async fn add_user_request(
Err(e) => return Json(e.into()), 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); stack.users.push(other_user.id);
// check number of stacks // check number of stacks

View file

@ -131,6 +131,7 @@ pub fn routes() -> Router {
.route("/stacks", get(stacks::list_request)) .route("/stacks", get(stacks::list_request))
.route("/stacks/{id}", get(stacks::feed_request)) .route("/stacks/{id}", get(stacks::feed_request))
.route("/stacks/{id}/manage", get(stacks::manage_request)) .route("/stacks/{id}/manage", get(stacks::manage_request))
.route("/stacks/add_user/{id}", get(stacks::add_user_request))
// journals // journals
.route("/journals", get(journals::redirect_request)) .route("/journals", get(journals::redirect_request))
.route("/journals/{journal}/{note}", get(journals::app_request)) .route("/journals/{journal}/{note}", get(journals::app_request))

View file

@ -157,3 +157,41 @@ pub async fn manage_request(
// return // return
Ok(Html(data.1.render("stacks/manage.html", &context).unwrap())) Ok(Html(data.1.render("stacks/manage.html", &context).unwrap()))
} }
/// `/stacks/add_user`
pub async fn add_user_request(
jar: CookieJar,
Path(id): Path<usize>,
Extension(data): Extension<State>,
) -> 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(),
))
}