add: ability to mute chats

This commit is contained in:
trisua 2025-09-08 23:09:16 -04:00
parent ff1c739367
commit 01fa758e63
11 changed files with 121 additions and 7 deletions

2
Cargo.lock generated
View file

@ -2998,7 +2998,7 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tawny"
version = "1.0.4"
version = "1.0.5"
dependencies = [
"ammonia",
"axum",

View file

@ -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"

View file

@ -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);
});
}

View file

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

View file

@ -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<usize>) -> "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<usize>) -> "UPDATE t_chats SET pinned_messages = $1 WHERE id = $2" --serde --cache-key-tmpl="twny.chat:{}");
auto_method!(update_chat_mutes(Vec<usize>) -> "UPDATE t_chats SET mutes = $1 WHERE id = $2" --serde --cache-key-tmpl="twny.chat:{}");
}

View file

@ -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
);

View file

@ -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 '[]';

View file

@ -33,6 +33,7 @@ pub struct Chat {
/// the UI and prevent every message showing a read receipt.
pub last_message_read_by: Vec<usize>,
pub pinned_messages: Vec<usize>,
pub mutes: Vec<usize>,
}
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(),
}
}

View file

@ -580,3 +580,68 @@ pub async fn clear_chat_notifications(
payload: (),
})
}
pub async fn mute_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
) -> 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<State>,
Path(id): Path<usize>,
) -> 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()),
}
}

View file

@ -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;
}

View file

@ -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))