generated from t/malachite
add: chat pins
This commit is contained in:
parent
9546c580e7
commit
82eafdadb3
21 changed files with 330 additions and 56 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2998,7 +2998,7 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|||
|
||||
[[package]]
|
||||
name = "tawny"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"axum",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "tawny"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
edition = "2024"
|
||||
authors = ["trisuaso"]
|
||||
repository = "https://trisua.com/t/tawny"
|
||||
|
@ -30,7 +30,10 @@ glob = "0.3.2"
|
|||
serde_json = "1.0.142"
|
||||
toml = "0.9.4"
|
||||
regex = "1.11.1"
|
||||
oiseau = { version = "0.1.2", default-features = false, features = ["postgres", "redis",] }
|
||||
oiseau = { version = "0.1.2", default-features = false, features = [
|
||||
"postgres",
|
||||
"redis",
|
||||
] }
|
||||
buckets-core = "1.0.4"
|
||||
axum-image = "0.1.1"
|
||||
futures-util = "0.3.31"
|
||||
|
|
|
@ -326,3 +326,27 @@ function create_direct_chat_with_user(id) {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
function pin_message(e, id) {
|
||||
fetch(`/api/v1/chats/${STATE.chat_id}/pins/${id}`, {
|
||||
method: "POST",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
show_message(res.message, res.ok);
|
||||
|
||||
if (res.ok) {
|
||||
e.target.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function unpin_message(id) {
|
||||
fetch(`/api/v1/chats/${STATE.chat_id}/pins/${id}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
show_message(res.message, res.ok);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -116,6 +116,10 @@ article {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.fadein {
|
||||
animation: fadein ease-in-out 1 0.5s forwards running;
|
||||
}
|
||||
|
@ -801,6 +805,7 @@ menu.col {
|
|||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
border-radius: var(--radius);
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.message.mine .body {
|
||||
|
@ -812,3 +817,8 @@ menu.col {
|
|||
.message:not(.mine) .body {
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.message:hover .dropdown.hidden,
|
||||
.message:focus .dropdown.hidden {
|
||||
display: flex !important;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
(a
|
||||
("class" "button tab camo")
|
||||
("href" "/chats")
|
||||
(text "chats")
|
||||
(text "{{ icon \"castle\" }} chats")
|
||||
(text "{% if user.missed_messages_count > 0 -%}") (b (text "({{ user.missed_messages_count }})")) (text "{%- endif %}"))
|
||||
(a
|
||||
("class" "button tab")
|
||||
|
@ -18,7 +18,7 @@
|
|||
(a
|
||||
("class" "button tab camo")
|
||||
("href" "/chats/{{ chat.id }}/manage")
|
||||
(text "{{ icon \"settings-2\" }} Manage"))))
|
||||
(text "{{ icon \"settings-2\" }} manage"))))
|
||||
(div
|
||||
("class" "flex flex_col card_nest reverse")
|
||||
("style" "flex: 1 0 auto")
|
||||
|
@ -29,10 +29,6 @@
|
|||
("class" "card flex flex_rev_col gap_2")
|
||||
("style" "flex: 1 0 auto")
|
||||
("id" "messages_stream")
|
||||
(text "{% if messages|length == 0 -%}")
|
||||
(i ("class" "flex gap_ch items_center fade") (text "{{ icon \"star\" }} This is the start of the chat!"))
|
||||
(text "{%- endif %}")
|
||||
|
||||
(div ("ui_ident" "data_marker")))
|
||||
(div ("id" "read_receipt_zone") ("class" "card") ("style" "min-height: 32.5px; position: sticky; bottom: 0")))
|
||||
(form
|
||||
|
@ -57,5 +53,5 @@
|
|||
|
||||
setTimeout(() => {
|
||||
scroll_bottom();
|
||||
}, 500);"))
|
||||
}, 1500);"))
|
||||
(text "{% endblock %}")
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
(a
|
||||
("class" "button tab")
|
||||
("href" "/chats")
|
||||
(text "chats")
|
||||
(text "{{ icon \"castle\" }} chats")
|
||||
(text "{% if user.missed_messages_count > 0 -%}") (b (text "({{ user.missed_messages_count }})")) (text "{%- endif %}")))
|
||||
(button
|
||||
("class" "button square")
|
||||
|
|
|
@ -41,20 +41,27 @@
|
|||
(text "{%- endif %}")
|
||||
(text "{%- endmacro %}")
|
||||
|
||||
(text "{% macro message(message) -%}")
|
||||
(text "{% macro message(message, is_pinned=false) -%}")
|
||||
(div
|
||||
("class" "flex w_full gap_ch message {%- if user.id == message.owner %} justify_right mine {%- endif %}")
|
||||
("id" "message_{{ message.id }}")
|
||||
(text "{% if message.owner == user.id -%}")
|
||||
(div
|
||||
("class" "dropdown")
|
||||
("class" "dropdown hidden")
|
||||
(button
|
||||
("onclick" "open_dropdown(event)")
|
||||
("exclude" "dropdown")
|
||||
("class" "button")
|
||||
("class" "button icon_only big_icon")
|
||||
(text "{{ icon \"ellipsis\" }}"))
|
||||
(div
|
||||
("class" "inner surface")
|
||||
(text "{% if not is_pinned -%}")
|
||||
(button
|
||||
("class" "button surface")
|
||||
("onclick" "pin_message(event, '{{ message.id }}')")
|
||||
(text "pin"))
|
||||
(text "{%- endif %}")
|
||||
|
||||
(text "{% if message.owner == user.id -%}")
|
||||
(button
|
||||
("class" "button surface")
|
||||
("onclick" "edit_message_ui('{{ message.id }}')")
|
||||
|
@ -62,8 +69,8 @@
|
|||
(button
|
||||
("class" "button surface red")
|
||||
("onclick" "delete_message('{{ message.id }}')")
|
||||
(text "delete"))))
|
||||
(text "{%- endif %}")
|
||||
(text "delete"))
|
||||
(text "{%- endif %}")))
|
||||
|
||||
(div
|
||||
("class" "body no_p_margin")
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
(a
|
||||
("class" "button tab camo")
|
||||
("href" "/chats")
|
||||
(text "chats"))
|
||||
(text "{{ icon \"castle\" }} chats"))
|
||||
(a
|
||||
("class" "button tab camo")
|
||||
("href" "/chats/{{ chat.id }}")
|
||||
|
@ -17,24 +17,31 @@
|
|||
(a
|
||||
("class" "button tab")
|
||||
("href" "/chats/{{ chat.id }}/manage")
|
||||
(text "{{ icon \"settings-2\" }} Manage"))))
|
||||
(text "{{ icon \"settings-2\" }} manage"))))
|
||||
(div
|
||||
("class" "flex flex_col gap_4 card")
|
||||
("style" "flex: 1 0 auto")
|
||||
(text "{% if chat.style != \"Direct\" -%}")
|
||||
; gc only
|
||||
(button
|
||||
("class" "button surface")
|
||||
("onclick" "rename_chat('{{ chat.id }}', GC_INFO)")
|
||||
(text "{{ icon \"pencil\" }} rename chat"))
|
||||
(div
|
||||
("class" "flex gap_2 flex_wrap")
|
||||
(text "{% if chat.style != \"Direct\" -%}")
|
||||
; gc only
|
||||
(button
|
||||
("class" "button surface")
|
||||
("onclick" "rename_chat('{{ chat.id }}', GC_INFO)")
|
||||
(text "{{ icon \"pencil\" }} rename chat"))
|
||||
|
||||
(script
|
||||
("type" "application/json")
|
||||
("id" "gc_info")
|
||||
(text "{{ chat.style.Group|json_encode() }}"))
|
||||
(script
|
||||
(text "globalThis.GC_INFO = JSON.parse(document.getElementById(\"gc_info\").innerHTML)"))
|
||||
(text "{%- endif %}")
|
||||
(script
|
||||
("type" "application/json")
|
||||
("id" "gc_info")
|
||||
(text "{{ chat.style.Group|json_encode() }}"))
|
||||
(script
|
||||
(text "globalThis.GC_INFO = JSON.parse(document.getElementById(\"gc_info\").innerHTML)"))
|
||||
(text "{%- endif %}")
|
||||
; every chat
|
||||
(a
|
||||
("class" "button surface")
|
||||
("href" "/chats/{{ chat.id }}/pins")
|
||||
(text "{{ icon \"pin\" }} view pins")))
|
||||
|
||||
(ul
|
||||
(li (b (text "Chat name: ")) (span (text "{{ components::chat_name(chat=chat, members=members) }}")))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{%- import \"components.lisp\" as components -%}")
|
||||
(text "{% for message in messages -%}")
|
||||
(text "{{ components::message(message=message) }}")
|
||||
(text "{{ components::message(message=message, is_pinned=message.id in pins) }}")
|
||||
(text "{%- endfor %}")
|
||||
|
||||
(div
|
||||
|
@ -12,4 +12,5 @@
|
|||
(div
|
||||
("class" "hidden")
|
||||
("id" "msgs_quit_{{ id }}"))
|
||||
(i ("class" "flex gap_ch items_center fade") (text "{{ icon \"star\" }} This is the start of the chat!"))
|
||||
(text "{%- endif %}")
|
||||
|
|
36
app/templates_src/pins.lisp
Normal file
36
app/templates_src/pins.lisp
Normal file
|
@ -0,0 +1,36 @@
|
|||
(text "{% extends \"root.lisp\" %} {% block head %}")
|
||||
(title
|
||||
(text "Pins in {{ components::chat_name(chat=chat, members=members) }} — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %}")
|
||||
(div
|
||||
("class" "flex w_full gap_2 justify_between items_center")
|
||||
(div
|
||||
("class" "tabs short bar flex")
|
||||
(a
|
||||
("class" "button tab camo")
|
||||
("href" "/chats")
|
||||
(text "{{ icon \"castle\" }} chats"))
|
||||
(a
|
||||
("class" "button tab camo")
|
||||
("href" "/chats/{{ chat.id }}")
|
||||
(text "{{ components::chat_name(chat=chat, members=members, advanced=true, avatar_size=\"18px\") }}"))
|
||||
(a
|
||||
("class" "button tab")
|
||||
("href" "/chats/{{ chat.id }}/manage")
|
||||
(text "{{ icon \"settings-2\" }} manage"))))
|
||||
(div
|
||||
("class" "flex flex_col gap_4 card")
|
||||
("style" "flex: 1 0 auto")
|
||||
(p (text "Using ") (b (text "{{ messages|length }} ")) (text "of ") (b (text "12 ")) (text "pins."))
|
||||
(text "{% for message in messages -%}")
|
||||
(hr)
|
||||
(button
|
||||
("class" "button surface red")
|
||||
("onclick" "unpin_message('{{ message.id }}')")
|
||||
(text "unpin"))
|
||||
(text "{{ components::message(message=message) }}")
|
||||
(text "{%- endfor %}"))
|
||||
|
||||
(script ("src" "/public/messages.js"))
|
||||
(script (text "STATE.chat_id = '{{ chat.id }}';"))
|
||||
(text "{% endblock %}")
|
|
@ -134,7 +134,9 @@
|
|||
}
|
||||
|
||||
.profile .banner {
|
||||
background: url(\"{{ config.service_hosts.buckets }}/banners/{{ profile.id }}\") no-repeat center !important;
|
||||
background-image: url(\"{{ config.service_hosts.buckets }}/banners/{{ profile.id }}\") !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-position: center !important;
|
||||
background-size: cover !important;
|
||||
border-radius: var(--radius) var(--radius) 0 0;
|
||||
height: 225px;
|
||||
|
|
|
@ -16,6 +16,7 @@ impl DataManager {
|
|||
members: serde_json::from_str(&get!(x->3(String))).unwrap(),
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,14 +121,15 @@ impl DataManager {
|
|||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO t_chats VALUES ($1, $2, $3, $4, $5, $6)",
|
||||
"INSERT INTO t_chats VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
&serde_json::to_string(&data.style).unwrap(),
|
||||
&serde_json::to_string(&data.members).unwrap(),
|
||||
&(data.last_message_created as i64),
|
||||
&serde_json::to_string(&data.last_message_read_by).unwrap()
|
||||
&serde_json::to_string(&data.last_message_read_by).unwrap(),
|
||||
&serde_json::to_string(&data.pinned_messages).unwrap(),
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -173,6 +175,7 @@ impl DataManager {
|
|||
|
||||
auto_method!(update_chat_style(ChatStyle) -> "UPDATE t_chats SET style = $1 WHERE id = $2" --serde --cache-key-tmpl="twny.chat:{}");
|
||||
auto_method!(update_chat_members(Vec<usize>) -> "UPDATE t_chats SET members = $1 WHERE id = $2" --serde --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_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:{}");
|
||||
}
|
||||
|
|
|
@ -52,6 +52,10 @@ impl DataManager {
|
|||
execute!(&conn, sql::CREATE_TABLE_CHATS).unwrap();
|
||||
execute!(&conn, sql::CREATE_TABLE_MESSAGES).unwrap();
|
||||
|
||||
for x in sql::VERSION_MIGRATIONS.split(";") {
|
||||
execute!(&conn, x).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub const CREATE_TABLE_CHATS: &str = include_str!("./create_chats.sql");
|
||||
pub const CREATE_TABLE_MESSAGES: &str = include_str!("./create_messages.sql");
|
||||
pub const VERSION_MIGRATIONS: &str = include_str!("./version_migrations.sql");
|
||||
|
|
2
src/database/sql/version_migrations.sql
Normal file
2
src/database/sql/version_migrations.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- chats pinned_messages
|
||||
ALTER TABLE t_chats ADD COLUMN IF NOT EXISTS pinned_messages TEXT NOT NULL DEFAULT '[]';
|
|
@ -32,6 +32,7 @@ pub struct Chat {
|
|||
/// keep up with if we store by chat instead. This will also declutter
|
||||
/// the UI and prevent every message showing a read receipt.
|
||||
pub last_message_read_by: Vec<usize>,
|
||||
pub pinned_messages: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Chat {
|
||||
|
@ -44,6 +45,7 @@ impl Chat {
|
|||
members,
|
||||
last_message_created: 0,
|
||||
last_message_read_by: Vec::new(),
|
||||
pinned_messages: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ pub async fn create_request(
|
|||
req.members.dedup();
|
||||
|
||||
if (req.members.len() > 2 && req.style == ChatStyle::Direct)
|
||||
| (req.members.len() > GC_MAXIMUM_MEMBERS && req.style != ChatStyle::Direct)
|
||||
| (req.members.len() >= GC_MAXIMUM_MEMBERS && req.style != ChatStyle::Direct)
|
||||
{
|
||||
return Json(Error::DataTooLong("members list".to_string()).into());
|
||||
} else if req.members.len() < 1 {
|
||||
|
@ -199,7 +199,7 @@ pub async fn add_member_request(
|
|||
return Json(Error::NotAllowed.into());
|
||||
}
|
||||
|
||||
if chat.style != ChatStyle::Direct && chat.members.len() > GC_MAXIMUM_MEMBERS {
|
||||
if chat.style != ChatStyle::Direct && chat.members.len() >= GC_MAXIMUM_MEMBERS {
|
||||
// can only have a maximum of GC_MAXIMUM_MEMBERS members in one chat
|
||||
return Json(Error::DataTooLong("members list".to_string()).into());
|
||||
}
|
||||
|
@ -460,3 +460,83 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, chat_id: String,
|
|||
.await;
|
||||
tracing::info!("socket terminate");
|
||||
}
|
||||
|
||||
pub const MAXIMUM_PINS: usize = 12;
|
||||
|
||||
pub async fn add_pin_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Path((id, message_id)): Path<(usize, 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.pinned_messages.contains(&message_id)
|
||||
|| chat.pinned_messages.len() >= MAXIMUM_PINS
|
||||
{
|
||||
return Json(Error::NotAllowed.into());
|
||||
}
|
||||
|
||||
chat.pinned_messages.push(message_id);
|
||||
match data
|
||||
.update_chat_pinned_messages(chat.id, chat.pinned_messages)
|
||||
.await
|
||||
{
|
||||
Ok(_) => Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Success".to_string(),
|
||||
payload: (),
|
||||
}),
|
||||
Err(e) => Json(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_pin_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Path((id, message_id)): Path<(usize, 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.pinned_messages.contains(&message_id) {
|
||||
return Json(Error::NotAllowed.into());
|
||||
}
|
||||
|
||||
chat.pinned_messages.remove(
|
||||
chat.pinned_messages
|
||||
.iter()
|
||||
.position(|x| *x == message_id)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
match data
|
||||
.update_chat_pinned_messages(chat.id, chat.pinned_messages)
|
||||
.await
|
||||
{
|
||||
Ok(_) => Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Success".to_string(),
|
||||
payload: (),
|
||||
}),
|
||||
Err(e) => Json(e.into()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,11 @@ pub fn routes() -> Router {
|
|||
"/chats/{id}/read_message",
|
||||
post(chats::read_message_request),
|
||||
)
|
||||
.route("/chats/{id}/pins/{message}", post(chats::add_pin_request))
|
||||
.route(
|
||||
"/chats/{id}/pins/{message}",
|
||||
delete(chats::remove_pin_request),
|
||||
)
|
||||
// messages
|
||||
.route("/messages/{id}", post(messages::create_request))
|
||||
.route("/messages/{id}", delete(messages::delete_request))
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
State, get_user_from_token,
|
||||
routes::{
|
||||
|
@ -50,7 +52,6 @@ pub async fn chat_request(
|
|||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Path(id): Path<usize>,
|
||||
Query(props): Query<PaginatedQuery>,
|
||||
) -> impl IntoResponse {
|
||||
let (ref data, ref tera, ref build_code) = *data.read().await;
|
||||
let user = match get_user_from_token!(jar, data.2) {
|
||||
|
@ -75,18 +76,10 @@ pub async fn chat_request(
|
|||
}
|
||||
};
|
||||
|
||||
let messages = match data.get_messages_by_chat(id, 24, props.page).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
return Err(render_error(e, tera, data.0.0.clone(), Some(user)).await);
|
||||
}
|
||||
};
|
||||
|
||||
let mut ctx = default_context(&data.0.0, &build_code, &Some(user));
|
||||
|
||||
ctx.insert("chat", &chat);
|
||||
ctx.insert("members", &members);
|
||||
ctx.insert("messages", &messages);
|
||||
|
||||
Ok(Html(tera.render("chat.lisp", &ctx).unwrap()))
|
||||
}
|
||||
|
@ -135,12 +128,47 @@ pub async fn messages_request(
|
|||
}
|
||||
};
|
||||
|
||||
let chat = match data.get_chat_by_id(id).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
return Err(render_error(e, tera, data.0.0.clone(), Some(user)).await);
|
||||
}
|
||||
};
|
||||
|
||||
let mut seen_user_blocks: HashMap<usize, bool> = HashMap::new();
|
||||
|
||||
let messages = match if before > 0 {
|
||||
data.get_messages_by_chat_before(id, before, 24, 0).await
|
||||
} else {
|
||||
data.get_messages_by_chat(id, 24, 0).await
|
||||
} {
|
||||
Ok(x) => x,
|
||||
Ok(x) => {
|
||||
let mut y = Vec::new();
|
||||
|
||||
for z in x {
|
||||
if let Some(status) = seen_user_blocks.get(&z.owner) {
|
||||
if *status {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
let is_blocked = data
|
||||
.2
|
||||
.get_userblock_by_initiator_receiver(user.id, z.owner)
|
||||
.await
|
||||
.is_ok();
|
||||
|
||||
seen_user_blocks.insert(z.owner, is_blocked);
|
||||
|
||||
if is_blocked {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
y.push(z);
|
||||
}
|
||||
|
||||
y
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(render_error(e, tera, data.0.0.clone(), Some(user)).await);
|
||||
}
|
||||
|
@ -156,6 +184,7 @@ pub async fn messages_request(
|
|||
},
|
||||
);
|
||||
|
||||
ctx.insert("pins", &chat.pinned_messages);
|
||||
ctx.insert("messages", &messages);
|
||||
ctx.insert("id", &props.use_id);
|
||||
|
||||
|
@ -225,3 +254,55 @@ pub async fn manage_chat_request(
|
|||
|
||||
Ok(Html(tera.render("manage.lisp", &ctx).unwrap()))
|
||||
}
|
||||
|
||||
pub async fn chat_pins_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Path(id): Path<usize>,
|
||||
) -> impl IntoResponse {
|
||||
let (ref data, ref tera, ref build_code) = *data.read().await;
|
||||
let user = match get_user_from_token!(jar, data.2) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return Err(render_error(Error::NotAllowed, tera, data.0.0.clone(), None).await);
|
||||
}
|
||||
};
|
||||
|
||||
let (chat, members) = match data.get_chat_by_id(id).await {
|
||||
Ok(x) => {
|
||||
if !x.members.contains(&user.id) {
|
||||
return Err(
|
||||
render_error(Error::NotAllowed, tera, data.0.0.clone(), Some(user)).await,
|
||||
);
|
||||
}
|
||||
|
||||
data.fill_chat(x).await
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(render_error(e, tera, data.0.0.clone(), Some(user)).await);
|
||||
}
|
||||
};
|
||||
|
||||
let messages = {
|
||||
let mut x = Vec::new();
|
||||
|
||||
for y in &chat.pinned_messages {
|
||||
x.push(match data.get_message_by_id(*y).await {
|
||||
Ok(z) => z,
|
||||
Err(e) => {
|
||||
return Err(render_error(e, tera, data.0.0.clone(), Some(user)).await);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
x
|
||||
};
|
||||
|
||||
let mut ctx = default_context(&data.0.0, &build_code, &Some(user));
|
||||
|
||||
ctx.insert("chat", &chat);
|
||||
ctx.insert("members", &members);
|
||||
ctx.insert("messages", &messages);
|
||||
|
||||
Ok(Html(tera.render("pins.lisp", &ctx).unwrap()))
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use axum::{
|
|||
use axum_extra::extract::CookieJar;
|
||||
use serde::Deserialize;
|
||||
use tera::Tera;
|
||||
use tetratto_core::model::{Error, Result, auth::User};
|
||||
use tetratto_core::model::{Error, Result, auth::User, permissions::FinePermission};
|
||||
|
||||
pub async fn render_error(
|
||||
e: Error,
|
||||
|
@ -57,6 +57,20 @@ pub async fn login_request(jar: CookieJar, Extension(data): Extension<State>) ->
|
|||
)
|
||||
}
|
||||
|
||||
async fn check_user_is_blocked(user: &User, other_user: &User, data: &DataManager) -> bool {
|
||||
(data
|
||||
.2
|
||||
.get_userblock_by_initiator_receiver(other_user.id, user.id)
|
||||
.await
|
||||
.is_ok()
|
||||
| data
|
||||
.2
|
||||
.get_user_stack_blocked_users(other_user.id)
|
||||
.await
|
||||
.contains(&user.id))
|
||||
&& !user.permissions.check(FinePermission::MANAGE_USERS)
|
||||
}
|
||||
|
||||
async fn check_user_blocked_or_private(
|
||||
user: &Option<User>,
|
||||
other_user: &User,
|
||||
|
@ -72,12 +86,7 @@ async fn check_user_blocked_or_private(
|
|||
{
|
||||
// private profile and other_user isn't following user
|
||||
return Err(Error::NotAllowed);
|
||||
} else if data
|
||||
.2
|
||||
.get_userblock_by_initiator_receiver(other_user.id, ua.id)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
} else if check_user_is_blocked(ua, other_user, data).await {
|
||||
// blocked
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ pub fn routes() -> Router {
|
|||
// chats
|
||||
.route("/chats", get(chats::list_request))
|
||||
.route("/chats/{id}", get(chats::chat_request))
|
||||
.route("/chats/{id}/pins", get(chats::chat_pins_request))
|
||||
.route("/chats/{id}/manage", get(chats::manage_chat_request))
|
||||
.route(
|
||||
"/chats/_templates/message/{id}",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue