From 01fa758e633133ea8a1d9018408386bc4eb0b691 Mon Sep 17 00:00:00 2001 From: trisua Date: Mon, 8 Sep 2025 23:09:16 -0400 Subject: [PATCH] add: ability to mute chats --- Cargo.lock | 2 +- Cargo.toml | 2 +- app/public/messages.js | 20 ++++++++ app/templates_src/manage.lisp | 21 +++++++- src/database/chats.rs | 5 +- src/database/sql/create_chats.sql | 4 +- src/database/sql/version_migrations.sql | 3 ++ src/model.rs | 2 + src/routes/api/chats.rs | 65 +++++++++++++++++++++++++ src/routes/api/messages.rs | 2 +- src/routes/api/mod.rs | 2 + 11 files changed, 121 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6795fef..f8b1864 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2998,7 +2998,7 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tawny" -version = "1.0.4" +version = "1.0.5" dependencies = [ "ammonia", "axum", diff --git a/Cargo.toml b/Cargo.toml index dca71de..77e0d7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tawny" -version = "1.0.4" +version = "1.0.5" edition = "2024" authors = ["trisuaso"] repository = "https://trisua.com/t/tawny" diff --git a/app/public/messages.js b/app/public/messages.js index a4a5b2a..2354ca1 100644 --- a/app/public/messages.js +++ b/app/public/messages.js @@ -441,3 +441,23 @@ function clear_notifications() { } }); } + +function mute_chat() { + fetch(`/api/v1/chats/${STATE.chat_id}/mute`, { + method: "POST", + }) + .then((res) => res.json()) + .then((res) => { + show_message(res.message, res.ok); + }); +} + +function unmute_chat() { + fetch(`/api/v1/chats/${STATE.chat_id}/mute`, { + method: "DELETE", + }) + .then((res) => res.json()) + .then((res) => { + show_message(res.message, res.ok); + }); +} diff --git a/app/templates_src/manage.lisp b/app/templates_src/manage.lisp index 006a1c9..932b76d 100644 --- a/app/templates_src/manage.lisp +++ b/app/templates_src/manage.lisp @@ -41,7 +41,20 @@ (a ("class" "button surface") ("href" "/chats/{{ chat.id }}/pins") - (text "{{ icon \"pin\" }} view pins"))) + (text "{{ icon \"pin\" }} view pins")) + + ; mute + (text "{% if not user.id in chat.mutes -%}") + (button + ("class" "button surface") + ("onclick" "mute_chat()") + (text "{{ icon \"bell-off\" }} mute")) + (text "{% else %}") + (button + ("class" "button surface") + ("onclick" "unmute_chat()") + (text "{{ icon \"bell-ring\" }} unmute")) + (text "{%- endif %}")) (ul (li (b (text "Chat name: ")) (span (text "{{ components::chat_name(chat=chat, members=members) }}"))) @@ -78,7 +91,10 @@ ("class" "flush flex items_center gap_ch") ("href" "/@{{ member.username }}") (text "{{ components::avatar(id=member.id) }}") - (text "{{ components::username(user=member) }}")) + (text "{{ components::username(user=member) }}") + (text "{% if member.id in chat.mutes -%}") + (text "{{ icon \"bell-off\" }}") + (text "{%- endif %}")) (span (text "{% if member.settings.status|length > 0 -%} {{ member.settings.status|markdown|safe }} {%- else -%} No status {%- endif %}")) (text "{% if is_owner -%}") (button @@ -121,4 +137,5 @@ (datalist ("id" "users_search")) (script ("src" "/public/messages.js")) +(script (text "STATE.chat_id = '{{ chat.id }}';")) (text "{% endblock %}") diff --git a/src/database/chats.rs b/src/database/chats.rs index ae4ab01..a14bd99 100644 --- a/src/database/chats.rs +++ b/src/database/chats.rs @@ -17,6 +17,7 @@ impl DataManager { last_message_created: get!(x->4(i64)) as usize, last_message_read_by: serde_json::from_str(&get!(x->5(String))).unwrap(), pinned_messages: serde_json::from_str(&get!(x->6(String))).unwrap(), + mutes: serde_json::from_str(&get!(x->7(String))).unwrap(), } } @@ -121,7 +122,7 @@ impl DataManager { let res = execute!( &conn, - "INSERT INTO t_chats VALUES ($1, $2, $3, $4, $5, $6, $7)", + "INSERT INTO t_chats VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", params![ &(data.id as i64), &(data.created as i64), @@ -130,6 +131,7 @@ impl DataManager { &(data.last_message_created as i64), &serde_json::to_string(&data.last_message_read_by).unwrap(), &serde_json::to_string(&data.pinned_messages).unwrap(), + &serde_json::to_string(&data.mutes).unwrap(), ] ); @@ -178,4 +180,5 @@ impl DataManager { auto_method!(update_chat_last_message_created(i64) -> "UPDATE t_chats SET last_message_created = $1 WHERE id = $2" --cache-key-tmpl="twny.chat:{}"); auto_method!(update_chat_last_message_read_by(Vec) -> "UPDATE t_chats SET last_message_read_by = $1 WHERE id = $2" --serde --cache-key-tmpl="twny.chat:{}"); auto_method!(update_chat_pinned_messages(Vec) -> "UPDATE t_chats SET pinned_messages = $1 WHERE id = $2" --serde --cache-key-tmpl="twny.chat:{}"); + auto_method!(update_chat_mutes(Vec) -> "UPDATE t_chats SET mutes = $1 WHERE id = $2" --serde --cache-key-tmpl="twny.chat:{}"); } diff --git a/src/database/sql/create_chats.sql b/src/database/sql/create_chats.sql index 2150fb7..8c0144a 100644 --- a/src/database/sql/create_chats.sql +++ b/src/database/sql/create_chats.sql @@ -4,5 +4,7 @@ CREATE TABLE IF NOT EXISTS t_chats ( style TEXT NOT NULL, members TEXT NOT NULL, last_message_created BIGINT NOT NULL, - last_message_read_by TEXT NOT NULL + last_message_read_by TEXT NOT NULL, + pinned_messages TEXT NOT NULL, + mutes TEXT NOT NULL ); diff --git a/src/database/sql/version_migrations.sql b/src/database/sql/version_migrations.sql index b17b4a6..ca65a25 100644 --- a/src/database/sql/version_migrations.sql +++ b/src/database/sql/version_migrations.sql @@ -3,3 +3,6 @@ ALTER TABLE t_chats ADD COLUMN IF NOT EXISTS pinned_messages TEXT NOT NULL DEFAU -- messages replying_to ALTER TABLE t_messages ADD COLUMN IF NOT EXISTS replying_to BIGINT NOT NULL DEFAULT 0; + +-- chats mutes +ALTER TABLE t_chats ADD COLUMN IF NOT EXISTS mutes TEXT NOT NULL DEFAULT '[]'; diff --git a/src/model.rs b/src/model.rs index da784ee..1e1ccc4 100644 --- a/src/model.rs +++ b/src/model.rs @@ -33,6 +33,7 @@ pub struct Chat { /// the UI and prevent every message showing a read receipt. pub last_message_read_by: Vec, pub pinned_messages: Vec, + pub mutes: Vec, } impl Chat { @@ -46,6 +47,7 @@ impl Chat { last_message_created: 0, last_message_read_by: Vec::new(), pinned_messages: Vec::new(), + mutes: Vec::new(), } } diff --git a/src/routes/api/chats.rs b/src/routes/api/chats.rs index d0c9a8e..3a9201b 100644 --- a/src/routes/api/chats.rs +++ b/src/routes/api/chats.rs @@ -580,3 +580,68 @@ pub async fn clear_chat_notifications( payload: (), }) } + +pub async fn mute_request( + jar: CookieJar, + Extension(data): Extension, + Path(id): Path, +) -> impl IntoResponse { + let data = &(data.read().await).0; + let user = match get_user_from_token!(jar, data.2) { + Some(x) => x, + None => return Json(Error::NotAllowed.into()), + }; + + // ... + let mut chat = match data.get_chat_by_id(id).await { + Ok(x) => x, + Err(e) => return Json(e.into()), + }; + + if !chat.members.contains(&user.id) || chat.mutes.contains(&user.id) { + return Json(Error::NotAllowed.into()); + } + + chat.mutes.push(user.id); + match data.update_chat_mutes(chat.id, chat.mutes).await { + Ok(_) => Json(ApiReturn { + ok: true, + message: "Success".to_string(), + payload: (), + }), + Err(e) => Json(e.into()), + } +} + +pub async fn unmute_request( + jar: CookieJar, + Extension(data): Extension, + Path(id): Path, +) -> impl IntoResponse { + let data = &(data.read().await).0; + let user = match get_user_from_token!(jar, data.2) { + Some(x) => x, + None => return Json(Error::NotAllowed.into()), + }; + + let mut chat = match data.get_chat_by_id(id).await { + Ok(x) => x, + Err(e) => return Json(e.into()), + }; + + if !chat.members.contains(&user.id) || !chat.mutes.contains(&user.id) { + return Json(Error::NotAllowed.into()); + } + + chat.mutes + .remove(chat.mutes.iter().position(|x| *x == user.id).unwrap()); + + match data.update_chat_mutes(chat.id, chat.mutes).await { + Ok(_) => Json(ApiReturn { + ok: true, + message: "Success".to_string(), + payload: (), + }), + Err(e) => Json(e.into()), + } +} diff --git a/src/routes/api/messages.rs b/src/routes/api/messages.rs index 5bebb8a..c068193 100644 --- a/src/routes/api/messages.rs +++ b/src/routes/api/messages.rs @@ -114,7 +114,7 @@ pub async fn create_request( // update users for member in chat.members { - if member == user.id { + if member == user.id || chat.mutes.contains(&member) { continue; } diff --git a/src/routes/api/mod.rs b/src/routes/api/mod.rs index 813b4c3..4dfcdd5 100644 --- a/src/routes/api/mod.rs +++ b/src/routes/api/mod.rs @@ -42,6 +42,8 @@ pub fn routes() -> Router { "/chats/{id}/notifications", delete(chats::clear_chat_notifications), ) + .route("/chats/{id}/mute", post(chats::mute_request)) + .route("/chats/{id}/mute", delete(chats::unmute_request)) // messages .route("/messages/{id}", post(messages::create_request)) .route("/messages/{id}", delete(messages::delete_request))